From ad1ed74ad3835d06139055d0e6c05dc26b256498 Mon Sep 17 00:00:00 2001 From: oana Date: Thu, 15 Sep 2011 10:52:08 +0000 Subject: [PATCH] Added module to perform LNP queries via SS7. git-svn-id: http://yate.null.ro/svn/yate/trunk@4607 acf43c95-373e-0410-b603-e72c3f656dc1 --- conf.d/ss7_lnp_ansi.conf.sample | 151 +++ modules/Makefile.in | 5 +- modules/ysig/ss7_lnp_ansi.cpp | 1726 +++++++++++++++++++++++++++++++ 3 files changed, 1881 insertions(+), 1 deletion(-) create mode 100644 conf.d/ss7_lnp_ansi.conf.sample create mode 100644 modules/ysig/ss7_lnp_ansi.cpp diff --git a/conf.d/ss7_lnp_ansi.conf.sample b/conf.d/ss7_lnp_ansi.conf.sample new file mode 100644 index 00000000..2d44a7a8 --- /dev/null +++ b/conf.d/ss7_lnp_ansi.conf.sample @@ -0,0 +1,151 @@ +[general] + +; priority for call.route message handler +;call.route=50 + +; set prefix with which all encoded and decoded LNP parameter names are to be built +; the format is prefix.LNPParam +;prefix=lnp + +; interval at which statistic counters are to be reset. Set in seconds +; default is 5 minutes according to the Telcordia GR-533-CORE standard +;count_time=300 + +; set to play an announcement if the response to the LNP query responds with CallerInteraction:Play Announcement or if the +; number is under Automatic Code Gapping control. +; NOTE: When this is set to true, call.route will always return true, except when a Routing number is returned (porting is done) +;play_announcements=false + + +[lnp] + +; TCAP component to which LNP should attach itself. Must be configured in ysigchan.conf +tcap= + +;Determines if all parameters received on TCAP LNP query response are to be copied into the call.route message +;copy_back_all=true + +; Timeout interval after which a LNP query is canceled. Set in miliseconds, must be in interval 1000 - 30 000 +;timeout=3000 + +; called: string: Template for the called party number of the request +; It should not be modified +;called=${called} + +; caller: string: Template for the caller party number of the request +; It can be used to force anonymous requests +;caller=${caller} + +; LATA: integer: Assignment of LNP LATA parameter +; It can be defined as ${lata$defvalue} +;LATA=${lata} + +; station_type: integer/string: Assign LNP Originating Station Type parameter +; It can be defined as ${oli$defvalue} +; Also these string values are admitted: +; { "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 }, +;station_type=${oli$normal} + +; cic_expansion: boolean: Assignment of LNP CIC expansion parameter +;cic_expansion=${cicexpansion$true} + +; number_nature: string: The nature of the number +; Values are: +; {"national", LNPClient::NatureNational}, +; {"international", LNPClient::NatureInternational}, +; Default to national, according to GR-533-Core +;number_nature=national + +; presentatation_restrict: boolean: Presentation Restriction for numbers +; Default to false, according to GR-533-Core +;presentation_restrict=false + +; numplan: string: Numbering plan for numbers +; Permitted values: +; {"isdn", LNPClient::NPISDN}, +; {"telephony", LNPClient::NPTelephony}, +; Defaults to ISDN +;numplan=isdn + +; number_encoding: string: Numbering encoding for numbers +; The only supported values is bcd, do not change! +;number_encoding=bcd + + +[sccp_addr] + +; route_on_gt: boolean: Enable building of SCCP CalledPartyAddress based on Global Title. Defaults to true. +;route_on_gt=true + +; gt.translation_type: integer: Translation type for Global Title +; defaults to 11, which, according to ATIS 1000112.2005 is Number Portability Translation Type +;gt.translation_type=11 + +; gt.numplan: string: Numbering plan for Global Title +; Permitted values: +; {"notused", LNPClient::NPNotUsed}, +; {"isdn", LNPClient::NPISDN}, +; {"telephony", LNPClient::NPTelephony}, +; {"data", LNPClient::NPData}, +; {"telex", LNPClient::NPTelex}, +; {"maritimemobile", LNPClient::NPMaritimeMobile}, +; {"landmobile", LNPClient::NPLandMobile}, +; {"private", LNPClient::NPPrivate}, +;gt.numplan=isdn + +; remote_ssn : integer: Remote SSN of the LNP application. +; NOTE: This parameter is used only when route_on_gt is false) +;remote_SSN= + +; remote_pointcode: string: Remote pointcode of the LNP application to be interrogated +; The format is network-cluster-member or number +; NOTE: This parameter is used only if route_on_gt is false +;remote_pointcode= + +;Type of pointcode: string +; Allowed values: +; ITU ITU-T Q.704 +; ANSI ANSI T1.111.4 +; ANSI8 8-bit SLS +; China GF 001-9001 +; Japan T-Q704, NTT-Q704 +; Japan5 5-bit SLS +;pointcodetype= + + +[announcements] +; This section allows configuration of messages to be played when the a network management request was received from the LNP database +; application or the response to the query was an announcement + +; Out of Band announcement +;outofband=tone/busy + +; Vacand Code announcement +;vacantcode=tone/busy + +; Number Disconnected announcement +;disconnected=tone/busy + +; ReorderBusy Announcement +;reorderbusy=tone/busy + +; Busy Announcement +;busy=tone/busy + +; No Circuit Announcement +;nocircuit=tone/busy + +; Reorder Announcement +;reorder=tone/busy diff --git a/modules/Makefile.in b/modules/Makefile.in index 6bedde84..1002f71e 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -44,7 +44,7 @@ INCFILES := @top_srcdir@/yateclass.h @top_srcdir@/yatengine.h @top_srcdir@/yatep JUSTSIG := server/ysigchan.yate server/analog.yate \ server/ciscosm.yate server/sigtransport.yate \ - server/isupmangler.yate + server/isupmangler.yate ysig/ss7_lnp_ansi.yate SUBDIRS := MKDEPS := ../config.status @@ -277,6 +277,9 @@ server/wpcard.yate server/tdmcard.yate: LOCALFLAGS = -I@top_srcdir@/libs/ysig @W server/zapcard.yate: LOCALFLAGS = -I@top_srcdir@/libs/ysig @ZAP_FLAGS@ $(JUSTSIG) server/wpcard.yate server/tdmcard.yate server/zapcard.yate: LOCALLIBS = -lyatesig +ysig/ss7_lnp_ansi.yate: LOCALFLAGS = -I@top_srcdir@/libs/ysig -I@top_srcdir@/libs/yasn +ysig/ss7_lnp_ansi.yate: LOCALLIBS = -lyatesig -L../libs/yasn -lyasn + server/analogdetect.yate: ../libs/ymodem/libyatemodem.a server/analogdetect.yate: LOCALFLAGS = -I@top_srcdir@/libs/ymodem server/analogdetect.yate: LOCALLIBS = -L../libs/ymodem -lyatemodem diff --git a/modules/ysig/ss7_lnp_ansi.cpp b/modules/ysig/ss7_lnp_ansi.cpp new file mode 100644 index 00000000..56154179 --- /dev/null +++ b/modules/ysig/ss7_lnp_ansi.cpp @@ -0,0 +1,1726 @@ +/** + * ss7_lnp_ansi.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * Query LNP databases using Telcordia GR-533-Core specification + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2011 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +using namespace TelEngine; + +namespace { + +class LNPClient; +class LNPQuery; +class BlockedCode; + +class SS7LNPDriver : public Module +{ +public: + enum LNPCounter { + Announcement = 1, + DBOverload, + OSSControls, + PortedQueries, + TimedOutQueries, + ErrorredQueries, + SendFailure, + TotalQueries, + }; + enum Cmds { + CmdList = 1, + CmdTest, + }; + SS7LNPDriver(); + ~SS7LNPDriver(); + virtual void initialize(); + virtual bool msgRoute(Message& msg); + virtual void msgTimer(Message& msg); + void incCounter(LNPCounter counter); + void resetCounters(bool globalToo = false); + static const TokenDict s_cmds[]; +protected: + virtual bool received(Message& msg, int id); + virtual bool commandComplete(Message& msg, const String& partLine, const String& partWord); + virtual bool commandExecute(String& retVal, const String& line); + virtual void statusModule(String& str); + virtual void statusParams(String& str); + virtual void statusDetail(String& str); + bool parseParams(const String& line, NamedList& parsed, String& error); + unsigned int m_overallCounts[TotalQueries]; + unsigned int m_currentCounts[TotalQueries]; + u_int64_t m_countReset; + LNPClient* m_lnp; +}; + +class LNPQuery : public GenObject +{ +public: + enum QueryStatus { + Pending, + TimedOut, + ReportedError, + ResponseRejected, + PortingDone, + Announcement, + UnderControl, + }; + LNPQuery(LNPClient* lnp, u_int8_t id, const String& called, Message* params); + ~LNPQuery(); + void endQuery(SS7TCAP::TCAPUserCompActions primitive, int opCode, const NamedList& params); + const String& toString() const + { return m_id; } + inline bool timedOut() + { return m_timeout < Time::msecNow(); } + inline QueryStatus status() + { return m_status; } + inline NamedList* parameters() + { return m_msg; } + inline SS7TCAP::TCAPUserCompActions primitive() + { return m_primitive; } + inline void setPrimitive(SS7TCAP::TCAPUserCompActions prim) + { m_primitive = prim; } + inline String& problemData() + { return m_problemData; } + inline void setProblemData(String& hexData) + { m_problemData = hexData; } + inline SS7TCAPError problem() + { return m_error; } + inline unsigned int dbSSN() + { return m_dbSSN; } + inline int dbPointCode() + { return m_dbPC; } + inline void setDialogID(const char* id) + { m_dialogID = id; } + inline String& dialogID() + { return m_dialogID; } + inline String& calledNumber() + { return m_called; } + inline void extractAddress(NamedList& params); +private: + String m_id; + u_int64_t m_timeout; + Message* m_msg; + QueryStatus m_status; + SS7TCAP::TCAPUserCompActions m_primitive; + String m_problemData; + SS7TCAPError m_error; + unsigned int m_dbSSN; + int m_dbPC; + String m_dialogID; + String m_called; + LNPClient* m_lnp; +}; + +class LNPClient : public TCAPUser { +public: + enum Operation { + None = 0x0, + ProvideInstructionsStart = 0x0301, + ConnectionControlConnect = 0x0401, + CallerInteractionPlay = 0x0501, + SendNotificationTermination = 0x0601, + NetworkManagementACG = 0x0701, + ProceduralError = 0x0803, + }; + enum PrivateLNP { + BillingIndicators = 0x41, + ConnectTime = 0x42, + EchoData = 0x43, + OrigStationType = 0x45, + TerminationIndicators = 0x46, + ACGIndicator = 0x47, + CICExpansion = 0x48, + DigitsPrivate = 0x49, + }; + enum LNPParams { + ServiceKey = 0xaa, + StandardAnnouncement = 0x82, + Digits = 0x84, + ProblemData = 0x86, + PrivateErrorCode = 0xd4, + PrivateParam = 0xdf, + }; + // Digit encoding as defined by ANSI ATIS-1000114.2004 T1.114.5 Figure 8 + enum DigitsNature { + NatureNational = 0x00, + NatureInternational = 0x01, + PresentationRestriction = 0x02, + }; + enum DigitsType { + DigitsNotUsed = 0x00, + CalledPartyNumber = 0x01, + CallingPartyNumber = 0x02, + CallerInteraction = 0x03, + RoutingNumber = 0x04, + BillingNumber = 0x05, + DestinationNumber = 0x06, + LATA = 0x07, + Carrier = 0x08, + LastCallingParty = 0x09, + LastCalledParty = 0x0a, + CallingDirectoryNumber = 0x0b, + VMSRIdentifier = 0x0c, + OriginalCalledNumber = 0x0d, + RedirectiongNumber = 0x0e, + ConnectedNumber = 0x0f, + }; + enum DigitsEncoding { + EncodingNotUsed = 0x00, + EncodingBCD = 0x01, + EncodingIA5 = 0x02, + }; + enum DigitsNumberingPlan { + NPNotUsed = 0x00, + NPISDN = 0x10, + NPTelephony = 0x20, + NPData = 0x30, + NPTelex = 0x40, + NPMaritimeMobile = 0x50, + NPLandMobile = 0x60, + NPPrivate = 0x70, + }; + enum ACGCause { + ACGVacantCode = 1, + ACGOutOfBand = 2, + ACGDatabaseOverload = 3, + ACGMassCalling = 4, + ACGOSSInitiated = 5, + }; + enum Announcements { + NotUsed = 0, + OutOfBand = 1, + VacandCode = 2, + DisconnectedNumber = 3, + ReorderBusy = 4, + Busy = 5, + NoCircuit = 6, + Reorder = 7, + Ringing = 8, + }; + LNPClient(); + ~LNPClient(); + virtual bool tcapIndication(NamedList& params); + virtual bool managementNotify(SCCP::Type type, NamedList& params); + virtual bool makeQuery(const String& called, Message& msg); + virtual bool tcapRequest(SS7TCAP::TCAPUserTransActions primitive, LNPQuery* query); + virtual int waitForQuery(LNPQuery* query); + bool mandatoryParams(Operation opCode, NamedList& params); + void checkTimeouts(); + bool findTCAP(); + void checkBlocked(); + BlockedCode* findACG(const char* code); + void statusBlocked(String& status); + inline int managementState() + { return SCCPManagement::UserInService; } + +protected: + virtual void destroyed(); + virtual SS7TCAPError decodeParameters(NamedList¶ms, String& hexData); + virtual void encodeParameters(Operation op, NamedList& params, String& hexData); + virtual SS7TCAPError decodeDigits(NamedList& params, DataBlock& data, const char* prefix = 0); + virtual void encodeDigits(DigitsType type, NamedList& params, DataBlock& data); + SS7TCAPError decodePrivateParam(TelEngine::NamedList&, TelEngine::DataBlock&); + void encodePrivateParam(PrivateLNP param, NamedList& params, DataBlock& data); + void encodeBCD(String& digits, DataBlock& data); + unsigned int decodeBCD(unsigned int length, String& digits, unsigned char* buff); + ObjList m_queries; + Mutex m_queriesMtx; + u_int8_t m_compID; + Mutex m_blockedMtx; + ObjList m_blocked; +}; + +class BlockedCode : public GenObject +{ +public: + BlockedCode(const char* code, u_int64_t duration, u_int64_t gap, LNPClient::ACGCause cause); + ~BlockedCode(); + void resetGapInterval(); + void update(u_int64_t duration, u_int64_t gap, LNPClient::ACGCause cause); + inline bool durationExpired() + { return (m_duration <= 2048 && m_durationExpiry < Time::secNow()); } + inline void setACGCause(LNPClient::ACGCause cause) + { m_cause = cause; } + inline LNPClient::ACGCause acgCause() + { return m_cause; } + const String& toString() const + { return m_code; } + inline bool codeAllowed() + { return (m_gap == 0 ? false : m_gapExpiry < Time::secNow()); } + inline unsigned int duration() + { return m_duration; } + inline unsigned int gap() + { return m_gap; } +private: + String m_code; + unsigned int m_duration; + u_int64_t m_durationExpiry; + unsigned int m_gap; + u_int64_t m_gapExpiry; + LNPClient::ACGCause m_cause; +}; + +static Configuration s_cfg; + +static SS7PointCode s_remotePC; +static SS7PointCode::Type s_remotePCType; +static unsigned int s_remoteSSN; + +static bool s_copyBack = true; +static String s_lnpPrefix = "lnp"; +static bool s_playAnnounce = true; + + +INIT_PLUGIN(SS7LNPDriver); + +static const TokenDict s_counters[] = { + {"Announcement", SS7LNPDriver::Announcement}, + {"DBOverload", SS7LNPDriver::DBOverload}, + {"UnderControl", SS7LNPDriver::OSSControls}, + {"Ported", SS7LNPDriver::PortedQueries}, + {"TimedOut", SS7LNPDriver::TimedOutQueries}, + {"Errorred", SS7LNPDriver::ErrorredQueries}, + {"SendFailure", SS7LNPDriver::SendFailure}, + {"Total", SS7LNPDriver::TotalQueries}, + {0,0}, +}; + +const TokenDict SS7LNPDriver::s_cmds[] = { + {"listblocked", CmdList}, + {"test", CmdTest}, + {0,0} +}; + +static const char* s_cmdsLine = "lnp {test [{called|caller|lata|origstation|cicexpansion}=value]| listblocked }"; + +static const TokenDict s_digitTypes[] = { + {"DigitsNotUsed", LNPClient::DigitsNotUsed}, + {"CalledPartyNumber", LNPClient::CalledPartyNumber}, + {"CallingPartyNumber", LNPClient::CallingPartyNumber}, + {"CallerInteraction", LNPClient::CallerInteraction}, + {"RoutingNumber", LNPClient::RoutingNumber}, + {"BillingNumber", LNPClient::BillingNumber}, + {"DestinationNumber", LNPClient::DestinationNumber}, + {"LATA", LNPClient::LATA}, + {"Carrier", LNPClient::Carrier}, + {"LastCallingParty", LNPClient::LastCallingParty}, + {"LastCalledParty", LNPClient::LastCalledParty}, + {"CallingDirectoryNumber", LNPClient::CallingDirectoryNumber}, + {"VMSRIdentifier", LNPClient::VMSRIdentifier}, + {"OriginalCalledNumber", LNPClient::OriginalCalledNumber}, + {"RedirectionNumber", LNPClient::RedirectiongNumber}, + {"ConnectedNumber", LNPClient::ConnectedNumber}, +}; + +static const TokenDict s_operations[] = { + {"ProvideInstructions:Start", LNPClient::ProvideInstructionsStart}, + {"ConnectionControl:Connect", LNPClient::ConnectionControlConnect}, + {"CallerInteraction:PlayAnnouncement", LNPClient::CallerInteractionPlay}, + {"SendNotification:Termination", LNPClient::SendNotificationTermination}, + {"NetworkManagement:ACG", LNPClient::NetworkManagementACG}, + {"Procedural:ReportError", LNPClient::ProceduralError}, + {"None", LNPClient::None}, +}; + +static const TokenDict s_nature[] = { + {"national", LNPClient::NatureNational}, + {"international", LNPClient::NatureInternational}, +}; + +static const TokenDict s_plans[] = { + {"notused", LNPClient::NPNotUsed}, + {"isdn", LNPClient::NPISDN}, + {"telephony", LNPClient::NPTelephony}, + {"data", LNPClient::NPData}, + {"telex", LNPClient::NPTelex}, + {"maritimemobile", LNPClient::NPMaritimeMobile}, + {"landmobile", LNPClient::NPLandMobile}, + {"private", LNPClient::NPPrivate}, +}; + +static const TokenDict s_encodings[] = { + {"notused", LNPClient::EncodingNotUsed}, + {"bcd", LNPClient::EncodingBCD}, + {"ia5", LNPClient::EncodingIA5}, +}; + +// 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 } +}; + +static const TokenDict s_announce[] = { + {"outofband", LNPClient::OutOfBand}, + {"vacantcode", LNPClient::VacandCode}, + {"disconnected", LNPClient::DisconnectedNumber}, + {"reorderbusy", LNPClient::ReorderBusy}, + {"busy", LNPClient::Busy}, + {"nocircuit", LNPClient::NoCircuit}, + {"reorder", LNPClient::Reorder}, + {"ring", LNPClient::Ringing}, + {"", LNPClient::NotUsed}, +}; + +static const TokenDict s_acgCauses[] = { + {"ACGVacantCode", LNPClient::ACGVacantCode}, + {"ACGOutOfBand", LNPClient::ACGOutOfBand}, + {"ACGDatabaseOverload", LNPClient::ACGDatabaseOverload}, + {"ACGMassCalling", LNPClient::ACGMassCalling}, + {"ACGOSSInitiated", LNPClient::ACGOSSInitiated}, + {0,0}, +}; + +static const TokenDict s_gaps[] = { + {"3", 1}, + {"4", 2}, + {"6", 3}, + {"8", 4}, + {"11", 5}, + {"16", 6}, + {"22", 7}, + {"30", 8}, + {"42", 9}, + {"58", 10}, + {"81", 11}, + {"112", 12}, + {"156", 13}, + {"217", 14}, + {"300", 15}, + {"0", 0}, +}; + +static const String s_remPC = "RemotePC"; +static const String s_cpdSSN = "CalledPartyAddress.ssn"; +static const String s_cpdGT = "CalledPartyAddress.gt"; +static const String s_cpdTT = "CalledPartyAddress.gt.tt"; +static const String s_cpdNP = "CalledPartyAddress.gt.np"; +static const String s_cpdEnc = "CalledPartyAddress.gt.encoding"; +static const String s_checkAddr = "tcap.checkAddress"; + +static const String s_lnpCfg = "lnp"; +static const String s_opCode = ".operationCode"; +static const String s_localID = ".localCID"; +static const String s_remoteID = ".remoteCID"; +static const String s_compType = ".componentType"; +static const String s_calledPN = ".CalledPartyNumber"; +static const String s_callingPN = ".CallingPartyNumber"; +static const String s_lata = ".LATA"; +static const String s_cicExp = ".CICExpansion"; +static const String s_stationType = ".OriginatingStationType"; +static const String s_problemData = ".ProblemData"; +static const String s_privateError = ".PrivateError"; +static const String s_acg = ".ACG"; +static const String s_acgDuration = ".ACG.Duration"; +static const String s_acgGap = ".ACG.Gap"; +static const String s_acgCause = ".ACG.ControlCause"; +static const String s_billing = ".BilingIndicators"; +static const String s_routingNumber = ".RoutingNumber"; +static const String s_announcement = ".StandardAnnouncement"; +static const String s_carrier = ".Carrier"; +static const String s_billAMA = ".BillingIndicators.AMACallType"; +static const String s_billFeature = ".BillingIndicators.ServiceFeature"; +static const String s_echoData = ".EchoData"; +static const String s_tcapTID = "tcap.transaction.localTID"; +static const String s_compCount = "tcap.component.count"; +static const String s_endNow = "tcap.transaction.endNow"; +static const String s_tcapComp = "tcap.component.1"; +static const String s_tcapCompType = "tcap.component.1.componentType"; +static const String s_opCodeType = "tcap.component.1.operationCodeType"; +static const String s_tcapOpCode = "tcap.component.1.operationCode"; +static const String s_tcapProblem = "tcap.component.1.problemCode"; +static const String s_tcapLocalCID = "tcap.component.1.localCID"; +static const String s_tcapReq = "tcap.request.type"; +static const String s_tcapUser = "tcap.user"; + + +static void copyLNPParams(NamedList* dest, NamedList* src, String paramsToCopy) +{ + if (!(dest && src)) + return; + + DDebug(&__plugin,DebugAll,"copyLNPParams(dest=%p,src=%p)",dest,src); + String called = s_cfg.getValue(s_lnpCfg,"called","${called}"); + src->replaceParams(called); + String caller = s_cfg.getValue(s_lnpCfg,"caller","${caller}"); + + String lata = s_cfg.getValue(s_lnpCfg,"LATA","${lata}"); + String origStation = s_cfg.getValue(s_lnpCfg,"station_type","${oli$normal}"); + String cicExpansion = s_cfg.getValue(s_lnpCfg,"cic_expansion","${cicexpansion$true}"); + + src->replaceParams(caller); + src->replaceParams(lata); + src->replaceParams(origStation); + src->replaceParams(cicExpansion); + + dest->setParam(s_lnpPrefix + s_calledPN,called); + if (!TelEngine::isE164(caller)) + caller = ""; + dest->setParam(s_lnpPrefix + s_callingPN,caller); + dest->setParam(s_lnpPrefix + s_lata,lata); + dest->setParam(s_lnpPrefix + s_cicExp,(cicExpansion ? "1" : "0")); + dest->setParam(s_lnpPrefix + s_stationType,origStation); +} + +// Get a space separated word from a buffer. msgUnescape() it if requested +// Return false if empty +static bool getWord(String& buf, String& word, bool unescape = false) +{ + XDebug(&__plugin,DebugAll,"getWord(%s)",buf.c_str()); + int pos = buf.find(" "); + if (pos >= 0) { + word = buf.substr(0,pos); + buf = buf.substr(pos + 1); + } + else { + word = buf; + buf = ""; + } + if (!word) + return false; + if (unescape) + word.msgUnescape(); + return true; +} + +#ifdef DEBUG +static void dumpData(int debugLevel, String message, void* obj, NamedList& params, + DataBlock data = DataBlock::empty()) +{ + if (obj) { + String tmp; + params.dump(tmp,"\r\n ",'\'',true); + String str; + str.hexify(data.data(),data.length(),' '); + Debug(&__plugin,debugLevel,message + " [%p] - \r\nparams='%s',\r\ndata='%s'",obj,tmp.c_str(),str.c_str()); + } +} +#endif + +/** + * LNPClient + */ +LNPClient::LNPClient() + : TCAPUser("LNP"), + m_queriesMtx(true,"LNPQueries"), + m_compID(0), + m_blockedMtx(true,"LNPBlocked") +{ + Debug(this,DebugAll,"LNPClient created [%p]",this); +} + +LNPClient::~LNPClient() +{ + Debug(this,DebugAll,"LNPClient destroyed [%p]",this); +} + +void LNPClient::destroyed() +{ + DDebug(&__plugin,DebugAll,"LNPClient::destroyed() [%p]",this); + if (tcap()) + attach(0); + m_blocked.clear(); + m_queries.clear(); +} + +bool LNPClient::findTCAP() +{ + SignallingComponent* tcap = 0; + SignallingEngine* engine = SignallingEngine::self(true); + if (engine) + tcap = engine->find(s_cfg.getValue(s_lnpCfg,"tcap",""),"SS7TCAPANSI",tcap); + if (tcap) { + Debug(this,DebugInfo,"LNP client attaching to TCAP"); + attach(YOBJECT(SS7TCAPANSI,tcap)); + } + return (tcap != 0); +} + +bool LNPClient::tcapIndication(NamedList& params) +{ + DDebug(this,DebugAll,"LNPClient::tcapIndication() [%p]",this); + + String localTID = params.getValue(s_tcapTID); + unsigned int compCount = params.getIntValue(s_compCount); + String paramRoot = "tcap.component."; + String acg = ""; + bool removeACG = true; + + int dialog = SS7TCAP::lookupTransaction(params.getValue("tcap.request.type")); + + Lock l(m_queriesMtx); + for (unsigned int i = 1; i <= compCount; i++) { + String prefix = paramRoot + String(i); + String payload = params.getValue(prefix); + + Operation opCode = (Operation)params.getIntValue(prefix + s_opCode); + NamedString* id = params.getParam(prefix + (dialog != SS7TCAP::TC_Notice ? s_localID : s_remoteID)); + LNPQuery* query = 0; + if (!TelEngine::null(id)) { + ObjList* o = m_queries.find(*id); + query = (o ? static_cast(o->get()) : 0); + if (query) { + query->extractAddress(params); + acg = query->calledNumber(); + } + } + + int primitive = SS7TCAP::lookupComponent(params.getValue(prefix + s_compType)); + if (dialog == SS7TCAP::TC_Response) { + + SS7TCAPError error = decodeParameters(params,payload); + if (error.error() != SS7TCAPError::NoError && query) { + // build error + DDebug(this,DebugAll,"Detected error=%s while decoding parameters [%p]",error.errorName().c_str(),this); + query->setPrimitive(SS7TCAP::TC_Invoke); + query->setProblemData(payload); + tcapRequest(SS7TCAP::TC_Unidirectional,query); + } + switch (primitive) { + case SS7TCAP::TC_Invoke: + if (opCode == NetworkManagementACG) { + // build blocked code + DDebug(this,DebugAll,"Executing NetworkManagement:ACG operation [%p]",this); + removeACG = false; + String code = params.getValue(s_lnpPrefix + s_calledPN); + u_int64_t duration = params.getIntValue(s_lnpPrefix + s_acgDuration); + u_int64_t gap = params.getIntValue(s_lnpPrefix + s_acgGap); + ACGCause cause = (ACGCause) lookup(params.getValue(s_lnpPrefix + s_acgCause),s_acgCauses,0); + m_blockedMtx.lock(); + BlockedCode* acg = findACG(code); + if (acg) + acg->update(duration,gap,cause); + else if (!TelEngine::null(code)) + m_blocked.append(new BlockedCode(code,duration,gap,cause)); + m_blockedMtx.unlock(); + } + else if (opCode == SendNotificationTermination) { + Debug(this,DebugStub,"LNPClient::handleOperation() [%p] - Operation SendNotification:Termination " + "was received, not implemented",this); + } + else if (opCode == CallerInteractionPlay) { + DDebug(this,DebugAll,"Executing CallerInteraction:PlayAnnouncement operation [%p]",this); + if (query) + query->endQuery((SS7TCAP::TCAPUserCompActions)primitive,opCode,params); + } + else + return false; + break; + case SS7TCAP::TC_InvokeNotLast: + if (opCode != ConnectionControlConnect) + return false; + if (query) { + DDebug(this,DebugAll,"Executing ConnectionControl:Connect operation [%p]",this); + query->endQuery((SS7TCAP::TCAPUserCompActions)primitive,opCode,params); + } + + break; + case SS7TCAP::TC_U_Error: + case SS7TCAP::TC_R_Reject: + case SS7TCAP::TC_L_Reject: + case SS7TCAP::TC_U_Reject: + case SS7TCAP::TC_L_Cancel: + // remove component and return false to call.route + DDebug(this,DebugAll,"Executing Cancel operation [%p]",this); + if (query) + query->endQuery((SS7TCAP::TCAPUserCompActions)primitive,None,params); + break; + + case SS7TCAP::TC_U_Cancel: + case SS7TCAP::TC_ResultLast: + case SS7TCAP::TC_ResultNotLast: + case SS7TCAP::TC_TimerReset: + default: + + break; + } + } + else if (dialog == SS7TCAP::TC_Notice) { + Debug(this,DebugInfo,"Received notice='%s' from sublayer, query=%p [%p]",params.getValue("ReturnCause"),query,this); + if (query) + query->endQuery(SS7TCAP::TC_L_Cancel,None,params); + else + return false; + } + else + return false; + } + + if (removeACG && !TelEngine::null(acg)) { + m_blockedMtx.lock(); + while (BlockedCode* c = findACG(acg)) + m_blocked.remove(c); + m_blockedMtx.unlock(); + } + params.setParam(s_endNow,String::boolText(true)); + return true; +} + +bool LNPClient::mandatoryParams(Operation opCode, NamedList& params) +{ + bool ok = true; + switch (opCode) { + case ProvideInstructionsStart: + // we dont check requests + break; + case ConnectionControlConnect: + for (unsigned int i = 0; i < 3; i++) { + String param; + if (i == 0) + param = s_lnpPrefix + s_routingNumber; + if (i == 1) + param = s_lnpPrefix + s_billing + "."; + if (i == 2) + param = s_lnpPrefix + s_carrier; + NamedList subParams(""); + if (TelEngine::null(params.getParam(param)) || !subParams.copySubParams(params,param,false).count()) { + ok = false; + break; + } + } + break; + case CallerInteractionPlay: + if (TelEngine::null(params.getParam(s_lnpPrefix + s_announcement))) + ok = false; + break; + case SendNotificationTermination: + // we dont verify send notification + break; + case NetworkManagementACG: + for (unsigned int i = 0; i < 2; i++) { + String param; + if (i == 0) + param = s_lnpPrefix + s_calledPN; + if (i == 1) + param = s_lnpPrefix + s_acg + "."; + NamedList subParams(""); + if (TelEngine::null(params.getParam(param)) || !subParams.copySubParams(params,param,false).count()) { + ok = false; + break; + } + } + case ProceduralError: + case None: + default: + break; + } + if (!ok) + Debug(&__plugin,DebugAll,"LNPClient::mandatoryParams() - check for mandatory parameters failed for operation=%s [%p]", + lookup(opCode,s_operations,""),this); + return ok; +} + +void LNPClient::checkTimeouts() +{ + ListIterator iter(m_queries); + for (;;) { + LNPQuery* query = static_cast(iter.get()); + if (!query) + break; + if (query->timedOut()) { + m_queriesMtx.lock(); + if (query->status() != LNPQuery::TimedOut) { + DDebug(this,DebugInfo,"Query for called=%s timed out [%p]",query->calledNumber().c_str(),this); + query->endQuery(SS7TCAP::TC_U_Cancel,(int)None,NamedList::empty()); + } + m_queriesMtx.unlock(); + } + } +} + +bool LNPClient::managementNotify(SCCP::Type type, NamedList& params) +{ + return true; +} + +bool LNPClient::makeQuery(const String& called, Message& msg) +{ + DDebug(this,DebugAll,"LNP Query for number=%s [%p]",called.c_str(),this); + __plugin.incCounter(SS7LNPDriver::TotalQueries); + BlockedCode* acg = findACG(called); + if (acg) { + if (!acg->codeAllowed()) { + Debug(this,DebugInfo,"Blocking LNP query for number=%s, ACG controlled",called.c_str()); + String announcement; + if (acg->acgCause() == ACGDatabaseOverload) { + announcement = lookup(NoCircuit,s_announce); + __plugin.incCounter(SS7LNPDriver::DBOverload); + } + else { + announcement = lookup(Busy,s_announce); + __plugin.incCounter(SS7LNPDriver::OSSControls); + } + if (s_copyBack) { + msg.setParam(s_lnpPrefix + s_acgCause,lookup(acg->acgCause(),s_acgCauses,String(acg->acgCause()))); + msg.setParam(s_lnpPrefix + s_acgDuration,String(acg->duration())); + msg.setParam(s_lnpPrefix + s_acgGap,String(acg->gap())); + } + if (s_playAnnounce) { + msg.retValue() << s_cfg.getValue("announcements",announcement,"tone/busy"); + msg.setParam("autoprogress",String::boolText(true)); + return true; + } + else + return false; + } + else { + DDebug(this,DebugInfo,"Allowing LNP query for number=%s which is ACG controlled",called.c_str()); + acg->resetGapInterval(); + } + } + + LNPQuery* code = new LNPQuery(this,m_compID++,called,&msg); + + if (!tcapRequest(SS7TCAP::TC_QueryWithPerm,code)) + return false; + + m_queriesMtx.lock(); + m_queries.append(code); + m_queriesMtx.unlock(); + bool status = false; + int t = waitForQuery(code); + if (s_playAnnounce && code->status() > LNPQuery::PortingDone) + status = true; + m_queriesMtx.lock(); + m_queries.remove(code); + m_queriesMtx.unlock(); + Debug(this,(t > 500) ? DebugNote : DebugAll,"LNP lookup took %d msec",t); + return status; +} + +bool LNPClient::tcapRequest(SS7TCAP::TCAPUserTransActions primitive, LNPQuery* code) +{ + // request parameters from code object + if (!code) + return false; + + DDebug(this,DebugAll,"LNPClient::tcapRequest(type=%s,query=%s) [%p]",SS7TCAP::lookupTransaction(primitive),code->toString().c_str(),this); + NamedList params("lnp"); + + // encode parameters + String hexPayload; + switch (primitive) { + case SS7TCAP::TC_Unidirectional: + if (code->primitive() == SS7TCAP::TC_Invoke) { + params.setParam(s_lnpPrefix + s_problemData,code->problemData()); + encodeParameters(ProceduralError,params,hexPayload); + params.setParam(s_opCodeType,"national"); + params.setParam(s_tcapOpCode,String(ProceduralError)); + } + else if (code->primitive() == SS7TCAP::TC_U_Reject) { + copyLNPParams(¶ms,code->parameters(),"CalledPartyNumber"); + encodeParameters(None,params,hexPayload); + params.setParam(s_tcapProblem,String(code->problem().errorCode())); + } + else + return false; + // complete SCCP data with dpc and SSN only + params.setParam(s_remPC,String(code->dbPointCode())); + params.setParam(s_cpdSSN,String(code->dbSSN())); + params.setParam(s_checkAddr,String::boolText(false)); + break; + case SS7TCAP::TC_Begin: + case SS7TCAP::TC_QueryWithPerm: + if (code->primitive() != SS7TCAP::TC_Invoke) + return false; + copyLNPParams(¶ms,code->parameters(),""); + encodeParameters(ProvideInstructionsStart,params,hexPayload); + params.setParam(s_tcapLocalCID,code->toString()); + params.setParam(s_opCodeType,"national"); + params.setParam(s_tcapOpCode,String(ProvideInstructionsStart)); + // complete sccp data, read from configure + if (s_cfg.getBoolValue("sccp_addr","route_on_gt",true)) { + const String& called = code->calledNumber(); + //params.setParam("CalledPartyAddress.route","gt"); + params.setParam(s_cpdGT,called); + params.setParam(s_cpdTT,s_cfg.getValue("sccp_addr","gt.translation_type","11")); // defaults to 11, which, + // according to ATIS 1000112.2005 is Number Portability Translation Type + params.setParam(s_cpdNP,s_cfg.getValue("sccp_addr","gt.numplan","isdn")); + params.setParam(s_cpdEnc,(called.length() % 2 ? "bcd-odd" : "bcd-even")); + } + else { + params.setParam(s_remPC,String(s_remotePC.pack(s_remotePCType))); + params.setParam(s_cpdSSN,String(s_remoteSSN)); + } + break; + case SS7TCAP::TC_Unknown: + params.setParam(s_tcapLocalCID,code->toString()); + params.setParam(s_checkAddr,String::boolText(false)); + params.setParam(s_tcapTID,code->dialogID()); + break; + case SS7TCAP::TC_QueryWithoutPerm: + case SS7TCAP::TC_Continue: + case SS7TCAP::TC_ConversationWithPerm: + case SS7TCAP::TC_ConversationWithoutPerm: + case SS7TCAP::TC_End: + case SS7TCAP::TC_Response: + case SS7TCAP::TC_U_Abort: + case SS7TCAP::TC_P_Abort: + case SS7TCAP::TC_Notice: + default: + return false; + } + + // set component parameters + params.setParam(s_compCount,"1"); + params.setParam(s_tcapComp,hexPayload); + params.setParam(s_tcapCompType,SS7TCAP::lookupComponent(code->primitive())); + // set transaction parameters + params.setParam(s_tcapReq,SS7TCAP::lookupTransaction(primitive)); + params.setParam(s_tcapUser,toString()); + // send to tcap + if (tcap()) { + SS7TCAPError err = tcap()->userRequest(params); + if (err.error() != SS7TCAPError::NoError) + return false; + if (primitive == SS7TCAP::TC_QueryWithPerm || primitive == SS7TCAP::TC_Begin) + code->setDialogID(params.getValue(s_tcapTID)); + } + else + return false; + return true; +} + +int LNPClient::waitForQuery(LNPQuery* query) +{ + u_int64_t t = Time::msecNow(); + while (true) { + Lock mylock(m_queriesMtx); + if (!query || (query && query->status() != LNPQuery::Pending)) + return (int)(Time::msecNow() - t); + mylock.drop(); + Thread::idle(); + } +} + +SS7TCAPError LNPClient::decodeParameters(NamedList& params, String& hexData) +{ + SS7TCAPError error(SS7TCAP::ANSITCAP); + + DataBlock data; + data.unHexify(hexData,hexData.length(),' '); + if (!data.length()) + return error; + + // decode parameter set + u_int8_t tag = data[0]; + if (tag != 0xf2) + return error; + data.cut(-1); + int len = ASNLib::decodeLength(data); + if (len < 0 || len > (int)data.length()) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + hexData.hexify(&tag,sizeof(tag),' '); + return error; + } + String value = ""; + u_int8_t aux = 0; + while (data.length() && error.error() == SS7TCAPError::NoError) { + tag = data[0]; + switch (tag) { + case ServiceKey: + data.cut(-1); + len = ASNLib::decodeLength(data); + error = decodeDigits(params,data,s_lnpPrefix); + break; + case StandardAnnouncement: + data.cut(-1); + len = ASNLib::decodeLength(data); + if (len != 1) + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + else { + params.setParam(s_lnpPrefix + s_announcement,lookup(data[0],s_announce,"busy")); + data.cut(-1); + } + break; + case Digits: + error = decodeDigits(params,data,s_lnpPrefix); + break; + case ProblemData: + data.cut(-1); + len = ASNLib::decodeLength(data); + value.hexify(data.data(),len,' '); + params.setParam(s_lnpPrefix + s_problemData,value); + data.cut(-len); + break; + case PrivateErrorCode: + data.cut(-1); + len = ASNLib::decodeLength(data); + if (len != 1) + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + else { + params.setParam(s_lnpPrefix + s_privateError,String(data[0])); + data.cut(-1); + } + break; + case PrivateParam: + data.cut(-1); + aux = data[0]; + error = decodePrivateParam(params,data); + break; + } + if (error.error() != SS7TCAPError::NoError) { + hexData.clear(); + if (aux) { + DataBlock db(&aux,sizeof(aux)); + db.insert(DataBlock(&tag,sizeof(tag))); + hexData.hexify(db.data(),db.length(),' '); + } + else + hexData.hexify(&tag,sizeof(tag),' '); + return error; + } + } + return error; +} + +void LNPClient::encodeParameters(Operation op, NamedList¶ms, String& hexPayload) +{ + // mask to know which parameters to look for when encoding + // assignment is bit A = ServiceKey, bit B = CalledPartyNumber, bit C = CallingPartyNumber, bit D = LATA, + // bit E = OriginatingStationLine, bit F = CICExpansion, big G = ProblemData + u_int8_t encodeMask = 0x00; + if (op == None) // should be a reject component + encodeMask = 0x02; + else if (op == ProceduralError) + encodeMask = 0x40; + else if (op == ProvideInstructionsStart) + encodeMask = 0x3d; + + DataBlock data; + u_int8_t tag = 0; + DataBlock db; + if ((encodeMask & 0x01)) {// ServiceKey + encodeDigits(CalledPartyNumber,params,db); + db.insert(ASNLib::buildLength(db)); + data.insert(db); + tag = ServiceKey; + data.insert(DataBlock(&tag,1)); + db.clear(); + } + if ((encodeMask & 0x02)) // CalledPartyNumber + encodeDigits(CalledPartyNumber,params,data); + if ((encodeMask & 0x04)) // CallingPartyNumber + encodeDigits(CallingPartyNumber,params,data); + if ((encodeMask & 0x08)) // LATA + encodeDigits(LATA,params,data); + if ((encodeMask & 0x30)) { + if ((encodeMask & 0x10)) + encodePrivateParam(OrigStationType,params,data); + if ((encodeMask & 0x20)) + encodePrivateParam(CICExpansion,params,data); + } + if ((encodeMask & 0x40)) { // ProblemData + String hex = params.getValue(s_lnpPrefix + s_problemData); + db.unHexify(hex,hex.length(),' '); + db.insert(ASNLib::buildLength(db)); + tag = ProblemData; + data.insert(db); + data.insert(DataBlock(&tag,1)); + } + + data.insert(ASNLib::buildLength(data)); + tag = 0xf2; + data.insert(DataBlock(&tag,1)); + hexPayload.hexify(data.data(),data.length(),' '); +} + +SS7TCAPError LNPClient::decodePrivateParam(NamedList& params, DataBlock& data) +{ + SS7TCAPError error(SS7TCAP::ANSITCAP); + // decode parameter set + if (data.length() < 2) + return error; + unsigned int tag = data[0]; + data.cut(-1); + unsigned int len = ASNLib::decodeLength(data); + switch (tag) { + case BillingIndicators: + if (len != 4) + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + else { + unsigned char* buff = data.data(0,2); + String digits = ""; + decodeBCD(3,digits,buff); + params.setParam(s_lnpPrefix + s_billAMA,digits); + buff = data.data(2,2); + digits.clear(); + decodeBCD(3,digits,buff); + params.setParam(s_lnpPrefix + s_billFeature,digits); + data.cut(-4); + } + break; + case ConnectTime: + case EchoData: + case TerminationIndicators: + if (len != 4) + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + else { + String value = ""; + value.hexify(data.data(),len,' '); + params.setParam(s_lnpPrefix + s_echoData,value); + data.cut(-4); + } + break; + case OrigStationType: + if (len != 1) + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + else { + params.setParam(s_lnpPrefix + s_stationType,lookup(data[0],s_dict_oli,String(data[0]))); + data.cut(-1); + } + break; + case ACGIndicator: + if (len != 3) + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + else { + params.setParam(s_lnpPrefix + s_acgCause,lookup(data[0],s_acgCauses,String(data[0]))); + unsigned int duration = 1 << (data[1] > 0 ? data[1] - 1 : 0); + params.setParam(s_lnpPrefix + s_acgDuration,String(duration)); + params.setParam(s_lnpPrefix + s_acgGap,lookup(data[2],s_gaps,"0")); + data.cut(-3); + } + break; + case CICExpansion: + if (len != 1) + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + else { + params.setParam(s_lnpPrefix + s_cicExp,String(data[0])); + data.cut(-1); + } + break; + case DigitsPrivate: + default: + break; + } + return error; +} + +void LNPClient::encodePrivateParam(PrivateLNP param, NamedList& params, DataBlock& data) +{ + u_int8_t tag = 0; + u_int8_t len = 0; + DataBlock db; + switch (param) { + case OrigStationType: + tag = lookup(params.getValue(s_lnpPrefix + s_stationType),s_dict_oli, + params.getIntValue(s_lnpPrefix + s_stationType)); + db.append(¶m,1); + len = 1; + db.append(&len,1); + db.append(&tag,1); + break; + case CICExpansion: + tag = params.getIntValue(s_lnpPrefix + s_cicExp,1); + db.append(¶m,1); + len = 1; + db.append(&len,1); + db.append(&tag,1); + break; + case BillingIndicators: + case ConnectTime: + case EchoData: + case TerminationIndicators: + case ACGIndicator: + case DigitsPrivate: + default: + break; + } + tag = PrivateParam; + data.insert(db); + data.insert(DataBlock(&tag,1)); +} + +SS7TCAPError LNPClient::decodeDigits(NamedList& params, DataBlock& data, const char* prefix) +{ + SS7TCAPError error(SS7TCAP::ANSITCAP); + if (data[0] != Digits) + return error; + data.cut(-1); + int len = ASNLib::decodeLength(data); + if (len < 0 || len > (int)data.length()) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + return error; + } + unsigned char* buff = data.data(0,len); + int index = 0; + String digits; + String param = (prefix ? String(prefix) + "." : ""); + while (index < len) { + u_int8_t byte = buff[index]; + switch (index) { + case 0: + param += lookup(byte,s_digitTypes); + params.setParam(param + ".type",lookup(byte,s_digitTypes,String(byte))); + break; + case 1: + params.setParam(param + ".nature",lookup((byte & NatureInternational),s_nature,String(byte & NatureInternational))); + params.setParam(param + ".restrict",((byte & PresentationRestriction) ? "true" : "false")); + break; + case 2: + params.setParam(param + ".plan",lookup((byte & 0xf0),s_plans,String((byte & 0xf0) >> 4))); + params.setParam(param + ".encoding",lookup((byte & 0x0f),s_encodings,String(byte & 0x0f))); + break; + case 3: + index += decodeBCD(byte,digits,(len > 4 ? data.data(4,(byte / 2 + (byte % 2 ? 1 : 0))) : 0)); + if (index >= len) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + return error; + } + params.setParam(param,digits); + break; + default: + break; + } + index++; + } + data.cut(-len); + return error; +} + +void LNPClient::encodeDigits(DigitsType type, NamedList& params, DataBlock& data) +{ + String prefix = lookup(type,s_digitTypes); + NamedString* digits = params.getParam(s_lnpPrefix + "." + prefix); + if (TelEngine::null(digits)) + return; + DDebug(this,DebugAll,"LNPClient::encodeDigits(type=%s,digits=%s [%p]",lookup(type,s_digitTypes,""),digits->c_str(),this); + DataBlock db; + encodeBCD(*digits,db); + + unsigned int index = 4; + while (index != 0) { + u_int8_t byte = 0; + switch (index) { + case 1: + byte = (type ? type : params.getIntValue(prefix + ".type",type)); + db.insert(DataBlock(&byte,sizeof(byte))); + break; + case 2: + byte |= (lookup(s_cfg.getValue(s_lnpCfg,"number_nature"),s_nature,NatureNational) & NatureInternational); + if (s_cfg.getBoolValue(s_lnpCfg,"presentation_restrict",false)) + byte |= PresentationRestriction; + db.insert(DataBlock(&byte,sizeof(byte))); + break; + case 3: + if (type == LATA || type == Carrier) + byte |= (NotUsed & 0xf0); + else { + String numPlan = s_cfg.getValue(s_lnpCfg,"numplan","isdn"); + byte |= (lookup(numPlan,s_plans,NPISDN) & 0xf0); + } + byte |= (lookup(s_cfg.getValue(s_lnpCfg,"number_encoding","bcd"),s_encodings,EncodingBCD) & 0x0f); + db.insert(DataBlock(&byte,sizeof(byte))); + break; + case 4: + byte = digits->length(); + db.insert(DataBlock(&byte,sizeof(byte))); + break; + default: + break; + } + index--; + } + db.insert(ASNLib::buildLength(db)); + u_int8_t tag = Digits; + data.insert(db); +#ifdef DEBUG + dumpData(DebugAll,"Encoded digits",this,params,db); +#endif + data.insert(DataBlock(&tag,sizeof(tag))); +} + +void LNPClient::encodeBCD(String& digits, DataBlock& data) +{ + unsigned int len = digits.length() / 2 + (digits.length() % 2 ? 1 : 0); + unsigned char buf[30]; + unsigned int i = 0; + unsigned int j = 0; + bool odd = false; + while((i < digits.length()) && (j < len)) { + char c = digits[i++]; + unsigned char d = 0; + if (('0' <= c) && (c <= '9')) + d = c - '0'; + else if ('A' == c) + d = 10; + else if ('B' == c) + d = 11; + else if ('C' == c) + d = 12; + else if ('*' == c) + d = 13; + else if ('#' == c) + d = 14; + else if ('.' == c) + d = 15; + else + continue; + odd = !odd; + if (odd) + buf[j] = d; + else + buf[j++] |= (d << 4); + } + if (odd) + j++; + data.append(&buf,j); +} + +unsigned int LNPClient::decodeBCD(unsigned int length, String& digits, unsigned char* buff) +{ + if (!(buff && length)) + return 0; + static const char s_digits[] = "0123456789ABC*#."; + unsigned int bytesNo = length / 2 + (length % 2 ? 1 : 0); + unsigned int index = 0; + while (index < bytesNo) { + digits += s_digits[(buff[index] & 0x0f)]; + if (index * 2 + 1 < length) + digits += s_digits[(buff[index] >> 4)]; + index++; + } + XDebug(this,DebugAll,"Decoded BCD digits=%s",digits.c_str()); + return index; +} + +void LNPClient::checkBlocked() +{ + ListIterator iter(m_blocked); + for (;;) { + BlockedCode* code = static_cast(iter.get()); + if (!code) + break; + if (code->durationExpired()) { + m_blockedMtx.lock(); + m_blocked.remove(code); + m_blockedMtx.unlock(); + } + } +} + +BlockedCode* LNPClient::findACG(const char* code) +{ + ObjList* o = m_blocked.find(code); + if (!o) { + ListIterator iter(m_blocked); + for(;;) { + BlockedCode* acg = static_cast(iter.get()); + if (!acg) + return 0;; + if (acg->toString().startsWith(code)) + return acg; + } + } + else + return static_cast(o->get()); +} + +void LNPClient::statusBlocked(String& status) +{ + Lock l(m_blockedMtx); + status.append("format=Cause|Duration|Gap|Allowed",","); + status.append("count=",";") << m_blocked.count(); + ListIterator iter(m_blocked); + String str; + for(;;) { + BlockedCode* acg = static_cast(iter.get()); + if (!acg) + break; + str.append(acg->toString(),",") << "=" << lookup(acg->acgCause(),s_acgCauses) << "|" << acg->duration() << "|" + << acg->gap() << "|" << String::boolText(acg->codeAllowed()); + } + status.append(str,";"); +} + +/** + * LNPQuery + */ +LNPQuery::LNPQuery(LNPClient* lnp, u_int8_t id, const String& called, Message* msg) + : m_id(id), m_msg(msg), m_status(Pending), m_primitive(SS7TCAP::TC_Invoke), m_error(SS7TCAP::ANSITCAP), + m_dbSSN(0), m_dbPC(0), m_dialogID(""), m_called(called), m_lnp(lnp) +{ + Debug(&__plugin,DebugAll,"LNPQuery::LNPQuery() created with id=%u, for called=%s [%p]",id,called.c_str(),this); + m_timeout = Time::msecNow() + s_cfg.getIntValue(s_lnpCfg,"timeout",3000,1000,30000); // milliseconds +} + +LNPQuery::~LNPQuery() +{ + Debug(&__plugin,DebugAll,"LNPQuery::LNPQuery() destroyed [%p]",this); + m_msg = 0; + m_lnp = 0; +} + +void LNPQuery::endQuery(SS7TCAP::TCAPUserCompActions primitive, int opCode, const NamedList& params) +{ + DDebug(&__plugin,DebugAll,"LNPQuery::endQuery() for id=%s, callednum=%s with request=%s [%p]", + m_id.c_str(),m_called.c_str(),SS7TCAP::lookupComponent(primitive),this); + + String retValue = ""; + bool copy = true; + switch (primitive) { + case SS7TCAP::TC_Invoke: + if (opCode == LNPClient::CallerInteractionPlay) { + retValue = s_cfg.getValue("announcements",params.getValue(s_lnpPrefix + s_announcement,"busy"),"tone/busy"); + m_status = Announcement; + __plugin.incCounter(SS7LNPDriver::Announcement); + } + break; + case SS7TCAP::TC_InvokeNotLast: + if (opCode == LNPClient::ConnectionControlConnect && m_msg) { + m_msg->setParam("querylnp",String::boolText(false)); + m_msg->setParam("npdi",String::boolText(true)); + String routing = params.getValue(s_lnpPrefix + s_routingNumber); + if (routing != calledNumber()) + m_msg->setParam("routing",routing); + m_status = PortingDone; + __plugin.incCounter(SS7LNPDriver::PortedQueries); + } + break; + case SS7TCAP::TC_U_Error: + case SS7TCAP::TC_R_Reject: + case SS7TCAP::TC_L_Reject: + case SS7TCAP::TC_U_Reject: + m_status = (primitive == SS7TCAP::TC_U_Error ? ReportedError : ResponseRejected); + __plugin.incCounter(SS7LNPDriver::ErrorredQueries); + break; + case SS7TCAP::TC_U_Cancel: + setPrimitive(SS7TCAP::TC_U_Cancel); + if (m_lnp) + m_lnp->tcapRequest(SS7TCAP::TC_Unknown,this); + m_status = TimedOut; + __plugin.incCounter(SS7LNPDriver::TimedOutQueries); + break; + case SS7TCAP::TC_L_Cancel: + m_status = ReportedError; + copy = false; + __plugin.incCounter(SS7LNPDriver::SendFailure); + break; + case SS7TCAP::TC_ResultLast: + case SS7TCAP::TC_ResultNotLast: + case SS7TCAP::TC_TimerReset: + default: + break; + } + if (m_msg) { + if (s_playAnnounce && !TelEngine::null(retValue)) { + m_msg->retValue() << retValue; + m_msg->setParam("autoprogress",String::boolText("true")); + } + if (copy) + m_msg->copyParam(params,s_lnpPrefix,'.'); + } +} + +void LNPQuery::extractAddress(NamedList& params) +{ + m_dbSSN = params.getIntValue("CallingPartyAddress.ssn"); + m_dbPC = params.getIntValue(s_remPC); + DDebug(&__plugin,DebugAll,"LNPQuery::extractAddress() - extract remoteSSN =%d, remotePC=%d [%p]",m_dbSSN,m_dbPC,this); +} + +/** + * BlockedCode + */ +BlockedCode::BlockedCode(const char* code, u_int64_t duration, u_int64_t gap, LNPClient::ACGCause cause) + : m_code(code) +{ + Debug(&__plugin,DebugAll,"BlockedCode created [%p] - code '%s' blocked for "FMT64" seconds with gap="FMT64" seconds, cause=%s", + this,m_code.c_str(),duration,gap,lookup(cause,s_acgCauses,"")); + update(duration,gap,cause); +} + +BlockedCode::~BlockedCode() +{ + Debug(&__plugin,DebugAll,"BlockedCode[%p] destroyed for code '%s'",this,m_code.c_str()); +} + +void BlockedCode::resetGapInterval() +{ + u_int64_t interval = (u_int64_t) (90.0 + (110 - 90) * ((double)Random::random() / (double)RAND_MAX)) / 100.0 * m_gap; + DDebug(&__plugin,DebugAll,"BlockedCode created [%p] - code '%s' has gap interval="FMT64" seconds",this,m_code.c_str(), + interval); + m_gapExpiry = Time::secNow() + interval; +} + +void BlockedCode::update(u_int64_t duration, u_int64_t gap, LNPClient::ACGCause cause) +{ + DDebug(&__plugin,DebugAll,"BlockedCode created [%p] - code '%s' update duration="FMT64" seconds, gap="FMT64" seconds",this,m_code.c_str(), + duration,gap); + m_cause = cause; + m_duration = (unsigned int)duration; + m_durationExpiry = Time::secNow() + duration; + m_gap = (unsigned int)gap; + resetGapInterval(); +} + +/** + * SS7LNPDriver + */ +SS7LNPDriver::SS7LNPDriver() + : Module("ss7_lnp_ansi","misc"), + m_lnp(0) +{ + Output("Loaded module SS7LnpAnsi"); +} + +SS7LNPDriver::~SS7LNPDriver() +{ + Output("Unloaded module SS7LnpAnsi"); + TelEngine::destruct(m_lnp); +} + +void SS7LNPDriver::initialize() +{ + Output("Initializing module SS7LnpAnsi"); + Module::initialize(); + lock(); + s_cfg = Engine::configFile(name()); + s_cfg.load(); + unlock(); + if (!m_lnp) + m_lnp = new LNPClient(); + installRelay(Route,s_cfg.getIntValue("general","call.route",50)); + installRelay(Timer); + installRelay(Help); + + s_copyBack = s_cfg.getBoolValue(s_lnpCfg,"copy_back_all",true); + s_lnpPrefix = s_cfg.getValue("general","prefix","lnp"); + s_playAnnounce = s_cfg.getBoolValue("general","play_announcements",false); + + s_remoteSSN = s_cfg.getIntValue("sccp_addr",YSTRING("remote_SSN"),0); + + const char* code = s_cfg.getValue("sccp_addr",YSTRING("remote_pointcode")); + s_remotePCType = SS7PointCode::lookup(s_cfg.getValue("sccp_addr",YSTRING("pointcodetype"),"")); + if (!(s_remotePC.assign(code,s_remotePCType) && s_remotePC.pack(s_remotePCType))) { + int codeInt = s_cfg.getIntValue("sccp_addr",YSTRING("remote_pointcode")); + if (!s_cfg.getBoolValue("sccp_addr","route_on_gt",true) && !s_remotePC.unpack(s_remotePCType,codeInt)) + Debug(this,DebugMild,"SS7LNPDriver::initialize() [%p] - Invalid remote_pointcode=%s value configured", + this,code); + } + resetCounters(true); +} + +bool SS7LNPDriver::msgRoute(Message& msg) +{ + XDebug(this,DebugAll,"SS7LNPDriver::msgRoute()"); + if (!msg.getBoolValue("querylnp_tcap",true)) + return false; + Lock mylock(this); + String called = s_cfg.getValue(s_lnpCfg,"called","${called}"); + msg.replaceParams(called); + if (!msg.getBoolValue("querylnp",TelEngine::isE164(called) && !msg.getBoolValue("npdi"))) + return false; + mylock.drop(); + bool ok = false; + if (m_lnp) + ok = m_lnp->makeQuery(called,msg); + if (!s_copyBack) + msg.clearParam(s_lnpPrefix,'.'); + return ok; +} + +void SS7LNPDriver::msgTimer(Message& msg) +{ + if (m_countReset < Time::secNow()) + resetCounters(); + if (m_lnp) { + if (!m_lnp->tcap()) + m_lnp->findTCAP(); + m_lnp->checkBlocked(); + m_lnp->checkTimeouts(); + } +} +bool SS7LNPDriver::received(Message& msg, int id) +{ + if (id == Help) { + String line = msg.getValue("line"); + if (line.null()) { + msg.retValue() << " " << s_cmdsLine << "\r\n"; + return false; + } + if (line != "lnp") + return false; + msg.retValue() << "Commands for the SS7 LNP module\r\n"; + msg.retValue() << s_cmdsLine << "\r\n"; + return true; + } + return Module::received(msg,id); +} + +bool SS7LNPDriver::commandComplete(Message& msg, const String& partLine, const String& partWord) +{ + if (partLine.null() && partWord.null()) + return false; + XDebug(this,DebugAll,"commandComplete() partLine='%s' partWord=%s", + partLine.c_str(),partWord.c_str()); + if (partLine.null() || partLine == "help") + return Module::itemComplete(msg.retValue(),"lnp",partWord); + // Line is module name: complete module commands + if (partLine == "lnp") { + for (const TokenDict* list = s_cmds; list->token; list++) + Module::itemComplete(msg.retValue(),list->token,partWord); + return true; + } + return Module::commandComplete(msg,partLine,partWord); +} + +bool SS7LNPDriver::parseParams(const String& line, NamedList& parsed, String& error) +{ + Debug(this,DebugAll,"SS7LNPDriver::parseParams(%s)",line.c_str()); + bool ok = true; + ObjList* list = line.split(' ',false); + for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { + String* s = static_cast(o->get()); + int pos = s->find("="); + // Empty parameter name is not allowed + if (pos < 1) { + error << "Invalid parameter " << *s; + ok = false; + break; + } + String name = s->substr(0,pos); + String value = s->substr(pos + 1); + name.msgUnescape(); + value.msgUnescape(); + parsed.addParam(name,value); + XDebug(&__plugin,DebugAll,"parseParams() found '%s'='%s'",name.c_str(),value.c_str()); + } + TelEngine::destruct(list); + return ok; +} + +bool SS7LNPDriver::commandExecute(String& retVal, const String& line) +{ + String tmp(line); + if (!tmp.startSkip("lnp",false)) + return false; + tmp.trimSpaces(); + XDebug(this,DebugAll,"commandExecute(%s)",tmp.c_str()); + // Retrieve the command + String cmdStr; + int cmd = 0; + if (getWord(tmp,cmdStr)) + cmd = lookup(cmdStr,s_cmds); + if (!cmd) { + retVal << "Unknown command\r\n"; + return true; + } + + // Execute the command + bool ok = false; + String error; + if (cmd == CmdList) { + String str; + Module::statusModule(str); + if (m_lnp) + m_lnp->statusBlocked(str); + ok = true; + retVal << str << "\r\n"; + } + else if (cmd == CmdTest){ + Message msg(""); + if (parseParams(tmp,msg,error)) { + if (TelEngine::null(msg.getParam("called")) || TelEngine::null(msg.getParam("caller"))) + error = "Parameter 'called' or 'caller' is missing, both are mandatory"; + else { + if (!m_lnp) + error = "LNP Client not instantiated"; + else { + m_lnp->makeQuery(msg.getParam("called"),msg); + msg.dump(retVal," "); + retVal << "\r\n"; + ok = true; + } + } + } + } + else { + Debug(this,DebugStub,"Command '%s' not implemented",cmdStr.c_str()); + error = "Unknown command"; + } + retVal << "lnp " << cmdStr << (ok ? " succeeded" : " failed"); + if (!ok && error) + retVal << ". " << error; + retVal << "\r\n"; + return true; +} + +void SS7LNPDriver::statusModule(String& str) +{ + Module::statusModule(str); + str.append("format=Total|Current",","); +} + +void SS7LNPDriver::statusParams(String& str) +{ + str.append("count=",",") << TotalQueries; +} + +void SS7LNPDriver::statusDetail(String& str) +{ + for (unsigned int i = 0; i < TotalQueries; i++) { + unsigned int current = m_currentCounts[i]; + unsigned int total = m_overallCounts[i]; + str.append(lookup(i+1,s_counters,""),",") << "=" << total << "|" << current; + } +} + +void SS7LNPDriver::incCounter(LNPCounter counter) +{ + if (counter < Announcement || counter > TotalQueries) + return; + m_currentCounts[counter - 1]++; + m_overallCounts[counter - 1]++; +} + +void SS7LNPDriver::resetCounters(bool globalToo) +{ + lock(); + for (unsigned int i = 0; i < TotalQueries; i++) { + XDebug(this,DebugAll,"Resetting statistic counters"); + m_currentCounts[i] = 0; + if (globalToo) + m_overallCounts[i] = 0; + } + m_countReset = Time::secNow() + s_cfg.getIntValue("general","count_time",300); // standard says reset every 5 minutes + unlock(); +} + +}; // anonymous namespace + +/* vi: set ts=8 sw=4 sts=4 noet: */