/** * isup.cpp * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Signalling Stack - implements the support for SS7, ISDN and PSTN * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2014 Null Team * * 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. */ #include "yatesig.h" #include #include using namespace TelEngine; #ifdef DEBUG #define ISUP_HANDLE_CIC_EVENT_CONTROL #else //#define ISUP_HANDLE_CIC_EVENT_CONTROL #endif // Maximum number of mandatory parameters including two terminators #define MAX_MANDATORY_PARAMS 16 // Timer limits and default values #define ISUP_T7_MINVAL 20000 #define ISUP_T7_DEFVAL 20000 #define ISUP_T7_MAXVAL 30000 #define ISUP_T9_MINVAL 90000 #define ISUP_T9_DEFVAL 0 #define ISUP_T9_MAXVAL 180000 #define ISUP_T27_MINVAL 30000 #define ISUP_T27_DEFVAL 240000 #define ISUP_T27_MAXVAL 300000 #define ISUP_T34_MINVAL 2000 #define ISUP_T34_DEFVAL 3000 #define ISUP_T34_MAXVAL 4000 // Utility: check if 2 cic codes are in valid range, return range if valid, 0 otherwise static inline int checkValidRange(int code, int extra) { int range = extra - code; return (range > -256 && range < 256) ? range : 0; } // Adjust range and status data when needed (a new range is used) static void adjustRangeAndStatus(char* status, unsigned int& code, unsigned int& range, int newRange) { if (!(status && newRange)) return; if (newRange > 0) { range = (unsigned int)newRange; status[0] = '1'; ::memset(status + 1,'0',range); } else { range = (unsigned int)(-newRange); code -= range; ::memset(status,'0',range); status[range] = '1'; } range++; } // Description of each ISUP parameter struct IsupParam { // numeric type of the parameter SS7MsgISUP::Parameters type; // size in octets, zero for variable unsigned char size; // SS7 name of the parameter const char* name; // decoder callback function bool (*decoder)(const SS7ISUP*,NamedList&,const IsupParam*, const unsigned char*,unsigned int,const String&); // encoder callback function unsigned char (*encoder)(const SS7ISUP*,SS7MSU&,unsigned char*, const IsupParam*,const NamedString*,const NamedList*,const String&); // table data to be used by the callback const void* data; }; // This structure describes parameters of each ISUP message for each dialect struct MsgParams { // type of the message described SS7MsgISUP::Type type; // does the message support optional part? bool optional; // parameters, fixed then variable, separated/terminated by EndOfParameters // using an array is a (moderate) waste of space const SS7MsgISUP::Parameters params[MAX_MANDATORY_PARAMS]; }; // Nature of Address Indicator static const TokenDict s_dict_nai[] = { { "subscriber", 1 }, { "unknown", 2 }, { "national", 3 }, { "international", 4 }, { "network-specific", 5 }, { "national-routing", 6 }, { "specific-routing", 7 }, { "routing-with-cdn", 8 }, { 0, 0 } }; // Numbering Plan Indicator static const TokenDict s_dict_numPlan[] = { { "unknown", 0 }, { "isdn", 1 }, { "data", 3 }, { "telex", 4 }, { "private", 5 }, { "national", 6 }, { 0, 0 } }; // Address Presentation static const TokenDict s_dict_presentation[] = { { "allowed", 0 }, { "restricted", 1 }, { "unavailable", 2 }, // aliases for restrict=... { "no", 0 }, { "false", 0 }, { "yes", 1 }, { "true", 1 }, { 0, 0 } }; // Screening Indicator static const TokenDict s_dict_screening[] = { { "user-provided", 0 }, { "user-provided-passed", 1 }, { "user-provided-failed", 2 }, { "network-provided", 3 }, // aliases for screened=... { "no", 0 }, { "false", 0 }, { "yes", 1 }, { "true", 1 }, { 0, 0 } }; // Generic number qualifier static const TokenDict s_dict_qual[] = { { "dialed-digits", 0 }, { "called-additional", 1 }, { "caller-failed", 2 }, { "caller-not-screened", 3 }, { "terminating", 4 }, { "connected-additional", 5 }, { "caller-additional", 6 }, { "called-original", 7 }, { "redirecting", 8 }, { "redirection", 9 }, { 0, 0 } }; // Generic name qualifier static const TokenDict s_dict_qual_name[] = { { "caller" , 0x20 }, { "called" , 0x40 }, { "redirecting" , 0x60 }, { "connected" , 0x80 }, { 0, 0 } }; // Redirection Information (Q,763 3.45) bits CBA static const TokenDict s_dict_redir_main[] = { { "none", 0 }, { "rerouted", 1 }, { "rerouted-restrict-all", 2 }, { "diverted", 3 }, { "diverted-restrict-all", 4 }, { "rerouted-restrict-number", 5 }, { "diverted-restrict-number", 6 }, { 0, 0 } }; // Redirection Information (Q,763 3.45) bits HGFE or PONM static const TokenDict s_dict_redir_reason[] = { { "busy", 1 }, { "noanswer", 2 }, { "always", 3 }, { "deflected", 4 }, { "diverted", 5 }, { "offline", 6 }, { 0, 0 } }; // Message Compatibility Information (Q.763 3.33) static const SignallingFlags s_flags_msgcompat[] = { { 0x01, 0x00, "transit" }, // End node / transit exchange { 0x01, 0x01, "end-node" }, { 0x02, 0x02, "release" }, // Release call indicator { 0x04, 0x04, "cnf" }, // Pass on set but not possible: Send CNF / RLC { 0x08, 0x08, "discard" }, // Discard / pass on message { 0x10, 0x00, "nopass-release" }, // Pass on not possible: Release call { 0x10, 0x10, "nopass-discard" }, // Pass on not possible: Discard message { 0, 0, 0 } }; // Parameter Compatibility Information (Q.763 3.41) static const SignallingFlags s_flags_paramcompat[] = { { 0x01, 0x00, "transit" }, // End node / transit exchange { 0x01, 0x01, "end-node" }, { 0x02, 0x02, "release" }, // Release call indicator { 0x04, 0x04, "cnf" }, // Parameter pass on set but not possible: Send CNF / RLC { 0x08, 0x08, "discard-msg" }, // Discard / pass on message { 0x18, 0x10, "discard-param" }, // Discard / pass on parameter (if not discarding message) { 0x60, 0x00, "nopass-release" }, // No pass on: release call { 0x60, 0x20, "nopass-msg" }, // No pass on: discard message { 0x60, 0x40, "nopass-param" }, // No pass on: discard parameter { 0x60, 0x60, "nopass-release" }, // Reserved, interpreted as 00 { 0, 0, 0 } }; // Application Transport Parameter instruction indicators (Q.763 3.82) static const SignallingFlags s_flags_apt_indicators[] = { { 0x01, 0x01, "release" }, // Release call indicator { 0x02, 0x02, "cnf" }, // Send CNF notification { 0, 0, 0 } }; // SLS special values on outbound calls static const TokenDict s_dict_callSls[] = { { "auto", SS7ISUP::SlsAuto }, // Let Layer3 deal with it { "last", SS7ISUP::SlsLatest }, // Last SLS used { "cic", SS7ISUP::SlsCircuit }, // Lower bits of CIC { 0, 0 } }; // Control operations static const TokenDict s_dict_control[] = { { "validate", SS7MsgISUP::CVT }, { "query", SS7MsgISUP::CQM }, { "conttest", SS7MsgISUP::CCR }, { "reset", SS7MsgISUP::RSC }, { "block", SS7MsgISUP::BLK }, { "unblock", SS7MsgISUP::UBL }, { "release", SS7MsgISUP::RLC }, { "parttest", SS7MsgISUP::UPT }, { "available", SS7MsgISUP::UPA }, { "save", SS7MsgISUP::CtrlSave }, #ifdef ISUP_HANDLE_CIC_EVENT_CONTROL { "circuitevent", SS7MsgISUP::CtrlCicEvent }, #endif { 0, 0 } }; static const TokenDict s_dict_CRG_process[] = { { "confusion", SS7ISUP::Confusion }, { "ignore", SS7ISUP::Ignore }, { "raw", SS7ISUP::Raw }, { "parsed", SS7ISUP::Parsed }, { 0, 0 } }; // Build next available parameter name static void buildName(const NamedList& list, const IsupParam* param, const String& prefix, String& name) { name = prefix + param->name; if (!list.getParam(name)) return; // conflict - find a free index for (unsigned int i = 1; ; i++) { String tmp(name); tmp << "." << i; if (!list.getParam(tmp)) { name = tmp; break; } } } // Default decoder, dumps raw octets static bool decodeRaw(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { if (len < 1) return false; String raw; raw.hexify((void*)buf,len,' '); DDebug(isup,DebugInfo,"decodeRaw decoded %s=%s",param->name,raw.c_str()); String preName; buildName(list,param,prefix,preName); list.addParam(preName,raw); return true; } // Raw decoder for unknown/failed parameter, dumps raw octets static bool decodeRawParam(const SS7ISUP* isup, NamedList& list, unsigned char value, const unsigned char* buf, unsigned int len, const String& prefix) { String name("Param_"); name << value; IsupParam p; p.type = (SS7MsgISUP::Parameters)value; p.size = len; p.name = name; p.decoder = 0; p.encoder = 0; p.data = 0; return decodeRaw(isup,list,&p,buf,len,prefix); }; // Integer decoder, interprets data as big endian integer static bool decodeInt(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { unsigned int val = 0; while (len--) val = (val << 8) | (unsigned int)(*buf++); DDebug(isup,DebugAll,"decodeInt decoded %s=%s (%u)",param->name,lookup(val,(const TokenDict*)param->data),val); String preName; buildName(list,param,prefix,preName); SignallingUtils::addKeyword(list,preName,(const TokenDict*)param->data,val); return true; } // Decoder for ISUP indicators (flags) static bool decodeFlags(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { const SignallingFlags* flags = (const SignallingFlags*)param->data; if (!flags) return false; String preName; buildName(list,param,prefix,preName); return SignallingUtils::decodeFlags(isup,list,preName,flags,buf,len); } // Utility function - extract just ISUP digits from a parameter static void getDigits(String& num, unsigned char oddNum, const unsigned char* buf, unsigned int len, bool ignoreUnk) { bool odd = (oddNum & 0x80) != 0; static const char digits1[] = "0123456789\0BC\0\0."; static const char digits2[] = "0123456789ABCDE."; const char* digits = ignoreUnk ? digits1 : digits2; for (unsigned int i = 0; i < len; i++) { num += digits[buf[i] & 0x0f]; if (odd && ((i+1) == len)) break; num += digits[buf[i] >> 4]; } } const char* getIsupParamName(unsigned char type); // Decoder for message or parameter compatibility // Q.763 3.33/3.41 static bool decodeCompat(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { if (!len) return false; String preName; buildName(list,param,prefix,preName); switch (param->type) { case SS7MsgISUP::MessageCompatInformation: SignallingUtils::decodeFlags(isup,list,preName,s_flags_msgcompat,buf,1); if (buf[0] & 0x80) { if (len == 1) return true; DDebug(isup,DebugMild, "decodeCompat invalid len=%u for %s with first byte having ext bit set",len,param->name); break; } return 0 != SignallingUtils::dumpDataExt(isup,list,preName+".more",buf+1,len-1); case SS7MsgISUP::ParameterCompatInformation: for (unsigned int i = 0; i < len;) { unsigned char val = buf[i++]; if (i == len) { Debug(isup,DebugMild,"decodeCompat unexpected end of data (len=%u) for %s",len,param->name); return false; } const char* paramName = getIsupParamName(val); String name = preName; if (paramName) name << "." << paramName; else { Debug(isup,DebugMild,"decodeCompat found unknown parameter %u for %s",val,param->name); name << "." << (unsigned int)val; } SignallingUtils::decodeFlags(isup,list,name,s_flags_paramcompat,buf+i,1); if (buf[i++] & 0x80) continue; unsigned int count = SignallingUtils::dumpDataExt(isup,list,name+".more",buf+i,len-i); if (!count) return false; i += count; } decodeRaw(isup,list,param,buf,len,prefix); return true; default: Debug(isup,DebugStub,"decodeCompat not implemented for %s",param->name); } return false; } // Decoder for various ISUP digit sequences (phone numbers) static bool decodeDigits(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { if (len < 2) return false; unsigned char qualifier = 0; if (SS7MsgISUP::GenericNumber == param->type) { if (--len < 2) return false; qualifier = buf[0]; buf++; } unsigned char nai = buf[0] & 0x7f; unsigned char plan = (buf[1] >> 4) & 7; unsigned char pres = (buf[1] >> 2) & 3; unsigned char scrn = buf[1] & 3; String tmp; getDigits(tmp,buf[0],buf+2,len-2,isup && isup->ignoreUnknownAddrSignals()); DDebug(isup,DebugAll,"decodeDigits decoded %s='%s' inn/ni=%u nai=%u plan=%u pres=%u scrn=%u", param->name,tmp.c_str(),buf[1] >> 7,nai,plan,pres,scrn); String preName; buildName(list,param,prefix,preName); list.addParam(preName,tmp); if (SS7MsgISUP::GenericNumber == param->type) SignallingUtils::addKeyword(list,preName+".qualifier",s_dict_qual,qualifier); SignallingUtils::addKeyword(list,preName+".nature",s_dict_nai,nai); SignallingUtils::addKeyword(list,preName+".plan",s_dict_numPlan,plan); switch (param->type) { case SS7MsgISUP::CalledPartyNumber: case SS7MsgISUP::RedirectionNumber: case SS7MsgISUP::LocationNumber: tmp = ((buf[1] & 0x80) == 0); list.addParam(preName+".inn",tmp); break; case SS7MsgISUP::CallingPartyNumber: case SS7MsgISUP::GenericNumber: tmp = ((buf[1] & 0x80) == 0); list.addParam(preName+".complete",tmp); break; case SS7MsgISUP::LastDivertingLineIdentity: case SS7MsgISUP::PresentationNumber: tmp = ((buf[1] & 0x80) != 0); list.addParam(preName+".pnp",tmp); break; default: break; } switch (param->type) { case SS7MsgISUP::CallingPartyNumber: case SS7MsgISUP::RedirectingNumber: case SS7MsgISUP::OriginalCalledNumber: case SS7MsgISUP::LocationNumber: case SS7MsgISUP::ConnectedNumber: case SS7MsgISUP::GenericNumber: case SS7MsgISUP::LastDivertingLineIdentity: case SS7MsgISUP::PresentationNumber: case SS7MsgISUP::CalledINNumber: case SS7MsgISUP::OriginalCalledINNumber: SignallingUtils::addKeyword(list,preName+".restrict",s_dict_presentation,pres); default: break; } switch (param->type) { case SS7MsgISUP::CallingPartyNumber: case SS7MsgISUP::LocationNumber: case SS7MsgISUP::ConnectedNumber: case SS7MsgISUP::GenericNumber: case SS7MsgISUP::LastDivertingLineIdentity: case SS7MsgISUP::PresentationNumber: SignallingUtils::addKeyword(list,preName+".screened",s_dict_screening,scrn); default: break; } return true; } // Special decoder for subsequent number static bool decodeSubseq(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { if (len < 1) return false; String tmp; getDigits(tmp,buf[0],buf+1,len-1,isup && isup->ignoreUnknownAddrSignals()); DDebug(isup,DebugAll,"decodeSubseq decoded %s='%s'",param->name,tmp.c_str()); String preName; buildName(list,param,prefix,preName); list.addParam(preName,tmp); return true; } // Decoder for circuit group range and status (Q.763 3.43) static bool decodeRangeSt(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { if (len < 1) return false; String preName; buildName(list,param,prefix,preName); // 1st octet is the range code (range - 1) len--; unsigned int range = 1 + *buf++; unsigned int octets = (range + 7) / 8; if (octets > len) { if (len) Debug(isup,DebugMild,"decodeRangeSt truncating range of %u bits to %u octets!",range,len); octets = len; } list.addParam(preName,String(range)); String map; if (len) { unsigned char mask = 1; unsigned int r = range; while (r--) { map += (buf[0] & mask) ? "1" : "0"; mask <<= 1; if (!mask) { ++buf; if (!--octets) break; mask = 1; } } list.addParam(preName+".map",map); } DDebug(isup,DebugAll,"decodeRangeSt decoded %s=%u '%s'",param->name,range,map.c_str()); return true; } // Decoder for generic notification indicators (Q.763 3.25) static bool decodeNotif(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { if (len < 1) return false; String flg; for (; len; len--) { unsigned char val = *buf++; const char* keyword = lookup(val & 0x7f,(const TokenDict*)param->data); if (keyword) flg.append(keyword,","); else { String tmp(0x7f & (int)val); flg.append(tmp,","); } if (val & 0x80) break; } DDebug(isup,DebugAll,"decodeNotif decoded %s='%s'",param->name,flg.c_str()); String preName; buildName(list,param,prefix,preName); list.addParam(preName,flg); return true; } // Decoder for User Service Information static bool decodeUSI(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { String preName; buildName(list,param,prefix,preName); return SignallingUtils::decodeCaps(isup,list,buf,len,preName,true); } // Decoder for cause indicators static bool decodeCause(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { String preName; buildName(list,param,prefix,preName); return SignallingUtils::decodeCause(isup,list,buf,len,preName,true); } // Decoder for application transport parameter static bool decodeAPT(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { if (len < 4) { if (len == 3) Debug(isup,DebugNote,"Received '%s' with no data",param->name); return false; } // Field extension on more then 1 octet is not supported if (0 == (buf[0] & buf[1] & buf[2] & 0x80)) { Debug(isup,DebugNote,"Received %s with unsupported extension bits set to 0", param->name); return false; } // Segmentation is not supported unsigned char si = (buf[2] & 0x40); unsigned char segments = (buf[2] & 0x3f); if (!si || segments) { Debug(isup,DebugNote,"Received unsupported segmented %s (si=%u segments=%u)", param->name,si,segments); return false; } len -= 3; // WARNING: HACK - ApplicationTransport does not follow naming convention String preName(prefix + param->name); String context((int)(buf[0] & 0x7f)); list.addParam(preName,context); preName << "." << context; // Application context identifier SignallingUtils::dumpData(isup,list,preName,buf + 3,len); // Instruction indicators unsigned char inds = (buf[1] & 0x7f); SignallingUtils::decodeFlags(isup,list,preName + ".indicators",s_flags_apt_indicators,&inds,1); return true; } // Decoder for generic name static bool decodeName(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { if (len < 1) return false; String val((const char*)buf+1,len-1); String preName; buildName(list,param,prefix,preName); list.addParam(preName,val); list.addParam(preName+".available",String::boolText((buf[0] & 0x10) == 0)); SignallingUtils::addKeyword(list,preName+".qualifier",s_dict_qual_name,buf[0] & 0xe0); SignallingUtils::addKeyword(list,preName+".restrict",s_dict_presentation,buf[0] & 0x03); DDebug(isup,DebugAll,"decodeName decoded %s='%s'",param->name,val.c_str()); return true; } // Decoder for Redirection information (Q.763 3.45) static bool decodeRedir(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { if (len < 1) return false; String preName; buildName(list,param,prefix,preName); SignallingUtils::addKeyword(list,preName,s_dict_redir_main,buf[0] & 0x07); unsigned int reason = buf[0] >> 4; if (reason) SignallingUtils::addKeyword(list,preName+".reason_original",s_dict_redir_reason,reason); if (len > 1) { int cnt = buf[1] & 0x07; if (cnt) list.addParam(preName+".counter",String(cnt)); reason = buf[1] >> 4; if (reason) SignallingUtils::addKeyword(list,preName+".reason",s_dict_redir_reason,reason); } return true; } // Default encoder, get hexified octets static unsigned char encodeRaw(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList*, const String&) { if (!(param && val)) return 0; DDebug(isup,DebugInfo,"encodeRaw encoding %s=%s",param->name,val->c_str()); DataBlock raw; if (!raw.unHexify(val->c_str(),val->length(),' ')) { DDebug(isup,DebugMild,"encodeRaw failed: invalid string"); return 0; } if (!raw.length() || raw.length() > 254 || (param->size && param->size != raw.length())) { DDebug(isup,DebugMild,"encodeRaw failed: param size=%u data length=%u", param->size,raw.length()); return 0; } if (buf) { ::memcpy(buf,raw.data(),raw.length()); return raw.length(); } unsigned char size = (unsigned char)raw.length(); msu.append(&size,1); msu += raw; return size; } // Encoder for fixed length ISUP indicators (flags) static unsigned char encodeFlags(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList*, const String&) { if (!param) return 0; unsigned int n = param->size; if (!(n && param->data)) return 0; unsigned int v = 0; if (val) v = SignallingUtils::encodeFlags(isup,*val,(const SignallingFlags*)param->data,param->name); else { // locate the defaults const SignallingFlags* flags = (const SignallingFlags*)param->data; while (flags->mask) flags++; v = flags->value; } DDebug(isup,DebugAll,"encodeFlags encoding %s=0x%x on %u octets",param->name,v,n); if (!buf) { unsigned int l = msu.length(); DataBlock dummy(0,n+1); msu += dummy; buf = (unsigned char*)msu.getData(l,n+1); *buf++ = n & 0xff; } while (n--) { *buf++ = v & 0xff; v >>= 8; } return param->size; } // Encoder for fixed length big-endian integer values static unsigned char encodeInt(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList*, const String&) { if (!param) return 0; unsigned int n = param->size; if (!n) return 0; unsigned int v = 0; if (val) v = val->toInteger((const TokenDict*)param->data); DDebug(isup,DebugAll,"encodeInt encoding %s=%u on %u octets",param->name,v,n); if (!buf) { unsigned int l = msu.length(); DataBlock dummy(0,n+1); msu += dummy; buf = (unsigned char*)msu.getData(l,n+1); *buf++ = n & 0xff; } buf += n; while (n--) { *(--buf) = v & 0xff; v >>= 8; } return param->size; } // Utility function - write digit sequences static unsigned char setDigits(SS7MSU& msu, const char* val, unsigned char nai, int b2 = -1, int b3 = -1, int b0 = -1) { unsigned char buf[32]; unsigned int len = 1; if (b0 >= 0) buf[len++] = b0 & 0xff; unsigned int naiPos = len++; buf[naiPos] = nai & 0x7f; if (b2 >= 0) { buf[len++] = b2 & 0xff; if (b3 >= 0) buf[len++] = b3 & 0xff; } bool odd = false; while (val && (len < sizeof(buf))) { char c = *val++; if (!c) break; unsigned char n = 0; if (('0' <= c) && (c <= '9')) n = c - '0'; else if ('.' == c) n = 15; else if ('A' == c) n = 10; else if ('B' == c) n = 11; else if ('C' == c) n = 12; else if ('D' == c) n = 13; else if ('E' == c) n = 14; else continue; odd = !odd; if (odd) buf[len] = n; else buf[len++] |= (n << 4); } if (odd) { buf[naiPos] |= 0x80; len++; } buf[0] = (len-1) & 0xff; DDebug(DebugAll,"setDigits encoding %u octets (%s)",len,odd ? "odd" : "even"); DataBlock tmp(buf,len,false); msu += tmp; tmp.clear(false); return buf[0]; } // Encoder for variable length digit sequences static unsigned char encodeDigits(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList* extra, const String& prefix) { if (!param || buf || param->size) return 0; unsigned char nai = 2; unsigned char plan = 1; String preName; if (val) preName = val->name(); else preName = prefix + param->name; int b0 = -1; if (SS7MsgISUP::GenericNumber == param->type) { b0 = 0; if (val && extra) b0 = 0xff & extra->getIntValue(preName+".qualifier",s_dict_qual,0); } if (val && extra) { nai = extra->getIntValue(preName+".nature",s_dict_nai,nai); plan = extra->getIntValue(preName+".plan",s_dict_numPlan,plan); } unsigned char b2 = (plan & 7) << 4; switch (param->type) { case SS7MsgISUP::CalledPartyNumber: case SS7MsgISUP::RedirectionNumber: case SS7MsgISUP::LocationNumber: if (val && extra && !extra->getBoolValue(preName+".inn",true)) b2 |= 0x80; break; case SS7MsgISUP::CallingPartyNumber: case SS7MsgISUP::GenericNumber: if (val && extra && !extra->getBoolValue(preName+".complete",true)) b2 |= 0x80; break; case SS7MsgISUP::LastDivertingLineIdentity: case SS7MsgISUP::PresentationNumber: if (!val || !extra || extra->getBoolValue(preName+".pnp",true)) b2 |= 0x80; break; default: break; } switch (param->type) { case SS7MsgISUP::CallingPartyNumber: case SS7MsgISUP::RedirectingNumber: case SS7MsgISUP::OriginalCalledNumber: case SS7MsgISUP::LocationNumber: case SS7MsgISUP::ConnectedNumber: case SS7MsgISUP::GenericNumber: case SS7MsgISUP::LastDivertingLineIdentity: case SS7MsgISUP::PresentationNumber: case SS7MsgISUP::CalledINNumber: case SS7MsgISUP::OriginalCalledINNumber: if (val && extra) b2 |= (extra->getIntValue(preName+".restrict",s_dict_presentation) & 3) << 2; default: break; } switch (param->type) { case SS7MsgISUP::CallingPartyNumber: case SS7MsgISUP::LocationNumber: case SS7MsgISUP::ConnectedNumber: case SS7MsgISUP::GenericNumber: case SS7MsgISUP::LastDivertingLineIdentity: case SS7MsgISUP::PresentationNumber: if (val && extra) b2 |= extra->getIntValue(preName+".screened",s_dict_screening) & 3; default: break; } return setDigits(msu,val ? val->c_str() : 0,nai,b2,-1,b0); } // Special encoder for subsequent number static unsigned char encodeSubseq(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList*, const String&) { return setDigits(msu,val ? val->c_str() : 0,0); } // Encoder for circuit group range and status (Q.763 3.43) static unsigned char encodeRangeSt(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList* extra, const String& prefix) { if (!(param && val)) return 0; unsigned char data[34] = {1}; // 1st octet is the range code (range - 1) // Q.763 3.43 Sent range value must be in interval 1..256 unsigned int range = val->toInteger(0); if (range < 1 || range > 256) { Debug(isup,DebugNote,"encodeRangeSt invalid range %s=%s",val->name().c_str(),val->safe()); return 0; } data[1] = range - 1; // Next octets: status bits for the circuits given by range NamedString* map = extra->getParam(prefix+param->name+".map"); if (map && map->length()) { // Max status bits is 256. Relevant status bits: range unsigned int nBits = map->length(); if (nBits > 256) { Debug(isup,DebugNote,"encodeRangeSt truncating status bits %u to 256",map->length()); nBits = 256; } unsigned char* src = (unsigned char*)map->c_str(); unsigned char* dest = data + 1; for (unsigned char crtBit = 0; nBits; nBits--, src++) { if (!crtBit) { data[0]++; *++dest = 0; } if (*src != '0') *dest |= (1 << crtBit); crtBit = (crtBit < 7 ? crtBit + 1 : 0); } } // Copy to msu DDebug(isup,DebugAll,"encodeRangeSt encoding %s on %u octets",param->name,data[0]); DataBlock tmp(data,data[0] + 1,false); msu += tmp; tmp.clear(false); return data[0]; } // Encoder for generic notification indicators (Q.763 3.25) static unsigned char encodeNotif(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList*, const String&) { if (!(param && val) || buf || param->size) return 0; unsigned char notif[32]; unsigned int len = 0; ObjList* lst = val->split(',',false); ObjList* p = lst->skipNull(); for (; p; p = p->skipNext()) { const String* s = static_cast(p->get()); int v = s->toInteger((const TokenDict*)param->data,-1); if (v < 0) continue; notif[++len] = v & 0x7f; if (len >= sizeof(notif)-1) break; } TelEngine::destruct(lst); DDebug(isup,DebugAll,"encodeNotif encoding %s on %u octets",param->name,len); if (!len) return 0; notif[len] |= 0x80; notif[0] = len & 0xff; DataBlock tmp(notif,len+1,false); msu += tmp; tmp.clear(false); return notif[0]; } // Encoder for User Service Information (Q.763 3.57, Q.931) static unsigned char encodeUSI(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList* extra, const String& prefix) { if (!param) return 0; String preName; if (val) preName = val->name(); else preName = prefix + param->name; DataBlock tmp; SignallingUtils::encodeCaps(isup,tmp,*extra,preName,true); DDebug(isup,DebugAll,"encodeUSI encoding %s on %u octets",param->name,tmp.length()); if (tmp.length() < 1) return 0; msu += tmp; return tmp.length() - 1; } // Encoder for cause indicators static unsigned char encodeCause(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList* extra, const String& prefix) { if (!param) return 0; String preName; if (val) preName = val->name(); else preName = prefix + param->name; DataBlock tmp; SignallingUtils::encodeCause(isup,tmp,*extra,preName,true); DDebug(isup,DebugAll,"encodeCause encoding %s on %u octets",param->name,tmp.length()); if (tmp.length() < 1) return 0; msu += tmp; return tmp.length() - 1; } // Encoder for application transport parameter static unsigned char encodeAPT(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList* extra, const String& prefix) { if (!param) return 0; if (TelEngine::null(val)) { if (val) Debug(isup,DebugNote,"Failed to encode empty %s",val->name().c_str()); return 0; } int context = val->toInteger(-1); if (context < 0 || context > 127) { // Assume binary parameter representation DataBlock data; if (!(data.unHexify(val->c_str(),val->length(),' ') && data.length()) || data.length() < 4 || data.length() > 254) { Debug(isup,DebugNote,"Failed to encode invalid %s=%s", param->name,val->c_str()); return 0; } unsigned char len = data.length(); msu.append(&len,1); msu += data; return 1 + data.length(); } // WARNING: HACK - ApplicationTransport does not follow naming convention String preName(prefix + param->name); preName << "." << context; unsigned char hdr[4] = {0,(unsigned char)(0x80 | context),0x80,0xc0}; // c0: extension bit set, new sequence bit set // Retrieve data. Make sure all bytes are correct and final length don't // overflow our return value DataBlock data; const String& tmp = extra ? (*extra)[preName] : String::empty(); if (!(data.unHexify(tmp.c_str(),tmp.length(),' ') && data.length()) || data.length() > (255 - sizeof(hdr))) { Debug(isup,DebugNote,"Failed to encode invalid %s=%s", param->name,tmp.c_str()); return 0; } String indName(preName + ".indicators"); const String* inds = extra ? extra->getParam(indName) : 0; if (inds) { unsigned int v = SignallingUtils::encodeFlags(isup,*inds,s_flags_apt_indicators,indName); hdr[2] |= (v & 0x7f); } else { // Set default indicators value: send CNF, no call release hdr[2] |= 0x02; } hdr[0] = data.length() + 3; msu.append(hdr,sizeof(hdr)); msu += data; return hdr[0]; } // Encoder for Generic Name static unsigned char encodeName(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList* extra, const String& prefix) { if (!(param && val) || buf || param->size) return 0; unsigned int len = val->length() + 1; if (len >= 127) return 0; unsigned char gn[2] = { (unsigned char)len, 3 }; if (extra) { String preName; if (val) preName = val->name(); else preName = prefix + param->name; if (!extra->getBoolValue(preName+".available",true)) gn[1] |= 0x10; gn[1] = (gn[1] & 0x1f) | (extra->getIntValue(preName+".qualifier",s_dict_qual_name,gn[1] & 0xe0) & 0xe0); gn[1] = (gn[1] & 0xfc) | (extra->getIntValue(preName+".restrict",s_dict_presentation,gn[1] & 0x03) & 0x03); } DataBlock tmp(gn,2); tmp += *val; DDebug(isup,DebugAll,"encodeName encoding %s on %u octets",param->name,tmp.length()); msu += tmp; return len; } // Encoder for Redirection information (Q.763 3.45) static unsigned char encodeRedir(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf, const IsupParam* param, const NamedString* val, const NamedList* extra, const String& prefix) { if (!(param && val) || buf || param->size) return 0; unsigned char ri[3] = { 2, 0, 0 }; if (extra) { String preName; if (val) preName = val->name(); else preName = prefix + param->name; ri[1] = (extra->getIntValue(preName,s_dict_redir_main,0) & 0x07) | ((extra->getIntValue(preName+".reason_original",s_dict_redir_reason,0) & 0x0f) << 4); ri[2] = (extra->getIntValue(preName+".counter") & 0x07) | ((extra->getIntValue(preName+".reason",s_dict_redir_reason,0) & 0x0f) << 4); } DataBlock tmp(ri,3,false); msu += tmp; tmp.clear(false); return ri[0]; } // Nature of Connection Indicators (Q.763 3.35) static const SignallingFlags s_flags_naci[] = { // TODO: add more flags { 0x03, 0x00, "0sat" }, { 0x03, 0x01, "1sat" }, { 0x03, 0x02, "2sat" }, { 0x0c, 0x00, "cont-check-none" }, // Continuity check not required { 0x0c, 0x04, "cont-check-this" }, // Continuity check required on this circuit { 0x0c, 0x08, "cont-check-prev" }, // Continuity check performed on a previous circuit { 0x10, 0x10, "echodev" }, // Outgoing half echo control device included { 0, 0, 0 } }; // Forward Call Indicators (Q.763 3.23) static const SignallingFlags s_flags_fwcallind[] = { { 0x0001, 0x0000, "national" }, // National/international call indicator { 0x0001, 0x0001, "international" }, { 0x0006, 0x0000, "e2e-none" }, // End-to-end method indicator (none available: only link-by-link) { 0x0006, 0x0002, "e2e-pass" }, // Pass along method available { 0x0006, 0x0004, "e2e-sccp" }, // SCCP along method available { 0x0006, 0x0006, "e2e-pass-sccp" }, // Pass along and SCCP method available { 0x0008, 0x0008, "interworking" }, // Interworking indicator (0: SS7 all the way) { 0x0010, 0x0010, "e2e-info" }, // End-to-end information available { 0x0020, 0x0020, "isup-path" }, // ISUP indicator (ISUP used all the way) { 0x00c0, 0x0000, "isup-pref" }, // ISUP preference indicator: preferred all the way { 0x00c0, 0x0040, "isup-notreq" }, // not required all the way { 0x00c0, 0x0080, "isup-req" }, // required all the way { 0x0100, 0x0100, "isdn-orig" }, // Originating from ISDN { 0x0600, 0x0000, "sccp-none" }, // SCCP method indicator: no indication { 0x0600, 0x0200, "sccp-less" }, // connectionless method available { 0x0600, 0x0400, "sccp-conn" }, // connection oriented method available { 0x0600, 0x0600, "sccp-less-conn" }, // connectionless and connection oriented methods available { 0x1000, 0x1000, "translated" }, // Number Translated (for portability) { 0x2000, 0x2000, "qor-routing" }, // QoR routing attempt in progress { 0, 0, 0 } }; // Backward Call Indicators (Q.763 3.5) static const SignallingFlags s_flags_bkcallind[] = { { 0x0003, 0x0001, "no-charge" }, // Charge indicator { 0x0003, 0x0002, "charge" }, { 0x000c, 0x0004, "called-free" }, // Called party's status indicator: subscriber free { 0x000c, 0x0008, "called-conn" }, // Called party's status indicator: connect when free { 0x0030, 0x0010, "called-ordinary" }, // Called party's category indicator: ordinary subscriber { 0x0030, 0x0020, "called-payphone" }, // Called party's category indicator: payphone { 0x00c0, 0x0000, "e2e-none" }, // End-to-end method indicator (none available: only link-by-link) { 0x00c0, 0x0040, "e2e-pass" }, // Pass along method available { 0x00c0, 0x0080, "e2e-sccp" }, // SCCP along method available { 0x00c0, 0x00c0, "e2e-pass-sccp" }, // Pass along and SCCP method available { 0x0100, 0x0100, "interworking" }, // Interworking indicator (0: SS7 all the way) { 0x0200, 0x0200, "e2e-info" }, // End-to-end information available { 0x0400, 0x0400, "isup-path" }, // ISUP indicator (ISUP used all the way) { 0x0800, 0x0800, "hold-req" }, // Holding indicator: holding requested { 0x1000, 0x1000, "isdn-end" }, // Terminating in ISDN { 0x2000, 0x2000, "echodev" }, // Incoming half echo control device included { 0xc000, 0x0000, "sccp-none" }, // SCCP method indicator: no indication { 0xc000, 0x4000, "sccp-less" }, // connectionless method available { 0xc000, 0x8000, "sccp-conn" }, // connection oriented method available { 0xc000, 0xc000, "sccp-less-conn" }, // connectionless and connection oriented methods available { 0, 0, 0 } }; // Call Diversion Information (Q.763 3.6) static const SignallingFlags s_flags_calldivinfo[] = { { 0x07, 0x01, "presentation-not-allowed" }, { 0x07, 0x02, "presentation-with-number" }, { 0x07, 0x03, "presentation-without-number" }, { 0x78, 0x08, "busy" }, { 0x78, 0x10, "noanswer" }, { 0x78, 0x18, "always" }, { 0x78, 0x20, "deflected-alerting" }, { 0x78, 0x28, "deflected-immediate" }, { 0x78, 0x30, "offline" }, { 0, 0, 0 } }; // Optional Forward Call Indicators (Q.763 3.38) static const SignallingFlags s_flags_optfwcallind[] = { { 0x03, 0x00, "non-CUG" }, { 0x03, 0x02, "CUG+out" }, { 0x03, 0x03, "CUG" }, { 0x04, 0x04, "segmentation" }, // Additional info will be sent in a segmentation message { 0x80, 0x80, "CLIR-requested" }, { 0, 0, 0 } }; // Optional Backward Call Indicators (Q.763 3.37) static const SignallingFlags s_flags_optbkcallind[] = { { 0x01, 0x01, "inband" }, { 0x02, 0x02, "diversion-possible" }, { 0x04, 0x04, "segmentation" }, // Additional info will be sent in a segmentation message { 0x08, 0x08, "MLPP-user" }, { 0, 0, 0 } }; // Event Information (Q.763 3.21) static const SignallingFlags s_flags_eventinfo[] = { { 0x7f, 0x01, "ringing" }, { 0x7f, 0x02, "progress" }, { 0x7f, 0x03, "inband" }, { 0x7f, 0x04, "forward-busy" }, { 0x7f, 0x05, "forward-noanswer" }, { 0x7f, 0x06, "forward-always" }, { 0x80, 0x80, "restricted" }, { 0, 0, 0 } }; // Continuity Indicators (Q.763 3.18) static const SignallingFlags s_flags_continuity[] = { { 0x01, 0x00, "failed" }, { 0x01, 0x01, "success" }, { 0, 0, 0 } }; // Group Supervision Type Indicator (Q.763 3.13) static const SignallingFlags s_flags_grptypeind[] = { { 0x03, 0x00, "maintenance" }, { 0x03, 0x01, "hw-failure" }, { 0x03, 0x02, "national" }, { 0, 0, 0 } }; // Access Delivery Information (Q.763 3.2) static const SignallingFlags s_flags_accdelinfo[] = { { 0x01, 0x00, "setup-generated" }, // A Setup message was generated { 0x01, 0x01, "no-setup" }, // No Setup message generated { 0, 0, 0 } }; // MCID Request or Response Indicators (Q.763 3.31 and 3.32) static const SignallingFlags s_flags_mcid[] = { { 0x01, 0x01, "MCID" }, { 0x02, 0x02, "holding" }, { 0, 0, 0 } }; // ANSI Circuit Validation Response Indicator static const SignallingFlags s_flags_ansi_cvri[] = { { 0x03, 0x00, "failed" }, { 0x03, 0x01, "success" }, { 0, 0, 0 } }; // ANSI Circuit Group Characteristics Indicator static const SignallingFlags s_flags_ansi_cgci[] = { { 0x03, 0x00, "carrier-unknown" }, { 0x03, 0x01, "carrier-analog" }, { 0x03, 0x02, "carrier-digital" }, { 0x03, 0x03, "carrier-mixed" }, { 0x0c, 0x00, "seize-none" }, { 0x0c, 0x04, "seize-odd" }, { 0x0c, 0x08, "seize-even" }, { 0x0c, 0x0c, "seize-all" }, { 0x30, 0x00, "alarm-default" }, { 0x30, 0x10, "alarm-software" }, { 0x30, 0x20, "alarm-hardware" }, { 0xc0, 0x00, "continuity-unknown" }, { 0xc0, 0x40, "continuity-none" }, { 0xc0, 0x80, "continuity-statistical" }, { 0xc0, 0xc0, "continuity-call" }, { 0, 0, 0 } }; // National Forward Call Indicators (NICC ND 1007 2001 3.2.1) static const SignallingFlags s_flags_nfci[] = { { 0x0001, 0x0000, "cli-blocked" }, // CLI Blocking Indicator (CBI) { 0x0001, 0x0001, "cli-allowed" }, { 0x0002, 0x0002, "translated" }, // Network translated address indicator { 0x0004, 0x0004, "iup-priority" }, // Priority access indicator (IUP) { 0x0008, 0x0008, "iup-protected" }, // Protection indicator (IUP) { 0, 0, 0 } }; // Calling Party Category (Q.763 3.11) static const TokenDict s_dict_callerCat[] = { { "unknown", 0 }, // calling party's category is unknown { "operator-FR", 1 }, // operator, language French { "operator-EN", 2 }, // operator, language English { "operator-DE", 3 }, // operator, language German { "operator-RU", 4 }, // operator, language Russian { "operator-ES", 5 }, // operator, language Spanish { "ordinary", 10 }, // ordinary calling subscriber { "priority", 11 }, // calling subscriber with priority { "data", 12 }, // data call (voice band data) { "test", 13 }, // test call { "payphone", 15 }, // payphone { 0, 0 } }; // Transmission Medium Requirement (Q.763 3.54) static const TokenDict s_dict_mediumReq[] = { { "speech", 0 }, { "64kbit", 2 }, { "3.1khz-audio", 3 }, { "64kb-preferred", 6 }, { "2x64kbit", 7 }, { "384kbit", 8 }, { "1536kbit", 9 }, { "1920kbit", 10 }, { 0, 0 } }; // Generic Notification Indicator (Q.763 3.25) static const TokenDict s_dict_notifications[] = { { "user-suspended", 0x00 }, { "user-resumed", 0x01 }, { "bearer-service-change", 0x02 }, { "call-completion-delay", 0x04 }, { "conf-established", 0x42 }, { "conf-disconnected", 0x43 }, { "party-added", 0x44 }, { "isolated", 0x45 }, { "reattached", 0x46 }, { "party-isolated", 0x47 }, { "party-reattached", 0x48 }, { "party-split", 0x49 }, { "party-disconnected", 0x4a }, { "conf-floating", 0x4b }, { "call-waiting", 0x60 }, { "call-diversion", 0x68 }, { "call-transfer-alerting", 0x69 }, { "call-transfer-active", 0x6a }, { "remote-hold", 0x79 }, { "remote-retrieval", 0x7a }, { "call-diverting", 0x7b }, { 0, 0 } }; // Number Portability Forward Information (Q.763 3.101) static const TokenDict s_dict_portability[] = { { "not-queried", 1 }, { "called-not-ported", 2 }, { "called-ported", 3 }, { 0, 0 } }; // ANSI Originating Line Info static const TokenDict s_dict_oli[] = { { "normal", 0 }, { "multiparty", 1 }, { "ani-failure", 2 }, { "hotel-room-id", 6 }, { "coinless", 7 }, { "restricted", 8 }, { "test-call-1", 10 }, { "aiod-listed-dn", 20 }, { "identified-line", 23 }, { "800-call", 24 }, { "coin-line", 27 }, { "restricted-hotel", 68 }, { "test-call-2", 95 }, { 0, 0 } }; #define MAKE_PARAM(p,s,a,d,t) { SS7MsgISUP::p,s,#p,a,d,t } static const IsupParam s_paramDefs[] = { // name len decoder encoder table References // Standard parameters, references to ITU Q.763 MAKE_PARAM(AccessDeliveryInformation, 1,decodeFlags, encodeFlags, s_flags_accdelinfo), // 3.2 MAKE_PARAM(AccessTransport, 0,0, 0, 0), // 3.3 MAKE_PARAM(AutomaticCongestionLevel, 1,decodeInt, encodeInt, 0), // 3.4 MAKE_PARAM(BackwardCallIndicators, 2,decodeFlags, encodeFlags, s_flags_bkcallind), // 3.5 MAKE_PARAM(CallDiversionInformation, 1,decodeFlags, encodeFlags, s_flags_calldivinfo), // 3.6 MAKE_PARAM(CallHistoryInformation, 2,decodeInt, encodeInt, 0), // 3.7 MAKE_PARAM(CallReference, 0,0, 0, 0), // 3.8 MAKE_PARAM(CalledPartyNumber, 0,decodeDigits, encodeDigits, 0), // 3.9 MAKE_PARAM(CallingPartyNumber, 0,decodeDigits, encodeDigits, 0), // 3.10 MAKE_PARAM(CallingPartyCategory, 1,decodeInt, encodeInt, s_dict_callerCat), // 3.11 MAKE_PARAM(CauseIndicators, 0,decodeCause, encodeCause, 0), // 3.12 Q.850-2.1 MAKE_PARAM(GroupSupervisionTypeIndicator, 1,decodeFlags, encodeFlags, s_flags_grptypeind), // 3.13 MAKE_PARAM(CircuitStateIndicator, 0,0, 0, 0), // 3.14 MAKE_PARAM(CUG_InterlockCode, 0,0, 0, 0), // 3.15 MAKE_PARAM(ConnectedNumber, 0,decodeDigits, encodeDigits, 0), // 3.16 MAKE_PARAM(ConnectionRequest, 0,0, 0, 0), // 3.17 MAKE_PARAM(ContinuityIndicators, 1,decodeFlags, encodeFlags, s_flags_continuity), // 3.18 MAKE_PARAM(EchoControlInformation, 0,0, 0, 0), // 3.19 MAKE_PARAM(EventInformation, 1,decodeFlags, encodeFlags, s_flags_eventinfo), // 3.21 MAKE_PARAM(FacilityIndicator, 1,0, 0, 0), // 3.22 MAKE_PARAM(ForwardCallIndicators, 2,decodeFlags, encodeFlags, s_flags_fwcallind), // 3.23 MAKE_PARAM(GenericDigits, 0,0, 0, 0), // 3.24 MAKE_PARAM(GenericNotification, 0,decodeNotif, encodeNotif, s_dict_notifications), // 3.25 MAKE_PARAM(GenericNumber, 0,decodeDigits, encodeDigits, 0), // 3.26 MAKE_PARAM(GenericReference, 0,0, 0, 0), // 3.27 MAKE_PARAM(InformationIndicators, 2,0, 0, 0), // 3.28 MAKE_PARAM(InformationRequestIndicators, 2,0, 0, 0), // 3.29 MAKE_PARAM(LocationNumber, 0,decodeDigits, encodeDigits, 0), // 3.30 MAKE_PARAM(MCID_RequestIndicator, 1,decodeFlags, encodeFlags, s_flags_mcid), // 3.31 MAKE_PARAM(MCID_ResponseIndicator, 1,decodeFlags, encodeFlags, s_flags_mcid), // 3.32 MAKE_PARAM(MessageCompatInformation, 0,decodeCompat, 0, 0), // 3.33 MAKE_PARAM(NatureOfConnectionIndicators, 1,decodeFlags, encodeFlags, s_flags_naci), // 3.35 MAKE_PARAM(NetworkSpecificFacilities, 0,0, 0, 0), // 3.36 MAKE_PARAM(OptionalBackwardCallIndicators, 1,decodeFlags, encodeFlags, s_flags_optbkcallind), // 3.37 MAKE_PARAM(OptionalForwardCallIndicators, 1,decodeFlags, encodeFlags, s_flags_optfwcallind), // 3.38 MAKE_PARAM(OriginalCalledNumber, 0,decodeDigits, encodeDigits, 0), // 3.39 MAKE_PARAM(OriginationISCPointCode, 0,0, 0, 0), // 3.40 MAKE_PARAM(ParameterCompatInformation, 0,decodeCompat, 0, 0), // 3.41 MAKE_PARAM(PropagationDelayCounter, 2,decodeInt, encodeInt, 0), // 3.42 MAKE_PARAM(RangeAndStatus, 0,decodeRangeSt, encodeRangeSt, 0), // 3.43 MAKE_PARAM(RedirectingNumber, 0,decodeDigits, encodeDigits, 0), // 3.44 MAKE_PARAM(RedirectionInformation, 0,decodeRedir, encodeRedir, 0), // 3.45 MAKE_PARAM(RedirectionNumber, 0,decodeDigits, encodeDigits, 0), // 3.46 MAKE_PARAM(RedirectionNumberRestriction, 0,0, 0, 0), // 3.47 MAKE_PARAM(RemoteOperations, 0,0, 0, 0), // 3.48 MAKE_PARAM(ServiceActivation, 0,0, 0, 0), // 3.49 MAKE_PARAM(SignallingPointCode, 0,0, 0, 0), // 3.50 MAKE_PARAM(SubsequentNumber, 0,decodeSubseq, encodeSubseq, 0), // 3.51 MAKE_PARAM(SuspendResumeIndicators, 1,0, 0, 0), // 3.52 MAKE_PARAM(TransitNetworkSelection, 0,0, 0, 0), // 3.53 MAKE_PARAM(TransmissionMediumRequirement, 1,decodeInt, encodeInt, s_dict_mediumReq), // 3.54 MAKE_PARAM(TransMediumRequirementPrime, 1,decodeInt, encodeInt, s_dict_mediumReq), // 3.55 MAKE_PARAM(TransmissionMediumUsed, 1,decodeInt, encodeInt, s_dict_mediumReq), // 3.56 MAKE_PARAM(UserServiceInformation, 0,decodeUSI, encodeUSI, 0), // 3.57 Q.931-4.5.5 MAKE_PARAM(UserServiceInformationPrime, 0,0, 0, 0), // 3.58 MAKE_PARAM(UserTeleserviceInformation, 0,0, 0, 0), // 3.59 MAKE_PARAM(UserToUserIndicators, 0,0, 0, 0), // 3.60 MAKE_PARAM(UserToUserInformation, 0,0, 0, 0), // 3.61 MAKE_PARAM(CCSScallIndication, 1,0, 0, 0), // 3.63 MAKE_PARAM(ForwardGVNS, 0,0, 0, 0), // 3.66 MAKE_PARAM(BackwardGVNS, 0,0, 0, 0), // 3.62 MAKE_PARAM(CalledINNumber, 0,decodeDigits, encodeDigits, 0), // 3.73 MAKE_PARAM(UID_ActionIndicators, 0,0, 0, 0), // 3.78 MAKE_PARAM(UID_CapabilityIndicators, 0,0, 0, 0), // 3.79 MAKE_PARAM(RedirectCapability, 0,0, 0, 0), // 3.96 MAKE_PARAM(RedirectCounter, 0,0, 0, 0), // 3.97 MAKE_PARAM(CCNRpossibleIndicator, 0,0, 0, 0), // 3.83 MAKE_PARAM(PivotRoutingIndicators, 0,0, 0, 0), // 3.85 MAKE_PARAM(CalledDirectoryNumber, 0,0, 0, 0), // 3.86 MAKE_PARAM(OriginalCalledINNumber, 0,0, 0, 0), // 3.87 MAKE_PARAM(CallingGeodeticLocation, 0,0, 0, 0), // 3.88 MAKE_PARAM(HTR_Information, 0,0, 0, 0), // 3.89 MAKE_PARAM(NetworkRoutingNumber, 0,0, 0, 0), // 3.90 MAKE_PARAM(QueryOnReleaseCapability, 0,0, 0, 0), // 3.91 MAKE_PARAM(PivotStatus, 0,0, 0, 0), // 3.92 MAKE_PARAM(PivotCounter, 0,0, 0, 0), // 3.93 MAKE_PARAM(PivotRoutingForwardInformation, 0,0, 0, 0), // 3.94 MAKE_PARAM(PivotRoutingBackInformation, 0,0, 0, 0), // 3.95 MAKE_PARAM(RedirectStatus, 0,0, 0, 0), // 3.98 MAKE_PARAM(RedirectForwardInformation, 0,0, 0, 0), // 3.99 MAKE_PARAM(RedirectBackwardInformation, 0,0, 0, 0), // 3.100 MAKE_PARAM(NumberPortabilityInformation, 0,decodeNotif, encodeNotif, s_dict_portability), // 3.101 // No references MAKE_PARAM(ApplicationTransport, 0,decodeAPT, encodeAPT, 0), // 3.82 MAKE_PARAM(BusinessGroup, 0,0, 0, 0), // MAKE_PARAM(CallModificationIndicators, 0,0, 0, 0), // MAKE_PARAM(CarrierIdentification, 0,0, 0, 0), // MAKE_PARAM(CircuitIdentificationName, 0,0, 0, 0), // MAKE_PARAM(CarrierSelectionInformation, 0,0, 0, 0), // MAKE_PARAM(ChargeNumber, 0,0, 0, 0), // MAKE_PARAM(CircuitAssignmentMap, 0,0, 0, 0), // MAKE_PARAM(CircuitGroupCharactIndicator, 1,decodeFlags, encodeFlags, s_flags_ansi_cgci), // T1.113 ?? MAKE_PARAM(CircuitValidationRespIndicator, 1,decodeFlags, encodeFlags, s_flags_ansi_cvri), // T1.113 ?? MAKE_PARAM(CommonLanguage, 0,0, 0, 0), // MAKE_PARAM(CUG_CheckResponseIndicators, 0,0, 0, 0), // MAKE_PARAM(Egress, 0,0, 0, 0), // MAKE_PARAM(FacilityInformationIndicators, 0,0, 0, 0), // MAKE_PARAM(FreephoneIndicators, 0,0, 0, 0), // MAKE_PARAM(GenericName, 0,decodeName, encodeName, 0), // MAKE_PARAM(HopCounter, 1,decodeInt, encodeInt, 0), // 3.80 MAKE_PARAM(Index, 0,0, 0, 0), // MAKE_PARAM(Jurisdiction, 0,0, 0, 0), // MAKE_PARAM(MLPP_Precedence, 0,0, 0, 0), // MAKE_PARAM(NetworkTransport, 0,0, 0, 0), // MAKE_PARAM(NotificationIndicator, 0,0, 0, 0), // MAKE_PARAM(OperatorServicesInformation, 0,0, 0, 0), // MAKE_PARAM(OriginatingLineInformation, 1,decodeInt, encodeInt, s_dict_oli), // MAKE_PARAM(OutgoingTrunkGroupNumber, 0,0, 0, 0), // MAKE_PARAM(Precedence, 0,0, 0, 0), // MAKE_PARAM(ServiceCodeIndicator, 0,0, 0, 0), // MAKE_PARAM(SpecialProcessingRequest, 0,0, 0, 0), // MAKE_PARAM(TransactionRequest, 0,0, 0, 0), // // National use (UK-ISUP), references to NICC ND 1007 2001/07 MAKE_PARAM(NationalForwardCallIndicators, 2,decodeFlags, encodeFlags, s_flags_nfci), // 3.2.1 MAKE_PARAM(NationalForwardCallIndicatorsLinkByLink,0,0, 0, 0), // 3.2.2 MAKE_PARAM(PresentationNumber, 0,decodeDigits, encodeDigits, 0), // 3.2.3 MAKE_PARAM(LastDivertingLineIdentity, 0,decodeDigits, encodeDigits, 0), // 3.2.4 MAKE_PARAM(PartialCLI, 0,0, 0, 0), // 3.2.5 MAKE_PARAM(CalledSubscribersBasicServiceMarks, 0,0, 0, 0), // 3.2.6 MAKE_PARAM(CallingSubscribersBasicServiceMarks, 0,0, 0, 0), // 3.2.7 MAKE_PARAM(CallingSubscribersOriginatingFacilMarks,0,0, 0, 0), // 3.2.8 MAKE_PARAM(CalledSubscribersTerminatingFacilMarks, 0,0, 0, 0), // 3.2.9 MAKE_PARAM(NationalInformationRequestIndicators, 0,0, 0, 0), // 3.2.10 MAKE_PARAM(NationalInformationIndicators, 0,0, 0, 0), // 3.2.11 { SS7MsgISUP::EndOfParameters, 0, 0, 0, 0, 0 } }; #undef MAKE_PARAM const char* getIsupParamName(unsigned char type) { for (unsigned int i = 0; s_paramDefs[i].type; i++) if (type == s_paramDefs[i].type) return s_paramDefs[i].name; return 0; } // Descriptor of ISUP message common across standards static const MsgParams s_common_params[] = { // call progress and release messages { SS7MsgISUP::ACM, true, { SS7MsgISUP::BackwardCallIndicators, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CON, true, { SS7MsgISUP::BackwardCallIndicators, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::ANM, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::REL, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::CauseIndicators, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::RLC, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::SAM, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::SubsequentNumber, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CPR, true, { SS7MsgISUP::EventInformation, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CNF, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::CauseIndicators, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::SUS, true, { SS7MsgISUP::SuspendResumeIndicators, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::RES, true, { SS7MsgISUP::SuspendResumeIndicators, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::INR, true, { SS7MsgISUP::InformationRequestIndicators, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::INF, true, { SS7MsgISUP::InformationIndicators, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, // circuit group reset and acknowledgement { SS7MsgISUP::GRS, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::RangeAndStatus, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::GRA, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::RangeAndStatus, SS7MsgISUP::EndOfParameters } }, // circuit group query { SS7MsgISUP::CQM, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::RangeAndStatus, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CQR, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::RangeAndStatus, SS7MsgISUP::CircuitStateIndicator, SS7MsgISUP::EndOfParameters } }, // circuit group blocking, unblocking and acknowledgement { SS7MsgISUP::CGB, false, { SS7MsgISUP::GroupSupervisionTypeIndicator, SS7MsgISUP::EndOfParameters, SS7MsgISUP::RangeAndStatus, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CGA, false, { SS7MsgISUP::GroupSupervisionTypeIndicator, SS7MsgISUP::EndOfParameters, SS7MsgISUP::RangeAndStatus, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CGU, false, { SS7MsgISUP::GroupSupervisionTypeIndicator, SS7MsgISUP::EndOfParameters, SS7MsgISUP::RangeAndStatus, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CUA, false, { SS7MsgISUP::GroupSupervisionTypeIndicator, SS7MsgISUP::EndOfParameters, SS7MsgISUP::RangeAndStatus, SS7MsgISUP::EndOfParameters } }, // circuit related messages - most without parameters, only CIC { SS7MsgISUP::BLK, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::BLA, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::UBL, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::UBA, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CCR, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::LPA, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::OLM, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::RSC, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::UEC, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::COT, false, { SS7MsgISUP::ContinuityIndicators, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, // user part test and response { SS7MsgISUP::UPT, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::UPA, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, // application transport { SS7MsgISUP::APM, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, // facility { SS7MsgISUP::FACR, true, { SS7MsgISUP::FacilityIndicator, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::FAA, true, { SS7MsgISUP::FacilityIndicator, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::FRJ, true, { SS7MsgISUP::FacilityIndicator, SS7MsgISUP::EndOfParameters, SS7MsgISUP::CauseIndicators, SS7MsgISUP::EndOfParameters } }, // miscellaneous { SS7MsgISUP::USR, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::UserToUserInformation, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::Unknown, false, { SS7MsgISUP::EndOfParameters } } }; // Descriptor of ITU-T version of ISUP messages static const MsgParams s_itu_params[] = { { SS7MsgISUP::IAM, true, { SS7MsgISUP::NatureOfConnectionIndicators, SS7MsgISUP::ForwardCallIndicators, SS7MsgISUP::CallingPartyCategory, SS7MsgISUP::TransmissionMediumRequirement, SS7MsgISUP::EndOfParameters, SS7MsgISUP::CalledPartyNumber, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::Unknown, false, { SS7MsgISUP::EndOfParameters } } }; // Descriptor of ANSI version of ISUP messages static const MsgParams s_ansi_params[] = { { SS7MsgISUP::IAM, true, { SS7MsgISUP::NatureOfConnectionIndicators, SS7MsgISUP::ForwardCallIndicators, SS7MsgISUP::CallingPartyCategory, SS7MsgISUP::EndOfParameters, SS7MsgISUP::UserServiceInformation, SS7MsgISUP::CalledPartyNumber, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::RLC, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::EXM, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CVT, false, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::CVR, true, { SS7MsgISUP::CircuitValidationRespIndicator, SS7MsgISUP::CircuitGroupCharactIndicator, SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }, { SS7MsgISUP::Unknown, false, { SS7MsgISUP::EndOfParameters } } }; // Descriptor for decoding of compatibility parameters of unsupported messages // with only optional parameters (all new messages should be like this) static const MsgParams s_compatibility = { SS7MsgISUP::Unknown, true, { SS7MsgISUP::EndOfParameters, SS7MsgISUP::EndOfParameters } }; // Generic decode helper function for a single parameter static bool decodeParam(const SS7ISUP* isup, NamedList& list, const IsupParam* param, const unsigned char* buf, unsigned int len, const String& prefix) { DDebug(isup,DebugAll,"decodeParam(%p,%p,%p,%u) type=0x%02x, size=%u, name='%s'", &list,param,buf,len,param->type,param->size,param->name); if (param->size && (param->size != len)) return false; if (param->decoder) return param->decoder(isup,list,param,buf,len,prefix); return decodeRaw(isup,list,param,buf,len,prefix); } // Generic encode helper function for a single mandatory parameter static unsigned char encodeParam(const SS7ISUP* isup, SS7MSU& msu, const IsupParam* param, const NamedList* params, ObjList& exclude, const String& prefix, unsigned char* buf = 0) { DDebug(isup,DebugAll,"encodeParam (mand) (%p,%p,%p,%p) type=0x%02x, size=%u, name='%s'", &msu,param,params,buf,param->type,param->size,param->name); // variable length must not receive fixed buffer if (buf && !param->size) return 0; NamedString* val = params ? params->getParam(prefix+param->name) : 0; if (val) exclude.append(val)->setDelete(false); if (param->encoder) return param->encoder(isup,msu,buf,param,val,params,prefix); return encodeRaw(isup,msu,buf,param,val,params,prefix); } // Generic encode helper for a single optional parameter static unsigned char encodeParam(const SS7ISUP* isup, SS7MSU& msu, const IsupParam* param, const NamedString* val, const NamedList* extra, const String& prefix) { DDebug(isup,DebugAll,"encodeParam (opt) (%p,%p,%p,%p) type=0x%02x, size=%u, name='%s'", &msu,param,val,extra,param->type,param->size,param->name); // add the parameter type now but remember the old length unsigned int len = msu.length(); unsigned char tmp = param->type; msu.append(&tmp,1); unsigned char size = 0; if (param->encoder) size = param->encoder(isup,msu,0,param,val,extra,prefix); else size = encodeRaw(isup,msu,0,param,val,extra,prefix); if (!size) { Debug(isup,DebugMild,"Unwinding type storage for failed parameter %s",param->name); msu.truncate(len); } return size; } // Locate the description for a parameter by type static const IsupParam* getParamDesc(SS7MsgISUP::Parameters type) { const IsupParam* param = s_paramDefs; for (; param->type != SS7MsgISUP::EndOfParameters; param++) { if (param->type == type) return param; } return 0; } // Locate the description for a parameter by name static const IsupParam* getParamDesc(const String& name) { const IsupParam* param = s_paramDefs; for (; param->type != SS7MsgISUP::EndOfParameters; param++) { if (name == param->name) return param; } return 0; } // Locate the description table for a message according to protocol type static const MsgParams* getIsupParams(SS7PointCode::Type type, SS7MsgISUP::Type msg) { const MsgParams* params = 0; switch (type) { case SS7PointCode::ITU: params = s_itu_params; break; case SS7PointCode::ANSI: case SS7PointCode::ANSI8: params = s_ansi_params; break; default: return 0; } // search first in specific table for (; params->type != SS7MsgISUP::Unknown; params++) { if (params->type == msg) return params; } // then search in common table for (params = s_common_params; params->type != SS7MsgISUP::Unknown; params++) { if (params->type == msg) return params; } return 0; } // Hexify a list of isup parameter values/names static void hexifyIsupParams(String& s, const String& list) { if (!list) return; ObjList* l = list.split(',',false); unsigned int len = l->count(); if (len) { unsigned char* buf = new unsigned char[len]; len = 0; for (ObjList* o = l->skipNull(); o; o = o->skipNext()) { String* str = static_cast(o->get()); int val = str->toInteger(-1); if (val < 0) { const IsupParam* p = getParamDesc(*str); if (p) val = p->type; } if (val >= 0 && val < 256) { // avoid duplicates for (unsigned int i = 0; i < len; i++) { if ((unsigned char)val == buf[i]) { val = -1; break; } } if (val >= 0) buf[len++] = (unsigned char)val; } } if (len) s.hexify(buf,len,' '); delete[] buf; } TelEngine::destruct(l); } // Check if an unhandled messages has only optional parameters #define MAKE_CASE(x) case SS7MsgISUP::x: static bool hasOptionalOnly(SS7MsgISUP::Type msg) { switch (msg) { MAKE_CASE(IAM) MAKE_CASE(SAM) MAKE_CASE(INR) MAKE_CASE(INF) MAKE_CASE(COT) MAKE_CASE(ACM) MAKE_CASE(CON) MAKE_CASE(REL) MAKE_CASE(SUS) MAKE_CASE(RES) MAKE_CASE(CCR) MAKE_CASE(RSC) MAKE_CASE(BLK) MAKE_CASE(UBL) MAKE_CASE(BLA) MAKE_CASE(UBA) MAKE_CASE(GRS) MAKE_CASE(CGB) MAKE_CASE(CGU) MAKE_CASE(CGA) MAKE_CASE(CUA) MAKE_CASE(FACR) MAKE_CASE(FAA) MAKE_CASE(FRJ) MAKE_CASE(LPA) MAKE_CASE(PAM) MAKE_CASE(GRA) MAKE_CASE(CQM) MAKE_CASE(CQR) MAKE_CASE(CPR) MAKE_CASE(USR) MAKE_CASE(UEC) MAKE_CASE(CNF) MAKE_CASE(OLM) return false; default: return true; } } #undef MAKE_CASE #define MAKE_NAME(x) { #x, SS7MsgISUP::x } static const TokenDict s_names[] = { // this list must be kept in synch with the header MAKE_NAME(IAM), MAKE_NAME(SAM), MAKE_NAME(INR), MAKE_NAME(INF), MAKE_NAME(COT), MAKE_NAME(ACM), MAKE_NAME(CON), MAKE_NAME(FOT), MAKE_NAME(ANM), MAKE_NAME(REL), MAKE_NAME(SUS), MAKE_NAME(RES), MAKE_NAME(RLC), MAKE_NAME(CCR), MAKE_NAME(RSC), MAKE_NAME(BLK), MAKE_NAME(UBL), MAKE_NAME(BLA), MAKE_NAME(UBA), MAKE_NAME(GRS), MAKE_NAME(CGB), MAKE_NAME(CGU), MAKE_NAME(CGA), MAKE_NAME(CGBA), // alias MAKE_NAME(CUA), MAKE_NAME(CMR), MAKE_NAME(CMC), MAKE_NAME(CMRJ), MAKE_NAME(FACR), MAKE_NAME(FAA), MAKE_NAME(FRJ), MAKE_NAME(FAD), MAKE_NAME(FAI), MAKE_NAME(LPA), MAKE_NAME(CSVR), MAKE_NAME(CSVS), MAKE_NAME(DRS), MAKE_NAME(PAM), MAKE_NAME(GRA), MAKE_NAME(CQM), MAKE_NAME(CQR), MAKE_NAME(CPR), MAKE_NAME(CPG), // alias MAKE_NAME(USR), MAKE_NAME(UEC), MAKE_NAME(UCIC), // alias MAKE_NAME(CNF), MAKE_NAME(OLM), MAKE_NAME(CRG), MAKE_NAME(NRM), MAKE_NAME(FAC), MAKE_NAME(UPT), MAKE_NAME(UPA), MAKE_NAME(IDR), MAKE_NAME(IRS), MAKE_NAME(SGM), MAKE_NAME(LOP), MAKE_NAME(APM), MAKE_NAME(PRI), MAKE_NAME(SDN), MAKE_NAME(CRA), MAKE_NAME(CRM), MAKE_NAME(CVR), MAKE_NAME(CVT), MAKE_NAME(EXM), { 0, 0 } }; #undef MAKE_NAME const TokenDict* SS7MsgISUP::names() { return s_names; } void SS7MsgISUP::toString(String& dest, const SS7Label& label, bool params, const void* raw, unsigned int rawLen) const { const char* enclose = "\r\n-----"; dest = enclose; dest << "\r\n" << name() << " [cic=" << m_cic << " label=" << label << ']'; if (raw && rawLen) { String tmp; tmp.hexify((void*)raw,rawLen,' '); dest << " " << tmp; } if (params) { unsigned int n = m_params.length(); for (unsigned int i = 0; i < n; i++) { NamedString* s = m_params.getParam(i); if (s) dest << "\r\n " << s->name() << "='" << *s << "'"; } } dest << enclose; } /** * Helper functions used to transmit responses */ // Push down the protocol stack a RLC (Release Complete) message // @param msg Optional received message to copy release parameters. Ignored if reason is valid static int transmitRLC(SS7ISUP* isup, unsigned int cic, const SS7Label& label, bool recvLbl, const char* reason = 0, const char* diagnostic = 0, const char* location = 0) { SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::RLC,cic); if (!TelEngine::null(reason)) { m->params().addParam("CauseIndicators",reason); m->params().addParam("CauseIndicators.location",location,false); m->params().addParam("CauseIndicators.diagnostic",diagnostic,false); } return isup->transmitMessage(m,label,recvLbl); } // Push down the protocol stack a CNF (Confusion) message static int transmitCNF(SS7ISUP* isup, unsigned int cic, const SS7Label& label, bool recvLbl, const char* reason, const char* diagnostic = 0, const char* location = 0) { SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CNF,cic); if (reason) m->params().addParam("CauseIndicators",reason); if (!location) location = isup->location(); m->params().addParam("CauseIndicators.location",location,false); m->params().addParam("CauseIndicators.diagnostic",diagnostic,false); return isup->transmitMessage(m,label,recvLbl); } // Utility used to check for called number completion static inline bool isCalledIncomplete(const NamedList& l, const String& p = "CalledPartyNumber") { return !l[p].endsWith("."); } // Fill call release or cnf flags from message compatibility info static void getMsgCompat(SS7MsgISUP* msg, bool& release, bool& cnf) { if (!msg) return; String* msgCompat = msg->params().getParam(YSTRING("MessageCompatInformation")); if (msgCompat) { ObjList* l = msgCompat->split(',',false); // Use a while() to make sure the list is destroyed do { release = (0 != l->find("release")); if (release) break; // Discard the message (no pass on). Check if CNF should be sent if (l->find("discard")) { cnf = (0 != l->find("cnf")); break; } // Pass on set: we didn't passed on the message. Check REL/CNF release = (0 != l->find("nopass-release")); cnf = !release && l->find("cnf"); } while(false); TelEngine::destruct(l); } else cnf = true; } static void setCallsTerminate(ObjList& lst, bool gracefully, const char* reason = 0, const char* diagnostic = 0, const char* location = 0) { for (ObjList* o = lst.skipNull(); o; o = o->skipNext()) { SS7ISUPCall* call = static_cast(o->get()); call->setTerminate(gracefully,reason,diagnostic,location); } } /** * SS7ISUPCall */ SS7ISUPCall::SS7ISUPCall(SS7ISUP* controller, SignallingCircuit* cic, const SS7PointCode& local, const SS7PointCode& remote, bool outgoing, int sls, const char* range, bool testCall) : SignallingCall(controller,outgoing), m_state(Null), m_testCall(testCall), m_circuit(cic), m_cicRange(range), m_terminate(false), m_gracefully(true), m_circuitChanged(false), m_circuitTesting(false), m_inbandAvailable(false), m_replaceCounter(3), m_iamMsg(0), m_sgmMsg(0), m_relMsg(0), m_sentSamDigits(0), m_relTimer(300000), // Q.764: T5 - 5..15 minutes m_iamTimer(ISUP_T7_DEFVAL), // Setup, Testing: Q.764: T7 - 20..30 seconds // Releasing: Q.764: T1: 15..60 seconds m_sgmRecvTimer(ISUP_T34_DEFVAL), // Q.764: T34 - 2..4 seconds m_contTimer(ISUP_T27_DEFVAL), // Q.764: T27 - 4 minutes m_anmTimer(0) // Q.764 T9 Q.118: 1.5 - 3 minutes, not always used { if (!(controller && m_circuit)) { Debug(isup(),DebugWarn, "SS7ISUPCall(%u). No call controller or circuit. Terminate [%p]", id(),this); setTerminate(true,m_circuit ? "temporary-failure" : "congestion"); return; } isup()->setLabel(m_label,local,remote,sls); if (isup()->m_t7Interval) m_iamTimer.interval(isup()->m_t7Interval); if (isup()->m_t9Interval) m_anmTimer.interval(isup()->m_t9Interval); if (isup()->m_t27Interval) m_contTimer.interval(isup()->m_t27Interval); if (isup()->m_t34Interval) m_sgmRecvTimer.interval(isup()->m_t34Interval); m_replaceCounter = isup()->m_replaceCounter; if (isup()->debugAt(DebugAll)) { String tmp; tmp << m_label; Debug(isup(),DebugAll,"Call(%u) direction=%s routing-label=%s range=%s [%p]", id(),(outgoing ? "outgoing" : "incoming"),tmp.c_str(),m_cicRange.safe(),this); } } SS7ISUPCall::~SS7ISUPCall() { TelEngine::destruct(m_iamMsg); TelEngine::destruct(m_sgmMsg); const char* timeout = 0; if (m_relTimer.started()) timeout = " (release timed out)"; else if (m_contTimer.started()) timeout = " (T27 timed out)"; releaseComplete(true,0,0,0 != timeout); Debug(isup(),!timeout ? DebugAll : DebugNote, "Call(%u) destroyed with reason='%s'%s [%p]", id(),m_reason.safe(),TelEngine::c_safe(timeout),this); TelEngine::destruct(m_relMsg); if (controller()) { if (!timeout) controller()->releaseCircuit(m_circuit); else isup()->startCircuitReset(m_circuit,m_relTimer.started() ? "T5" : "T16"); } else TelEngine::destruct(m_circuit); } // Stop waiting for a SGM (Segmentation) message when another message is // received by the controller void SS7ISUPCall::stopWaitSegment(bool discard) { Lock mylock(this); if (!m_sgmMsg) return; m_sgmRecvTimer.stop(); if (discard) TelEngine::destruct(m_sgmMsg); } // Helper functions called in getEvent inline static bool timeout(SS7ISUP* isup, SS7ISUPCall* call, SignallingTimer& timer, const Time& when, const char* req, bool stop = true) { if (!timer.timeout(when.msec())) return false; if (stop) timer.stop(); Debug(isup,DebugNote,"Call(%u). %s timed out [%p]",call->id(),req,call); return true; } // Get an event from this call SignallingEvent* SS7ISUPCall::getEvent(const Time& when) { Lock mylock(this,SignallingEngine::maxLockWait()); if (m_lastEvent || m_state == Released || !mylock.locked()) return 0; SS7MsgISUP* msg = 0; while (true) { if (m_terminate) { if (m_state < Releasing && m_state > Null) if (m_gracefully) m_lastEvent = release(); else m_lastEvent = releaseComplete(false,0); else if (m_state == Null || m_state == Released) { m_gracefully = false; m_lastEvent = releaseComplete(false,0); } m_terminate = false; break; } // Check if waiting for SGM // Stop if: timeout, the controller stopped the timer or received a message other then SGM if (m_sgmMsg) { msg = static_cast(dequeue(false)); if (!msg && !m_sgmRecvTimer.timeout(when.msec()) && m_sgmRecvTimer.started()) return 0; msg = ((msg && msg->type() == SS7MsgISUP::SGM) ? static_cast(dequeue()) : 0); processSegmented(msg,m_sgmRecvTimer.timeout(when.msec())); break; } // Process received messages msg = static_cast(dequeue()); if (msg && validMsgState(false,msg->type(),(msg->params().getParam(YSTRING("BackwardCallIndicators")) != 0))) switch (msg->type()) { case SS7MsgISUP::IAM: case SS7MsgISUP::CCR: case SS7MsgISUP::COT: case SS7MsgISUP::ACM: case SS7MsgISUP::EXM: case SS7MsgISUP::CPR: case SS7MsgISUP::ANM: case SS7MsgISUP::CON: case SS7MsgISUP::CRG: m_sgmMsg = msg; { const char* sgmParam = "OptionalBackwardCallIndicators"; if (msg->type() == SS7MsgISUP::IAM) { copyParamIAM(msg); setOverlapped(isCalledIncomplete(msg->params())); sgmParam = "OptionalForwardCallIndicators"; } // Check segmentation. Keep the message and start timer if segmented if (SignallingUtils::hasFlag(msg->params(),sgmParam,"segmentation")) { m_sgmRecvTimer.start(when.msec()); return 0; } } msg = 0; processSegmented(0,false); break; case SS7MsgISUP::SAM: setOverlapped(isCalledIncomplete(msg->params(),"SubsequentNumber")); msg->params().addParam("tone",msg->params().getValue(YSTRING("SubsequentNumber"))); msg->params().addParam("dialing",String::boolText(true)); m_lastEvent = new SignallingEvent(SignallingEvent::Info,msg,this); break; case SS7MsgISUP::RLC: m_gracefully = false; if (m_state < Releasing) { setReason(0,msg); m_location = isup()->location(); m_lastEvent = release(0,msg); } else { m_relTimer.stop(); m_lastEvent = releaseComplete(false,msg); } break; case SS7MsgISUP::REL: if (m_state < Releasing) { m_relTimer.stop(); m_lastEvent = releaseComplete(false,msg); } else transmitRLC(isup(),msg->cic(),m_label,false); break; case SS7MsgISUP::SGM: DDebug(isup(),DebugInfo,"Call(%u). Received late 'SGM' [%p]",id(),this); break; case SS7MsgISUP::SUS: m_lastEvent = new SignallingEvent(SignallingEvent::Suspend,msg,this); break; case SS7MsgISUP::RES: m_lastEvent = new SignallingEvent(SignallingEvent::Resume,msg,this); break; case SS7MsgISUP::APM: m_lastEvent = new SignallingEvent(SignallingEvent::Generic,msg,this); break; default: ; Debug(isup(),DebugStub,"Call(%u). Unhandled '%s' message in getEvent() [%p]", id(),msg->name(),this); } break; } if (msg) msg->deref(); // No events: check timeouts if (!m_lastEvent) { switch (m_state) { case Testing: case Setup: if (timeout(isup(),this,m_iamTimer,when,"IAM")) { m_contTimer.stop(); if (m_circuitTesting) { if (m_iamMsg) setReason("bearer-cap-not-available",0); else { setTerminate(true,"bearer-cap-not-available"); break; } } else setReason("timeout",0); m_lastEvent = release(); break; } if (timeout(isup(),this,m_contTimer,when,"T27",false)) { m_gracefully = false; m_lastEvent = releaseComplete(false,0,0,true); } break; case Releasing: if (timeout(isup(),this,m_relTimer,when,"REL",false)) m_lastEvent = releaseComplete(false,0,"noresponse",true); else if (timeout(isup(),this,m_iamTimer,when,"T1")) { m_iamTimer.stop(); m_iamTimer.start(when.msec()); transmitREL(); } break; default: if (outgoing() && m_anmTimer.started() && m_state >= Accepted && m_state < Answered && timeout(isup(),this,m_anmTimer,when,"T9")) { setReason("noresponse",0,0,isup()->location()); m_lastEvent = release(); } } } // Reset overlapped if our state is greater then Setup if (m_state > Setup) setOverlapped(false,false); // Check circuit event if (!m_lastEvent && m_circuit) { SignallingCircuitEvent* cicEvent = m_circuit->getEvent(when); if (cicEvent) { if (isup()) m_lastEvent = isup()->processCircuitEvent(cicEvent,this); TelEngine::destruct(cicEvent); } } if (m_lastEvent) XDebug(isup(),DebugNote,"Call(%u). Raising event (%p,'%s') [%p]", id(),m_lastEvent,m_lastEvent->name(),this); return m_lastEvent; } // Helper that copies all parameters starting with a capital letter static void copyUpper(NamedList& dest, const NamedList& src) { static const Regexp r("^[A-Z][A-Za-z0-9_.]\\+$"); unsigned int n = src.length(); for (unsigned int i = 0; i < n; i++) { const NamedString* p = src.getParam(i); if (!p || !r.matches(p->name())) continue; dest.setParam(p->name(),*p); } } // Send an event to this call bool SS7ISUPCall::sendEvent(SignallingEvent* event) { Lock mylock(this); if (!event) return false; if (m_terminate || m_state == Released) { mylock.drop(); delete event; return false; } bool result = false; switch (event->type()) { case SignallingEvent::NewCall: if (validMsgState(true,SS7MsgISUP::IAM)) { if (!event->message()) { DDebug(isup(),DebugNote, "Call(%u). No parameters for outgoing call [%p]",id(),this); setTerminate(true,"temporary-failure"); break; } m_iamMsg = new SS7MsgISUP(SS7MsgISUP::IAM,id()); copyParamIAM(m_iamMsg,true,event->message()); // Update overlap String* called = m_iamMsg->params().getParam(YSTRING("CalledPartyNumber")); if (called && (called->length() > isup()->m_maxCalledDigits)) { // Longer than maximum digits allowed - send remainder with SAM m_samDigits = called->substr(isup()->m_maxCalledDigits); *called = called->substr(0,isup()->m_maxCalledDigits); setOverlapped(true); } else setOverlapped(isCalledIncomplete(m_iamMsg->params())); result = transmitIAM(); } break; case SignallingEvent::Progress: case SignallingEvent::Ringing: if (validMsgState(true,SS7MsgISUP::CPR)) { SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CPR,id()); m->params().addParam("EventInformation", event->type() == SignallingEvent::Ringing ? "ringing": "progress"); bool inband = m_inbandAvailable; if (event->message()) { copyUpper(m->params(),event->message()->params()); m_inbandAvailable = m_inbandAvailable || event->message()->params().getBoolValue(YSTRING("earlymedia")); inband = event->message()->params().getBoolValue(YSTRING("send-inband"),m_inbandAvailable); } if (inband && !outgoing()) SignallingUtils::appendFlag(m->params(),"OptionalBackwardCallIndicators","inband"); m_state = Ringing; mylock.drop(); result = transmitMessage(m); } break; case SignallingEvent::Accept: if (validMsgState(true,SS7MsgISUP::ACM)) { SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::ACM,id()); bool inband = m_inbandAvailable; if (event->message()) { copyUpper(m->params(),event->message()->params()); m_inbandAvailable = m_inbandAvailable || event->message()->params().getBoolValue(YSTRING("earlymedia")); inband = event->message()->params().getBoolValue(YSTRING("send-inband"),m_inbandAvailable); } if (inband && !outgoing()) SignallingUtils::appendFlag(m->params(),"OptionalBackwardCallIndicators","inband"); m_state = Accepted; mylock.drop(); result = transmitMessage(m); } break; case SignallingEvent::Answer: if (validMsgState(true,SS7MsgISUP::ANM)) { SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::ANM,id()); if (event->message()) copyUpper(m->params(),event->message()->params()); m_state = Answered; mylock.drop(); result = transmitMessage(m); } break; case SignallingEvent::Release: if (validMsgState(true,SS7MsgISUP::REL)) { release(event); result = true; } break; case SignallingEvent::Generic: if (event->message()) { const String& oper = event->message()->params()[YSTRING("operation")]; if (oper == "charge") { if (!validMsgState(true,SS7MsgISUP::CRG)) break; SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CRG,id()); copyUpper(m->params(),event->message()->params()); mylock.drop(); result = transmitMessage(m); break; } if (oper != "transport") break; if (!validMsgState(true,SS7MsgISUP::APM)) break; SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::APM,id()); copyUpper(m->params(),event->message()->params()); mylock.drop(); result = transmitMessage(m); } break; case SignallingEvent::Suspend: if (event->message()) { if (!validMsgState(true,SS7MsgISUP::SUS)) break; SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::SUS,id()); copyUpper(m->params(),event->message()->params()); mylock.drop(); result = transmitMessage(m); } break; case SignallingEvent::Resume: if (event->message()) { if (!validMsgState(true,SS7MsgISUP::RES)) break; SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::RES,id()); copyUpper(m->params(),event->message()->params()); mylock.drop(); result = transmitMessage(m); } break; case SignallingEvent::Info: if (validMsgState(true,SS7MsgISUP::SAM)) { mylock.drop(); transmitSAM(event->message()->params().getValue(YSTRING("tone"))); result = true; break; } //case SignallingEvent::Message: //case SignallingEvent::Transfer: case SignallingEvent::Charge: if (event->message()) { if (!validMsgState(true,SS7MsgISUP::CRG)) break; SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CRG,id()); copyUpper(m->params(),event->message()->params()); mylock.drop(); result = transmitMessage(m); } break; default: DDebug(isup(),DebugStub, "Call(%u). sendEvent not implemented for '%s' [%p]", id(),event->name(),this); } // Reset overlapped if our state is greater then Setup if (m_state > Setup) setOverlapped(false,false); XDebug(isup(),DebugAll,"Call(%u). Event (%p,'%s') sent. Result: %s [%p]", id(),event,event->name(),String::boolText(result),this); mylock.drop(); delete event; return result; } // Get reserved circuit or this object void* SS7ISUPCall::getObject(const String& name) const { if (name == YSTRING("SignallingCircuit")) return m_circuit; if (name == YSTRING("SS7ISUPCall")) return (void*)this; return SignallingCall::getObject(name); } // Check if the circuit can be replaced // Returns true unless the counter is already zero bool SS7ISUPCall::canReplaceCircuit() { if (m_replaceCounter <= 0) return false; m_replaceCounter--; return true; } // Replace the circuit reserved for this call. Release the already reserved circuit. // Retransmit the initial IAM request on success. // On failure set the termination flag and release the new circuit if valid bool SS7ISUPCall::replaceCircuit(SignallingCircuit* circuit, SS7MsgISUP* msg) { Lock mylock(this); clearQueue(); if (m_state > Setup || !circuit || !outgoing()) { Debug(isup(),DebugNote,"Call(%u). Failed to replace circuit [%p]",id(),this); m_iamTimer.stop(); if (controller()) { controller()->releaseCircuit(m_circuit); controller()->releaseCircuit(circuit); } setTerminate(false,"congestion"); TelEngine::destruct(msg); return false; } transmitMessage(msg); unsigned int oldId = id(); if (controller()) controller()->releaseCircuit(m_circuit); m_circuit = circuit; Debug(isup(),DebugNote,"Call(%u). Circuit replaced by %u [%p]",oldId,id(),this); m_circuitChanged = true; return transmitIAM(); } // Stop timers. Send a RLC (Release Complete) message if it should terminate gracefully // Decrease the object's refence count and generate a Release event if not final // @param final True if called from destructor // @param msg Received message with parameters if any // @param reason Optional release reason SignallingEvent* SS7ISUPCall::releaseComplete(bool final, SS7MsgISUP* msg, const char* reason, bool timeout) { if (timeout) m_gracefully = false; m_iamTimer.stop(); setReason(reason,msg); stopWaitSegment(true); if (m_state == Released) return 0; if (isup() && m_gracefully) { int sls = transmitRLC(isup(),id(),m_label,false); if (sls != -1 && m_label.sls() == 255) m_label.setSls(sls); } m_state = Released; if (final) return 0; // Return event and decrease reference counter bool create = (msg == 0); if (create) msg = new SS7MsgISUP(SS7MsgISUP::RLC,id()); if (m_circuit) m_circuit->disconnect(); msg->params().setParam("reason",m_reason); SignallingEvent* event = new SignallingEvent(SignallingEvent::Release,msg,this); // deref() msg if created here. If received, it will be deref()'d in getEvent() if (create) msg->deref(); deref(); DDebug(isup(),DebugInfo,"Call(%u). Released with reason '%s' [%p]", id(),m_reason.safe(),this); return event; } // Helper function to copy parameters inline void param(NamedList& dest, NamedList& src, const String& destParam, const String& srcParam, const char* defVal) { const char* val = src.getValue(srcParam,src.getValue(destParam,defVal)); if ((val != defVal) || !dest.getParam(destParam)) dest.setParam(destParam,val); } // Initialize/set IAM message parameters // @param msg Valid ISUP message // @param outgoing Message direction: true for outgoing // @param sigMsg Valid signalling message with parameters if outgoing bool SS7ISUPCall::copyParamIAM(SS7MsgISUP* msg, bool outgoing, SignallingMessage* sigMsg) { NamedList& dest = msg->params(); if (outgoing) { NamedList& src = sigMsg->params(); copyUpper(dest,src); param(dest,src,"CalledPartyNumber","called",""); param(dest,src,"CalledPartyNumber.inn","inn",String::boolText(isup()->m_inn)); param(dest,src,"CalledPartyNumber.nature","callednumtype",isup()->m_numType); param(dest,src,"CalledPartyNumber.plan","callednumplan",isup()->m_numPlan); param(dest,src,"CallingPartyCategory","callercategory",isup()->m_callerCat); param(dest,src,"CallingPartyNumber","caller",""); param(dest,src,"CallingPartyNumber.nature","callernumtype",isup()->m_numType); param(dest,src,"CallingPartyNumber.plan","callernumplan",isup()->m_numPlan); param(dest,src,"CallingPartyNumber.restrict","callerpres",isup()->m_numPresentation); param(dest,src,"CallingPartyNumber.screened","callerscreening",isup()->m_numScreening); param(dest,src,"CallingPartyNumber.complete","complete","true"); m_format = src.getValue(YSTRING("format"),isup()->format()); dest.setParam("UserServiceInformation",m_format); return true; } // Incoming call m_format = dest.getValue(YSTRING("UserServiceInformation"),isup()->format()); dest.setParam("format",m_format); dest.setParam("caller",dest.getValue(YSTRING("CallingPartyNumber"))); //dest.setParam("callername",dest.getValue("")); dest.setParam("callernumtype",dest.getValue(YSTRING("CallingPartyNumber.nature"))); dest.setParam("callernumplan",dest.getValue(YSTRING("CallingPartyNumber.plan"))); dest.setParam("callerpres",dest.getValue(YSTRING("CallingPartyNumber.restrict"))); dest.setParam("callerscreening",dest.getValue(YSTRING("CallingPartyNumber.screened"))); dest.setParam("called",dest.getValue(YSTRING("CalledPartyNumber"))); dest.setParam("callednumtype",dest.getValue(YSTRING("CalledPartyNumber.nature"))); dest.setParam("callednumplan",dest.getValue(YSTRING("CalledPartyNumber.plan"))); dest.setParam("inn",dest.getValue(YSTRING("CalledPartyNumber.inn"))); if (m_label.sls() != 0xff) dest.setParam("sls",String((unsigned int)m_label.sls())); return true; } // If already releasing, set termination flag. Otherwise, send REL (Release) message // @param event Event with the parameters. 0 if release is started on unspecified interworking SignallingEvent* SS7ISUPCall::release(SignallingEvent* event, SS7MsgISUP* msg) { m_iamTimer.stop(); if (event) setReason(0,event->message()); else setReason("interworking",0); stopWaitSegment(true); XDebug(isup(),DebugAll,"Call(%u). Releasing call with reason '%s' [%p]", id(),m_reason.safe(),this); if (!isup() || m_state >= Releasing) { m_terminate = true; return 0; } m_iamTimer.interval(isup() ? isup()->m_t1Interval : 1); m_relTimer.interval(isup() ? isup()->m_t5Interval : 1); m_iamTimer.start(); m_relTimer.start(); m_state = Releasing; transmitREL((event && event->message()) ? &(event->message()->params()) : 0); if (event) return 0; bool create = (msg == 0); if (create) msg = new SS7MsgISUP(SS7MsgISUP::REL,id()); msg->params().setParam("reason",m_reason); SignallingEvent* ev = new SignallingEvent(SignallingEvent::Release,msg,this); // deref() msg if created here. If received, it will be deref()'d in getEvent() if (create) TelEngine::destruct(msg); return ev; } // Set termination reason from received text or message void SS7ISUPCall::setReason(const char* reason, SignallingMessage* msg, const char* diagnostic, const char* location) { if (!m_reason.null()) return; if (reason) { m_reason = reason; m_diagnostic = diagnostic; m_location = location; } else if (msg) { m_reason = msg->params().getValue(YSTRING("CauseIndicators"),msg->params().getValue(YSTRING("reason"))); m_diagnostic = msg->params().getValue(YSTRING("CauseIndicators.diagnostic"),diagnostic); m_location = msg->params().getValue(YSTRING("CauseIndicators.location"),location); } } // Accept send/receive messages in current state based on call direction bool SS7ISUPCall::validMsgState(bool send, SS7MsgISUP::Type type, bool hasBkwCallInd) { bool handled = true; switch (type) { case SS7MsgISUP::CCR: // Continuity check if (m_state == Testing && send == outgoing()) return true; // fall through case SS7MsgISUP::IAM: // Initial address if (m_state != Null || send != outgoing()) break; return true; case SS7MsgISUP::COT: // Continuity if (m_state != Testing || send != outgoing()) break; return true; case SS7MsgISUP::ACM: // Address complete case SS7MsgISUP::EXM: // Exit Message (ANSI) if (m_state != Setup || send == outgoing()) break; return true; case SS7MsgISUP::CPR: // Call progress if (m_state < (hasBkwCallInd ? Setup : Accepted) || m_state >= Releasing) break; return true; case SS7MsgISUP::CON: // Connect // CON can be sent/received on not accepted calls if (m_state == Setup && send != outgoing()) return true; case SS7MsgISUP::ANM: // Answer if (m_state < (hasBkwCallInd ? Setup : Accepted) || m_state >= Answered || send == outgoing()) break; return true; case SS7MsgISUP::SAM: // Subsequent address if (m_state != Setup || !m_overlap || send != outgoing()) break; return true; case SS7MsgISUP::REL: // Release if (send && m_state >= Releasing) break; // fall through case SS7MsgISUP::RLC: // Release complete case SS7MsgISUP::CRG: // Charging if (m_state == Null || m_state == Released) break; return true; case SS7MsgISUP::SUS: // Suspend case SS7MsgISUP::RES: // Resume if (m_state != Answered) break; return true; case SS7MsgISUP::SGM: // Segmentation case SS7MsgISUP::APM: // Application Transport return true; default: handled = false; } Debug(isup(),handled?DebugNote:DebugStub, "Call(%u). Can't %s %smessage '%s' in state %u [%p]", id(),send?"send":"accept",handled?"":"unhandled ", SS7MsgISUP::lookup(type,""),m_state,this); return false; } // Connect or test the reserved circuit. Return false if it fails. // Return true if this call is a signalling only one bool SS7ISUPCall::connectCircuit(const char* special) { bool ok = signalOnly(); if (TelEngine::null(special)) special = 0; if (m_circuit && !ok) { u_int64_t t = Time::msecNow(); if (special) { m_circuit->updateFormat(m_format,0); ok = m_circuit->setParam("special_mode",special) && m_circuit->status(SignallingCircuit::Special); } else ok = m_circuit->connected() || m_circuit->connect(m_format); t = Time::msecNow() - t; if (t > 100) { int level = DebugInfo; if (t > 300) level = DebugMild; else if (t > 200) level = DebugNote; Debug(isup(),level,"Call(%u). Spent %u ms connecting circuit [%p]", id(),(unsigned int)t,this); } #ifdef DEBUG else Debug(isup(),DebugAll,"Call(%u). Spent %u ms connecting circuit [%p]", id(),(unsigned int)t,this); #endif } if (!ok) Debug(isup(),DebugMild,"Call(%u). Circuit %s failed (format='%s')%s [%p]", id(),(special ? special : "connect"), m_format.safe(),(m_circuit ? "" : ". No circuit"),this); if (m_sgmMsg) { if (m_circuitChanged) { m_sgmMsg->params().setParam("circuit-change","true"); m_circuitChanged = false; } m_sgmMsg->params().setParam("format",m_format); } return ok; } // Transmit the IAM message. Start IAM timer if not started bool SS7ISUPCall::transmitIAM() { if (!m_iamTimer.started()) m_iamTimer.start(); if (!m_iamMsg) return false; if (needsTesting(m_iamMsg)) { if (m_circuitTesting && !(isup() && isup()->m_continuity)) { Debug(isup(),DebugWarn,"Call(%u). Continuity check requested but not configured [%p]", id(),this); return false; } m_state = Testing; if (m_circuitTesting && !connectCircuit("test:" + isup()->m_continuity)) return false; Debug(isup(),DebugNote,"Call(%u). %s continuity check [%p]", id(),(m_circuitTesting ? "Executing" : "Forwarding"),this); } else m_state = Setup; m_iamMsg->m_cic = id(); m_iamMsg->ref(); // Reset SAM digits: this might be a re-send m_sentSamDigits = 0; bool ok = transmitMessage(m_iamMsg); if (ok && m_overlap) transmitSAM(); return ok; } // Transmit SAM digits bool SS7ISUPCall::transmitSAM(const char* extra) { if (!m_overlap) return false; m_samDigits << extra; while (m_samDigits.length() > m_sentSamDigits) { unsigned int send = m_samDigits.length() - m_sentSamDigits; if (send > isup()->m_maxCalledDigits) send = isup()->m_maxCalledDigits; SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::SAM,id()); String number = m_samDigits.substr(m_sentSamDigits,send); m->params().addParam("SubsequentNumber",number); bool complete = !isCalledIncomplete(m->params(),"SubsequentNumber"); bool ok = transmitMessage(m); if (ok) { m_sentSamDigits += send; if (complete) { if (m_samDigits.length() > m_sentSamDigits) Debug(isup(),DebugNote, "Call(%u). Completed number sending remaining='%s' [%p]", id(),m_samDigits.substr(m_sentSamDigits).c_str(),this); // Reset overlap sending setOverlapped(false); break; } } else { Debug(isup(),DebugNote,"Call(%u). Failed to send SAM with '%s' [%p]", id(),number.c_str(),this); complete = false; break; } } return true; } // (Re)transmit REL. Create and populate the message if needed // Remember sls bool SS7ISUPCall::transmitREL(const NamedList* params) { if (!isup()) return false; if (!m_relMsg) { m_relMsg = new SS7MsgISUP(SS7MsgISUP::REL,id()); if (m_reason) m_relMsg->params().addParam("CauseIndicators",m_reason); m_relMsg->params().addParam("CauseIndicators.diagnostic",m_diagnostic,false); m_relMsg->params().addParam("CauseIndicators.location",m_location,false); if (params) copyUpper(m_relMsg->params(),*params); } // transmitMessage will dereference message so make sure we preserve it m_relMsg->ref(); int sls = isup()->transmitMessage(m_relMsg,m_label,false); if (sls != -1 && m_label.sls() == 255) m_label.setSls(sls); return sls != -1; } bool SS7ISUPCall::needsTesting(const SS7MsgISUP* msg) { if ((m_state >= Testing) || !msg) return false; const String* naci = msg->params().getParam(YSTRING("NatureOfConnectionIndicators")); if (!naci) return false; ObjList* list = naci->split(',',false); m_circuitTesting = (0 != list->find("cont-check-this")); bool checkIt = m_circuitTesting || (0 != list->find("cont-check-prev")); TelEngine::destruct(list); return checkIt; } // Stop waiting for a SGM (Segmentation) message. Copy parameters to // the pending segmented message if sgm is valid. // Change call state and set m_lastEvent SignallingEvent* SS7ISUPCall::processSegmented(SS7MsgISUP* sgm, bool timeout) { if (sgm) if (sgm->type() == SS7MsgISUP::SGM) { // Copy parameters from SGM as defined in Q.763 - Table 49 // (Segmentation message) and Q.764 - 2.1.12 (re-assembly) #define COPY_PARAM(param) \ m_sgmMsg->params().copyParam(sgm->params(),param); \ m_sgmMsg->params().copyParam(sgm->params(),param,'.'); COPY_PARAM(YSTRING("AccessTranport")) COPY_PARAM(YSTRING("UserToUserInformation")) COPY_PARAM(YSTRING("MessageCompatInformation")) COPY_PARAM(YSTRING("GenericDigits")) COPY_PARAM(YSTRING("GenericNotification")) COPY_PARAM(YSTRING("GenericNumber")) #undef COPY_PARAM } else Debug(isup(),DebugStub,"Call(%u). stopWaitSegment() called with non-SGM message !!! [%p]", id(),this); else if (timeout) Debug(isup(),DebugMild,"Call(%u). Segment waiting message '%s' timed out [%p]", id(),m_sgmMsg->name(),this); m_sgmRecvTimer.stop(); // Raise event, connect the reserved circuit, change call state m_iamTimer.stop(); switch (m_sgmMsg->type()) { case SS7MsgISUP::COT: { const String* cont = m_sgmMsg->params().getParam(YSTRING("ContinuityIndicators")); bool ok = cont && (*cont == YSTRING("success")); if (ok) { Debug(isup(),DebugNote,"Call(%u). Continuity check succeeded [%p]", id(),this); m_circuitTesting = false; } else { Debug(isup(),DebugWarn,"Call(%u). Continuity check failed [%p]", id(),this); m_contTimer.start(); break; } if (!(ok && m_iamMsg)) { m_lastEvent = new SignallingEvent(SignallingEvent::Info,m_sgmMsg,this); break; } } TelEngine::destruct(m_sgmMsg); m_sgmMsg = m_iamMsg; m_iamMsg = 0; // intentionally fall through case SS7MsgISUP::IAM: if (needsTesting(m_sgmMsg)) { m_state = Testing; if (m_circuitTesting && !(isup() && isup()->m_continuity)) { Debug(isup(),DebugWarn,"Call(%u). Continuity check requested but not configured [%p]", id(),this); setTerminate(true,"service-not-implemented",0,isup()->location()); break; } if (m_circuitTesting && !connectCircuit(isup()->m_continuity)) { setTerminate(true,"bearer-cap-not-available",0,isup()->location()); break; } Debug(isup(),DebugNote,"Call(%u). Waiting for continuity check [%p]", id(),this); // Save message for later m_iamMsg = m_sgmMsg; m_sgmMsg = 0; return 0; } m_state = Setup; if (!connectCircuit() && isup() && (isup()->mediaRequired() >= SignallingCallControl::MediaAlways)) { setTerminate(true,"bearer-cap-not-available",0,isup()->location()); break; } m_sgmMsg->params().setParam("overlapped",String::boolText(m_overlap)); m_lastEvent = new SignallingEvent(SignallingEvent::NewCall,m_sgmMsg,this); break; case SS7MsgISUP::CCR: if (m_state < Testing) { m_state = Testing; if (!(isup() && isup()->m_continuity)) { Debug(isup(),DebugWarn,"Call(%u). Continuity check requested but not configured [%p]", id(),this); setTerminate(true,"service-not-implemented",0,isup()->location()); break; } m_circuitTesting = true; if (!connectCircuit(isup()->m_continuity)) { setTerminate(true,"bearer-cap-not-available",0,isup()->location()); break; } Debug(isup(),DebugNote,"Call(%u). Continuity test only [%p]", id(),this); } else if (!m_circuitTesting) { setTerminate(true,"wrong-state-message",0,isup()->location()); break; } m_contTimer.stop(); m_iamTimer.start(); if (isup()->m_confirmCCR) transmitMessage(new SS7MsgISUP(SS7MsgISUP::LPA,id())); break; case SS7MsgISUP::ACM: m_state = Accepted; if (!connectCircuit() && isup() && (isup()->mediaRequired() >= SignallingCallControl::MediaAlways)) { setReason("bearer-cap-not-available",0,0,isup()->location()); m_lastEvent = release(); break; } m_lastEvent = 0; m_inbandAvailable = m_inbandAvailable || SignallingUtils::hasFlag(m_sgmMsg->params(),"OptionalBackwardCallIndicators","inband"); if (isup() && isup()->m_earlyAcm) { // If the called party is known free report ringing // If it may become free or there is inband audio report progress bool ring = SignallingUtils::hasFlag(m_sgmMsg->params(),"BackwardCallIndicators","called-free"); if (m_inbandAvailable || ring || SignallingUtils::hasFlag(m_sgmMsg->params(),"BackwardCallIndicators","called-conn")) { m_sgmMsg->params().setParam("earlymedia",String::boolText(m_inbandAvailable)); m_lastEvent = new SignallingEvent(ring ? SignallingEvent::Ringing : SignallingEvent::Progress,m_sgmMsg,this); } } if (!m_lastEvent) { m_sgmMsg->params().setParam("earlymedia",String::boolText(m_inbandAvailable)); m_lastEvent = new SignallingEvent(SignallingEvent::Accept,m_sgmMsg,this); } // intentionally fall through case SS7MsgISUP::EXM: // Start T9 timer if (m_anmTimer.interval() && !m_anmTimer.started()) m_anmTimer.start(); break; case SS7MsgISUP::CPR: m_state = Ringing; if (!connectCircuit() && isup() && (isup()->mediaRequired() >= SignallingCallControl::MediaRinging)) { setTerminate(true,"bearer-cap-not-available",0,isup()->location()); break; } m_inbandAvailable = m_inbandAvailable || SignallingUtils::hasFlag(m_sgmMsg->params(),"OptionalBackwardCallIndicators","inband") || SignallingUtils::hasFlag(m_sgmMsg->params(),"EventInformation","inband"); m_sgmMsg->params().setParam("earlymedia",String::boolText(m_inbandAvailable)); m_lastEvent = new SignallingEvent( SignallingUtils::hasFlag(m_sgmMsg->params(),"EventInformation","ringing") ? SignallingEvent::Ringing : SignallingEvent::Progress, m_sgmMsg,this); break; case SS7MsgISUP::ANM: case SS7MsgISUP::CON: m_state = Answered; m_anmTimer.stop(); if (!connectCircuit() && isup() && (isup()->mediaRequired() >= SignallingCallControl::MediaAnswered)) { setTerminate(true,"bearer-cap-not-available",0,isup()->location()); break; } m_lastEvent = new SignallingEvent(SignallingEvent::Answer,m_sgmMsg,this); break; case SS7MsgISUP::CRG: m_lastEvent = new SignallingEvent(SignallingEvent::Charge,m_sgmMsg,this); break; default: Debug(isup(),DebugStub,"Call(%u). Segment waiting message is '%s' [%p]", id(),m_sgmMsg->name(),this); } TelEngine::destruct(m_sgmMsg); return m_lastEvent; } // Transmit message. Set routing label's link if not already set bool SS7ISUPCall::transmitMessage(SS7MsgISUP* msg) { if (!msg || !isup()) { TelEngine::destruct(msg); return false; } DDebug(isup(),DebugAll,"Call(%u). Transmitting messsage (%s,%p) [%p]", id(),msg->name(),msg,this); int sls = isup()->transmitMessage(msg,m_label,false); if (sls == -1) return false; if (m_label.sls() == 255) m_label.setSls(sls); return true; } SS7ISUP* SS7ISUPCall::isup() const { return static_cast(SignallingCall::controller()); } // Set overlapped flag. Output a debug message void SS7ISUPCall::setOverlapped(bool on, bool numberComplete) { if (m_overlap == on) return; m_overlap = on; const char* reason = on ? "" : (numberComplete ? " (number complete)" : " (state changed)"); Debug(isup(),DebugAll,"Call(%u). Overlapped dialing is %s%s [%p]", id(),String::boolText(on),reason,this); } /** * SS7ISUP */ SS7ISUP::SS7ISUP(const NamedList& params, unsigned char sio) : SignallingComponent(params.safe("SS7ISUP"),¶ms,"ss7-isup"), SignallingCallControl(params,"isup."), SS7Layer4(sio,¶ms), m_cicLen(2), m_type(SS7PointCode::Other), m_defPoint(0), m_remotePoint(0), m_sls(255), m_earlyAcm(true), m_inn(false), m_defaultSls(SlsLatest), m_maxCalledDigits(16), m_confirmCCR(true), m_dropOnUnknown(true), m_ignoreGRSSingle(false), m_ignoreCGBSingle(false), m_ignoreCGUSingle(false), m_duplicateCGB(false), m_ignoreUnkDigits(true), m_l3LinkUp(false), m_chargeProcessType(Confusion), m_t1Interval(15000), // Q.764 T1 15..60 seconds m_t5Interval(300000), // Q.764 T5 5..15 minutes m_t7Interval(ISUP_T7_DEFVAL), // Q.764 T7 20..30 seconds m_t9Interval(0), // Q.764 T9 Q.118 1.5 - 3 minutes, not always used m_t12Interval(20000), // Q.764 T12 (BLK) 15..60 seconds m_t13Interval(300000), // Q.764 T13 (BLK global) 5..15 minutes m_t14Interval(20000), // Q.764 T14 (UBL) 15..60 seconds m_t15Interval(300000), // Q.764 T15 (UBL global) 5..15 minutes m_t16Interval(20000), // Q.764 T16 (RSC) 15..60 seconds m_t17Interval(300000), // Q.764 T17 5..15 minutes m_t18Interval(20000), // Q.764 T18 (CGB) 15..60 seconds m_t19Interval(300000), // Q.764 T19 (CGB global) 5..15 minutes m_t20Interval(20000), // Q.764 T20 (CGU) 15..60 seconds m_t21Interval(300000), // Q.764 T21 (CGU global) 5..15 minutes m_t27Interval(ISUP_T27_DEFVAL), // Q.764 T27 4 minutes m_t34Interval(ISUP_T34_DEFVAL), // Q.764 T34 2..4 seconds m_uptTimer(0), m_userPartAvail(true), m_uptMessage(SS7MsgISUP::UPT), m_uptCicCode(0), m_cicWarnLevel(DebugMild), m_replaceCounter(3), m_rscTimer(0), m_rscCic(0), m_rscSpeedup(0), m_lockTimer(2000), m_lockGroup(true), m_printMsg(false), m_extendedDebug(false) { #ifdef DEBUG if (debugAt(DebugAll)) { String tmp; params.dump(tmp,"\r\n ",'\'',true); Debug(this,DebugAll,"SS7ISUP::SS7ISUP(%p) [%p]%s", ¶ms,this,tmp.c_str()); } #endif const char* stype = params.getValue(YSTRING("pointcodetype")); m_type = SS7PointCode::lookup(stype); if (m_type == SS7PointCode::Other) { Debug(this,DebugWarn,"Invalid point code type '%s'",c_safe(stype)); return; } if (m_type == SS7PointCode::ITU) m_defaultSls = SlsCircuit; m_format = params.getValue(YSTRING("format")); if (-1 == lookup(m_format,SignallingUtils::dict(1,0),-1)) switch (m_type) { case SS7PointCode::ANSI: case SS7PointCode::ANSI8: case SS7PointCode::Japan: case SS7PointCode::Japan5: m_format = "mulaw"; break; default: m_format = "alaw"; } const char* rpc = params.getValue(YSTRING("remotepointcode")); m_remotePoint = new SS7PointCode(0,0,0); if (!(m_remotePoint->assign(rpc,m_type) && m_remotePoint->pack(m_type))) { Debug(this,DebugMild,"Invalid remotepointcode='%s'",rpc); TelEngine::destruct(m_remotePoint); } m_lockGroup = params.getBoolValue(YSTRING("lockgroup"),m_lockGroup); m_earlyAcm = params.getBoolValue(YSTRING("earlyacm"),m_earlyAcm); m_inn = params.getBoolValue(YSTRING("inn"),m_inn); m_numPlan = params.getValue(YSTRING("numplan")); if (-1 == lookup(m_numPlan,s_dict_numPlan,-1)) m_numPlan = "unknown"; m_numType = params.getValue(YSTRING("numtype")); if (-1 == lookup(m_numType,s_dict_nai,-1)) m_numType = "unknown"; m_numPresentation = params.getValue(YSTRING("presentation")); if (-1 == lookup(m_numPresentation,s_dict_presentation,-1)) m_numPresentation = "allowed"; m_numScreening = params.getValue(YSTRING("screening")); if (-1 == lookup(m_numScreening,s_dict_screening,-1)) m_numScreening = "user-provided"; m_callerCat = params.getValue(YSTRING("callercategory")); if (-1 == lookup(m_callerCat,s_dict_callerCat,-1)) m_callerCat = "ordinary"; m_rscTimer.interval(params,"channelsync",60,300,true,true); m_rscInterval = m_rscTimer.interval(); // Remote user part test m_uptTimer.interval(params,"userparttest",10,60,true,true); if (m_uptTimer.interval()) m_userPartAvail = false; else m_lockTimer.start(); // Timers m_t7Interval = SignallingTimer::getInterval(params,"t7",ISUP_T7_MINVAL,ISUP_T7_DEFVAL,ISUP_T7_MAXVAL,false); m_t9Interval = SignallingTimer::getInterval(params,"t9",ISUP_T9_MINVAL,ISUP_T9_DEFVAL,ISUP_T9_MAXVAL,true); m_t27Interval = SignallingTimer::getInterval(params,"t27",ISUP_T27_MINVAL,ISUP_T27_DEFVAL,ISUP_T27_MAXVAL,false); m_t34Interval = SignallingTimer::getInterval(params,"t34",ISUP_T34_MINVAL,ISUP_T34_DEFVAL,ISUP_T34_MAXVAL,false); m_continuity = params.getValue(YSTRING("continuity")); m_confirmCCR = params.getBoolValue(YSTRING("confirm_ccr"),true); m_dropOnUnknown = params.getBoolValue(YSTRING("drop_unknown"),true); m_ignoreGRSSingle = params.getBoolValue(YSTRING("ignore-grs-single")); m_ignoreCGBSingle = params.getBoolValue(YSTRING("ignore-cgb-single")); m_ignoreCGUSingle = params.getBoolValue(YSTRING("ignore-cgu-single")); m_duplicateCGB = params.getBoolValue(YSTRING("duplicate-cgb"), (SS7PointCode::ANSI == m_type || SS7PointCode::ANSI8 == m_type)); m_chargeProcessType = (ChargeProcess)params.getIntValue(YSTRING("charge-process"),s_dict_CRG_process,m_chargeProcessType); int testMsg = params.getIntValue(YSTRING("parttestmsg"),s_names,SS7MsgISUP::UPT); switch (testMsg) { case SS7MsgISUP::CVT: if (SS7PointCode::ANSI != m_type && SS7PointCode::ANSI8 != m_type) break; // fall through case SS7MsgISUP::RSC: case SS7MsgISUP::UBL: case SS7MsgISUP::UPT: m_uptMessage = (SS7MsgISUP::Type)testMsg; } m_replaceCounter = params.getIntValue(YSTRING("max_replaces"),3,0,31); m_ignoreUnkDigits = params.getBoolValue(YSTRING("ignore-unknown-digits"),true); m_defaultSls = params.getIntValue(YSTRING("sls"),s_dict_callSls,m_defaultSls); m_maxCalledDigits = params.getIntValue(YSTRING("maxcalleddigits"),m_maxCalledDigits); if (m_maxCalledDigits < 1) m_maxCalledDigits = 16; setDebug(params.getBoolValue(YSTRING("print-messages"),false), params.getBoolValue(YSTRING("extended-debug"),false)); if (debugAt(DebugInfo)) { String s; s << "pointcode-type=" << stype; s << " format=" << m_format; s << " plan/type/pres/screen=" << m_numPlan << "/" << m_numType << "/" << m_numPresentation << "/" << m_numScreening; s << " caller-category=" << m_callerCat; s << " remote-pointcode="; if (m_remotePoint) s << *m_remotePoint; else s << "missing"; s << " SIF/SSF=" << (unsigned int)sif() << "/" << (unsigned int)ssf(); s << " lockcircuits=" << params.getValue(YSTRING("lockcircuits")); s << " userpartavail=" << String::boolText(m_userPartAvail); s << " lockgroup=" << String::boolText(m_lockGroup); s << " mediareq=" << lookup(m_mediaRequired,s_mediaRequired); const char* sls = lookup(m_defaultSls,s_dict_callSls); s << " outboundsls="; if (sls) s << sls; else s << m_defaultSls; if (m_continuity) s << " continuity=" << m_continuity; Debug(this,DebugInfo,"ISUP Call Controller %s [%p]",s.c_str(),this); } } SS7ISUP::~SS7ISUP() { cleanup(); if (m_remotePoint) m_remotePoint->destruct(); Debug(this,DebugInfo,"ISUP Call Controller destroyed [%p]",this); } bool SS7ISUP::initialize(const NamedList* config) { #ifdef DEBUG String tmp; if (config && debugAt(DebugAll)) config->dump(tmp,"\r\n ",'\'',true); Debug(this,DebugInfo,"SS7ISUP::initialize(%p) [%p]%s",config,this,tmp.c_str()); #endif if (config) { debugLevel(config->getIntValue(YSTRING("debuglevel_isup"), config->getIntValue(YSTRING("debuglevel"),-1))); setDebug(config->getBoolValue(YSTRING("print-messages"),false), config->getBoolValue(YSTRING("extended-debug"),false)); m_lockGroup = config->getBoolValue(YSTRING("lockgroup"),m_lockGroup); m_earlyAcm = config->getBoolValue(YSTRING("earlyacm"),m_earlyAcm); m_continuity = config->getValue(YSTRING("continuity"),m_continuity); m_confirmCCR = config->getBoolValue(YSTRING("confirm_ccr"),true); m_dropOnUnknown = config->getBoolValue(YSTRING("drop_unknown"),true); m_ignoreGRSSingle = config->getBoolValue(YSTRING("ignore-grs-single")); m_ignoreCGBSingle = config->getBoolValue(YSTRING("ignore-cgb-single")); m_ignoreCGUSingle = config->getBoolValue(YSTRING("ignore-cgu-single")); m_duplicateCGB = config->getBoolValue(YSTRING("duplicate-cgb"), (SS7PointCode::ANSI == m_type || SS7PointCode::ANSI8 == m_type)); int testMsg = config->getIntValue(YSTRING("parttestmsg"),s_names,SS7MsgISUP::UPT); switch (testMsg) { case SS7MsgISUP::CVT: if (SS7PointCode::ANSI != m_type && SS7PointCode::ANSI8 != m_type) break; // fall through case SS7MsgISUP::RSC: case SS7MsgISUP::UBL: case SS7MsgISUP::UPT: m_uptMessage = (SS7MsgISUP::Type)testMsg; } m_replaceCounter = config->getIntValue(YSTRING("max_replaces"),3,0,31); m_ignoreUnkDigits = config->getBoolValue(YSTRING("ignore-unknown-digits"),true); m_defaultSls = config->getIntValue(YSTRING("sls"),s_dict_callSls,m_defaultSls); m_chargeProcessType = (ChargeProcess)config->getIntValue(YSTRING("charge-process"),s_dict_CRG_process,m_chargeProcessType); m_mediaRequired = (MediaRequired)config->getIntValue(YSTRING("needmedia"), s_mediaRequired,m_mediaRequired); // Timers m_t7Interval = SignallingTimer::getInterval(*config,"t7",ISUP_T7_MINVAL,ISUP_T7_DEFVAL,ISUP_T7_MAXVAL,false); m_t9Interval = SignallingTimer::getInterval(*config,"t9",ISUP_T9_MINVAL,ISUP_T9_DEFVAL,ISUP_T9_MAXVAL,true); m_t27Interval = SignallingTimer::getInterval(*config,"t27",ISUP_T27_MINVAL,ISUP_T27_DEFVAL,ISUP_T27_MAXVAL,false); m_t34Interval = SignallingTimer::getInterval(*config,"t34",ISUP_T34_MINVAL,ISUP_T34_DEFVAL,ISUP_T34_MAXVAL,false); } m_cicWarnLevel = DebugMild; return SS7Layer4::initialize(config); } const char* SS7ISUP::statusName() const { if (exiting()) return "Exiting"; if (!m_l3LinkUp) return "Layer 3 down"; if (!m_userPartAvail) return "Remote unavailable"; if (!m_defPoint) return "No local PC set"; if (!m_remotePoint) return "No remote PC set"; return "Operational"; } void SS7ISUP::attach(SS7Layer3* network) { SS7Layer4::attach(network); m_l3LinkUp = network && network->operational(); } // Append a point code to the list of point codes serviced by this controller // Set default point code bool SS7ISUP::setPointCode(SS7PointCode* pc, bool def) { if (!(pc && pc->pack(m_type))) return false; Lock mylock(this); // Force default if we are not having one or the list is empty def = def || !m_defPoint || !m_pointCodes.skipNull(); // Force not default if the received point code is the same as the default one if (def && m_defPoint && *m_defPoint == *pc) def = false; SS7PointCode* p = hasPointCode(*pc); if (def) m_defPoint = p ? p : pc; String tmp; tmp << (def ? *m_defPoint : *pc); if (!p) { m_pointCodes.append(pc); DDebug(this,DebugAll,"Added new point code '%s'%s",tmp.safe(),def?". Set to default":""); } else { TelEngine::destruct(pc); if (def) Debug(this,DebugAll,"Set default point code '%s'",tmp.safe()); } return true; } // Add all point codes described in a parameter list unsigned int SS7ISUP::setPointCode(const NamedList& params) { unsigned int count = 0; unsigned int n = params.length(); bool hadDef = false; for (unsigned int i= 0; i < n; i++) { NamedString* ns = params.getParam(i); if (!ns) continue; bool defPc = false; if (ns->name() == YSTRING("defaultpointcode")) defPc = true; else if (ns->name() != YSTRING("pointcode")) continue; SS7PointCode* pc = new SS7PointCode(0,0,0); if (pc->assign(*ns,m_type) && setPointCode(pc,defPc && !hadDef)) { count++; if (defPc) { if (hadDef) Debug(this,DebugMild,"Added point code '%s' as non-default",ns->safe()); else hadDef = true; } } else { Debug(this,DebugWarn,"Invalid '%s'='%s' in parameters '%s'", ns->name().c_str(),ns->safe(),params.safe()); TelEngine::destruct(pc); } } return count; } // Check if the given point code is serviced by this controller SS7PointCode* SS7ISUP::hasPointCode(const SS7PointCode& pc) { Lock mylock(this); for (ObjList* o = m_pointCodes.skipNull(); o; o = o->skipNext()) { SS7PointCode* p = static_cast(o->get()); if (*p == pc) return p; } return 0; } SS7MSU* SS7ISUP::createMSU(SS7MsgISUP::Type type, unsigned char ssf, const SS7Label& label, unsigned int cic, const NamedList* params) const { return buildMSU(type,sif() | (ssf & 0xf0),label,cic,params); } // Make an outgoing call SignallingCall* SS7ISUP::call(SignallingMessage* msg, String& reason) { if (!msg) { reason = "noconn"; return 0; } if (exiting() || !m_l3LinkUp) { Debug(this,DebugInfo,"Denying outgoing call request, reason: %s.", exiting() ? "exiting" : "L3 down"); TelEngine::destruct(msg); reason = "net-out-of-order"; return 0; } if (!m_userPartAvail) { Debug(this,DebugNote,"Remote User Part is unavailable"); TelEngine::destruct(msg); reason = "noconn"; return 0; } SS7PointCode dest; SignallingCircuit* cic = 0; const char* range = msg->params().getValue(YSTRING("circuits")); reason.clear(); Lock mylock(this); // Check while (true) { if (!m_defPoint) { Debug(this,DebugNote,"Source point code is missing"); reason = "noconn"; break; } String pc = msg->params().getValue(YSTRING("calledpointcode")); if (!(dest.assign(pc,m_type) && dest.pack(m_type))) { if (!m_remotePoint) { Debug(this,DebugNote, "Destination point code is missing (calledpointcode=%s)",pc.safe()); reason = "noconn"; break; } dest = *m_remotePoint; } for (int attempts = 3; attempts; attempts--) { if (!reserveCircuit(cic,range,SignallingCircuit::LockLockedBusy)) { Debug(this,DebugNote,"Can't reserve circuit"); break; } SS7ISUPCall* call2 = findCall(cic->code()); if (!call2) break; Debug(this,DebugWarn,"Circuit %u is already used by call %p", cic->code(),call2); TelEngine::destruct(cic); } if (!cic) reason = "congestion"; break; } SS7ISUPCall* call = 0; if (reason.null()) { String* cicParams = msg->params().getParam(YSTRING("circuit_parameters")); if (cicParams) { NamedList* p = YOBJECT(NamedList,cicParams); if (p) cic->setParams(*p); } int sls = msg->params().getIntValue(YSTRING("sls"),s_dict_callSls,m_defaultSls); switch (sls) { case SlsCircuit: if (cic) { sls = cic->code(); break; } // fall through case SlsLatest: sls = m_sls; break; } call = new SS7ISUPCall(this,cic,*m_defPoint,dest,true,sls,range); call->ref(); m_calls.append(call); SignallingEvent* event = new SignallingEvent(SignallingEvent::NewCall,msg,call); // (re)start RSC timer if not currently reseting if (!m_rscCic && m_rscTimer.interval()) m_rscTimer.start(); // Drop lock and send the event mylock.drop(); if (!event->sendEvent()) { call->setTerminate(false,"failure"); TelEngine::destruct(call); reason = "failure"; } } TelEngine::destruct(msg); return call; } // Converts an ISUP message to a Message Signal Unit and push it down the protocol stack // The given message is consumed int SS7ISUP::transmitMessage(SS7MsgISUP* msg, const SS7Label& label, bool recvLbl, int sls) { if (!msg) return -1; const SS7Label* p = &label; SS7Label tmp; if (recvLbl) { switch (sls) { case SlsCircuit: sls = msg->cic(); break; case SlsLatest: sls = m_sls; break; case SlsDefault: sls = label.sls(); break; } tmp.assign(label.type(),label.opc(),label.dpc(),sls,label.spare()); p = &tmp; } lock(); SS7MSU* msu = createMSU(msg->type(),ssf(),*p,msg->cic(),&msg->params()); if (m_printMsg && debugAt(DebugInfo)) { String tmp; void* data = 0; unsigned int len = 0; if (m_extendedDebug && msu) { unsigned int offs = 2 + label.length() + m_cicLen; data = msu->getData(offs); len = data ? msu->length() - offs : 0; } msg->toString(tmp,*p,debugAt(DebugAll),data,len); Debug(this,DebugInfo,"Sending message (%p)%s",msg,tmp.c_str()); } else if (debugAt(DebugAll)) { String tmp; tmp << *p; Debug(this,DebugAll,"Sending message '%s' cic=%u label=%s", msg->name(),msg->cic(),tmp.c_str()); } sls = -1; if (msu && m_l3LinkUp) { unlock(); sls = transmitMSU(*msu,*p,p->sls()); lock(); if ((m_sls == 255) && (sls != -1)) m_sls = (unsigned char)sls; } unlock(); #ifdef XDEBUG if (sls == -1) Debug(this,DebugMild,"Failed to send message (%p): '%s'",msg,msg->name()); #endif TelEngine::destruct(msu); TelEngine::destruct(msg); return sls; } void SS7ISUP::cleanup(const char* reason) { ObjList terminate; lock(); for (ObjList* o = m_calls.skipNull(); o; o = o->skipNext()) { SS7ISUPCall* call = static_cast(o->get()); if (call->ref()) terminate.append(call); } releaseCircuit(m_rscCic); m_rscTimer.stop(); unlock(); setCallsTerminate(terminate,true,reason); clearCalls(); } // Remove all links with other layers. Disposes the memory void SS7ISUP::destroyed() { lock(); clearCalls(); unlock(); SignallingCallControl::attach(0); SS7Layer4::destroyed(); } // Utility: find a pending (un)block message for a given circuit static bool findPendingMsgTimerLock(ObjList& list, unsigned int code) { for (ObjList* o = list.skipNull(); o; o = o->skipNext()) { SignallingMessageTimer* m = static_cast(o->get()); SS7MsgISUP* msg = static_cast(m->message()); if (!msg || code < msg->cic()) continue; if (msg->type() == SS7MsgISUP::BLK || msg->type() == SS7MsgISUP::UBL) { if (msg->cic() == code) return true; continue; } if (msg->type() != SS7MsgISUP::CGB && msg->type() != SS7MsgISUP::CGU) continue; const String& map = msg->params()[YSTRING("RangeAndStatus.map")]; if (map[code - msg->cic()] == '1') return true; } return false; } void SS7ISUP::timerTick(const Time& when) { Lock mylock(this,SignallingEngine::maxLockWait()); if (!(mylock.locked() && m_l3LinkUp && circuits())) return; // Test remote user part if (m_remotePoint && !m_userPartAvail && m_uptTimer.interval()) { if (m_uptTimer.started()) { if (!m_uptTimer.timeout(when.msec())) return; DDebug(this,DebugNote,"%s timed out. Retransmitting",lookup(m_uptMessage,s_names)); } ObjList* o = circuits()->circuits().skipNull(); SignallingCircuit* cic = o ? static_cast(o->get()) : 0; m_uptCicCode = cic ? cic->code() : 1; SS7MsgISUP* msg = new SS7MsgISUP(m_uptMessage,m_uptCicCode); SS7Label label(m_type,*m_remotePoint,*m_defPoint, (m_defaultSls == SlsCircuit) ? m_uptCicCode : m_sls); m_uptTimer.start(when.msec()); mylock.drop(); transmitMessage(msg,label,false); return; } // Blocking/unblocking circuits if (m_lockTimer.timeout(when.msec())) { DDebug(this,DebugAll,"Re-checking local lock sending"); m_lockTimer.stop(); mylock.drop(); sendLocalLock(when); return; } // Pending messages ObjList reInsert; ObjList sendMsgs; ObjList rsc; while (true) { SignallingMessageTimer* m = m_pending.timeout(when); if (!m) break; SS7MsgISUP* msg = static_cast(m->message()); if (!msg) { TelEngine::destruct(m); continue; } if (msg->type() != SS7MsgISUP::RSC && msg->type() != SS7MsgISUP::REL && msg->type() != SS7MsgISUP::CGB && msg->type() != SS7MsgISUP::CGU && msg->type() != SS7MsgISUP::BLK && msg->type() != SS7MsgISUP::UBL) { Debug(this,DebugStub,"Unhandled pending message '%s'",msg->name()); TelEngine::destruct(m); continue; } // Global timer timed out: set retransmission timer from it if (m->global().timeout(when.msec())) { if (msg->type() != SS7MsgISUP::REL) { m->interval(m->global().interval()); m->global().stop(); m->global().interval(0); msg->params().setParam("isup_alert_maint",String::boolText(true)); } else { Debug(this,DebugNote,"Pending operation '%s' cic=%u timed out", msg->name(),msg->cic()); SignallingCircuit* c = circuits() ? circuits()->find(msg->cic()) : 0; TelEngine::destruct(m); if (c && c->ref()) rsc.append(c)->setDelete(false); continue; } } // Check if message is still in use if (msg->type() == SS7MsgISUP::CGB || msg->type() == SS7MsgISUP::CGU) { String* map = msg->params().getParam(YSTRING("RangeAndStatus.map")); bool ok = !TelEngine::null(map); String removedCics; if (ok) { unsigned int nCics = 0; int flg = 0; int flgReset = 0; if ((msg->params()[YSTRING("GroupSupervisionTypeIndicator")] == YSTRING("hw-failure"))) { flg = SignallingCircuit::LockLocalHWFail; flgReset = SignallingCircuit::LockingHWFail; } else { flg = SignallingCircuit::LockLocalMaint; flgReset = SignallingCircuit::LockingMaint; } int on = (msg->type() == SS7MsgISUP::CGB) ? flg : 0; char* s = (char*)map->c_str(); for (unsigned int i = 0; i < map->length(); i++) { if (s[i] == '0') continue; unsigned int code = msg->cic() + i; SignallingCircuit* cic = circuits()->find(code); if (cic && (on == cic->locked(flg))) { nCics++; continue; } // Don't reset locking flag if there is another operation in progress if (cic && !(findPendingMsgTimerLock(m_pending,code) || findPendingMsgTimerLock(reInsert,code)) && cic->locked(flgReset)) { cic->resetLock(flgReset); Debug(this,DebugNote,"Pending %s reset flag=0x%x cic=%u current=0x%x", msg->name(),flgReset,code,cic->locked());; } s[i] = '0'; removedCics.append(String(code),","); } if (nCics) msg->params().setParam("RangeAndStatus",String(nCics)); else ok = false; } if (!ok) { Debug(this,DebugNote,"Removed empty pending operation '%s' cic=%u", msg->name(),msg->cic()); TelEngine::destruct(m); continue; } if (removedCics) Debug(this,DebugAll,"Removed cics=%s from pending operation '%s' map cic=%u", removedCics.c_str(),msg->name(),msg->cic()); } else if (msg->type() == SS7MsgISUP::BLK || msg->type() == SS7MsgISUP::UBL) { // We set the following param when sending BLK/UBL for HW fail reason bool maint = !msg->params().getBoolValue(YSTRING("isup_pending_block_hwfail")); int flg = maint ? SignallingCircuit::LockLocalMaint : SignallingCircuit::LockLocalHWFail; int on = (msg->type() == SS7MsgISUP::BLK) ? flg : 0; SignallingCircuit* cic = circuits()->find(msg->cic()); if (!cic || on != cic->locked(flg)) { flg = maint ? SignallingCircuit::LockingMaint : SignallingCircuit::LockingHWFail; // Don't reset locking flag if there is another operation in progress if (cic && !(findPendingMsgTimerLock(m_pending,msg->cic()) || findPendingMsgTimerLock(reInsert,msg->cic())) && cic->locked(flg)) { cic->resetLock(flg); Debug(this,DebugNote,"Pending %s reset flag=0x%x cic=%u current=0x%x", msg->name(),flg,cic->code(),cic->locked()); } Debug(this,DebugNote,"Removed empty pending operation '%s' cic=%u", msg->name(),msg->cic()); TelEngine::destruct(m); continue; } } bool alert = msg->params().getBoolValue(YSTRING("isup_alert_maint")); const char* reason = msg->params().getValue(YSTRING("isup_pending_reason"),""); Debug(this,!alert ? DebugAll : DebugMild, "Pending operation '%s' cic=%u reason='%s' timed out", msg->name(),msg->cic(),reason); if (alert) { // TODO: alert maintenance } msg->ref(); reInsert.append(m)->setDelete(false); sendMsgs.append(msg)->setDelete(false); } // Re-insert ObjList* o = reInsert.skipNull(); ObjList* oRsc = rsc.skipNull(); if (o || oRsc) { for (; o; o = o->skipNext()) m_pending.add(static_cast(o->get()),when); mylock.drop(); transmitMessages(sendMsgs); for (; oRsc; oRsc = oRsc->skipNext()) { SignallingCircuit* c = static_cast(oRsc->get()); c->resetLock(SignallingCircuit::Resetting); startCircuitReset(c,"T5"); } return; } // Circuit reset disabled ? if (!m_rscTimer.interval()) return; if (m_rscTimer.started()) { if (!m_rscTimer.timeout(when.msec())) return; m_rscTimer.stop(); if (m_rscCic) { Debug(this,DebugMild,"Circuit reset timed out for cic=%u",m_rscCic->code()); m_rscCic->resetLock(SignallingCircuit::Resetting); releaseCircuit(m_rscCic); return; } } if (m_rscSpeedup && !--m_rscSpeedup) { Debug(this,DebugNote,"Reset interval back to %u ms",m_rscInterval); m_rscTimer.interval(m_rscInterval); } m_rscTimer.start(when.msec()); // Pick the next circuit to reset. Ignore circuits locally locked or busy if (m_defPoint && m_remotePoint && reserveCircuit(m_rscCic,0,SignallingCircuit::LockLocal | SignallingCircuit::LockBusy)) { // Avoid already resetting cic if (!findPendingMessage(SS7MsgISUP::RSC,m_rscCic->code())) { m_rscCic->setLock(SignallingCircuit::Resetting); SS7MsgISUP* msg = new SS7MsgISUP(SS7MsgISUP::RSC,m_rscCic->code()); SS7Label label(m_type,*m_remotePoint,*m_defPoint, (m_defaultSls == SlsCircuit) ? m_rscCic->code() : m_sls); DDebug(this,DebugNote,"Periodic restart on cic=%u",m_rscCic->code()); mylock.drop(); transmitMessage(msg,label,false); } else releaseCircuit(m_rscCic); } } // Process a component control request bool SS7ISUP::control(NamedList& params) { String* ret = params.getParam(YSTRING("completion")); const String* oper = params.getParam(YSTRING("operation")); const char* cmp = params.getValue(YSTRING("component")); int cmd = oper ? oper->toInteger(s_dict_control,-1) : -1; if (ret) { if (oper && (cmd < 0)) return false; String part = params.getValue(YSTRING("partword")); if (cmp) { if (toString() != cmp) return false; for (const TokenDict* d = s_dict_control; d->token; d++) Module::itemComplete(*ret,d->token,part); return true; } return Module::itemComplete(*ret,toString(),part); } if (!(cmp && toString() == cmp)) return false; Lock mylock(this); if (!m_remotePoint) return TelEngine::controlReturn(¶ms,false); unsigned int code1 = 1; if (circuits()) { ObjList* o = circuits()->circuits().skipNull(); if (o) { SignallingCircuit* cic = static_cast(o->get()); if (cic) code1 = cic->code(); } } switch (cmd) { case SS7MsgISUP::UPT: case SS7MsgISUP::CVT: { unsigned int code = params.getIntValue(YSTRING("circuit"),code1); SS7MsgISUP* msg = new SS7MsgISUP((SS7MsgISUP::Type)cmd,code); SS7Label label(m_type,*m_remotePoint,*m_defPoint,m_sls); mylock.drop(); transmitMessage(msg,label,false); } return TelEngine::controlReturn(¶ms,true); case SS7MsgISUP::CQM: { unsigned int code = params.getIntValue(YSTRING("circuit"),code1); unsigned int range = params.getIntValue(YSTRING("range"),1); SS7MsgISUP* msg = new SS7MsgISUP(SS7MsgISUP::CQM,code); msg->params().addParam("RangeAndStatus",String(range)); SS7Label label(m_type,*m_remotePoint,*m_defPoint,m_sls); mylock.drop(); transmitMessage(msg,label,false); } return TelEngine::controlReturn(¶ms,true); case SS7MsgISUP::CCR: { unsigned int code = params.getIntValue(YSTRING("circuit"),code1); // TODO: create a test call, not just send CCR SS7MsgISUP* msg = 0; const String& ok = params[YSTRING("success")]; if (ok.isBoolean()) { msg = new SS7MsgISUP(SS7MsgISUP::COT,code); msg->params().addParam("ContinuityIndicators", ok.toBoolean() ? "success" : "failed"); } else msg = new SS7MsgISUP(SS7MsgISUP::CCR,code); SS7Label label(m_type,*m_remotePoint,*m_defPoint,m_sls); mylock.drop(); transmitMessage(msg,label,false); } return TelEngine::controlReturn(¶ms,true); case SS7MsgISUP::RSC: if (0 == (m_rscSpeedup = circuits() ? circuits()->count() : 0)) return TelEngine::controlReturn(¶ms,false); // Temporarily speed up reset interval to 10s or as provided m_rscTimer.interval(params,"interval",2,10,false,true); Debug(this,DebugNote,"Fast reset of %u circuits every %u ms", m_rscSpeedup,(unsigned int)m_rscTimer.interval()); if (m_rscTimer.started()) m_rscTimer.start(Time::msecNow()); return TelEngine::controlReturn(¶ms,true); case SS7MsgISUP::BLK: case SS7MsgISUP::UBL: return TelEngine::controlReturn(¶ms,handleCicBlockCommand(params,cmd == SS7MsgISUP::BLK)); case SS7MsgISUP::RLC: { int code = params.getIntValue(YSTRING("circuit")); if (code <= 0) return TelEngine::controlReturn(¶ms,false); SignallingMessageTimer* pending = findPendingMessage(SS7MsgISUP::RSC,code,true); if (pending) { resetCircuit((unsigned int)code,false,false); TelEngine::destruct(pending); SS7Label label(m_type,*m_remotePoint,*m_defPoint,m_sls); mylock.drop(); transmitRLC(this,code,label,false); } else { RefPointer call; findCall(code,call); if (!call) return TelEngine::controlReturn(¶ms,false); mylock.drop(); call->setTerminate(true,params.getValue(YSTRING("reason"),"normal")); } } return TelEngine::controlReturn(¶ms,true); case SS7MsgISUP::UPA: if (!m_userPartAvail) { const char* oldStat = statusName(); m_uptTimer.stop(); m_userPartAvail = true; m_lockTimer.start(); if (statusName() != oldStat) { NamedList params(""); params.addParam("from",toString()); params.addParam("type","trunk"); params.addParam("operational",String::boolText(m_l3LinkUp)); params.addParam("available",String::boolText(m_userPartAvail)); params.addParam("text",statusName()); engine()->notify(this,params); } } return TelEngine::controlReturn(¶ms,true); case SS7MsgISUP::CtrlSave: setVerify(true,true); return TelEngine::controlReturn(¶ms,true); #ifdef ISUP_HANDLE_CIC_EVENT_CONTROL case SS7MsgISUP::CtrlCicEvent: return TelEngine::controlReturn(¶ms,handleCicEventCommand(params)); #endif } mylock.drop(); return SignallingComponent::control(params); } // Process a notification generated by the attached network layer void SS7ISUP::notify(SS7Layer3* link, int sls) { if (!(link && network())) return; Lock mylock(this); SS7Route::State state = m_remotePoint ? network()->getRouteState(m_type,*m_remotePoint) : SS7Route::Unknown; bool linkTmp = m_l3LinkUp; bool partAvail = m_userPartAvail; const char* oldStat = statusName(); // Copy linkset operational state m_l3LinkUp = network()->operational(); // Reset remote user part's availability state if supported // Force UPT re-send if (m_uptTimer.interval() && (!m_l3LinkUp || (SS7Route::Prohibited == state))) { m_uptTimer.stop(); m_userPartAvail = false; } Debug(this,DebugInfo, "L3 '%s' sls=%d is %soperational.%s Route is %s. Remote User Part is %savailable", link->toString().safe(),sls, (link->operational() ? "" : "not "), (network() == link ? "" : (m_l3LinkUp ? " L3 is up." : " L3 is down.")), SS7Route::stateName(state), (m_userPartAvail ? "" : "un")); if (linkTmp != m_l3LinkUp || partAvail != m_userPartAvail) { NamedList params(""); params.addParam("from",toString()); params.addParam("type","trunk"); params.addParam("operational",String::boolText(m_l3LinkUp)); params.addParam("available",String::boolText(m_userPartAvail)); params.addParam("link",link->toString()); if (statusName() != oldStat) params.addParam("text",statusName()); engine()->notify(this,params); } } SS7MSU* SS7ISUP::buildMSU(SS7MsgISUP::Type type, unsigned char sio, const SS7Label& label, unsigned int cic, const NamedList* params) const { // Special treatment for charge message // Check if it is in raw format if (type == SS7MsgISUP::CRG && params && params->getParam(YSTRING("Charge"))) return encodeRawMessage(type,sio,label,cic,(*params)[YSTRING("Charge")]); if (type == SS7MsgISUP::PAM && params) return encodeRawMessage(type,sio,label,cic,(*params)[YSTRING("PassAlong")]); // see what mandatory parameters we should put in this message const MsgParams* msgParams = getIsupParams(label.type(),type); if (!msgParams) { if (!hasOptionalOnly(type)) { const char* name = SS7MsgISUP::lookup(type); if (name) Debug(this,DebugWarn,"No parameter table for ISUP MSU type %s [%p]",name,this); else Debug(this,DebugWarn,"Cannot create ISUP MSU type 0x%02x [%p]",type,this); return 0; } msgParams = &s_compatibility; } unsigned int len = m_cicLen + 1; const SS7MsgISUP::Parameters* plist = msgParams->params; SS7MsgISUP::Parameters ptype; // first add the length of mandatory fixed parameters while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) { const IsupParam* param = getParamDesc(ptype); if (!param) { // this is fatal as we don't know the length Debug(this,DebugCrit,"Missing description of fixed ISUP parameter 0x%02x [%p]",ptype,this); return 0; } if (!param->size) { Debug(this,DebugCrit,"Invalid (variable) description of fixed ISUP parameter 0x%02x [%p]",ptype,this); return 0; } len += param->size; } // initialize the pointer array offset just past the mandatory fixed part unsigned int ptr = label.length() + 1 + len; // then add one pointer octet to each mandatory variable parameter while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) { const IsupParam* param = getParamDesc(ptype); if (!param) { // this is fatal as we won't be able to populate later Debug(this,DebugCrit,"Missing description of variable ISUP parameter 0x%02x [%p]",ptype,this); return 0; } if (param->size) Debug(this,DebugMild,"Invalid (fixed) description of variable ISUP parameter 0x%02x [%p]",ptype,this); len++; } // finally add a pointer to the optional part only if supported by type if (msgParams->optional) len++; SS7MSU* msu = new SS7MSU(sio,label,0,len); unsigned char* d = msu->getData(label.length()+1,len); unsigned int i = m_cicLen; while (i--) { *d++ = cic & 0xff; cic >>= 8; } *d++ = type; #ifdef XDEBUG if (params && debugAt(DebugAll)) { String tmp; params->dump(tmp,"\r\n ",'\'',true); Debug(this,DebugAll,"SS7ISUP::buildMSU params:%s", tmp.c_str()); } #endif ObjList exclude; plist = msgParams->params; String prefix = params->getValue(YSTRING("message-prefix")); // first populate with mandatory fixed parameters while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) { const IsupParam* param = getParamDesc(ptype); if (!param) { Debug(this,DebugFail,"Stage 2: no description of fixed ISUP parameter 0x%02x [%p]",ptype,this); continue; } if (!param->size) { Debug(this,DebugFail,"Stage 2: Invalid (variable) description of fixed ISUP parameter %s [%p]",param->name,this); continue; } if (!encodeParam(this,*msu,param,params,exclude,prefix,d)) Debug(this,DebugCrit,"Could not encode fixed ISUP parameter %s [%p]",param->name,this); d += param->size; } // now populate with mandatory variable parameters for (; (ptype = *plist++) != SS7MsgISUP::EndOfParameters; ptr++) { const IsupParam* param = getParamDesc(ptype); if (!param) { Debug(this,DebugFail,"Stage 2: no description of variable ISUP parameter 0x%02x [%p]",ptype,this); continue; } if (param->size) { Debug(this,DebugFail,"Stage 2: Invalid (fixed) description of variable ISUP parameter %s [%p]",param->name,this); continue; } // remember the offset this parameter will actually get stored len = msu->length(); unsigned char size = encodeParam(this,*msu,param,params,exclude,prefix); d = msu->getData(0,len+1); if (!(size && d)) { Debug(this,DebugCrit,"Could not encode variable ISUP parameter %s [%p]",param->name,this); continue; } if ((d[len] != size) || (msu->length() != (len+1+size))) { Debug(this,DebugCrit,"Invalid encoding variable ISUP parameter %s (len=%u size=%u stor=%u) [%p]", param->name,len,size,d[len],this); continue; } // store pointer to parameter d[ptr] = len - ptr; } if (msgParams->optional && params) { // remember the offset past last mandatory == first optional parameter len = msu->length(); // optional parameters are possible - try to set anything left in the message unsigned int n = params->length(); for (unsigned int i = 0; i < n; i++) { NamedString* ns = params->getParam(i); if (!ns || exclude.find(ns)) continue; if (prefix && !ns->name().startsWith(prefix)) continue; String tmp(ns->name()); tmp >> prefix.c_str(); static const Regexp s_suffix("\\.[0-9]\\+$"); if (tmp.matches(s_suffix)) { tmp.assign(tmp,tmp.matchOffset()); // WARNING: HACK - ApplicationTransport does not follow naming convention if (tmp == YSTRING("ApplicationTransport")) continue; } const IsupParam* param = getParamDesc(tmp); unsigned char size = 0; if (param) size = encodeParam(this,*msu,param,ns,params,prefix); else if (tmp.startSkip("Param_",false)) { int val = tmp.toInteger(-1); if (val >= 0 && val <= 255) { IsupParam p; p.name = tmp; p.type = (SS7MsgISUP::Parameters)val; p.size = 0; p.encoder = 0; size = encodeParam(this,*msu,&p,ns,params,prefix); } } if (!size) continue; if (len) { d = msu->getData(0,len+1); d[ptr] = len - ptr; len = 0; } } if (!len) { // we stored some optional parameters so we need to put the terminator DataBlock tmp(0,1); *msu += tmp; } } return msu; } SS7MSU* SS7ISUP::encodeRawMessage(SS7MsgISUP::Type type, unsigned char sio, const SS7Label& label, unsigned int cic, const String& param) const { DataBlock raw; if (!raw.unHexify(param.c_str(),param.length(),' ')) { DDebug(this,DebugMild,"Encode raw charge failed: invalid string"); return 0; } if (raw.length() > 254) { DDebug(this,DebugMild,"Encode raw charge failed: data length=%u", raw.length()); return 0; } SS7MSU* msu = new SS7MSU(sio,label,0,m_cicLen + 1); unsigned char* d = msu->getData(label.length()+1,m_cicLen + 1); unsigned int i = m_cicLen; while (i--) { *d++ = cic & 0xff; cic >>= 8; } *d++ = type; *msu += raw; return msu; } // Decode a buffer to a list of parameters bool SS7ISUP::decodeMessage(NamedList& msg, SS7MsgISUP::Type msgType, SS7PointCode::Type pcType, const unsigned char* paramPtr, unsigned int paramLen) { String msgTypeName((int)msgType); const char* msgName = SS7MsgISUP::lookup(msgType,msgTypeName); #ifdef XDEBUG String tmp; tmp.hexify((void*)paramPtr,paramLen,' '); Debug(this,DebugAll,"Decoding msg=%s len=%u: %s [%p]", msgName,paramLen,tmp.c_str(),this); #else DDebug(this,DebugAll,"Decoding msg=%s len=%u [%p]", msgName,paramLen,this); #endif // see what parameters we expect for this message const MsgParams* params = getIsupParams(pcType,msgType); if (!params) { if (hasOptionalOnly(msgType)) { Debug(this,DebugNote,"Unsupported message %s, decoding compatibility [%p]",msgName,this); params = &s_compatibility; } else if (msgType != SS7MsgISUP::PAM) { Debug(this,DebugWarn,"Unsupported message %s or point code type [%p]",msgName,this); return false; } else if (!paramLen) { // PAM message must have at least 1 byte for message type Debug(this,DebugNote,"Empty %s [%p]",msgName,this); return false; } } // Get parameter prefix String prefix = msg.getValue(YSTRING("message-prefix")); // Add protocol and message type if (!msg.getValue(prefix+"protocol-type")) { switch (pcType) { case SS7PointCode::ITU: msg.setParam(prefix+"protocol-type","itu-t"); break; case SS7PointCode::ANSI: case SS7PointCode::ANSI8: msg.setParam(prefix+"protocol-type","ansi"); break; default: ; } } msg.addParam(prefix+"message-type",msgName); // Special decoder for PAM if (msgType == SS7MsgISUP::PAM) { String raw; raw.hexify((void*)paramPtr,paramLen,' '); msg.addParam(prefix + "PassAlong",raw); return true; } // Decode raw CRG if specified if (msgType == SS7MsgISUP::CRG && getChargeProcessType() != Parsed) { String raw; raw.hexify((void*)paramPtr,paramLen,' '); msg.addParam(prefix + "Charge",raw); return true; } String unsupported; const SS7MsgISUP::Parameters* plist = params->params; SS7MsgISUP::Parameters ptype; // first decode any mandatory fixed parameters the message should have while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) { const IsupParam* param = getParamDesc(ptype); if (!param) { // this is fatal as we don't know the length Debug(this,DebugCrit,"Missing description of fixed ISUP parameter 0x%02x [%p]",ptype,this); return false; } if (!param->size) { Debug(this,DebugCrit,"Invalid (variable) description of fixed ISUP parameter %s [%p]",param->name,this); return false; } if (paramLen < param->size) { Debug(this,DebugWarn,"Truncated ISUP message! [%p]",this); return false; } if (!decodeParam(this,msg,param,paramPtr,param->size,prefix)) { Debug(this,DebugWarn,"Could not decode fixed ISUP parameter %s [%p]",param->name,this); decodeRaw(this,msg,param,paramPtr,param->size,prefix); SignallingUtils::appendFlag(unsupported,param->name); } paramPtr += param->size; paramLen -= param->size; } // while ((ptype = *plist++)... bool mustWarn = true; // next decode any mandatory variable parameters the message should have while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) { mustWarn = false; const IsupParam* param = getParamDesc(ptype); if (!param) { // we could skip over unknown mandatory variable length but it's still bad Debug(this,DebugCrit,"Missing description of variable ISUP parameter 0x%02x [%p]",ptype,this); return false; } if (param->size) Debug(this,DebugMild,"Invalid (fixed) description of variable ISUP parameter %s [%p]",param->name,this); unsigned int offs = paramPtr[0]; if ((offs < 1) || (offs >= paramLen)) { Debug(this,DebugWarn,"Invalid offset %u (len=%u) ISUP parameter %s [%p]", offs,paramLen,param->name,this); return false; } unsigned int size = paramPtr[offs]; if ((size < 1) || (offs+size >= paramLen)) { Debug(this,DebugWarn,"Invalid size %u (ofs=%u, len=%u) ISUP parameter %s [%p]", size,offs,paramLen,param->name,this); return false; } if (!decodeParam(this,msg,param,paramPtr+offs+1,size,prefix)) { Debug(this,DebugWarn,"Could not decode variable ISUP parameter %s (size=%u) [%p]", param->name,size,this); decodeRaw(this,msg,param,paramPtr+offs+1,size,prefix); SignallingUtils::appendFlag(unsupported,param->name); } paramPtr++; paramLen--; } // while ((ptype = *plist++)... // now decode the optional parameters if the message supports them if (params->optional) { unsigned int offs = paramLen ? paramPtr[0] : 0; if (offs >= paramLen) { if (paramLen) { Debug(this,DebugWarn,"Invalid ISUP optional offset %u (len=%u) [%p]", offs,paramLen,this); return false; } Debug(this,DebugMild,"ISUP message %s lacking optional parameters [%p]", msgName,this); } else if (offs) { mustWarn = true; // advance pointer past mandatory parameters paramPtr += offs; paramLen -= offs; while (paramLen) { ptype = (SS7MsgISUP::Parameters)(*paramPtr++); paramLen--; if (ptype == SS7MsgISUP::EndOfParameters) break; if (paramLen < 2) { Debug(this,DebugWarn,"Only %u octets while decoding optional ISUP parameter 0x%02x [%p]", paramLen,ptype,this); return false; } unsigned int size = *paramPtr++; paramLen--; if ((size < 1) || (size >= paramLen)) { Debug(this,DebugWarn,"Invalid size %u (len=%u) ISUP optional parameter 0x%02x [%p]", size,paramLen,ptype,this); return false; } const IsupParam* param = getParamDesc(ptype); if (!param) { Debug(this,DebugMild,"Unknown optional ISUP parameter 0x%02x (size=%u) [%p]",ptype,size,this); decodeRawParam(this,msg,ptype,paramPtr,size,prefix); SignallingUtils::appendFlag(unsupported,String((unsigned int)ptype)); } else if (!decodeParam(this,msg,param,paramPtr,size,prefix)) { Debug(this,DebugWarn,"Could not decode optional ISUP parameter %s (size=%u) [%p]",param->name,size,this); decodeRaw(this,msg,param,paramPtr,size,prefix); SignallingUtils::appendFlag(unsupported,param->name); } paramPtr += size; paramLen -= size; } // while (paramLen) } // else if (offs) else paramLen = 0; } if (unsupported) msg.addParam(prefix + "parameters-unsupported",unsupported); String release,cnf,npRelease; String pCompat(prefix + "ParameterCompatInformation."); unsigned int n = msg.length(); for (unsigned int i = 0; i < n; i++) { NamedString* ns = msg.getParam(i); if (!(ns && ns->name().startsWith(pCompat) && !ns->name().endsWith(".more"))) continue; ObjList* l = ns->split(',',false); for (ObjList* ol = l->skipNull(); ol; ol = ol->skipNext()) { String* s = static_cast(ol->get()); if (*s == YSTRING("release")) { SignallingUtils::appendFlag(release,ns->name().substr(pCompat.length())); break; } if (*s == YSTRING("cnf")) SignallingUtils::appendFlag(cnf,ns->name().substr(pCompat.length())); if (*s == YSTRING("nopass-release")) SignallingUtils::appendFlag(npRelease,ns->name().substr(pCompat.length())); } TelEngine::destruct(l); } if (release) msg.setParam(prefix + "parameters-unhandled-release",release); if (cnf) msg.setParam(prefix + "parameters-unhandled-cnf",cnf); if (npRelease) msg.setParam(prefix + "parameters-nopass-release",npRelease); if (paramLen && mustWarn) Debug(this,DebugWarn,"Got %u garbage octets after message type 0x%02x [%p]", paramLen,msgType,this); return true; } // Encode an ISUP list of parameters to a buffer bool SS7ISUP::encodeMessage(DataBlock& buf, SS7MsgISUP::Type msgType, SS7PointCode::Type pcType, const NamedList& params, unsigned int* cic) { unsigned int circuit = cic ? *cic : 0; SS7Label label(pcType,1,1,1); SS7MSU* msu = buildMSU(msgType,1,label,circuit,¶ms); if (!msu) return false; unsigned int start = 1 + label.length() + (cic ? 0 : m_cicLen); buf.assign(((char*)msu->data()) + start,msu->length() - start); TelEngine::destruct(msu); return true; } // Process parameter compatibility lists // Terminate an existing call or send CNF // Return true if any parameter compatibility was handled bool SS7ISUP::processParamCompat(const NamedList& list, unsigned int cic, bool* callReleased) { if (!cic) return true; const String& prefix = list[YSTRING("message-prefix")]; // Release call params String relCall = list[prefix + "parameters-unhandled-release"]; relCall.append(list[prefix + "parameters-nopass-release"],","); if (relCall) { Lock lock(this); SS7ISUPCall* call = findCall(cic); Debug(this,DebugNote, "Terminating call (%p) on cic=%u: unknown/unhandled params='%s' [%p]", call,cic,relCall.c_str(),this); String diagnostic; hexifyIsupParams(diagnostic,relCall); if (call) { lock.drop(); call->setTerminate(true,"unknown-ie",diagnostic,m_location); } else if (m_remotePoint) { // No call: make sure the circuit is released at remote party SS7Label label(m_type,*m_remotePoint,*m_defPoint, (m_defaultSls == SlsCircuit) ? cic : m_sls); lock.drop(); transmitRLC(this,cic,label,false,"unknown-ie",diagnostic,m_location); } if (callReleased) *callReleased = true; return true; } // Send CNF params const String& cnf = list[prefix + "parameters-unhandled-cnf"]; if (!cnf) return false; DDebug(this,DebugAll,"processParamCompat() cic=%u sending CNF for '%s' [%p]", cic,cnf.c_str(),this); String diagnostic; hexifyIsupParams(diagnostic,cnf); if (diagnostic && m_remotePoint) { SS7Label label(m_type,*m_remotePoint,*m_defPoint, (m_defaultSls == SlsCircuit) ? cic : m_sls); transmitCNF(this,cic,label,false,"unknown-ie",diagnostic,m_location); } return !diagnostic.null(); } HandledMSU SS7ISUP::receivedMSU(const SS7MSU& msu, const SS7Label& label, SS7Layer3* network, int sls) { if (msu.getSIF() != sif() || !hasPointCode(label.dpc()) || !handlesRemotePC(label.opc())) return HandledMSU::Rejected; // we should have at least 2 bytes CIC and 1 byte message type const unsigned char* s = msu.getData(label.length()+1,3); if (!s) { Debug(this,DebugNote,"Got short MSU"); return false; } unsigned int len = msu.length()-label.length()-1; unsigned int cic = s[0] | (s[1] << 8); SS7MsgISUP::Type type = (SS7MsgISUP::Type)s[2]; String name = SS7MsgISUP::lookup(type); if (!name) { String tmp; tmp.hexify((void*)s,len,' '); Debug(this,DebugMild,"Received unknown ISUP type 0x%02x, cic=%u, length %u: %s", type,cic,len,tmp.c_str()); name = (int)type; } if (!(circuits() && circuits()->find(cic))) { Debug(this,m_cicWarnLevel,"Received ISUP type 0x%02x (%s) for unknown cic=%u", type,name.c_str(),cic); m_cicWarnLevel = DebugAll; return HandledMSU::NoCircuit; } bool ok = processMSU(type,cic,s+3,len-3,label,network,sls); if (!ok && debugAt(DebugMild)) { String tmp; tmp.hexify((void*)s,len,' '); Debug(this,DebugMild,"Unhandled ISUP type %s, cic=%u, length %u: %s", name.c_str(),cic,len,tmp.c_str()); } return ok; } bool SS7ISUP::processMSU(SS7MsgISUP::Type type, unsigned int cic, const unsigned char* paramPtr, unsigned int paramLen, const SS7Label& label, SS7Layer3* network, int sls) { XDebug(this,DebugAll,"SS7ISUP::processMSU(%u,%u,%p,%u,%p,%p,%d) [%p]", type,cic,paramPtr,paramLen,&label,network,sls,this); SS7MsgISUP* msg = new SS7MsgISUP(type,cic); if (!SS7MsgISUP::lookup(type)) { String tmp; tmp.hexify(&type,1); msg->params().assign("Message_" + tmp); } if (!decodeMessage(msg->params(),type,label.type(),paramPtr,paramLen)) { TelEngine::destruct(msg); return false; } if (m_printMsg && debugAt(DebugInfo)) { String tmp; msg->toString(tmp,label,debugAt(DebugAll), m_extendedDebug ? paramPtr : 0,paramLen); Debug(this,DebugInfo,"Received message (%p)%s",msg,tmp.c_str()); } else if (debugAt(DebugAll)) { String tmp; tmp << label; Debug(this,DebugAll,"Received message '%s' cic=%u label=%s", msg->name(),msg->cic(),tmp.c_str()); } // TODO: check parameters-unsupported vs. ParameterCompatInformation // Check if we expected some response to UPT // Ignore if (!m_userPartAvail && m_uptTimer.started()) { m_uptTimer.stop(); const char* oldStat = statusName(); m_userPartAvail = true; m_lockTimer.start(); Debug(this,DebugInfo,"Remote user part is available"); if (statusName() != oldStat) { NamedList params(""); params.addParam("from",toString()); params.addParam("type","trunk"); params.addParam("operational",String::boolText(m_l3LinkUp)); params.addParam("available",String::boolText(m_userPartAvail)); params.addParam("text",statusName()); engine()->notify(this,params); } if (msg->cic() == m_uptCicCode && (msg->type() == SS7MsgISUP::UPA || msg->type() == SS7MsgISUP::CVR || msg->type() == SS7MsgISUP::CNF || msg->type() == SS7MsgISUP::UEC)) { m_uptCicCode = 0; TelEngine::destruct(msg); return true; } } switch (msg->type()) { case SS7MsgISUP::IAM: case SS7MsgISUP::SAM: case SS7MsgISUP::ACM: case SS7MsgISUP::EXM: case SS7MsgISUP::CPR: case SS7MsgISUP::ANM: case SS7MsgISUP::CON: case SS7MsgISUP::REL: case SS7MsgISUP::SGM: case SS7MsgISUP::CCR: case SS7MsgISUP::COT: case SS7MsgISUP::APM: case SS7MsgISUP::SUS: case SS7MsgISUP::RES: processCallMsg(msg,label,sls); break; case SS7MsgISUP::CRG: switch (getChargeProcessType()) { case Confusion: processControllerMsg(msg,label,sls); case Ignore: break; default: processCallMsg(msg,label,sls); } break; case SS7MsgISUP::RLC: if (m_rscCic && m_rscCic->code() == msg->cic()) processControllerMsg(msg,label,sls); else { SignallingMessageTimer* m = findPendingMessage(SS7MsgISUP::RSC,msg->cic(),true); if (m) { DDebug(this,DebugAll,"RSC confirmed for pending cic=%u",msg->cic()); resetCircuit(msg->cic(),false,false); TelEngine::destruct(m); } else processCallMsg(msg,label,sls); } break; default: processControllerMsg(msg,label,sls); } TelEngine::destruct(msg); return true; } // MTP notification that remote user part is unavailable void SS7ISUP::receivedUPU(SS7PointCode::Type type, const SS7PointCode node, SS7MSU::Services part, unsigned char cause, const SS7Label& label, int sls) { if (part != sif() || !handlesRemotePC(node)) return; if (!(m_userPartAvail && m_uptTimer.interval())) return; const char* oldStat = statusName(); Debug(this,DebugNote,"Remote User Part is unavailable (received UPU)"); m_userPartAvail = false; m_uptTimer.start(); if (statusName() != oldStat) { NamedList params(""); params.addParam("from",toString()); params.addParam("type","trunk"); params.addParam("operational",String::boolText(m_l3LinkUp)); params.addParam("available",String::boolText(m_userPartAvail)); params.addParam("text",statusName()); engine()->notify(this,params); } } // Process an event received from a non-reserved circuit SignallingEvent* SS7ISUP::processCircuitEvent(SignallingCircuitEvent*& event, SignallingCall* call) { if (!event) return 0; SignallingEvent* ev = 0; switch (event->type()) { case SignallingCircuitEvent::Alarm: case SignallingCircuitEvent::NoAlarm: if (event->circuit()) { lock(); bool block = (event->type() == SignallingCircuitEvent::Alarm); bool blocked = (0 != event->circuit()->locked(SignallingCircuit::LockLocalHWFail)); // Avoid notifying the same state if (block != blocked) { event->circuit()->hwLock(block,false,true,true); if (!m_lockTimer.started()) m_lockTimer.start(); if (block) cicHwBlocked(event->circuit()->code(),String("1")); } unlock(); ev = new SignallingEvent(event,call); } break; case SignallingCircuitEvent::Dtmf: if (event->getValue(YSTRING("tone"))) { SignallingMessage* msg = new SignallingMessage(event->c_str()); msg->params().addParam("tone",event->getValue(YSTRING("tone"))); msg->params().addParam("inband",event->getValue(YSTRING("inband"),String::boolText(true))); ev = new SignallingEvent(SignallingEvent::Info,msg,call); TelEngine::destruct(msg); } break; default: ev = new SignallingEvent(event,call); } TelEngine::destruct(event); return ev; } // Initiate a circuit reset bool SS7ISUP::startCircuitReset(SignallingCircuit*& cic, const String& timer) { if (!cic) return false; bool ok = false; do { Lock lock(this); // Check if the circuit can be reset // Do nothing on locally locked circuit: this would clear our lock // state at remote side. See Q.764 2.9.3.1 if (cic->locked(SignallingCircuit::LockLocal)) { Debug(this,DebugNote, "Failed to start reset on locally locked circuit (cic=%u timer=%s) [%p]", cic->code(),timer.c_str(),this); ok = SignallingCallControl::releaseCircuit(cic); break; } // Check if there is any management operation in progress on the cic if (cic->locked(SignallingCircuit::LockBusy)) break; bool relTimeout = (timer == "T5"); Debug(this,!relTimeout ? DebugAll : DebugNote, "Starting circuit %u reset on timer %s [%p]", cic->code(),timer.c_str(),this); // TODO: alert maintenance if T5 timer expired SignallingMessageTimer* m = 0; if (relTimeout) m = new SignallingMessageTimer(m_t17Interval); else m = new SignallingMessageTimer(m_t16Interval,m_t17Interval); m = m_pending.add(m); if (m) { cic->setLock(SignallingCircuit::Resetting); SS7MsgISUP* msg = new SS7MsgISUP(SS7MsgISUP::RSC,cic->code()); msg->params().addParam("isup_pending_reason",timer,false); if (relTimeout) msg->params().addParam("isup_alert_maint",String::boolText(true)); msg->ref(); m->message(msg); lock.drop(); ok = true; SS7Label label; if (setLabel(label,msg->cic())) transmitMessage(msg,label,false); } else { Debug(this,DebugNote, "Failed to add circuit %u reset to pending messages timer=%s [%p]", cic->code(),timer.c_str(),this); ok = SignallingCallControl::releaseCircuit(cic); } } while (false); TelEngine::destruct(cic); return ok; } // Process call related messages void SS7ISUP::processCallMsg(SS7MsgISUP* msg, const SS7Label& label, int sls) { // Find a call for this message, create a new one or drop the message RefPointer call; findCall(msg->cic(),call); const char* reason = 0; while (true) { #define DROP_MSG(res) { reason = res; break; } // Avoid cic == 0 if (!msg->cic()) DROP_MSG("invalid CIC") // non IAM message. Drop it if there is no call for it if ((msg->type() != SS7MsgISUP::IAM) && (msg->type() != SS7MsgISUP::CCR)) { if (!call) { if (msg->type() == SS7MsgISUP::REL) DROP_MSG("no call") if (msg->type() != SS7MsgISUP::RLC) { // Initiate circuit reset SignallingCircuit* cic = 0; String s(msg->cic()); if (reserveCircuit(cic,0,SignallingCircuit::LockLockedBusy,&s)) startCircuitReset(cic,"T16"); } return; } break; } // IAM or CCR message SignallingCircuit* circuit = 0; // Check collision if (call) { // If existing call is an incoming one, drop the message (retransmission ?) if (!call->outgoing()) { if (msg->type() == SS7MsgISUP::CCR) break; else DROP_MSG("retransmission") } Debug(this,DebugNote,"Incoming call %u collide with existing outgoing",msg->cic()); // *** See Q.764 2.9.1.4 // Drop the request if the outgoing call already received some response or // the destination point code is greater then the originating and the CIC is even if (call->state() > SS7ISUPCall::Setup) DROP_MSG("collision - outgoing call responded") // The greater point code should have the even circuit unsigned int dpc = label.dpc().pack(label.type()); unsigned int opc = label.opc().pack(label.type()); bool controlling = (dpc > opc); bool even = (0 == (msg->cic() % 2)); if (controlling == even) DROP_MSG("collision - we control the CIC") // Accept the incoming request. Change the call's circuit reserveCircuit(circuit,call->cicRange(),SignallingCircuit::LockLockedBusy); call->replaceCircuit(circuit); circuit = 0; call = 0; } int flags = SignallingCircuit::LockLockedBusy; // Q.764 2.8.2 - accept test calls even if the remote side is blocked // Q.764 2.8.2.3 (xiv) - unblock remote side of the circuit for non-test calls if ((msg->type() == SS7MsgISUP::CCR) || (msg->params()[YSTRING("CallingPartyCategory")] == YSTRING("test"))) { Debug(this,DebugInfo,"Received test call on circuit %u",msg->cic()); flags = 0; } else { circuit = circuits() ? circuits()->find(msg->cic()) : 0; if (circuit && circuit->locked(SignallingCircuit::LockRemote)) { Debug(this,DebugNote,"Unblocking remote circuit %u on IAM request",msg->cic()); circuit->hwLock(false,true,0!=circuit->locked(SignallingCircuit::LockRemoteHWFail),false); circuit->maintLock(false,true,0!=circuit->locked(SignallingCircuit::LockRemoteMaint),false); m_verifyEvent = true; } circuit = 0; } String s(msg->cic()); if (reserveCircuit(circuit,0,flags,&s,true)) { call = new SS7ISUPCall(this,circuit,label.dpc(),label.opc(),false,label.sls(), 0,msg->type() == SS7MsgISUP::CCR); m_calls.append(call); break; } // Congestion: send REL SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::REL,msg->cic()); m->params().addParam("CauseIndicators","congestion"); transmitMessage(m,label,true); DROP_MSG("can't reserve circuit") #undef DROP_MSG } if (!reason) { msg->ref(); call->enqueue(msg); } else { if (msg->type() != SS7MsgISUP::IAM && msg->type() != SS7MsgISUP::RLC) transmitRLC(this,msg->cic(),label,true); if (msg->type() != SS7MsgISUP::RLC) Debug(this,DebugNote,"'%s' with cic=%u: %s",msg->name(),msg->cic(),reason); } } unsigned int getRangeAndStatus(NamedList& nl, unsigned int minRange, unsigned int maxRange, unsigned int maxMap = 0, String** map = 0, unsigned int nCicsMax = 0) { unsigned int range = nl.getIntValue(YSTRING("RangeAndStatus")); if (range < minRange || range > maxRange) return 0; if (!maxMap) return range; NamedString* ns = nl.getParam(YSTRING("RangeAndStatus.map")); if (!ns || ns->length() > maxMap || ns->length() < range) return 0; if (map) { if (nCicsMax) { // Check the number of bits set to 1 (circuits affected) for (unsigned int i = 0; i < ns->length(); i++) { if ((*ns)[i] != '1') continue; if (!nCicsMax) return 0; nCicsMax--; } } *map = ns; } return range; } // Retrieve maintenance/hwfail type indicator // Return false if invalid static bool getGrpTypeInd(SS7ISUP* isup, SS7MsgISUP* msg, bool& hwFail, NamedString** ns = 0) { if (!msg) return false; NamedString* s = msg->params().getParam(YSTRING("GroupSupervisionTypeIndicator")); if (s) { if (ns) *ns = s; hwFail = (*s == YSTRING("hw-failure")); if (hwFail || (*s == YSTRING("maintenance"))) return true; } Debug(isup,DebugNote,"%s with unknown/unsupported GroupSupervisionTypeIndicator=%s [%p]", msg->name(),TelEngine::c_safe(s),isup); return false; } // Utility: set invalid-ie reason and diagnostic static inline void setInvalidIE(unsigned char ie, const char*& reason, String& diagnostic) { reason = "invalid-ie"; diagnostic.hexify(&ie,1); } // Process controller related messages // Q.764 2.1.12: stop waiting for SGM if message is not: COT,BLK,BLA,UBL,UBA,CGB,CGA,CGU,CUA,CQM,CQR void SS7ISUP::processControllerMsg(SS7MsgISUP* msg, const SS7Label& label, int sls) { const char* reason = 0; String diagnostic; bool impl = true; bool stopSGM = false; // TODO: Check if segmentation should stop for all affected circuits received withing range (CGB,GRS, ...) switch (msg->type()) { case SS7MsgISUP::CNF: // Confusion // TODO: check if this message was received in response to RSC, UBL, UBK, CGB, CGU Debug(this,DebugNote,"%s with cic=%u cause='%s' diagnostic='%s'", msg->name(),msg->cic(), msg->params().getValue(YSTRING("CauseIndicators")), msg->params().getValue(YSTRING("CauseIndicators.diagnostic"))); stopSGM = true; break; case SS7MsgISUP::RLC: // Release Complete // Response to RSC: reset local lock flags. Release m_rscCic resetCircuit(msg->cic(),false,false); break; case SS7MsgISUP::RSC: // Reset Circuit if (resetCircuit(msg->cic(),true,true)) { // Send BLK on previously blocked cic: Q.764 2.9.3.1 c) lock(); SignallingCircuit* cic = circuits() ? circuits()->find(msg->cic()) : 0; SS7MsgISUP* m = 0; if (cic && cic->locked(SignallingCircuit::LockLocalMaint) && !cic->locked(SignallingCircuit::LockingMaint)) m = buildCicBlock(cic,true,true); unlock(); if (m) transmitMessage(m,label,true); transmitRLC(this,msg->cic(),label,true); } else reason = "unknown-channel"; stopSGM = true; break; case SS7MsgISUP::GRS: // Circuit Group Reset stopSGM = true; { // Q.763 3.43 min=1 max=31 unsigned int n = getRangeAndStatus(msg->params(),1,31); if (!n) { Debug(this,DebugNote,"%s with invalid range %s",msg->name(), msg->params().getValue(YSTRING("RangeAndStatus"))); break; } else if (n == 1 && m_ignoreGRSSingle) { Debug(this,DebugAll,"Ignoring %s with range 1",msg->name()); break; } String map('0',n); char* d = (char*)map.c_str(); for (unsigned int i = 0; i < n; i++) if (!resetCircuit(msg->cic()+i,true,true)) d[i] = '1'; SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::GRA,msg->cic()); m->params().addParam("RangeAndStatus",String(n)); m->params().addParam("RangeAndStatus.map",map); transmitMessage(m,label,true); } break; case SS7MsgISUP::UBL: // Unblocking if (blockCircuit(msg->cic(),false,true,false,true,true)) transmitMessage(new SS7MsgISUP(SS7MsgISUP::UBA,msg->cic()),label,true); else reason = "unknown-channel"; break; case SS7MsgISUP::BLK: // Blocking if (blockCircuit(msg->cic(),true,true,false,true,true)) { transmitMessage(new SS7MsgISUP(SS7MsgISUP::BLA,msg->cic()),label,true); // Replace circuit for outgoing call in initial state replaceCircuit(msg->cic(),String('1')); } else reason = "unknown-channel"; break; case SS7MsgISUP::UBA: // Unblocking Acknowledgement case SS7MsgISUP::BLA: // Blocking Acknowledgement { bool block = (msg->type() == SS7MsgISUP::BLA); SS7MsgISUP::Type type = block ? SS7MsgISUP::BLK : SS7MsgISUP::UBL; SignallingMessageTimer* t = findPendingMessage(type,msg->cic(),true); if (t) { SS7MsgISUP* m = static_cast(t->message()); bool hw = m && m->params().getBoolValue(YSTRING("isup_pending_block_hwfail")); DDebug(this,m ? DebugAll : DebugNote,"%s confirmed for pending cic=%u", block ? "BLK" : "UBL",msg->cic()); TelEngine::destruct(t); blockCircuit(msg->cic(),block,false,hw,true,false,true); sendLocalLock(); } else reason = "wrong-state-message"; } break; case SS7MsgISUP::CGA: // Circuit Group Blocking Acknowledgement case SS7MsgISUP::CUA: // Circuit Group Unblocking Acknowledgement // Q.763 3.43 range can be 1..256. Max bits set to 1 should be 32 // Bit: 0-no indication 1-block/unblock { bool hwFail = false; NamedString* grpSuperType = 0; if (!getGrpTypeInd(this,msg,hwFail,&grpSuperType)) break; String* srcMap = 0; unsigned int nCics = getRangeAndStatus(msg->params(),1,256,256,&srcMap,32); if (!nCics) { Debug(this,DebugNote,"%s (%s) cic=%u with invalid range %s or map=%s", msg->name(),grpSuperType->c_str(),msg->cic(), msg->params().getValue(YSTRING("RangeAndStatus")), msg->params().getValue(YSTRING("RangeAndStatus.map"))); break; } bool block = (msg->type() == SS7MsgISUP::CGA); lock(); // Check for correct response: same msg type, circuit code, type indicator, circuit map SS7MsgISUP::Type type = block ? SS7MsgISUP::CGB : SS7MsgISUP::CGU; SignallingMessageTimer* t = findPendingMessage(type,msg->cic(), grpSuperType->name(),*grpSuperType); if (!t) { Debug(this,DebugNote,"%s (%s) cic=%u: no request for it in our queue", msg->name(),grpSuperType->c_str(),msg->cic()); unlock(); break; } SS7MsgISUP* m = static_cast(t->message()); String map; while (m) { // Check map map = m->params()[YSTRING("RangeAndStatus.map")]; if (!map) break; if (map.length() != nCics) { map.clear(); break; } for (unsigned int i = 0; i < map.length(); i++) if (map[i] == '0' && (*srcMap)[i] != '0') { map.clear(); break; } break; } if (map) { DDebug(this,DebugAll,"%s (%s) confirmed for pending cic=%u", m->name(),grpSuperType->c_str(),msg->cic()); m_pending.remove(t); } unlock(); if (!map) { Debug(this,DebugNote,"%s (%s) cic=%u with unnacceptable range %s or map=%s", msg->name(),grpSuperType->c_str(),msg->cic(), msg->params().getValue(YSTRING("RangeAndStatus")), msg->params().getValue(YSTRING("RangeAndStatus.map"))); break; } for (unsigned int i = 0; i < map.length(); i++) if (map[i] != '0') blockCircuit(msg->cic()+i,block,false,hwFail,true,false,true); sendLocalLock(); } break; case SS7MsgISUP::CGB: // Circuit Group Blocking case SS7MsgISUP::CGU: // Circuit Group Unblocking // Q.763 3.43 range can be 1..256. Max bits set to 1 should be 32 // Bit: 0-no indication 1-block/unblock { bool hwFail = false; if (!getGrpTypeInd(this,msg,hwFail)) break; bool block = (msg->type() == SS7MsgISUP::CGB); String* srcMap = 0; unsigned int nCics = getRangeAndStatus(msg->params(),1,256,256,&srcMap,32); if (!nCics) { Debug(this,DebugNote,"%s with invalid range %s or map=%s",msg->name(), msg->params().getValue(YSTRING("RangeAndStatus")), msg->params().getValue(YSTRING("RangeAndStatus.map"))); break; } else if (nCics == 1 && ((block && m_ignoreCGBSingle) || (!block && m_ignoreCGUSingle))) { Debug(this,DebugAll,"Ignoring %s with range 1",msg->name()); break; } String map('0',srcMap->length()); char* d = (char*)map.c_str(); for (unsigned int i = 0; i < srcMap->length(); i++) if (srcMap->at(i) != '0' && blockCircuit(msg->cic()+i,block,true,hwFail,true,true)) d[i] = '1'; SS7MsgISUP* m = new SS7MsgISUP(block?SS7MsgISUP::CGA:SS7MsgISUP::CUA,msg->cic()); m->params().copyParam(msg->params(),"GroupSupervisionTypeIndicator"); m->params().addParam("RangeAndStatus",String(nCics)); m->params().addParam("RangeAndStatus.map",map); transmitMessage(m,label,true); // Replace circuits for outgoing calls in initial state // Terminate all others when blocking for hw failure if (block) { if (hwFail) cicHwBlocked(msg->cic(),map); else replaceCircuit(msg->cic(),map); } } break; case SS7MsgISUP::UEC: // Unequipped CIC (national use) Debug(this,DebugWarn,"%s for cic=%u. Circuit is unequipped on remote side", msg->name(),msg->cic()); blockCircuit(msg->cic(),true,true,false,true,true); break; case SS7MsgISUP::UPT: // User Part Test transmitMessage(new SS7MsgISUP(SS7MsgISUP::UPA,msg->cic()),label,true); break; case SS7MsgISUP::UPA: // User Part Available if (m_uptCicCode && m_uptCicCode == msg->cic()) { DDebug(this,DebugInfo,"Received valid %s",msg->name()); m_uptCicCode = 0; } #ifdef DEBUG else Debug(this,DebugMild,"Received unexpected %s",msg->name()); #endif break; case SS7MsgISUP::GRA: // Circuit Group Reset Acknowledgement // TODO: stop receiving segments reason = "wrong-state-message"; break; case SS7MsgISUP::CVT: // Circuit Validation Test (ANSI) if (circuits() && circuits()->find(msg->cic())) { SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CVR,msg->cic()); m->params().addParam("CircuitValidationRespIndicator","success"); transmitMessage(m,label,true); } else reason = "unknown-channel"; break; case SS7MsgISUP::CQM: // Circuit Group Query (national use) if (circuits()) { // Q.763 3.43 min=1 max=31 unsigned int n = getRangeAndStatus(msg->params(),1,31); if (!n) { reason = "invalid-ie"; break; } DataBlock si(0,n); for (unsigned int i = 0; i < n; i++) { unsigned char* state = si.data(i); if (!state) break; SignallingCircuit* circuit = circuits()->find(msg->cic()+i); if (circuit && (circuit->status() != SignallingCircuit::Missing)) { switch (circuit->locked(SignallingCircuit::LockLocalMaint | SignallingCircuit::LockRemoteMaint)) { case SignallingCircuit::LockLocalMaint: *state = 0x01; // locally maint blocked break; case SignallingCircuit::LockRemoteMaint: *state = 0x02; // remote maint blocked break; case SignallingCircuit::LockLocalMaint | SignallingCircuit::LockRemoteMaint: *state = 0x03; // locally and remote maint blocked break; } switch (circuit->locked(SignallingCircuit::LockLocalHWFail | SignallingCircuit::LockRemoteHWFail)) { case SignallingCircuit::LockLocalHWFail: *state |= 0x1c; // locally hw blocked continue; case SignallingCircuit::LockRemoteHWFail: *state |= 0x2c; // locally hw blocked continue; case SignallingCircuit::LockLocalHWFail | SignallingCircuit::LockRemoteHWFail: *state |= 0x3c; // locally and remotely hw blocked continue; } if (circuit->connected()) *state |= 0x04; // incoming busy else if (!circuit->available()) *state |= 0x08; // outgoing busy else *state |= 0x0c; // idle } else *state = 0x03; // Unequipped } String tmp; tmp.hexify(si.data(),si.length(),' '); DDebug(this,DebugInfo,"Sending CQR (%u+%u): %s",msg->cic(),n,tmp.c_str()); SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CQR,msg->cic()); m->params().addParam("RangeAndStatus",String(n)); m->params().addParam("CircuitStateIndicator",tmp); transmitMessage(m,label,true); } else reason = "unknown-channel"; break; case SS7MsgISUP::CQR: // Circuit Group Query Response (national use) case SS7MsgISUP::CVR: // Circuit Validation Response (ANSI) case SS7MsgISUP::LPA: // Loopback Acknowledge (national use) // Known but not implemented responses, just ignore them impl = false; break; default: impl = false; // Q.764 2.9.5.1: call in Setup state: // incoming: drop it and reset cic // outgoing: repeat IAM and reset cic lock(); SS7ISUPCall* call = findCall(msg->cic()); if (call) call->ref(); unlock(); if (m_dropOnUnknown && call && call->earlyState() && msg->type() != SS7MsgISUP::CRG) { Debug(this,DebugNote, "Received unexpected message for call %u (%p) in initial state", msg->cic(),call); if (call->outgoing()) replaceCircuit(msg->cic(),String("1"),false); else { call->setTerminate(false,"normal",0,m_location); SignallingCircuit* c = call->m_circuit; if (c && c->ref()) startCircuitReset(c,String::empty()); } } else { bool cnf = false; bool release = false; getMsgCompat(msg,release,cnf); if (cnf || release) { reason = "unknown-message"; unsigned char type = msg->type(); diagnostic.hexify(&type,1); if (release) { if (call) call->setTerminate(true,reason,diagnostic,m_location); else transmitRLC(this,msg->cic(),label,true,reason,diagnostic,m_location); // Avoid sending CNF reason = 0; } } } TelEngine::destruct(call); } if (stopSGM) { RefPointer call; findCall(msg->cic(),call); if (call) call->stopWaitSegment(false); call = 0; } if (reason || !impl) { Debug(this,impl?DebugNote:DebugStub,"'%s' with cic=%u: %s", msg->name(),msg->cic(),(reason ? reason : "Not implemented, ignoring")); if (reason) transmitCNF(this,msg->cic(),label,true,reason,diagnostic); } } // Replace a call's circuit if checkCall is true // Release currently reseting circuit if the code match // Clear lock flags // See Q.764 2.9.3.1 bool SS7ISUP::resetCircuit(unsigned int cic, bool remote, bool checkCall) { SignallingCircuit* circuit = circuits() ? circuits()->find(cic) : 0; if (!circuit) return false; DDebug(this,DebugAll,"Reseting circuit %u",cic); if (checkCall) { RefPointer call; findCall(cic,call); if (call) { if (call->outgoing() && call->state() == SS7ISUPCall::Setup) { SignallingCircuit* newCircuit = 0; reserveCircuit(newCircuit,call->cicRange(),SignallingCircuit::LockLockedBusy); call->replaceCircuit(newCircuit); } else call->setTerminate(false,"normal"); } } // Remove remote lock flags (Q.764 2.9.3.1) if (remote && circuit->locked(SignallingCircuit::LockRemote)) { Debug(this,DebugNote,"Unblocking remote circuit %u on reset request",cic); circuit->hwLock(false,true,0!=circuit->locked(SignallingCircuit::LockRemoteHWFail),false); circuit->maintLock(false,true,0!=circuit->locked(SignallingCircuit::LockRemoteMaint),false); m_verifyEvent = true; } // Remove pending RSC/REL. Reset 'Resetting' flag' SignallingMessageTimer* m = findPendingMessage(SS7MsgISUP::RSC,cic,true); if (!m) m = findPendingMessage(SS7MsgISUP::REL,cic,true); if (m) { Debug(this,DebugAll,"Pending %s`cic=%u removed",m->message()->name(),cic); TelEngine::destruct(m); } circuit->resetLock(SignallingCircuit::Resetting); if (m_rscCic && m_rscCic->code() == cic) releaseCircuit(m_rscCic); else circuit->status(SignallingCircuit::Idle); return true; } // Block/unblock a circuit // See Q.764 2.8.2 bool SS7ISUP::blockCircuit(unsigned int cic, bool block, bool remote, bool hwFail, bool changed, bool changedState, bool resetLocking) { XDebug(this,DebugAll,"blockCircuit(%u,%u,%u,%u,%u,%u,%u)", cic,block,remote,hwFail,changed,changedState,resetLocking); SignallingCircuit* circuit = circuits() ? circuits()->find(cic) : 0; if (!circuit) return false; bool something = false; if (hwFail) something = circuit->hwLock(block,remote,changed,changedState); else something = circuit->maintLock(block,remote,changed,changedState); if (resetLocking && !remote) circuit->resetLock(hwFail ? SignallingCircuit::LockingHWFail : SignallingCircuit::LockingMaint); if (something) { Debug(this,DebugNote,"%s %s side of circuit %u. Current flags 0x%x", (block ? "Blocked" : "Unblocked"), (remote ? "remote" : "local"), cic,circuit->locked(-1)); m_verifyEvent = true; } return true; } SS7ISUPCall* SS7ISUP::findCall(unsigned int cic) { for (ObjList* o = m_calls.skipNull(); o; o = o->skipNext()) { SS7ISUPCall* call = static_cast(o->get()); if (call->id() == cic) return call; } return 0; } // Utility used in sendLocalLock() // Check if a circuit has lock change flag set and can be locked (not busy) static inline bool canLock(SignallingCircuit* cic, bool hw) { if (hw) return cic->locked(SignallingCircuit::LockLocalHWFailChg) && !cic->locked(SignallingCircuit::LockingHWFail | SignallingCircuit::Resetting); return cic->locked(SignallingCircuit::LockLocalMaintChg) && !cic->locked(SignallingCircuit::LockingMaint | SignallingCircuit::Resetting); } // Utility: check if a circuit needs lock and not currently locking static inline void checkNeedLock(SignallingCircuit* cic, bool& needLock) { if (needLock) return; needLock = cic->locked(SignallingCircuit::LockLocalChg) && !cic->locked(SignallingCircuit::LockingHWFail | SignallingCircuit::LockingMaint); } // Send blocking/unblocking messages // Return false if no request was sent bool SS7ISUP::sendLocalLock(const Time& when) { Lock lock(this); if (!circuits()) return false; bool needLock = false; ObjList msgs; while (true) { bool hwReq = false; bool lockReq = false; unsigned int code = 0; int locking = 0; // Peek a starting circuit whose local state changed ObjList* o = circuits()->circuits().skipNull(); SignallingCircuitSpan* span = 0; for (; o; o = o->skipNext()) { SignallingCircuit* cic = static_cast(o->get()); if (canLock(cic,true)) { hwReq = true; lockReq = (0 != cic->locked(SignallingCircuit::LockLocalHWFail)); locking = SignallingCircuit::LockingHWFail; } else if (canLock(cic,false)) { hwReq = false; lockReq = (0 != cic->locked(SignallingCircuit::LockLocalMaint)); locking = SignallingCircuit::LockingMaint; } else { checkNeedLock(cic,needLock); continue; } code = cic->code(); span = cic->span(); cic->setLock(locking); o = o->skipNext(); break; } if (!code) break; // If remote doesn't support group block/unblock just send BLK/UBL if (!m_lockGroup) o = 0; // Check if we can pick a range of circuits within the same span // with the same operation to do // Q.763 3.43: range can be 2..256. Bit: 0-no indication 1-block/unblock. // Max bits set to 1 must be 32 char d[256]; d[0] = '1'; unsigned int cics = 1; unsigned int lockRange = 1; ObjList* cicPos = o; int newRange = 0; int flag = hwReq ? SignallingCircuit::LockLocalHWFail : SignallingCircuit::LockLocalMaint; for (; o && cics < 32 && lockRange < 256; o = o->skipNext()) { SignallingCircuit* cic = static_cast(o->get()); // Presume all circuits belonging to the same span to follow each other in the list if (span != cic->span()) break; // Make sure the circuit codes are sequential if ((code + lockRange) != cic->code()) { if (!newRange) newRange = checkValidRange(code,cic->code()); checkNeedLock(cic,needLock); continue; } // Add circuit to map. Skip busy circuits // Circuit must have the same lock type and flags as the base circuit's if (canLock(cic,hwReq) && (lockReq == (0 != cic->locked(flag)))) { cic->setLock(locking); d[lockRange] = '1'; cics++; } else { checkNeedLock(cic,needLock); d[lockRange] = '0'; } lockRange++; } if (cics == 1) { if (lockRange > 1) { // Shorten the range: be nice to remote party, no need to check the whole map if (hwReq) lockRange = 2; else lockRange = 1; } else if (m_lockGroup && hwReq) { if (!newRange) { // Bad luck: check for a code before the circuit for (o = circuits()->circuits().skipNull(); o && o != cicPos; o = o->skipNext()) { SignallingCircuit* cic = static_cast(o->get()); if (span != cic->span()) continue; newRange = checkValidRange(code,cic->code()); if (newRange) break; } } if (newRange) adjustRangeAndStatus(d,code,lockRange,newRange); else Debug(this,DebugNote, "Failed to pick a second circuit to group HW %sblock cic=%u [%p]", lockReq ? "" : "un",code,this); } } else { // Shorten range unsigned int last = lockRange; while (d[--last] == '0') lockRange--; } // Build and send the message // Don't send individual circuit blocking for HW failure (they are supposed // to be sent for maintenance reason) String map(d,lockRange); SS7MsgISUP* msg = 0; SignallingMessageTimer* t = 0; if (m_lockGroup && (map.length() > 1 || hwReq)) { msg = new SS7MsgISUP((lockReq ? SS7MsgISUP::CGB : SS7MsgISUP::CGU),code); msg->params().addParam("GroupSupervisionTypeIndicator", (hwReq ? "hw-failure" : "maintenance")); msg->params().addParam("RangeAndStatus",String(map.length())); msg->params().addParam("RangeAndStatus.map",map); if (lockReq) t = new SignallingMessageTimer(m_t18Interval,m_t19Interval); else t = new SignallingMessageTimer(m_t20Interval,m_t21Interval); } else { msg = new SS7MsgISUP(lockReq ? SS7MsgISUP::BLK : SS7MsgISUP::UBL,code); // Remember HW/maintenance flag if (hwReq) msg->params().addParam("isup_pending_block_hwfail",String::boolText(true)); if (lockReq) t = new SignallingMessageTimer(m_t12Interval,m_t13Interval); else t = new SignallingMessageTimer(m_t14Interval,m_t15Interval); } t->message(msg); m_pending.add(t); msg->ref(); msgs.append(msg)->setDelete(false); } // Restart timer if we still have cics needing lock DDebug(this,DebugAll,"%s circuit locking timer",needLock ? "Starting" : "Stopping"); if (needLock) m_lockTimer.start(when.msec()); else m_lockTimer.stop(); lock.drop(); return transmitMessages(msgs); } // Fill label from local/remote point codes bool SS7ISUP::setLabel(SS7Label& label, unsigned int cic) { Lock lock(this); if (!(m_remotePoint && m_defPoint)) return false; label.assign(m_type,*m_remotePoint,*m_defPoint, (m_defaultSls == SlsCircuit) ? cic : m_sls); return true; } // Retrieve a pending message SignallingMessageTimer* SS7ISUP::findPendingMessage(SS7MsgISUP::Type type, unsigned int cic, bool remove) { Lock lock(this); for (ObjList* o = m_pending.skipNull(); o; o = o->skipNext()) { SignallingMessageTimer* m = static_cast(o->get()); SS7MsgISUP* msg = static_cast(m->message()); if (msg && msg->type() == type && msg->cic() == cic) { if (remove) o->remove(false); return m; } } return 0; } // Retrieve a pending message with given parameter SignallingMessageTimer* SS7ISUP::findPendingMessage(SS7MsgISUP::Type type, unsigned int cic, const String& param, const String& value, bool remove) { Lock lock(this); for (ObjList* o = m_pending.skipNull(); o; o = o->skipNext()) { SignallingMessageTimer* m = static_cast(o->get()); SS7MsgISUP* msg = static_cast(m->message()); if (msg && msg->type() == type && msg->cic() == cic && msg->params()[param] == value) { if (remove) o->remove(false); return m; } } return 0; } // Transmit a list of messages. Return true if at least 1 message was sent bool SS7ISUP::transmitMessages(ObjList& list) { ObjList* o = list.skipNull(); if (!o) return false; for (; o; o = o->skipNext()) { SS7MsgISUP* msg = static_cast(o->get()); SS7Label label; setLabel(label,msg->cic()); if (m_duplicateCGB && (msg->type() == SS7MsgISUP::CGB)) { // ANSI needs the CGB duplicated msg->ref(); transmitMessage(msg,label,false); } transmitMessage(msg,label,false); } return true; } // Utility: check if a circuit exists and can be start an (un)block operation static const char* checkBlockCic(SignallingCircuit* cic, bool block, bool maint, bool force) { if (!cic) return "not found"; int flg = cic->locked(maint ? SignallingCircuit::LockLocalMaint : SignallingCircuit::LockLocalHWFail); if ((block == (0 != flg)) && !force) return "already in the same state"; flg = maint ? SignallingCircuit::LockingMaint : SignallingCircuit::LockingHWFail; if (cic->locked(flg | SignallingCircuit::Resetting) && !force) return "busy locking or resetting"; return 0; } // Handle circuit(s) (un)block command bool SS7ISUP::handleCicBlockCommand(const NamedList& p, bool block) { if (!circuits()) return false; SS7MsgISUP* msg = 0; SS7MsgISUP::Type remove = SS7MsgISUP::Unknown; bool force = p.getBoolValue(YSTRING("force")); String* param = p.getParam(YSTRING("circuit")); bool remote = p.getBoolValue(YSTRING("remote")); Lock mylock(this); if (param) { if (remote) { unsigned int code = param->toInteger(); return handleCicBlockRemoteCommand(p,&code,1,block); } SignallingCircuit* cic = circuits()->find(param->toInteger()); msg = buildCicBlock(cic,block,force); if (!msg) return false; if (force) remove = block ? SS7MsgISUP::UBL : SS7MsgISUP::BLK; } else { // NOTE: we assume the circuits belongs to the same span for local (un)block param = p.getParam(YSTRING("circuits")); if (TelEngine::null(param)) { Debug(this,DebugNote,"Circuit '%s' missing circuit(s)", p.getValue(YSTRING("operation"))); return false; } // Parse the range unsigned int count = 0; unsigned int* cics = SignallingUtils::parseUIntArray(*param,1,0xffffffff,count,true); if (!cics) { // Allow '*' (all circuits) for remote if (!(remote && *param == YSTRING("*"))) { SignallingCircuitRange* range = circuits()->findRange(*param); if (range) cics = range->copyRange(count); } else { String tmp; circuits()->getCicList(tmp); SignallingCircuitRange* range = new SignallingCircuitRange(tmp); cics = range->copyRange(count); TelEngine::destruct(range); } if (!cics) { Debug(this,DebugNote,"Circuit group '%s': invalid circuits=%s", p.getValue(YSTRING("operation")),param->c_str()); return false; } } if (remote) { bool ok = handleCicBlockRemoteCommand(p,cics,count,block); delete[] cics; return ok; } if (count > 32) { Debug(this,DebugNote,"Circuit group '%s': too many circuits %u (max=32)", p.getValue(YSTRING("operation")),count); delete[] cics; return false; } // Check if all circuits can be (un)blocked ObjList list; bool maint = !p.getBoolValue(YSTRING("hwfail")); for (unsigned int i = 0; i < count; i++) { SignallingCircuit* c = circuits()->find(cics[i]); const char* reason = checkBlockCic(c,block,maint,force); if (reason) { Debug(this,DebugNote,"Circuit group '%s' range=%s failed for cic=%u: %s", p.getValue(YSTRING("operation")),param->c_str(),cics[i],reason); delete[] cics; return false; } list.append(c)->setDelete(false); } // Retrieve the code: the lowest circuit code unsigned int code = cics[0]; for (unsigned int i = 1; i < count; i++) if (cics[i] < code) code = cics[i]; // Build the range. Fail if falling outside maximum range char d[256]; ::memset(d,'0',256); d[0] = '1'; unsigned int lockRange = 1; unsigned int nCics = 0; for (; nCics < count; nCics++) { if (code == cics[nCics]) continue; unsigned int pos = cics[nCics] - code; if (pos > 255) break; d[pos++] = '1'; if (pos > lockRange) lockRange = pos; } delete[] cics; if (nCics != count) { Debug(this,DebugNote,"Circuit group '%s': invalid circuit map=%s", p.getValue(YSTRING("operation")),param->c_str()); return false; } if (nCics == 1) { // Try to pick another circuit for map SignallingCircuit* cic = static_cast(list.skipNull()->get()); int newRange = 0; for (ObjList* o = circuits()->circuits().skipNull(); o ; o = o->skipNext()) { SignallingCircuit* c = static_cast(o->get()); if (c->span() != cic->span() || c == cic) continue; newRange = checkValidRange(cic->code(),c->code()); if (newRange) break; } if (!newRange) { Debug(this,DebugNote, "Circuit group '%s': failed to pick another circuit to send group command", p.getValue(YSTRING("operation"))); return false; } adjustRangeAndStatus(d,code,lockRange,newRange); } // Ok: block circuits and send the request int flg = maint ? SignallingCircuit::LockingMaint : SignallingCircuit::LockingHWFail; for (ObjList* o = list.skipNull(); o ; o = o->skipNext()) { SignallingCircuit* c = static_cast(o->get()); blockCircuit(c->code(),block,false,!maint,true,true); c->setLock(flg); } String map(d,lockRange); msg = new SS7MsgISUP(block ? SS7MsgISUP::CGB : SS7MsgISUP::CGU,code); msg->params().addParam("GroupSupervisionTypeIndicator", (maint ? "maintenance" : "hw-failure")); msg->params().addParam("RangeAndStatus",String(map.length())); msg->params().addParam("RangeAndStatus.map",map); SignallingMessageTimer* t = 0; if (block) t = new SignallingMessageTimer(m_t18Interval,m_t19Interval); else t = new SignallingMessageTimer(m_t20Interval,m_t21Interval); t->message(msg); m_pending.add(t); msg->ref(); if (force) remove = block ? SS7MsgISUP::CGU : SS7MsgISUP::CGB; } if (SS7MsgISUP::Unknown != remove) { bool removed = false; SignallingMessageTimer* pending = 0; if (remove != SS7MsgISUP::CGB && remove != SS7MsgISUP::CGU) { while (0 != (pending = findPendingMessage(remove,msg->cic(),true))) { TelEngine::destruct(pending); removed = true; } } else { NamedString* ns = msg->params().getParam(YSTRING("GroupSupervisionTypeIndicator")); while (ns && 0 != (pending = findPendingMessage(remove,msg->cic(),ns->name(),*ns,true))) { TelEngine::destruct(pending); removed = true; } } if (removed) Debug(this,DebugNote,"Removed pending operation '%s' cic=%u", SS7MsgISUP::lookup(remove),msg->cic()); } SS7Label label; setLabel(label,msg->cic()); mylock.drop(); if (m_duplicateCGB && (msg->type() == SS7MsgISUP::CGB)) { // ANSI needs the CGB duplicated msg->ref(); transmitMessage(msg,label,false); } transmitMessage(msg,label,false); return true; } // Handle remote circuit(s) (un)block command bool SS7ISUP::handleCicBlockRemoteCommand(const NamedList& p, unsigned int* cics, unsigned int count, bool block) { if (!(cics && count)) return false; bool hwFail = p.getBoolValue(YSTRING("hwfail")); if (debugAt(DebugNote)) { String s; for (unsigned int i = 0; i < count; i++) s.append(String(cics[i]),","); Debug(this,DebugNote,"Circuit remote '%s' command: hwfail=%s circuits=%s [%p]", p.getValue(YSTRING("operation")),String::boolText(hwFail),s.c_str(),this); } bool found = false; for (unsigned int i = 0; i < count; i++) { if (blockCircuit(cics[i],block,true,hwFail,true,true)) found = true; else Debug(this,DebugNote,"Circuit remote '%s' command: cic %u not found [%p]", p.getValue(YSTRING("operation")),cics[i],this); } if (found) m_verifyEvent = true; return found; } // Handle circuit(s) event generation command bool SS7ISUP::handleCicEventCommand(const NamedList& p) { if (!circuits()) return false; int evType = p.getIntValue(YSTRING("type")); if (evType <= 0) { Debug(this,DebugNote,"Control '%s': invalid type '%s'", p.getValue(YSTRING("operation")),p.getValue(YSTRING("type"))); return false; } ObjList cics; String* param = p.getParam(YSTRING("circuit")); if (param) { SignallingCircuit* cic = circuits()->find(param->toInteger()); if (!cic) { Debug(this,DebugNote,"Control '%s' circuit %s not found", p.getValue(YSTRING("operation")),param->c_str()); return false; } cics.append(cic)->setDelete(false); } else { param = p.getParam(YSTRING("circuits")); if (TelEngine::null(param)) { Debug(this,DebugNote,"Control '%s' missing circuit(s)", p.getValue(YSTRING("operation"))); return false; } // Parse the range unsigned int count = 0; unsigned int* cList = SignallingUtils::parseUIntArray(*param,1,0xffffffff,count,true); if (!cList) { Debug(this,DebugNote,"Control '%s' invalid circuits=%s", p.getValue(YSTRING("operation")),param->c_str()); return false; } for (unsigned int i = 0; i < count; i++) { SignallingCircuit* cic = circuits()->find(cList[i]); if (cic) { cics.append(cic)->setDelete(false); continue; } Debug(this,DebugNote,"Control '%s' circuit %u not found", p.getValue(YSTRING("operation")),cList[i]); cics.clear(); break; } delete[] cList; } ObjList* o = cics.skipNull(); if (!o) return false; for (; o; o = o->skipNext()) { SignallingCircuit* cic = static_cast(o->get()); SignallingCircuitEvent* cicEvent = new SignallingCircuitEvent(cic, (SignallingCircuitEvent::Type)evType); cicEvent->copyParams(p); SignallingEvent* ev = processCircuitEvent(cicEvent,0); TelEngine::destruct(cicEvent); if (ev) delete ev; } return true; } // Try to start single circuit (un)blocking. Set a pending operation on success // @param force True to ignore resetting/(un)blocking flags of the circuit // Return built message to be sent on success SS7MsgISUP* SS7ISUP::buildCicBlock(SignallingCircuit* cic, bool block, bool force) { const char* reason = checkBlockCic(cic,block,true,force); if (reason) { Debug(this,DebugNote,"Failed to start circuit %sblocking for %u: %s", block ? "" : "un",cic ? cic->code() : 0,reason); return 0; } blockCircuit(cic->code(),block,false,false,true,true); cic->setLock(SignallingCircuit::LockingMaint); SS7MsgISUP* m = new SS7MsgISUP(block ? SS7MsgISUP::BLK : SS7MsgISUP::UBL,cic->code()); SignallingMessageTimer* t = 0; if (block) t = new SignallingMessageTimer(m_t12Interval,m_t13Interval); else t = new SignallingMessageTimer(m_t14Interval,m_t15Interval); t->message(m); m_pending.add(t); m->ref(); return m; } // Replace circuit for outgoing calls in Setup state void SS7ISUP::replaceCircuit(unsigned int cic, const String& map, bool rel) { ObjList calls; lock(); for (unsigned int i = 0; i < map.length(); i++) { if (map[i] != '1') continue; // Replace circuit for call (Q.764 2.8.2.1) SS7ISUPCall* call = findCall(cic + i); if (call && call->outgoing() && call->state() == SS7ISUPCall::Setup && call->ref()) calls.append(call); } unlock(); for (ObjList* o = calls.skipNull(); o; o = o->skipNext()) { SS7ISUPCall* call = static_cast(o->get()); Debug(this,DebugInfo,"Replacing remotely blocked cic=%u for existing call",call->id()); SignallingCircuit* newCircuit = 0; if (call->canReplaceCircuit()) reserveCircuit(newCircuit,call->cicRange(),SignallingCircuit::LockLockedBusy); if (!newCircuit) { call->setTerminate(rel,"congestion",0,m_location); if (!rel) { SignallingCircuit* c = call->m_circuit; if (c && c->ref()) startCircuitReset(c,String::empty()); } continue; } lock(); SignallingCircuit* c = circuits()->find(call->id()); SS7MsgISUP* m = 0; if (c && !c->locked(SignallingCircuit::Resetting)) { c->setLock(SignallingCircuit::Resetting); m = new SS7MsgISUP(rel ? SS7MsgISUP::REL : SS7MsgISUP::RSC,call->id()); if (rel) { m->params().addParam("CauseIndicators","normal"); m->params().addParam("CauseIndicators.location",m_location,false); } m->ref(); } unlock(); call->replaceCircuit(newCircuit,m); if (m) { SignallingMessageTimer* t = 0; if (rel) t = new SignallingMessageTimer(m_t1Interval,m_t5Interval); else t = new SignallingMessageTimer(m_t16Interval,m_t17Interval); t->message(m); m_pending.add(t); } } } // Handle circuit hw-fail block // Replace cics for outgoing calls. Terminate incoming void SS7ISUP::cicHwBlocked(unsigned int cic, const String& map) { Debug(this,DebugNote,"Circuit(s) in HW failure cic=%u map=%s",cic,map.c_str()); replaceCircuit(cic,map,true); ObjList terminate; lock(); for (unsigned int i = 0; i < map.length(); i++) { if (map[i] != '1') continue; SS7ISUPCall* call = findCall(cic + i); // We've made retransmit attempt for outgoing bool processed = !call || (call->outgoing() && call->state() == SS7ISUPCall::Setup); if (!processed && call->ref()) terminate.append(call); } unlock(); setCallsTerminate(terminate,true,"normal",0,m_location); } /** * SS7BICC */ SS7BICC::SS7BICC(const NamedList& params, unsigned char sio) : SignallingComponent(params.safe("SS7BICC"),¶ms,"ss7-bicc"), SS7ISUP(params,sio) { m_cicLen = 4; Debug(this,DebugInfo,"BICC Call Controller [%p]",this); } SS7BICC::~SS7BICC() { cleanup(); Debug(this,DebugInfo,"BICC Call Controller destroyed [%p]",this); } HandledMSU SS7BICC::receivedMSU(const SS7MSU& msu, const SS7Label& label, SS7Layer3* network, int sls) { if (msu.getSIF() != sif() || !hasPointCode(label.dpc()) || !handlesRemotePC(label.opc())) return HandledMSU::Rejected; // we should have at least 4 bytes CIC and 1 byte message type const unsigned char* s = msu.getData(label.length()+1,5); if (!s) return false; unsigned int len = msu.length()-label.length()-1; unsigned int cic = s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24); SS7MsgISUP::Type type = (SS7MsgISUP::Type)s[4]; const char* name = SS7MsgISUP::lookup(type); if (name) { bool ok = processMSU(type,cic,s+5,len-5,label,network,sls); String tmp; tmp.hexify((void*)s,len,' '); Debug(this,ok ? DebugInfo : DebugMild,"Unhandled BICC type %s, cic=%u, length %u: %s", name,cic,len,tmp.c_str()); return ok; } String tmp; tmp.hexify((void*)s,len,' '); Debug(this,DebugMild,"Received unknown BICC type 0x%02x, cic=%u, length %u: %s", type,cic,len,tmp.c_str()); return false; } /* vi: set ts=8 sw=4 sts=4 noet: */