1733 lines
53 KiB
C++
1733 lines
53 KiB
C++
/**
|
|
* 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 <yatephone.h>
|
|
#include <yatesig.h>
|
|
#include <yateasn.h>
|
|
#include <stdlib.h>
|
|
|
|
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);
|
|
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 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_cpdRoute = "CalledPartyAddress.route";
|
|
static const String s_checkAddr = "tcap.checkAddress";
|
|
|
|
static const String s_lnpCfg = "lnp";
|
|
static const String s_sccpCfg = "sccp_addr";
|
|
static const String s_sccpPrefix = "sccp.";
|
|
static const String s_tcapPrefix = "tcap";
|
|
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_compTimeout = "tcap.component.1.timeout";
|
|
static const String s_tcapReq = "tcap.request.type";
|
|
static const String s_tcapUser = "tcap.user";
|
|
static const String s_tcapBasicTerm = "tcap.transaction.terminationBasic";
|
|
|
|
|
|
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);
|
|
|
|
if (src->getBoolValue("copyparams",false)) {
|
|
dest->copySubParams(*src,s_sccpPrefix);
|
|
dest->copyParam(*src,s_tcapPrefix,'.');
|
|
dest->copyParam(*src,s_lnpPrefix,'.');
|
|
}
|
|
}
|
|
|
|
// 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, const char* 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,"%s [%p]\r\nparams='%s',\r\ndata='%s'",message,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) {
|
|
__plugin.lock();
|
|
NamedString* name = s_cfg.getKey(s_lnpCfg,"tcap");
|
|
__plugin.unlock();
|
|
if (!TelEngine::null(name))
|
|
tcap = engine->find(*name,"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<LNPQuery*>(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
|
|
Debug(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:
|
|
case SS7TCAP::TC_InvokeNotLast:
|
|
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 if (opCode == ConnectionControlConnect) {
|
|
if (query) {
|
|
DDebug(this,DebugAll,"Executing ConnectionControl:Connect operation [%p]",this);
|
|
query->endQuery((SS7TCAP::TCAPUserCompActions)primitive,opCode,params);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
__plugin.lock();
|
|
msg.retValue() << s_cfg.getValue("announcements",announcement,"tone/busy");
|
|
__plugin.unlock();
|
|
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;
|
|
NamedList* sect = 0;
|
|
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));
|
|
params.setParam(s_cpdRoute,"ssn");
|
|
break;
|
|
case SS7TCAP::TC_Begin:
|
|
case SS7TCAP::TC_QueryWithPerm:
|
|
if (code->primitive() != SS7TCAP::TC_Invoke)
|
|
return false;
|
|
__plugin.lock();
|
|
params.setParam(s_tcapLocalCID,code->toString());
|
|
params.setParam(s_opCodeType,"national");
|
|
params.setParam(s_tcapOpCode,String(ProvideInstructionsStart));
|
|
params.setParam(s_compTimeout,String(s_cfg.getIntValue(s_lnpCfg,"timeout",3000) / 1000 + 1));
|
|
// complete sccp data, read from configure
|
|
sect = s_cfg.getSection(s_sccpCfg);
|
|
if (!sect) {
|
|
Debug(this,DebugInfo,"Section [sccp_addr] is missing, query abort");
|
|
return false;
|
|
}
|
|
params.copySubParams(*sect,s_sccpPrefix);
|
|
if (String("gt") == params.getValue(s_cpdRoute,"gt")) {
|
|
params.setParam(s_cpdGT,code->calledNumber());
|
|
// Translation Type defaults to 11, which,
|
|
// according to ATIS 1000112.2005 is Number Portability Translation Type
|
|
if (TelEngine::null(params.getParam(s_cpdTT)))
|
|
params.setParam(s_cpdTT,String(11));
|
|
}
|
|
params.setParam(s_remPC,String(s_remotePC.pack(s_remotePCType)));
|
|
params.setParam(s_checkAddr,String::boolText(sect->getBoolValue("check_addr",false)));
|
|
copyLNPParams(¶ms,code->parameters(),"");
|
|
encodeParameters(ProvideInstructionsStart,params,hexPayload);
|
|
__plugin.unlock();
|
|
break;
|
|
case SS7TCAP::TC_Response:
|
|
case SS7TCAP::TC_Unknown:
|
|
params.setParam(s_tcapLocalCID,code->toString());
|
|
params.setParam(s_checkAddr,String::boolText(false));
|
|
params.setParam(s_tcapTID,code->dialogID());
|
|
params.setParam(s_tcapBasicTerm,String::boolText(false));
|
|
break;
|
|
case SS7TCAP::TC_QueryWithoutPerm:
|
|
case SS7TCAP::TC_Continue:
|
|
case SS7TCAP::TC_ConversationWithPerm:
|
|
case SS7TCAP::TC_ConversationWithoutPerm:
|
|
case SS7TCAP::TC_End:
|
|
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);
|
|
if (query->timedOut() && query->status() != LNPQuery::TimedOut) {
|
|
Debug(this,DebugAll,"Query for called=%s timed out [%p]",query->calledNumber().c_str(),this);
|
|
query->endQuery(SS7TCAP::TC_U_Cancel,(int)None,NamedList::empty());
|
|
}
|
|
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()
|
|
{
|
|
Lock l(m_blockedMtx);
|
|
ListIterator iter(m_blocked);
|
|
for (;;) {
|
|
BlockedCode* code = static_cast<BlockedCode*>(iter.get());
|
|
if (!code)
|
|
break;
|
|
if (code->durationExpired())
|
|
m_blocked.remove(code);
|
|
}
|
|
}
|
|
|
|
BlockedCode* LNPClient::findACG(const char* code)
|
|
{
|
|
ObjList* o = m_blocked.find(code);
|
|
if (!o) {
|
|
ListIterator iter(m_blocked);
|
|
for(;;) {
|
|
BlockedCode* acg = static_cast<BlockedCode*>(iter.get());
|
|
if (!acg)
|
|
return 0;;
|
|
if (acg->toString().startsWith(code))
|
|
return acg;
|
|
}
|
|
}
|
|
else
|
|
return static_cast<BlockedCode*>(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<BlockedCode*>(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);
|
|
Lock l(__plugin);
|
|
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:
|
|
case SS7TCAP::TC_InvokeNotLast:
|
|
if (opCode == LNPClient::CallerInteractionPlay) {
|
|
__plugin.lock();
|
|
retValue = s_cfg.getValue("announcements",params.getValue(s_lnpPrefix + s_announcement,"busy"),"tone/busy");
|
|
__plugin.unlock();
|
|
m_status = Announcement;
|
|
__plugin.incCounter(SS7LNPDriver::Announcement);
|
|
}
|
|
else 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_Response,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 "FMT64U" seconds with gap="FMT64U" 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);
|
|
|
|
const char* code = s_cfg.getValue(s_sccpCfg,YSTRING("remote_pointcode"));
|
|
s_remotePCType = SS7PointCode::lookup(s_cfg.getValue(s_sccpCfg,YSTRING("pointcodetype"),""));
|
|
if (!(s_remotePC.assign(code,s_remotePCType) && s_remotePC.pack(s_remotePCType))) {
|
|
int codeInt = s_cfg.getIntValue(s_sccpCfg,YSTRING("remote_pointcode"));
|
|
if (!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();
|
|
}
|
|
}
|
|
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<String*>(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: */
|