diff --git a/conf.d/ysigchan.conf.sample b/conf.d/ysigchan.conf.sample index fb1628bd..9d3591d7 100644 --- a/conf.d/ysigchan.conf.sample +++ b/conf.d/ysigchan.conf.sample @@ -826,3 +826,48 @@ ; have the same ssn ;ssn=0 + +; Example of a SS7 Transaction Capabilities Application Part +; This component is created when encountering a section of this kind +;[ss7tcap] +; type: keyword: identifies the component as a SS7 Transfer Capabilities Application Part +; type = ss7-tcap-ansi for ANSI TCAP +; type = ss7-tcap-itu for ITU TCAP - not implemented yet +;type= + +; local_SSN: local SubSystem Number. Mandatory +;local_SSN= + +; default_remote_SSN: remote SubSystem Number to provide to SCCP if the application does not provide one +;default_remote_SSN= + +; default_remote_pointcode: remote Point Code to provide to SCCP if the application does not provide one +;default_remote_pointcode= + +; pointcodetype: remote Point Code type. See above documentation for available types +;pointcodetype= + +; sccp: Name of the SCCP component to create and be used by this TCAP +; A section with the name given here must exist in order to configure the SCCP level +;sccp= + +; transact_timeout: Time in seconds to timeout a TCAP transaction after no more activity was registered on it +; Defaults to 300 seconds +;transact_timeout=300 + +;print-messages: Boolean to enable/disable printing of decoding/encoding of TCAP messages +; This option applies on reload +;print-messages=false + +;extended-debug: Boolean to enable/disable printing of the step-by-step decoding/encoding of TCAP messages +; This option applies on reload. print-messages must be true. +;extended-debug=false + + +[tcapuser_test] +; mimicks a TCAP User, which emits and receives TCAP messages in Message form +; type is ss7-tcap-user to identify it as the specific user +;type=ss7-tcap-user + +; tcap: The TCAP to which this user is to attach itself to it +;tcap= diff --git a/libs/ysig/Makefile.in b/libs/ysig/Makefile.in index eeaca81e..fdafd7e8 100644 --- a/libs/ysig/Makefile.in +++ b/libs/ysig/Makefile.in @@ -7,11 +7,12 @@ CXX := @CXX@ -Wall AR := ar DEFS := LIBTHR := @THREAD_LIB@ -INCLUDES := -I@top_srcdir@ -I../.. -I@srcdir@ +INCLUDES := -I@top_srcdir@ -I../.. -I@srcdir@ -I../yasn CFLAGS := @CFLAGS@ @MODULE_CPPFLAGS@ @INLINE_FLAGS@ LDFLAGS:= @LDFLAGS@ SONAME_OPT := @SONAME_OPT@ YATELIBS := -L../.. -lyate @LIBS@ +YASNLIB := -L../yasn -lyasn INCFILES := @top_srcdir@/yateclass.h @srcdir@/yatesig.h PROGS= yate-ss7test @@ -24,6 +25,7 @@ OBJS = engine.o address.o sigcall.o sigtran.o \ LIBD_VER:= libyatesig.so.@PACKAGE_VERSION@ LIBD_DEV:= libyatesig.so LIBD:= ../../$(LIBD_VER) ../../$(LIBD_DEV) +YASN:= ../yasn/libyasn.a DOCS = classes.png LOCALFLAGS = @@ -65,8 +67,8 @@ clean: Makefile: @srcdir@/Makefile.in ../../config.status cd ../.. && ./config.status -../../$(LIBD_VER): $(OBJS) - $(LINK) -o $@ $(SONAME_OPT)$(LIBD_VER) $^ $(YATELIBS) +../../$(LIBD_VER): $(OBJS) $(YASN) + $(LINK) -o $@ $(SONAME_OPT)$(LIBD_VER) $^ $(YASNLIB) $(YATELIBS) ../../$(LIBD_DEV): ../../$(LIBD_VER) cd ../.. && ln -sf $(LIBD_VER) $(LIBD_DEV) @@ -77,6 +79,9 @@ yate-%: @srcdir@/main-%.cpp $(MKDEPS) $(LIBS) ../../libyate.so $(INCFILES) $(LIBS): $(OBJS) $(AR) rcs $@ $^ +$(YASN): + $(MAKE) -C ../yasn + yate-ss7test: LOCALLIBS += -L. -lyatesig %.png: @srcdir@/%.dia diff --git a/libs/ysig/tcap.cpp b/libs/ysig/tcap.cpp index fada4e39..c01b7ac3 100644 --- a/libs/ysig/tcap.cpp +++ b/libs/ysig/tcap.cpp @@ -1,11 +1,11 @@ /** * tcap.cpp - * This file is part of the YATE Project http://YATE.null.ro + * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Signalling Stack - implements the support for SS7, ISDN and PSTN * * Yet Another Telephony Engine - a fully featured software PBX and IVR - * Copyright (C) 2004-2006 Null Team + * Copyright (C) 2004-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 @@ -23,17 +23,2796 @@ */ #include "yatesig.h" +#include "yateasn.h" using namespace TelEngine; +#ifdef DEBUG +static void dumpData(int debugLevel, SS7TCAP* tcap, String message, void* obj, NamedList& params, + DataBlock data = DataBlock::empty()) +{ + if (tcap) { + String tmp; + params.dump(tmp,"\r\n ",'\'',true); + String str; + str.hexify(data.data(),data.length(),' '); + Debug(tcap,debugLevel,message + " [%p] - \r\nparams='%s',\r\ndata='%s'",obj,tmp.c_str(),str.c_str()); + } +} +#endif + TCAPUser::~TCAPUser() { + Debug(this,DebugAll,"TCAPUser::~TCAPUser() [%p] - tcap user destroyed",this); + Lock lock(m_tcapMtx); + if (m_tcap) { + m_tcap->detach(this); + Debug(this,DebugAll,"TCAPUser::~TCAPUser() [%p] - Detached from TCAP (%p,%s)",this,m_tcap,m_tcap->toString().safe()); + m_tcap->deref(); + m_tcap = 0; + } } void TCAPUser::attach(SS7TCAP* tcap) { - Debug(DebugStub,"Please implement TCAPUser::attach()"); + Lock lock(m_tcapMtx); + + if (m_tcap == tcap) + return; + SS7TCAP* tmp = m_tcap; + m_tcap = tcap; + lock.drop(); + DDebug(this,DebugAll,"TCAPUser::atach(tcap=%s [%p], replacing tcap=%s [%p] [%p]",(m_tcap ? m_tcap->toString().safe() : ""),m_tcap, + (tmp ? tmp->toString().c_str() : ""),tmp,this); + if (tmp) { + tmp->detach(this); + Debug(this,DebugAll,"TCAPUser::attach() - Detached from TCAP (%p,%s) [%p]",tmp,tmp->toString().safe(),this); + tmp->deref(); + tmp = 0; + } + if (!tcap) + return; + + tcap->attach(this); + tcap->ref(); + Debug(this,DebugAll,"Attached to TCAP (%p,%s) [%p] tcapRefCount=%d",tcap,tcap->toString().safe(),this,tcap->refcount()); +} + +bool TCAPUser::tcapIndication(NamedList& params) +{ + Debug(this,DebugStub,"Please implement TCAPUser::tcapIndication()"); + return false; +} + +bool TCAPUser::managementNotify(SCCP::Type type, NamedList& params) +{ + Debug(this,DebugStub,"Please implement TCAPUser::managementNotify()"); + return false; +} + +int TCAPUser::managementState() +{ + return SCCPManagement::UserOutOfService; +} + +struct PrimitiveMapping { + int primitive; + int mappedTo; +}; + +static bool s_extendedDbg = false; +static bool s_printMsgs = false; +static const char* s_callingPA = "CallingPartyAddress"; +static const char* s_callingSSN = "CallingPartyAddress.ssn"; +static const char* s_calledPA = "CalledPartyAddress"; +static const char* s_calledPC = "CalledPartyAddress.pointcode"; +static const char* s_calledSSN = "CalledPartyAddress.ssn"; + +// TCAP message parameters +static const String s_tcapUser = "tcap.user"; +static const String s_tcapBasicTerm = "tcap.transaction.terminationBasic"; +static const String s_tcapEndNow = "tcap.transaction.endNow"; +static const String s_tcapRequest = "tcap.request.type"; +static const String s_tcapRequestError = "tcap.request.error"; +static const String s_tcapTransPrefix = "tcap.transaction"; +static const String s_tcapMsgType = "tcap.transaction.messageType"; +static const String s_tcapLocalTID = "tcap.transaction.localTID"; +static const String s_tcapRemoteTID = "tcap.transaction.remoteTID"; +static const String s_tcapAbortCause = "tcap.transaction.abort.cause"; +static const String s_tcapAbortInfo = "tcap.transaction.abort.information"; + +static const String s_tcapDialogPrefix = "tcap.dialogPDU"; +static const String s_tcapProtoVers = "tcap.dialogPDU.protocol-version"; +static const String s_tcapIntAppID = "tcap.dialogPDU.integerApplicationId"; +static const String s_tcapObjAppID = "tcap.dialogPDU.objectApplicationId"; +static const String s_tcapIntSecID = "tcap.dialogPDU.integerSecurityId"; +static const String s_tcapObjSecID = "tcap.dialogPDU.objectSecurityId"; +static const String s_tcapIntConfidID = "tcap.dialogPDU.integerConfidentialityId"; +static const String s_tcapObjConfidID = "tcap.dialogPDU.objectConfidentialityId"; +static const String s_tcapReference = "tcap.dialogPDU.userInformation.direct-reference"; +static const String s_tcapDataDesc = "tcap.dialogPDU.userInformation.data-descriptor"; +static const String s_tcapEncodingContent = "tcap.dialogPDU.userInformation.encoding-contents"; +static const String s_tcapEncodingType = "tcap.dialogPDU.userInformation.encoding-type"; + +static const String s_tcapCompCount = "tcap.component.count"; +static const String s_tcapCompPrefix = "tcap.component"; +static const String s_tcapLocalCID = "localCID"; +static const String s_tcapRemoteCID = "remoteCID"; +static const String s_tcapCompType = "componentType"; +static const String s_tcapOpCodeType = "operationCodeType"; +static const String s_tcapOpCode = "operationCode"; +static const String s_tcapErrCodeType = "errorCodeType"; +static const String s_tcapErrCode = "errorCode"; +static const String s_tcapProblemCode = "problemCode"; +static const String s_tcapPayload = "payload"; + + +static void populateSCCPAddress(NamedList& localAddr, NamedList& remoteAddr, NamedList& initParams, bool initLocal, + bool keepPrefix = false) +{ + String localParam(initLocal ? s_callingPA : s_calledPA); + String remoteParam(initLocal ? s_calledPA : s_callingPA); + + NamedList aux(""); + aux.copySubParams(initParams,localParam + "."); + if (keepPrefix) { + for (unsigned int i = 0; i < aux.count(); i++) { + NamedString* p = aux.getParam(i); + if (!TelEngine::null(p)) { + localAddr.setParam(remoteParam + "." + p->name(),*p); + } + } + } + else + localAddr.copyParams(aux); + + aux.clearParams(); + aux.copySubParams(initParams,remoteParam + "."); + if (keepPrefix) { + for (unsigned int i = 0; i < aux.count(); i++) { + NamedString* p = aux.getParam(i); + if (!TelEngine::null(p)) { + remoteAddr.setParam(localParam + "." + p->name(),*p); + } + } + } + else + remoteAddr.copyParams(aux); +} + +static void compPrefix(String& prefix, unsigned int index, bool endSep = false) +{ + prefix = s_tcapCompPrefix; + prefix << "." << index << (endSep ? "." : ""); +} + +/** + * SS7TCAP implementation + */ +const TokenDict SS7TCAP::s_tcapVersion[] = { + {"UnknownTCAP", SS7TCAP::UnknownTCAP}, + {"ITU-T TCAP", SS7TCAP::ITUTCAP}, + {"ANSI TCAP", SS7TCAP::ANSITCAP}, + {0,-1}, +}; + +const TokenDict SS7TCAP::s_compPrimitives[] = { + {"Invoke", SS7TCAP::TC_Invoke}, + {"ResultLast", SS7TCAP::TC_ResultLast}, + {"U_Error", SS7TCAP::TC_U_Error}, + {"U_Reject", SS7TCAP::TC_U_Reject}, + {"R_Reject", SS7TCAP::TC_R_Reject}, + {"L_Reject", SS7TCAP::TC_L_Reject}, + {"InvokeNotLast", SS7TCAP::TC_InvokeNotLast}, + {"ResultNotLast", SS7TCAP::TC_ResultNotLast}, + {"L_Cancel", SS7TCAP::TC_L_Cancel}, + {"U_Cancel", SS7TCAP::TC_U_Cancel}, + {"TimerReset", SS7TCAP::TC_TimerReset}, + {0,0}, +}; + +const TokenDict SS7TCAP::s_transPrimitives[] = { + {"Unidirectional", SS7TCAP::TC_Unidirectional}, + {"Begin", SS7TCAP::TC_Begin}, + {"QueryWithPerm", SS7TCAP::TC_QueryWithPerm}, + {"QueryWithoutPerm", SS7TCAP::TC_QueryWithoutPerm}, + {"Continue", SS7TCAP::TC_Continue}, + {"ConversationWithPerm", SS7TCAP::TC_ConversationWithPerm}, + {"ConversationWithoutPerm", SS7TCAP::TC_ConversationWithoutPerm}, + {"End", SS7TCAP::TC_End}, + {"Response", SS7TCAP::TC_Response}, + {"U_Abort", SS7TCAP::TC_U_Abort}, + {"P_Abort", SS7TCAP::TC_P_Abort}, + {"Notice", SS7TCAP::TC_Notice}, + {"Unknown", SS7TCAP::TC_Unknown}, + {0,0}, +}; + +const TokenDict SS7TCAP::s_compOperClasses[] = { + {"reportAll", SS7TCAP::SuccessOrFailureReport}, + {"reportFail", SS7TCAP::FailureOnlyReport}, + {"reportSuccess", SS7TCAP::SuccessOnlyReport}, + {"reportNone", SS7TCAP::NoReport}, +}; + + +SS7TCAP::SS7TCAP(const NamedList& params) + : SCCPUser(params), + m_usersMtx(true,"TCAPUsers"), + m_inQueueMtx(true,"TCAPPendingMsg"), + m_SSN(0), + m_defaultRemoteSSN(0), + m_defaultRemotePC(0), + m_remoteTypePC(SS7PointCode::Other), + m_trTimeout(300), + m_transactionsMtx(true,"TCAPTransactions"), + m_tcapType(UnknownTCAP), + m_idsPool(0) +{ + Debug(this,DebugAll,"SS7TCAP::SS7TCAP() [%p] created",this); + m_recvMsgs = m_sentMsgs = m_discardMsgs = m_normalMsgs = m_abnormalMsgs = 0; +} + +SS7TCAP::~SS7TCAP() +{ + Debug(this,DebugAll,"SS7TCAP::~SS7TCAP() [%p] destroyed, refCount=%d, usersCount=%d",this,refcount(),m_users.count()); + if (m_users.count()) { + Debug(this,DebugGoOn,"SS7TCAP destroyed while having %d user(s) still attached [%p]",m_users.count(),this); + ListIterator iter(m_users); + for (;;) { + TCAPUser* user = static_cast(iter.get()); + // End of iteration? + if (!user) + break; + if(user->tcap()) + user->setTCAP(0); + } + m_users.setDelete(false); + } + m_transactions.clear(); + m_inQueue.clear(); + +} + +bool SS7TCAP::initialize(const NamedList* config) +{ +#ifdef DEBUG + if(config && debugAt(DebugAll)) { + String tmp; + config->dump(tmp,"\r\n ",'\'',true); + Debug(this,DebugAll,"SS7TCAP::initialize([%p]) [%p] for configuration '%s'",config,this,tmp.c_str()); + } +#endif + if (config) { + // read local point code and default remote point code + m_SSN = config->getIntValue(YSTRING("local_SSN"),0); + m_defaultRemoteSSN = config->getIntValue(YSTRING("default_remote_SSN"),0); + + const char* code = config->getValue(YSTRING("default_remote_pointcode")); + m_remoteTypePC = SS7PointCode::lookup(config->getValue(YSTRING("pointcodetype"),"")); + if (!(m_defaultRemotePC.assign(code,m_remoteTypePC) && m_defaultRemotePC.pack(m_remoteTypePC))) { + int codeInt = config->getIntValue(YSTRING("default_remote_pointcode")); + if (!m_defaultRemotePC.unpack(m_remoteTypePC,codeInt)) + Debug(this,DebugMild,"SS7TCAP::initialize([%p]) [%p] - Invalid default_remote_pointcode=%s value configured", + config,this,code); + } + + m_trTimeout = config->getIntValue(YSTRING("transact_timeout"),m_trTimeout / 1000) * 1000; // seconds to miliseconds + s_printMsgs = config->getBoolValue(YSTRING("print-messages"),false); + s_extendedDbg = config->getBoolValue(YSTRING("extended-debug"),false); + } + return SCCPUser::initialize(config); +} + +bool SS7TCAP::sendData(DataBlock& data, NamedList& params) +{ + String dpc = params.getValue(s_calledPC,""); + if (dpc.null()) + params.addParam(s_calledPC,String(m_defaultRemotePC.pack(m_remoteTypePC))); + int ssn = params.getIntValue(s_calledSSN,-1); + if (ssn < 0) + params.setParam(s_calledSSN,String(m_defaultRemoteSSN)); + + if (params.getBoolValue("tcap.checkAddress",true)) { + ssn = params.getIntValue(s_callingSSN,-1); + if (ssn < 0) { + params.setParam(s_callingSSN,String(m_SSN)); + params.setParam("CallingPartyAddress.route","ssn"); + } + } +#ifdef DEBUG + if (s_printMsgs && debugAt(DebugInfo)) + dumpData(DebugInfo,this,"Sending to SCCP : ",this,params,data); +#endif + return SCCPUser::sendData(data,params); +} + +HandledMSU SS7TCAP::receivedData(DataBlock& data, NamedList& params) +{ + HandledMSU result; + if (!data.length()) + return result; +#ifdef DEBUG + if (s_printMsgs && debugAt(DebugInfo)) + dumpData(DebugInfo,this,"Received from SCCP: ",this,params,data); +#endif + unsigned int cpaSSN = params.getIntValue(s_calledSSN,0); + unsigned int ssn = params.getIntValue("ssn",0); + if (m_SSN != cpaSSN && m_SSN != ssn) + return result; + enqueue(new SS7TCAPMessage(params,data)); + result = HandledMSU::Accepted; + return result; +} + +HandledMSU SS7TCAP::notifyData(DataBlock& data, NamedList& params) +{ + HandledMSU result; +#ifdef DEBUG + if (s_printMsgs && debugAt(DebugInfo)) + dumpData(DebugInfo,this,"Received notify from SCCP: ",this,params,data); +#endif + enqueue(new SS7TCAPMessage(params,data,true)); + return result; +} + +bool SS7TCAP::managementNotify(SCCP::Type type, NamedList& params) +{ + Lock lock(m_usersMtx); + ListIterator iter(m_users); + bool ok = false; + + if (type == SCCP::SubsystemStatus && m_SSN != (unsigned int)params.getIntValue("ssn")) { + params.setParam("subsystem-status","UserOutOfService"); + return true; + } + bool inService = false; + for (;;) { + TCAPUser* user = static_cast(iter.get()); + // End of iteration? + if (!user) + break; + if (user->managementNotify(type,params)) + ok = true; + if (user->managementState() == (int) SCCPManagement::SCCPManagement::UserInService) + inService = true; + } + if (type == SCCP::SubsystemStatus) + params.setParam("subsystem-status",(inService ? "UserInService" : "UserOutOfService")); + return ok; +} + +void SS7TCAP::attach(TCAPUser* user) +{ + if (!user) + return; + DDebug(this,DebugAll,"SS7TCAP::attach(user=%s [%p]) [%p]",user->toString().safe(),user,this); + Lock l(m_usersMtx); + if (m_users.find(user)) + return; + m_users.append(user); + Debug(this,DebugAll,"SS7TCAP '%s'[%p] attached user=%s [%p]",toString().safe(),this,user->toString().safe(),user); +} + +void SS7TCAP::detach(TCAPUser* user) +{ + if (!user) + return; + DDebug(this,DebugAll,"SS7TCAP::detach(user=%s [%p]) [%p], refCount=%d",user->toString().safe(),user,this,refcount()); + Lock l(m_usersMtx); + if (m_users.find(user)) { + m_users.remove(user,false); + Debug(this,DebugAll,"SS7TCAP '%s'[%p] detached user=%s [%p], refCount=%d",toString().safe(),this,user->toString().c_str(),user,refcount()); + } +} + +void SS7TCAP::enqueue(SS7TCAPMessage* msg) +{ + if (!msg) + return; + Lock lock(m_inQueueMtx); + m_inQueue.append(msg); + XDebug(this,DebugAll,"SS7TCAP::enqueue(). Enqueued transaction wrapper (%p) [%p]",msg,this); +} + +SS7TCAPMessage* SS7TCAP::dequeue() +{ + Lock lock(m_inQueueMtx); + ObjList* obj = m_inQueue.skipNull(); + if (!obj) + return 0; + SS7TCAPMessage* msg = static_cast(obj->get()); + m_inQueue.remove(msg,false); + XDebug(this,DebugAll,"SS7TCAP::dequeue(). Dequeued transaction wrapper (%p) [%p]",msg,this); + return msg; +} + +u_int32_t SS7TCAP::allocTransactionID() +{ + u_int32_t tmp = m_idsPool; + m_idsPool++; + XDebug(this,DebugAll,"SS7TCAP::allocTransactionID() - allocated new transaction ID=%u [%p]",tmp,this); + return tmp; +} + +bool SS7TCAP::sendToUser(NamedList& params) +{ + String userName = params.getValue(s_tcapUser,""); // if it has a specified user, send it to that user + Lock lock(m_usersMtx); + if (!userName.null()) { + ObjList* obj = m_users.find(userName); + if (!obj) { + Debug(this,DebugInfo,"SS7TCAP::sendToUser() [%p] - failed to send message with id=%s to user=%s," + " no such application",this,params.getValue(s_tcapLocalTID),userName.c_str()); + return false; + } + TCAPUser* user = static_cast(obj->get()); + if (!user) { + Debug(this,DebugInfo,"SS7TCAP::sendToUser() [%p] - failed to send message with id=%s to user,%s" + " no such application",this,params.getValue(s_tcapLocalTID),userName.c_str()); + return false; + } +#ifdef DEBUG + if (s_printMsgs && debugAt(DebugInfo)) + dumpData(DebugInfo,this,"Sent to TCAP user: ",this,params); +#endif + return user->tcapIndication(params); + } + else { + ListIterator iter(m_users); + for (;;) { + TCAPUser* user = static_cast(iter.get()); + // End of iteration? + if (!user) { + Debug(this,DebugInfo,"SS7TCAP::sendToUser() [%p] - failed to send message with id=%s to any user", + this,params.getValue(s_tcapLocalTID)); + return false; + } + if (user->tcapIndication(params)) { + params.setParam(s_tcapUser,user->toString()); // set the user for this transaction +#ifdef DEBUG + if (s_printMsgs && debugAt(DebugInfo)) + dumpData(DebugInfo,this,"Sent to TCAP user: ",this,params); +#endif + break; + } + } + } + return true; +} + +void SS7TCAP::status(NamedList& status) +{ + status.setParam("totalIncoming",String(m_recvMsgs)); + status.setParam("totalOutgoing",String(m_sentMsgs)); + status.setParam("totalDiscarded",String(m_discardMsgs)); + status.setParam("totalNormal",String(m_normalMsgs)); + status.setParam("totalAbnormal",String(m_abnormalMsgs)); +} + +void SS7TCAP::userStatus(NamedList& status) +{ + Debug(this,DebugStub,"Please implement SS7TCAP::userStatus()"); +} + +SS7TCAPTransaction* SS7TCAP::getTransaction(const String& tid) +{ + SS7TCAPTransaction* tr = 0; + ObjList* o = m_transactions.find(tid); + if (o) + tr = static_cast(o->get()); + return tr; +} + +void SS7TCAP::removeTransaction(SS7TCAPTransaction* tr) +{ + Lock lock(m_transactionsMtx); + m_transactions.remove(tr); +} + +void SS7TCAP::timerTick(const Time& when) +{ + // first check pending received messages + SS7TCAPMessage* msg = dequeue(); + + while (msg) { + processSCCPData(msg); + TelEngine::destruct(msg); + //break; + msg = dequeue(); + } + + // update/handle rest of transactions + ListIterator iter(m_transactions); + for (;;) { + SS7TCAPTransaction* tr = static_cast(iter.get()); + // End of iteration? + if (!tr) + break; + NamedList params(""); + DataBlock data; + tr->checkComponents(); + if (tr->endNow()) + tr->setState(SS7TCAPTransaction::Idle); + if (tr->timedOut()) { + DDebug(this,DebugInfo,"SS7TCAP::timerTick() - transaction with id=%s(%p) timed out [%p]",tr->toString().c_str(),tr,this); + tr->updateToEnd(); + buildSCCPData(params,tr); + sendToUser(params); + tr->setState(SS7TCAPTransaction::Idle); + } + + if (tr->transactionState() == SS7TCAPTransaction::Idle) + removeTransaction(tr); + } +} + +HandledMSU SS7TCAP::processSCCPData(SS7TCAPMessage* msg) +{ + HandledMSU result; + if (!msg) + return result; + XDebug(this,DebugAll,"SS7TCAP::processSCCPData(msg=[%p]) [%p]",msg,this); + + NamedList& msgParams = msg->msgParams(); + DataBlock& msgData = msg->msgData(); + + SS7TCAPError transactError = decodeTransactionPart(msgParams,msgData); + if (transactError.error() != SS7TCAPError::NoError) + return handleError(transactError,msgParams,msgData); + + NamedString* trID = msgParams.getParam(s_tcapLocalTID); + String trType = msgParams.getValue(s_tcapRequest); + SS7TCAP::TCAPUserTransActions type = (SS7TCAP::TCAPUserTransActions)trType.toInteger(SS7TCAP::s_transPrimitives); + + // check if it's a notice from SCCP, switch the ids if so + if (msg->isNotice()) { + trID = msgParams.getParam(s_tcapRemoteTID); + msgParams.setParam(s_tcapRemoteTID,msgParams.getValue(s_tcapLocalTID)); + msgParams.setParam(s_tcapLocalTID,(TelEngine::null(trID) ? "" : *trID)); + type = TC_Notice; + msgParams.setParam(s_tcapRequest,lookup(type,SS7TCAP::s_transPrimitives,"Notice")); + } + else + incCounter(SS7TCAP::IncomingMsgs); + + SS7TCAPTransaction* tr = 0; + Lock lock(m_transactionsMtx); + switch (type) { + case SS7TCAP::TC_Unidirectional: + case SS7TCAP::TC_Begin: + case SS7TCAP::TC_QueryWithPerm: + case SS7TCAP::TC_QueryWithoutPerm: + // if there isn't a destination ID, allocate a new one and build a transaction + if (TelEngine::null(trID)) { + u_int32_t newID = allocTransactionID(); + tr = buildTransaction(type,newID,msgParams,false);//new SS7TCAPTransaction(this,type,newID,msgParams,m_trTimeout,false); + m_transactions.append(tr); + msgParams.setParam(s_tcapLocalTID,String(newID)); + } + break; + case SS7TCAP::TC_Continue: + case SS7TCAP::TC_ConversationWithPerm: + case SS7TCAP::TC_ConversationWithoutPerm: + case SS7TCAP::TC_End: + case SS7TCAP::TC_Response: + case SS7TCAP::TC_P_Abort: + case SS7TCAP::TC_U_Abort: + case SS7TCAP::TC_Notice: + if (TelEngine::null(trID)) { + transactError.setError(SS7TCAPError::Transact_UnassignedTransactionID); + return handleError(transactError,msgParams,msgData); + } + tr = getTransaction(*trID); + if (!tr) { + transactError.setError(SS7TCAPError::Transact_UnassignedTransactionID); + return handleError(transactError,msgParams,msgData); + } + transactError = tr->update((SS7TCAP::TCAPUserTransActions)type,msgParams,false); + if (transactError.error() != SS7TCAPError::NoError) + return handleError(transactError,msgParams,msgData,tr); + break; + default: + incCounter(SS7TCAP::DiscardedMsgs); + return result; + } + lock.drop(); + if (tr) { + transactError = tr->handleData(msgParams,msgData); + if (transactError.error() != SS7TCAPError::NoError) + return handleError(transactError,msgParams,msgData,tr); + + tr->addSCCPAddressing(msgParams,true); + if (sendToUser(msgParams)) { + tr->setUserName(msgParams.getValue(s_tcapUser)); + tr->endNow(msgParams.getBoolValue(s_tcapEndNow,false)); + tr->updateState(false); + + if (tr->transactionType() == SS7TCAP::TC_Unidirectional + || tr->transactionType() == SS7TCAP::TC_U_Abort + || tr->transactionType() == SS7TCAP::TC_P_Abort + || tr->transactionType() == SS7TCAP::TC_End + || tr->transactionType() == SS7TCAP::TC_Response) + tr->setState(SS7TCAPTransaction::Idle); + else + tr->setTransmitState(SS7TCAPTransaction::Transmitted); + } + else + tr->setState(SS7TCAPTransaction::Idle); + } + result = HandledMSU::Accepted; + incCounter(SS7TCAP::NormalMsgs); + return result; +} + +SS7TCAPError SS7TCAP::userRequest(NamedList& params) +{ +#ifdef DEBUG + if (s_printMsgs && debugAt(DebugInfo)) + dumpData(DebugInfo,this,"SS7TCAP::userRequest() - received request ",this,params); +#endif + + NamedString* req = params.getParam(s_tcapRequest); + + NamedString* otid = params.getParam(s_tcapLocalTID); + NamedString* user = params.getParam(s_tcapUser); + SS7TCAPError error(m_tcapType); + if (TelEngine::null(req)) { + Debug(this,DebugInfo,"SS7TCAP::userRequest()[%p] - received a transaction request from user=%s with originating ID=%s" + " without request type, rejecting it",this,(user ? user->c_str() : ""),(otid ? otid->c_str() : "")); + params.setParam(s_tcapRequestError,"missing_primitive"); + error.setError(SS7TCAPError::Transact_IncorrectTransactionPortion); + return error; + } + + SS7TCAPTransaction* tr = 0; + Lock lock(m_transactionsMtx); + if (!TelEngine::null(req)) { + int type = req->toInteger(SS7TCAP::s_transPrimitives); + switch (type) { + case SS7TCAP::TC_Unidirectional: + case SS7TCAP::TC_Begin: + case SS7TCAP::TC_QueryWithPerm: + case SS7TCAP::TC_QueryWithoutPerm: + // if otid not set, alloc one and set it + if (TelEngine::null(otid)) { + params.setParam(s_tcapLocalTID,String(allocTransactionID())); + otid = params.getParam(s_tcapLocalTID); + } + // if set, check if we already have it + else if (m_transactions.find(*otid)) { + Debug(this,DebugInfo,"SS7TCAP::userRequest()[%p] - received a new transaction request from user=%s with originating ID=%s which is the ID " + "of an already existing transaction, rejecting the request",this,(user ? user->c_str() : ""),otid->c_str()); + params.setParam(s_tcapRequestError,"allocated_id"); + error.setError(SS7TCAPError::Transact_IncorrectTransactionPortion); + return error; + } + // create transaction + tr = buildTransaction((SS7TCAP::TCAPUserTransActions)type,otid->toInteger(),params,true); + if (!TelEngine::null(user)) + tr->setUserName(user); + m_transactions.append(tr); + break; + 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: + // find transaction and update + if (!TelEngine::null(otid)) { + tr = getTransaction(*otid); + if (!tr) { + params.setParam(s_tcapRequestError,"unknown_transaction"); + error.setError(SS7TCAPError::Transact_UnassignedTransactionID); + return error; + } + error = tr->update((SS7TCAP::TCAPUserTransActions)type,params); + if (error.error() != SS7TCAPError::NoError) + return error; + } + else { + params.setParam(s_tcapRequestError,"need_transaction_id"); + error.setError(SS7TCAPError::Transact_UnassignedTransactionID); + return error; + } + break; + case SS7TCAP::TC_Unknown: + if (!TelEngine::null(otid)) + tr = getTransaction(*otid); + break; + case SS7TCAP::TC_P_Abort: + case SS7TCAP::TC_Notice: + default: + Debug(this,DebugAll,"SS7TCAP::userRequest() - received user request with unsuited primitive='%s' [%p]",req->c_str(),this); + params.setParam(s_tcapRequestError,"wrong_primitive"); + error.setError(SS7TCAPError::Transact_UnrecognizedPackageType); + return error; + } + } + if (tr) { + error = tr->handleDialogPortion(params,true); + if (error.error() != SS7TCAPError::NoError) + return error; + error = tr->handleComponents(params,true); + if (error.error() != SS7TCAPError::NoError) + return error; + if (tr->transmitState() == SS7TCAPTransaction::PendingTransmit) { + buildSCCPData(params,tr); + tr->setTransmitState(SS7TCAPTransaction::Transmitted); + tr->updateState(true); + } + else if (tr->transmitState() == SS7TCAPTransaction::NoTransmit) { + lock.drop(); + removeTransaction(tr); + } + } + return error; +} + +void SS7TCAP::buildSCCPData(NamedList& params, SS7TCAPTransaction* tr) +{ + if (!tr) + return; + DDebug(this,DebugAll,"SS7TCAP::buildSCCPData(tr=%p) for local transaction ID=%s [%p]",tr,tr->toString().c_str(),this); + + bool sendOk = true; + int type = tr->transactionType(); + if (type == SS7TCAP::TC_End + || type == TC_Response) { + if (!tr->basicEnd()) { + sendOk = false; // prearranged end, don't send to remote Transaction End message + Debug(this,DebugAll,"SS7TCAPITU::buildSCCPData(tr=%p) [%p] - transaction with id=%s has set prearranged end, won't be" + " sending anything to SCCP",tr,this,tr->toString().c_str()); + } + } + + if (sendOk) { + DataBlock data; + tr->requestContent(params,data); + tr->addSCCPAddressing(params,false); + encodeTransactionPart(params,data); + + if (!sendData(data,params)) { + params.setParam("ReturnCause","Network failure"); + enqueue(new SS7TCAPMessage(params,data,true)); + Debug(this,DebugInfo,"SS7TCAPITU::buildSCCPData(tr=%p) [%p] - message for transaction with id=%s failed to be sent", + tr,this,tr->toString().c_str()); + return; + }; + incCounter(SS7TCAP::OutgoingMsgs); + } +} + +HandledMSU SS7TCAP::handleError(SS7TCAPError& error, NamedList& params, DataBlock& data, SS7TCAPTransaction* tr) +{ + Debug(this,DebugInfo,"SS7TCAPITU::handleError(error=%s) for transaction with id=%s(%p) [%p]",error.errorName().c_str(), + (tr ? tr->toString().c_str() : "unknown"),tr,this); + HandledMSU result = HandledMSU::Accepted; + + int type = lookup(params.getValue(s_tcapRequest,""),SS7TCAP::s_transPrimitives); + NamedString* rtid = params.getParam(s_tcapRemoteTID); + NamedString* ltid = params.getParam(s_tcapLocalTID); + bool buildRemAbort = false; + bool buildLocAbort = false; + switch (type) { + case SS7TCAP::TC_Unidirectional: + incCounter(SS7TCAP::DiscardedMsgs); + return result; // return with rejected, meaning Discarded + case SS7TCAP::TC_Begin: + case SS7TCAP::TC_QueryWithPerm: + case SS7TCAP::TC_QueryWithoutPerm: + if (!TelEngine::null(rtid)) + buildRemAbort = true; + else { + // no originating ID, we don't know to whom to send the Abort, meaning we'll discard the message + incCounter(SS7TCAP::DiscardedMsgs); + return result; + } + break; + case SS7TCAP::TC_Continue: + case SS7TCAP::TC_ConversationWithPerm: + case SS7TCAP::TC_ConversationWithoutPerm: + if (TelEngine::null(rtid) && TelEngine::null(ltid)) { + incCounter(SS7TCAP::DiscardedMsgs); + return result; + } + if (!TelEngine::null(rtid)) { + buildRemAbort = true; + if (!TelEngine::null(ltid)) + buildLocAbort = true; + } + break; + case SS7TCAP::TC_End: + case SS7TCAP::TC_Response: + case SS7TCAP::TC_P_Abort: + case SS7TCAP::TC_U_Abort: + if (TelEngine::null(ltid)) { + incCounter(SS7TCAP::DiscardedMsgs); + return result; + } + else + buildLocAbort = true; + break; + default: + if (!TelEngine::null(rtid)) { + buildRemAbort = true; + if (!TelEngine::null(ltid)) + buildLocAbort = true; + } + else { + incCounter(SS7TCAP::DiscardedMsgs); + return result; + } + break; + } + + if (buildLocAbort && !TelEngine::null(ltid)) { // notify user of the abort + params.setParam(s_tcapRequest,lookup(SS7TCAP::TC_P_Abort,SS7TCAP:: s_transPrimitives)); + params.setParam(s_tcapAbortCause,"pAbort"); + params.setParam(s_tcapAbortInfo,String(error.errorCode())); + if (tr) { + tr->update(SS7TCAP::TC_P_Abort,params,false); + tr->updateState(); + } + sendToUser(params); + } + if (buildRemAbort) { + // clean dataBlock + data.clear(); + + if (!TelEngine::null(rtid)) { // we have the remote ID, notify of abort + NamedList addr(""); + populateSCCPAddress(addr,addr,params,false,true); + params.copyParams(addr); + + if (error.error() != SS7TCAPError::Dialog_Abnormal) { + params.setParam(s_tcapRequest,lookup(SS7TCAP::TC_P_Abort,SS7TCAP::s_transPrimitives)); + params.setParam(s_tcapAbortCause,"pAbort"); + params.setParam(s_tcapAbortInfo,String(error.errorCode())); + } + if (tcapType() == ANSITCAP) + SS7TCAPTransactionANSI::encodePAbort(tr,params,data); + + encodeTransactionPart(params,data); + sendData(data,params); + } + } + if (buildRemAbort || buildLocAbort) { + incCounter(SS7TCAP::AbnormalMsgs); + result = HandledMSU::Accepted; + } + return result; +} + +/** + * SS7TCAPError implementation + */ +struct TCAPError { + SS7TCAPError::ErrorType errorType; + u_int16_t errorCode; +}; + +static const TCAPError s_ansiErrorDefs[] = { + // error fullcode + { SS7TCAPError::Transact_UnrecognizedPackageType, 0x01}, + { SS7TCAPError::Transact_IncorrectTransactionPortion, 0x02}, + { SS7TCAPError::Transact_BadlyStructuredTransaction, 0x03}, + { SS7TCAPError::Transact_UnassignedTransactionID, 0x04}, + { SS7TCAPError::Transact_PermissionToReleaseProblem, 0x05}, + { SS7TCAPError::Transact_ResourceUnavailable, 0x06}, + + { SS7TCAPError::Dialog_UnrecognizedDialoguePortionID, 0x07}, + { SS7TCAPError::Dialog_BadlyStructuredDialoguePortion, 0x08}, + { SS7TCAPError::Dialog_MissingDialoguePortion, 0x09}, + { SS7TCAPError::Dialog_InconsistentDialoguePortion, 0x0a}, + + // GeneralProblem + { SS7TCAPError::General_UnrecognizedComponentType, 0x101}, + { SS7TCAPError::General_IncorrectComponentPortion, 0x102}, + { SS7TCAPError::General_BadlyStructuredCompPortion, 0x103}, + { SS7TCAPError::General_IncorrectComponentCoding, 0x104}, + + // InvokeProblem + { SS7TCAPError::Invoke_DuplicateInvokeID, 0x201}, + { SS7TCAPError::Invoke_UnrecognizedOperationCode, 0x202}, + { SS7TCAPError::Invoke_IncorrectParameter, 0x203}, + { SS7TCAPError::Invoke_UnrecognizedCorrelationID, 0x204}, + + // ReturnResult + { SS7TCAPError::Result_UnrecognisedCorrelationID, 0x301}, + { SS7TCAPError::Result_UnexpectedReturnResult, 0x302}, + { SS7TCAPError::Result_IncorrectParameter, 0x303}, + + // ReturnError + { SS7TCAPError::Error_UnrecognisedCorrelationID, 0x401}, + { SS7TCAPError::Error_UnexpectedReturnError, 0x402}, + { SS7TCAPError::Error_UnrecognisedError, 0x403}, + { SS7TCAPError::Error_UnexpectedError, 0x404}, + { SS7TCAPError::Error_IncorrectParameter, 0x405}, + + { SS7TCAPError::NoError, 0xfff}, +}; + +static const TCAPError s_ituErrorDefs[] = { + // error fullcode + { SS7TCAPError::Transact_UnrecognizedPackageType, 0x00}, + { SS7TCAPError::Transact_UnassignedTransactionID, 0x01}, + { SS7TCAPError::Transact_BadlyStructuredTransaction, 0x02}, + { SS7TCAPError::Transact_IncorrectTransactionPortion, 0x03}, + { SS7TCAPError::Transact_ResourceUnavailable, 0x04}, + + { SS7TCAPError::Dialog_Abnormal, 0x7000}, + + // GeneralProblem + { SS7TCAPError::General_UnrecognizedComponentType, 0x8000}, + { SS7TCAPError::General_IncorrectComponentPortion, 0x8001}, + { SS7TCAPError::General_BadlyStructuredCompPortion, 0x8002}, + + // InvokeProblem + { SS7TCAPError::Invoke_DuplicateInvokeID, 0x8100}, + { SS7TCAPError::Invoke_UnrecognizedOperationCode, 0x8101}, + { SS7TCAPError::Invoke_IncorrectParameter, 0x8102}, + { SS7TCAPError::Invoke_UnrecognizedCorrelationID, 0x8105}, + { SS7TCAPError::Invoke_ResourceLimitation, 0x8103}, + { SS7TCAPError::Invoke_InitiatingRelease, 0x8104}, + { SS7TCAPError::Invoke_LinkedResponseUnexpected, 0x8106}, + { SS7TCAPError::Invoke_UnexpectedLinkedOperation, 0x8107}, + + // ReturnResult + { SS7TCAPError::Result_UnrecognizedInvokeID, 0x8200}, + { SS7TCAPError::Result_UnexpectedReturnResult, 0x8201}, + { SS7TCAPError::Result_IncorrectParameter, 0x8202}, + + // ReturnError + { SS7TCAPError::Error_UnrecognizedInvokeID, 0x8300}, + { SS7TCAPError::Error_UnexpectedReturnError, 0x8301}, + { SS7TCAPError::Error_UnrecognisedError, 0x8302}, + { SS7TCAPError::Error_UnexpectedError, 0x8303}, + { SS7TCAPError::Error_IncorrectParameter, 0x8304}, + + { SS7TCAPError::NoError, 0xffff}, +}; + +const TokenDict SS7TCAPError::s_errorTypes[] = { + {"Transact-UnrecognizedPackageType", SS7TCAPError::Transact_UnrecognizedPackageType}, + {"Transact-IncorrectTransactionPortion", SS7TCAPError::Transact_IncorrectTransactionPortion}, + {"Transact-BadlyStructuredTransaction", SS7TCAPError::Transact_BadlyStructuredTransaction}, + {"Transact-UnassignedTransactionID", SS7TCAPError::Transact_UnassignedTransactionID }, + {"Transact-PermissionToReleaseProblem", SS7TCAPError::Transact_PermissionToReleaseProblem}, + {"Transact-ResourceUnavailable", SS7TCAPError::Transact_ResourceUnavailable}, + + {"Dialog-UnrecognizedDialoguePortionID", SS7TCAPError::Dialog_UnrecognizedDialoguePortionID}, + {"Dialog-BadlyStructuredDialoguePortion", SS7TCAPError::Dialog_BadlyStructuredDialoguePortion}, + {"Dialog-MissingDialoguePortion", SS7TCAPError::Dialog_MissingDialoguePortion}, + {"Dialog-InconsistentDialoguePortion", SS7TCAPError::Dialog_InconsistentDialoguePortion}, + {"Dialog-Abnormal", SS7TCAPError::Dialog_Abnormal}, + + {"General-UnrecognizedComponentType", SS7TCAPError::General_UnrecognizedComponentType}, + {"General-IncorrectComponentPortion", SS7TCAPError::General_IncorrectComponentPortion}, + {"General-BadlyStructuredCompPortion", SS7TCAPError::General_BadlyStructuredCompPortion}, + {"General-IncorrectComponentCoding", SS7TCAPError::General_IncorrectComponentCoding}, + + {"Invoke-DuplicateInvokeID", SS7TCAPError::Invoke_DuplicateInvokeID}, + {"Invoke-UnrecognizedOperationCode", SS7TCAPError::Invoke_UnrecognizedOperationCode}, + {"Invoke-IncorrectParameter", SS7TCAPError::Invoke_IncorrectParameter}, + {"Invoke-UnrecognizedCorrelationID", SS7TCAPError::Invoke_UnrecognizedCorrelationID}, + {"Invoke-ResourceLimitation", SS7TCAPError::Invoke_ResourceLimitation}, + {"Invoke-InitiatingRelease", SS7TCAPError::Invoke_InitiatingRelease}, + {"Invoke-LinkedResponseUnexpected", SS7TCAPError::Invoke_LinkedResponseUnexpected}, + {"Invoke-UnexpectedLinkedOperation", SS7TCAPError::Invoke_UnexpectedLinkedOperation}, + + {"Result-UnrecognizedInvokeID", SS7TCAPError::Result_UnrecognizedInvokeID}, + {"Result-UnrecognisedCorrelationID", SS7TCAPError::Result_UnrecognisedCorrelationID}, + {"Result-UnexpectedReturnResult", SS7TCAPError::Result_UnexpectedReturnResult}, + {"Result-IncorrectParameter", SS7TCAPError::Result_IncorrectParameter}, + + {"Error-UnrecognizedInvokeID", SS7TCAPError::Error_UnrecognizedInvokeID}, + {"Error-UnrecognisedCorrelationID", SS7TCAPError::Error_UnrecognisedCorrelationID}, + {"Error-UnexpectedReturnError", SS7TCAPError::Error_UnexpectedReturnError}, + {"Error-UnrecognisedError", SS7TCAPError::Error_UnrecognisedError}, + {"Error-UnexpectedError", SS7TCAPError::Error_UnexpectedError}, + {"Error-IncorrectParameter", SS7TCAPError::Error_IncorrectParameter}, + + {"NoError", SS7TCAPError::NoError}, + {0,0}, +}; + +SS7TCAPError::SS7TCAPError(SS7TCAP::TCAPType tcapType) + : m_tcapType(tcapType), m_error(SS7TCAPError::NoError) +{ +} + +SS7TCAPError::SS7TCAPError(SS7TCAP::TCAPType tcapType, ErrorType error) + : m_tcapType(tcapType), m_error(error) +{ + XDebug(DebugAll,"SS7TCAPError created TCAP=%s with error=%s [%p]",lookup(tcapType,SS7TCAP::s_tcapVersion), + lookup(error,s_errorTypes),this); +} + +SS7TCAPError::~SS7TCAPError() +{ +} + +const String SS7TCAPError::errorName() +{ + return lookup(m_error,s_errorTypes,"NoError"); +} + +u_int16_t SS7TCAPError::errorCode() +{ + const TCAPError* errDef = (m_tcapType == SS7TCAP::ANSITCAP ? s_ansiErrorDefs : s_ituErrorDefs); + for (;errDef && errDef->errorCode != SS7TCAPError::NoError; errDef++) { + if (errDef->errorType == m_error) + break; + } + return errDef->errorCode; +} + +/** + * SS7TCAPTransaction + */ +SS7TCAPTransaction::SS7TCAPTransaction(SS7TCAP* tcap, SS7TCAP::TCAPUserTransActions type, + u_int32_t transactID, NamedList& params, u_int64_t timeout, bool initLocal) + : m_tcap(tcap), m_tcapType(SS7TCAP::UnknownTCAP), m_userName(""), m_localID(transactID), m_type(type), + m_localSCCPAddr(""), m_remoteSCCPAddr(""), m_basicEnd(true), m_endNow(false), m_timeout(timeout) +{ + + DDebug(m_tcap,DebugAll,"SS7TCAPTransaction(tcap = '%s' [%p], transactID = %u) created [%p]", + m_tcap->toString().c_str(),tcap,transactID,this); + + m_remoteID = params.getValue(s_tcapRemoteTID); + populateSCCPAddress(m_localSCCPAddr,m_remoteSCCPAddr,params,initLocal,false); + m_endNow = params.getBoolValue(s_tcapEndNow,false); + if (initLocal) + setState(PackageSent); + else + setState(PackageReceived); +} + +SS7TCAPTransaction::~SS7TCAPTransaction() +{ + DDebug(tcap(),DebugAll,"Transaction with ID=%s of user=%s destroyed [%p]", + m_localID.c_str(),m_userName.c_str(),this); + m_components.clear(); + m_tcap = 0; +} + +SS7TCAPComponent* SS7TCAPTransaction::findComponent(const String& id) +{ + SS7TCAPComponent* comp = 0; + ObjList* o = m_components.find(id); + if (o) + comp = static_cast(o->get()); + return comp; +} + +SS7TCAPError SS7TCAPTransaction::update(SS7TCAP::TCAPUserTransActions type, NamedList& params, bool updateByUser) +{ + DDebug(tcap(),DebugStub,"SS7TCAPTransaction::update() [%p], localID=%s - stub",this,m_localID.c_str()); + SS7TCAPError error(m_tcapType); + return error; +} + +SS7TCAPError SS7TCAPTransaction::buildComponentError(SS7TCAPError& error, NamedList& params, DataBlock& data) +{ + if (error.error() == SS7TCAPError::NoError) + return error; + Debug(tcap(),DebugInfo,"SS7TCAPTransaction::buildComponentError(error=%s) for transaction with id=%s [%p]",error.errorName().c_str(), + toString().c_str(),this); + int compCount = params.getIntValue(s_tcapCompCount,1); + + if (!compCount) + return error; + + String param; + compPrefix(param,compCount,true); + bool buildRej = false; + NamedString* typeStr = params.getParam(param + s_tcapCompType); + if (TelEngine::null(typeStr)) + buildRej = true; + else { + int type = typeStr->toInteger(SS7TCAP::s_compPrimitives); + NamedString* invokeID = params.getParam(param + s_tcapRemoteCID); + + switch (type) { + case SS7TCAP::TC_ResultLast: + case SS7TCAP::TC_ResultNotLast: + case SS7TCAP::TC_U_Error: + if (!TelEngine::null(invokeID)) { + SS7TCAPComponent* comp = findComponent(*invokeID); + if (comp) + m_components.remove(comp); + } + break; + case SS7TCAP::TC_Invoke: + case SS7TCAP::TC_R_Reject: + default: + break; + } + buildRej = true; + } + + params.setParam(param + s_tcapCompType,lookup(SS7TCAP::TC_L_Reject,SS7TCAP::s_compPrimitives,"L_Reject")); + params.setParam(param + s_tcapProblemCode,String(error.error())); + if (buildRej) { + SS7TCAPComponent* comp = SS7TCAPComponent::componentFromNamedList(m_tcapType,this,params,compCount); + if (comp) + m_components.append(comp); + } + return error; +} + +SS7TCAPError SS7TCAPTransaction::handleComponents(NamedList& params, bool updateByUser) +{ + XDebug(tcap(),DebugAll,"SS7TCAPTransaction::handleComponents(updateByUser=%s) [%p]",String::boolText(updateByUser),this); + int count = params.getIntValue(s_tcapCompCount,0); + SS7TCAPError error(m_tcapType); + if (!count) + return error; + int index = 0; + while (index < count) { + index++; + String paramRoot; + compPrefix(paramRoot,index,true); + + NamedString* localCID = params.getParam(paramRoot + s_tcapLocalCID); + NamedString* typeStr = params.getParam(paramRoot + s_tcapCompType); + if (TelEngine::null(typeStr)) + continue; + int type = typeStr->toInteger(SS7TCAP::s_compPrimitives); + switch (type) { + case SS7TCAP::TC_Invoke: + case SS7TCAP::TC_InvokeNotLast: + if (!updateByUser) { + if (!TelEngine::null(localCID)) { + // we have a linked/correlation ID, check the state of that component + SS7TCAPComponent* linkedTo = findComponent(*localCID); + if (!linkedTo) { + type = SS7TCAP::TC_L_Reject; + params.setParam(paramRoot + s_tcapProblemCode,String(SS7TCAPError::Invoke_UnrecognizedCorrelationID)); + } + else { + if (linkedTo->state() != SS7TCAPComponent::OperationSent) { + type = SS7TCAP::TC_L_Reject; + params.setParam(paramRoot + s_tcapProblemCode,String(SS7TCAPError::Invoke_UnexpectedLinkedOperation)); + } + } + } + if (type == SS7TCAP::TC_L_Reject) { + params.setParam(paramRoot + s_tcapCompType,lookup(type,SS7TCAP::s_compPrimitives,"L_Reject")); + SS7TCAPComponent* comp = SS7TCAPComponent::componentFromNamedList(m_tcapType,this,params,index); + if (comp) + m_components.append(comp); + } + } + else { + if (!TelEngine::null(localCID)) { + if (findComponent(*localCID)) { + error.setError(SS7TCAPError::Invoke_DuplicateInvokeID); + return error; + } + else { + SS7TCAPComponent* comp = SS7TCAPComponent::componentFromNamedList(m_tcapType,this,params,index); + if (comp) { + m_components.append(comp); + comp->setState(SS7TCAPComponent::OperationSent); + } + } + } + } + break; + case SS7TCAP::TC_ResultLast: + case SS7TCAP::TC_ResultNotLast: + case SS7TCAP::TC_U_Error: + if (!updateByUser) { + if (!TelEngine::null(localCID)) { + SS7TCAPComponent* comp = findComponent(*localCID); + if (comp) + comp->update(params,index); + else { + params.setParam(paramRoot + s_tcapCompType,lookup(SS7TCAP::TC_L_Reject,SS7TCAP::s_compPrimitives,"L_Reject")); + params.setParam(paramRoot + s_tcapProblemCode,String(SS7TCAPError::Invoke_UnexpectedLinkedOperation)); + SS7TCAPComponent* comp = SS7TCAPComponent::componentFromNamedList(m_tcapType,this,params,index); + if (comp) + m_components.append(comp); + } + } + } + break; + case SS7TCAP::TC_R_Reject: + case SS7TCAP::TC_U_Reject: + if (!updateByUser) { + params.setParam(paramRoot + s_tcapCompType,lookup(SS7TCAP::TC_R_Reject,SS7TCAP::s_compPrimitives,"R_Reject")); + if (!TelEngine::null(localCID)) { + SS7TCAPComponent* comp = findComponent(*localCID); + if (comp) + m_components.remove(comp); + } + } + else if (!TelEngine::null(localCID)) { + m_components.remove(*localCID); + } + break; + case SS7TCAP::TC_L_Reject: + case SS7TCAP::TC_U_Cancel: + if (updateByUser) { + if (!TelEngine::null(localCID)) + m_components.remove(*localCID); + } + break; + case SS7TCAP::TC_TimerReset: + if (updateByUser && !TelEngine::null(localCID) && m_tcapType == SS7TCAP::ITUTCAP) { + SS7TCAPComponent* comp = findComponent(*localCID); + if (comp) + comp->resetTimer(params,index); + } + break; + case SS7TCAP::TC_L_Cancel: + default: + break; + } + } + DDebug(tcap(),DebugAll,"SS7TCAPTransaction::handleComponents() - transaction with localID=%s handled %d components [%p]", + m_localID.c_str(),index,this); + return error; +} + +void SS7TCAPTransaction::requestComponents(NamedList& params, DataBlock& data) +{ + Lock(this); + unsigned int index = params.getIntValue(s_tcapCompCount); + for (ObjList* o = m_components.skipNull(); o; o = o->skipNext()) { + SS7TCAPComponent* comp = static_cast(o->get()); + if (comp && comp->state() == SS7TCAPComponent::OperationPending) { + comp->fill(index,params); + index++; + } + } +#ifdef DEBUG + if (tcap() && s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,tcap(),"SS7TCAPTransaction::requestComponents() preparing to encode components:",this,params,data); +#endif + params.setParam(s_tcapCompCount,String(index)); + encodeComponents(params,data); +#ifdef DEBUG + if (tcap() && s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,tcap(),"SS7TCAPTransaction::requestComponents() encoded components'",this,params,data); +#endif +} + +void SS7TCAPTransaction::transactionData(NamedList& params) +{ + params.setParam(s_tcapRequest,lookup(m_type,SS7TCAP::s_transPrimitives)); + params.setParam(s_tcapLocalTID,m_localID); + params.setParam(s_tcapRemoteTID,m_remoteID); +#ifdef DEBUG + if (s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,tcap(),"SS7TCAPTransaction::transactionData() - added transaction data",this,params); +#endif +} + +void SS7TCAPTransaction::checkComponents() +{ + NamedList params(""); + int index = 0; + ListIterator iter(m_components); + for (;;) { + SS7TCAPComponent* comp = static_cast(iter.get()); + if (!comp) + break; + if (comp->timedOut()) { + XDebug(tcap(),DebugInfo,"SS7TCAPTransaction::checkComponents() - component with local ID = %s timed out in" + " transaction with local ID = %s [%p]",comp->toString().c_str(),toString().c_str(),this); + SS7TCAP::TCAPUserCompActions type = comp->type(); + String paramRoot = ""; + switch (type) { + case SS7TCAP::TC_Invoke: + case SS7TCAP::TC_InvokeNotLast: + if (comp->operationClass() != SS7TCAP::NoReport) { + comp->fill(index,params); + compPrefix(paramRoot,index,false); + params.setParam(paramRoot + ".componentType",lookup(SS7TCAP::TC_L_Cancel,SS7TCAP::s_compPrimitives,"L_Cancel")); + index++; + } + comp->setState(SS7TCAPComponent::Idle); + break; + case SS7TCAP::TC_ResultLast: + case SS7TCAP::TC_U_Error: + comp->setState(SS7TCAPComponent::Idle); + break; + case SS7TCAP::TC_ResultNotLast: + case SS7TCAP::TC_U_Reject: + case SS7TCAP::TC_L_Reject: + case SS7TCAP::TC_R_Reject: + case SS7TCAP::TC_L_Cancel: + case SS7TCAP::TC_U_Cancel: + case SS7TCAP::TC_TimerReset: + default: + break; + } + } + if (comp->state() == SS7TCAPComponent::Idle) + m_components.remove(comp); + } + if (params.count()) { + transactionData(params); + params.clearParam(s_tcapRequest); + tcap()->sendToUser(params); + } + if (m_components.count() == 0) {// we don't have any more components + if (!m_timeout.started()) { + m_timeout.start(); + XDebug(tcap(),DebugInfo,"SS7TCAPTransactionITU::checkComponents() - timer for transaction with localID=%s has been started [%p]", + toString().c_str(),this); + } + } +} + +void SS7TCAPTransaction::setTransmitState(TransactionTransmit state) +{ + Lock l(this); + m_transmit = state; + if (m_transmit == Transmitted) { + switch (m_type) { + case SS7TCAP::TC_Unidirectional: + case SS7TCAP::TC_P_Abort: + case SS7TCAP::TC_U_Abort: + case SS7TCAP::TC_Response: + case SS7TCAP::TC_End: + m_state = Idle; + break; + case SS7TCAP::TC_Notice: + case SS7TCAP::TC_Begin: + case SS7TCAP::TC_QueryWithPerm: + case SS7TCAP::TC_QueryWithoutPerm: + case SS7TCAP::TC_Continue: + case SS7TCAP::TC_ConversationWithPerm: + case SS7TCAP::TC_ConversationWithoutPerm: + default: + break; + } + } +} + +void SS7TCAPTransaction::addSCCPAddressing(NamedList& fillParams, bool local) +{ + String localParam(local ? s_calledPA : s_callingPA); + String remoteParam(local ? s_callingPA : s_calledPA); + fillParams.clearParam(s_calledPA,'.'); + fillParams.clearParam(s_callingPA,'.'); + for (unsigned int i = 0; i < m_localSCCPAddr.count(); i++) { + NamedString* ns = m_localSCCPAddr.getParam(i); + if (ns && *ns && !(*ns).null()) { + String name = ns->name(); + fillParams.setParam(localParam + name,*ns); + } + } + for (unsigned int i = 0; i < m_remoteSCCPAddr.count(); i++) { + NamedString* ns = m_remoteSCCPAddr.getParam(i); + if (ns && *ns && !(*ns).null()) { + String name = ns->name(); + fillParams.setParam(remoteParam + name,*ns); + } + } +} + +SS7TCAPError SS7TCAPTransaction::handleData(NamedList& params, DataBlock& data) +{ + DDebug(tcap(),DebugAll,"SS7TCAPTransactionITU::handleData() transactionID=%s data length=%u [%p]",m_localID.c_str(), + data.length(),this); + Lock lock(this); + // in case of Abort message, check Cause Information + SS7TCAPError error(m_tcapType); + return error; +} + +void SS7TCAPTransaction::updateToEnd() +{ +} + + +/** + * SS7TCAPComponent + */ +const TokenDict SS7TCAPComponent::s_compStates[] = { + {"Idle", SS7TCAPComponent::Idle}, + {"OperationPending", SS7TCAPComponent::OperationPending}, + {"OperationSent", SS7TCAPComponent::OperationSent}, + {"WaitForReject", SS7TCAPComponent::WaitForReject}, +}; + +SS7TCAPComponent::SS7TCAPComponent(SS7TCAP::TCAPType type, SS7TCAPTransaction* trans, NamedList& params, unsigned int index) + : m_transact(trans), m_state(Idle), + m_id(""), m_corrID(""), m_opClass(SS7TCAP::SuccessOrFailureReport), m_opTimer(0), m_error(type) +{ + String paramRoot; + compPrefix(paramRoot,index,true); + + m_type = (SS7TCAP::TCAPUserCompActions) lookup(params.getValue(paramRoot + s_tcapCompType),SS7TCAP::s_compPrimitives); + m_id = params.getValue(paramRoot + s_tcapLocalCID); + m_corrID = params.getValue(paramRoot + s_tcapRemoteCID); + + setState(OperationPending); + + NamedString* opClass = params.getParam(paramRoot + "operationClass"); + if (!TelEngine::null(opClass)) + m_opClass = (SS7TCAP::TCAPComponentOperationClass) opClass->toInteger(SS7TCAP::s_compOperClasses,SS7TCAP::SuccessOrFailureReport); + m_opTimer.interval(params.getIntValue(paramRoot + "timeout",5) * 1000); + + m_error.setError((SS7TCAPError::ErrorType)params.getIntValue(paramRoot + s_tcapProblemCode)); + + DDebug(m_transact->tcap(),DebugAll,"SS7TCAPComponent() [%p] created for transaction='%s' [%p] with localID=%s, remoteID=%s," + " type=%s, class=%s",this, (m_transact ? m_transact->toString().c_str() :""),m_transact,m_id.c_str(), + m_corrID.c_str(),lookup(m_type,SS7TCAP::s_compPrimitives),lookup(m_opClass,SS7TCAP::s_compOperClasses)); +} + +SS7TCAPComponent::~SS7TCAPComponent() +{ + DDebug(m_transact->tcap(),DebugAll,"SS7TCAPComponent::~SS7TCAPComponent() - component [%p] destroyed",this); + m_transact = 0; +} + +void SS7TCAPComponent::update(NamedList& params, unsigned int index) +{ + String paramRoot; + compPrefix(paramRoot,index,false); + DDebug(m_transact->tcap(),DebugAll,"SS7TCAPComponent::update() - update component with localID=%s [%p]",m_id.c_str(),this); + + m_type = (SS7TCAP::TCAPUserCompActions) lookup(params.getValue(paramRoot + "." + s_tcapCompType),SS7TCAP::s_compPrimitives); + switch (m_type) { + case SS7TCAP::TC_ResultLast: + if (m_opClass == SS7TCAP::SuccessOrFailureReport || m_opClass == SS7TCAP::SuccessOnlyReport) + setState(WaitForReject); + else if (m_opClass == SS7TCAP::FailureOnlyReport || m_opClass == SS7TCAP::NoReport) { + // build reject component + m_type = SS7TCAP::TC_L_Reject; + params.setParam(paramRoot + "." + s_tcapCompType,lookup(SS7TCAP::TC_L_Reject,SS7TCAP::s_compPrimitives)); + params.setParam(paramRoot + "." + s_tcapProblemCode,String(SS7TCAPError::Result_UnexpectedReturnResult)); + m_error.setError(SS7TCAPError::Result_UnexpectedReturnResult); + setState(OperationPending); + } + break; + case SS7TCAP::TC_ResultNotLast: + if (m_opClass == SS7TCAP::FailureOnlyReport || m_opClass == SS7TCAP::NoReport) { + // build reject component + m_type = SS7TCAP::TC_L_Reject; + params.setParam(paramRoot + "." + s_tcapCompType,lookup(SS7TCAP::TC_L_Reject,SS7TCAP::s_compPrimitives)); + params.setParam(paramRoot + "." + s_tcapProblemCode,String(SS7TCAPError::Result_UnexpectedReturnResult)); + m_error.setError(SS7TCAPError::Result_UnexpectedReturnResult); + setState(OperationPending); + } + else if (m_opClass == SS7TCAP::SuccessOnlyReport) + setState(WaitForReject); + break; + case SS7TCAP::TC_U_Error: + if (m_opClass == SS7TCAP::FailureOnlyReport) + setState(WaitForReject); + else if (m_opClass == SS7TCAP::SuccessOnlyReport || m_opClass == SS7TCAP::NoReport) { + m_type = SS7TCAP::TC_L_Reject; + params.setParam(paramRoot + "." + s_tcapCompType,lookup(SS7TCAP::TC_L_Reject,SS7TCAP::s_compPrimitives)); + params.setParam(paramRoot + "." + s_tcapProblemCode,String(SS7TCAPError::Error_UnexpectedReturnError)); + m_error.setError(SS7TCAPError::Error_UnexpectedReturnError); + setState(OperationPending); + } + break; + case SS7TCAP::TC_TimerReset: + default: + break; + } +} + +SS7TCAPComponent* SS7TCAPComponent::componentFromNamedList(SS7TCAP::TCAPType tcapType, SS7TCAPTransaction* tr, NamedList& params, unsigned int index) +{ + if (!tr) + return 0; + + String paramRoot; + compPrefix(paramRoot,index,true); + NamedString* str = params.getParam(paramRoot + s_tcapLocalCID); + if (TelEngine::null(str)) + str = params.getParam(paramRoot + s_tcapRemoteCID); + if (TelEngine::null(str)) + return 0; + int type = lookup(params.getValue(paramRoot + s_tcapCompType),SS7TCAP::s_compPrimitives); + // we allow building Reject components that have been built by Component layer or Invokes requested by local user + if (type != SS7TCAP::TC_Invoke && type != SS7TCAP::TC_InvokeNotLast && type != SS7TCAP::TC_L_Reject + && type != SS7TCAP::TC_U_Reject && type != SS7TCAP::TC_R_Reject) + return 0; + + SS7TCAPComponent* comp = new SS7TCAPComponent(tcapType,tr,params,index); + return comp; +} + +void SS7TCAPComponent::setState(TCAPComponentState state) +{ +#ifdef DEBUG + if (m_transact && m_transact->tcap() && s_extendedDbg) + DDebug(m_transact->tcap(),DebugAll,"SS7TCAPComponent::setState(%s), locaID=%s remoteID=%s [%p]",lookup(state,s_compStates), + m_id.c_str(),m_corrID.c_str(),this); +#endif + m_state = state; + m_opTimer.stop(); + if (!(state == Idle || state == OperationPending)) + m_opTimer.start(); +} + +void SS7TCAPComponent::fill(unsigned int index, NamedList& fillIn) +{ +#ifdef DEBUG + if (m_transact && m_transact->tcap() && s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + DDebug(m_transact->tcap(),DebugAll,"SS7TCAPComponent::fill() - component with localID=%s,remoteID=%s of transaction=%s " + "fill for index=%u [%p]",m_id.c_str(),m_corrID.c_str(),m_transact->toString().c_str(),index,this); +#endif + String paramRoot; + compPrefix(paramRoot,index,true); + + fillIn.setParam(paramRoot + s_tcapLocalCID,m_id); + fillIn.setParam(paramRoot + s_tcapRemoteCID,m_corrID); + fillIn.setParam(paramRoot + s_tcapCompType,lookup(m_type,SS7TCAP::s_compPrimitives,"Unknown")); + + if (m_error.error() != SS7TCAPError::NoError) { + if (m_type == SS7TCAP::TC_U_Error) + fillIn.setParam(paramRoot + s_tcapErrCode,String(m_error.errorCode())); + else if (m_type == SS7TCAP::TC_L_Reject || m_type == SS7TCAP::TC_U_Reject || m_type == SS7TCAP::TC_R_Reject) + fillIn.setParam(paramRoot + s_tcapProblemCode,String(m_error.errorCode())); + } + if (m_type == SS7TCAP::TC_U_Reject || m_type == SS7TCAP::TC_R_Reject || m_type == SS7TCAP::TC_L_Reject) + setState(Idle); +} + +void SS7TCAPComponent::resetTimer(NamedList& params, unsigned int index) +{ + DDebug(m_transact->tcap(),DebugInfo,"SS7TCAPComponentITU::resetTimer() [%p]",this); + String paramRoot; + compPrefix(paramRoot,index,false); + if (state() == OperationSent) + m_opTimer.start(); + params.clearParam(paramRoot,'.'); +} + +// class SS7TCAPANSI + +static u_int8_t s_tcapProtoVersion = 0x04; + +static const PrimitiveMapping s_componentsANSIMap[] = { + {SS7TCAP::TC_Invoke, SS7TCAPTransactionANSI::InvokeLast}, + {SS7TCAP::TC_ResultLast, SS7TCAPTransactionANSI::ReturnResultLast}, + {SS7TCAP::TC_U_Error, SS7TCAPTransactionANSI::ReturnError}, + {SS7TCAP::TC_U_Reject, SS7TCAPTransactionANSI::Reject}, + {SS7TCAP::TC_R_Reject, SS7TCAPTransactionANSI::Reject}, + {SS7TCAP::TC_L_Reject, SS7TCAPTransactionANSI::Reject}, + {SS7TCAP::TC_InvokeNotLast, SS7TCAPTransactionANSI::InvokeNotLast}, + {SS7TCAP::TC_ResultNotLast, SS7TCAPTransactionANSI::ReturnResultNotLast}, + {SS7TCAP::TC_L_Cancel, SS7TCAPTransactionANSI::Local}, + {SS7TCAP::TC_U_Cancel, SS7TCAPTransactionANSI::Local}, + {SS7TCAP::TC_TimerReset, SS7TCAPTransactionANSI::Local}, +}; + +static const PrimitiveMapping s_transANSIMap[] = { + {SS7TCAP::TC_Unidirectional, SS7TCAPTransactionANSI::Unidirectional}, + {SS7TCAP::TC_QueryWithPerm, SS7TCAPTransactionANSI::QueryWithPermission}, + {SS7TCAP::TC_QueryWithoutPerm, SS7TCAPTransactionANSI::QueryWithoutPermission}, + {SS7TCAP::TC_Begin, SS7TCAPTransactionANSI::QueryWithPermission}, // on receiving a ITU_T Begin, we'll map it to ANSI QueryWithPermission + {SS7TCAP::TC_ConversationWithPerm, SS7TCAPTransactionANSI::ConversationWithPermission}, + {SS7TCAP::TC_ConversationWithoutPerm, SS7TCAPTransactionANSI::ConversationWithoutPermission}, + {SS7TCAP::TC_Continue, SS7TCAPTransactionANSI::ConversationWithPermission}, + {SS7TCAP::TC_Response, SS7TCAPTransactionANSI::Response}, + {SS7TCAP::TC_End, SS7TCAPTransactionANSI::Response}, + {SS7TCAP::TC_U_Abort, SS7TCAPTransactionANSI::Abort}, + {SS7TCAP::TC_P_Abort, SS7TCAPTransactionANSI::Abort}, + {SS7TCAP::TC_Notice, SS7TCAPTransactionANSI::Unknown}, + {SS7TCAP::TC_Unknown, SS7TCAPTransactionANSI::Unknown}, +}; + +static const PrimitiveMapping* mapCompPrimitivesANSI(int primitive, int comp = -1) +{ + const PrimitiveMapping* map = s_componentsANSIMap; + for (; map->primitive != SS7TCAP::TC_Unknown; map++) { + if (primitive != -1) { + if (map->primitive == primitive ) + break; + } + else if (comp != -1) + if (map->mappedTo == comp) + break; + } + return map; +} + +static const PrimitiveMapping* mapTransPrimitivesANSI(int primitive, int trans = -1) +{ + const PrimitiveMapping* map = s_transANSIMap; + for (; map->primitive != SS7TCAP::TC_Unknown; map++) { + if (primitive != -1) { + if (map->primitive == primitive ) + break; + } + else if (trans != -1) + if (map->mappedTo == trans) + break; + } + return map; +} + +static const SS7TCAPTransactionANSI::ANSITransactionType primitiveToTransactANSI(String primitive, + SS7TCAP::TCAPUserTransActions primitiveType = SS7TCAP::TC_Unknown) +{ + SS7TCAPTransactionANSI::ANSITransactionType type = SS7TCAPTransactionANSI::Unknown; + + if (!primitive.null()) + primitiveType = (SS7TCAP::TCAPUserTransActions)primitive.toInteger(SS7TCAP::s_transPrimitives); + + const PrimitiveMapping* map = mapTransPrimitivesANSI(primitiveType); + if (map) + type = (SS7TCAPTransactionANSI::ANSITransactionType)map->mappedTo; + return type; +} + +SS7TCAPANSI::SS7TCAPANSI(const NamedList& params) + : SignallingComponent(params.safe("SS7TCAPANSI"),¶ms), + SS7TCAP(params) +{ + String tmp; + params.dump(tmp,"\r\n ",'\'',true); + DDebug(this,DebugAll,"SS7TCAPANSI::SS7TCAPANSI(%s)",tmp.c_str()); + setTCAPType(SS7TCAP::ANSITCAP); +} + +SS7TCAPANSI::~SS7TCAPANSI() +{ + DDebug(this,DebugAll,"SS7TCAPANSI::~SS7TCAPANSI() [%p] destroyed with %d transactions, refCount=%d", + this,m_transactions.count(),refcount()); +} + +SS7TCAPTransaction* SS7TCAPANSI::buildTransaction(SS7TCAP::TCAPUserTransActions type, u_int32_t transactID, NamedList& params, + bool initLocal) +{ + return new SS7TCAPTransactionANSI(this,type,transactID,params,m_trTimeout,initLocal); +} + +SS7TCAPError SS7TCAPANSI::decodeTransactionPart(NamedList& params, DataBlock& data) +{ + SS7TCAPError error(SS7TCAP::ANSITCAP); + if (data.length() < 2) // should find out which is the minimal TCAP message length + return error; + + // decode message type + u_int8_t msgType = data[0]; + data.cut(-1); + + const PrimitiveMapping* map = mapTransPrimitivesANSI(-1,msgType); + if (map) { + String type = lookup(map->primitive,SS7TCAP::s_transPrimitives,"Unknown"); + params.setParam(s_tcapRequest,type); + } + + // decode message length + unsigned int len = ASNLib::decodeLength(data); + if (len != data.length()) + return error; + // decode transaction IDs, start with Transaction Identifier + u_int8_t tag = data[0]; + if (tag != TransactionIDTag) {// 0xc7 + error.setError(SS7TCAPError::Transact_IncorrectTransactionPortion); + return error; // check it + } + data.cut(-1); + + // if we'll detect an error, it should be a BadlyStructuredTransaction error + error.setError(SS7TCAPError::Transact_BadlyStructuredTransaction); + + // decode transaction IDs + u_int64_t transactionIDs; + len = ASNLib::decodeUINT64(data,&transactionIDs,false); + if (len < 0) + return error; + + // transaction IDs shall be decoded according to message type + switch (msgType) { + case SS7TCAPTransactionANSI::Unidirectional: + if (len != 0) + return error; + break; + case SS7TCAPTransactionANSI::QueryWithPermission: + case SS7TCAPTransactionANSI::QueryWithoutPermission: + if (len != 4) + return error; + params.setParam(s_tcapRemoteTID,String((int)transactionIDs)); + break; + case SS7TCAPTransactionANSI::Response: + case SS7TCAPTransactionANSI::Abort: + if (len != 4) + return error; + params.setParam(s_tcapLocalTID,String((int)transactionIDs)); + break; + case SS7TCAPTransactionANSI::ConversationWithPermission: + case SS7TCAPTransactionANSI::ConversationWithoutPermission: + if (len != 8) + return error; + params.setParam(s_tcapRemoteTID,String((int)(transactionIDs >> 4 * 8))); + params.setParam(s_tcapLocalTID,String((int)transactionIDs)); + break; + default: + error.setError(SS7TCAPError::Transact_UnrecognizedPackageType); + return error; + }; + +#ifdef DEBUG + if (s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,this,"SS7TCAPANSI::decodeTransactionPart() finished",this,params,data); +#endif + + error.setError(SS7TCAPError::NoError); + return error; +} + +void SS7TCAPANSI::encodeTransactionPart(NamedList& params, DataBlock& data) +{ +#ifdef DEBUG + if (s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,this,"SS7TCAPANSI::encodeTransactionPart() - to be encoded",this,params,data); +#endif + + int msgType = primitiveToTransactANSI(params.getValue(s_tcapRequest)); + + const String otid = params[s_tcapLocalTID]; + const String dtid = params[s_tcapRemoteTID]; + + DataBlock db; + u_int32_t origNet = htonl(otid.toInteger()); + u_int32_t destNet = htonl(dtid.toInteger()); + + switch (msgType) { + case SS7TCAPTransactionANSI::Unidirectional: + break; + case SS7TCAPTransactionANSI::QueryWithPermission: + case SS7TCAPTransactionANSI::QueryWithoutPermission: + db.append(&origNet,sizeof(u_int32_t)); + break; + case SS7TCAPTransactionANSI::Response: + case SS7TCAPTransactionANSI::Abort: + db.append(&destNet,sizeof(u_int32_t)); + break; + case SS7TCAPTransactionANSI::ConversationWithPermission: + case SS7TCAPTransactionANSI::ConversationWithoutPermission: + db.append(&origNet,sizeof(u_int32_t)); + db.append(&destNet,sizeof(u_int32_t)); + break; + default: + break; + }; + + db.insert(ASNLib::buildLength(db)); + int tag = TransactionIDTag; + db.insert(DataBlock(&tag,1)); + + data.insert(db); + data.insert(ASNLib::buildLength(data)); + data.insert(DataBlock(&msgType,1)); +} + +/** + * SS7TCAPTransactionANSI implementation + */ +const TokenDict SS7TCAPTransactionANSI::s_ansiTransactTypes[] = { + {"Unidirectional", SS7TCAPTransactionANSI::Unidirectional}, + {"QueryWithPermission", SS7TCAPTransactionANSI::QueryWithPermission}, + {"QueryWithoutPermission", SS7TCAPTransactionANSI::QueryWithoutPermission}, + {"Response", SS7TCAPTransactionANSI::Response}, + {"ConversationWithPermission", SS7TCAPTransactionANSI::ConversationWithPermission}, + {"ConversationWithoutPermission", SS7TCAPTransactionANSI::ConversationWithoutPermission}, + {"Abort", SS7TCAPTransactionANSI::Abort}, + {0,0}, +}; + +SS7TCAPTransactionANSI::SS7TCAPTransactionANSI(SS7TCAP* tcap, SS7TCAP::TCAPUserTransActions type, + u_int32_t transactID, NamedList& params, u_int64_t timeout, bool initLocal) + : SS7TCAPTransaction(tcap,type,transactID,params,timeout,initLocal), + m_prevType(type) +{ + DDebug(tcap,DebugAll,"SS7TCAPTransactionANSI[%p] created with type='%s' and localID='%s'",this, + lookup(type,SS7TCAP::s_transPrimitives),m_localID.c_str()); +} + +SS7TCAPTransactionANSI::~SS7TCAPTransactionANSI() +{ + DDebug(tcap(),DebugAll,"Transaction with ID=%s of user=%s destroyed, TCAP refcount=%d [%p]", + m_localID.c_str(),m_userName.c_str(),tcap()->refcount(),this); +} + +SS7TCAPError SS7TCAPTransactionANSI::handleData(NamedList& params, DataBlock& data) +{ + XDebug(tcap(),DebugAll,"SS7TCAPTransactionANSI::handleData() transactionID=%s data length=%u [%p]",m_localID.c_str(), + data.length(),this); + Lock lock(this); + // decode DialogPortion + SS7TCAPError error = decodeDialogPortion(params,data); + if (error.error() != SS7TCAPError::NoError) + return error; + error = handleDialogPortion(params,false); + if (error.error() != SS7TCAPError::NoError) + return error; + + // in case of Abort message, check Cause Information + String msg = params.getValue(s_tcapMsgType); + if (msg.toInteger(s_ansiTransactTypes) == Abort) { + error = decodePAbort(this,params,data); + if (error.error() != SS7TCAPError::NoError) + return error; + } + // decodeComponents + error = decodeComponents(params,data); + if (error.error() != SS7TCAPError::NoError) + buildComponentError(error,params,data); + + error = handleComponents(params,false); + return error; +} + +SS7TCAPError SS7TCAPTransactionANSI::update(SS7TCAP::TCAPUserTransActions type, NamedList& params, bool updateByUser) +{ + DDebug(tcap(),DebugAll,"SS7TCAPTransactionANSI::update() [%p], localID=%s - update to type=%s initiated by %s",this,m_localID.c_str(), + lookup(type,SS7TCAP::s_transPrimitives,"Unknown"), (updateByUser ? "user" : "remote")); +#ifdef DEBUG + if (s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,tcap(),"SS7TCAPTransactionANSI::update() with",this,params); +#endif + Lock l(this); + SS7TCAPError error(SS7TCAP::ANSITCAP); + switch (type) { + case SS7TCAP::TC_Begin: + case SS7TCAP::TC_QueryWithPerm: + case SS7TCAP::TC_QueryWithoutPerm: + case SS7TCAP::TC_Unidirectional: + Debug(tcap(),DebugInfo,"SS7TCAPTransactionANSI::update() [%p], localID=%s - invalid update: trying to update from type=%s to type=%s", + this,m_localID.c_str(),lookup(m_type,SS7TCAP::s_transPrimitives,"Unknown"), + lookup(type,SS7TCAP::s_transPrimitives,"Unknown")); + params.setParam(s_tcapRequestError,"invalid_update"); + params.setParam("tcap.request.error.currentState",lookup(m_type,SS7TCAP::s_transPrimitives,"Unknown")); + error.setError(SS7TCAPError::Transact_IncorrectTransactionPortion); + return error; + + case SS7TCAP::TC_End: + case SS7TCAP::TC_Response: + if (m_type == SS7TCAP::TC_QueryWithoutPerm || m_type == SS7TCAP::TC_ConversationWithoutPerm) { + params.setParam(s_tcapRequestError,"invalid_update"); + params.setParam("tcap.request.error.currentState",lookup(m_type,SS7TCAP::s_transPrimitives,"Unknown")); + error.setError(SS7TCAPError::Transact_IncorrectTransactionPortion); + return error; + } + else { + if (!m_basicEnd) + // prearranged end, no need to transmit to remote end + m_transmit = NoTransmit; + else + m_transmit = PendingTransmit; + m_type = type; + } + break; + case SS7TCAP::TC_Continue: + case SS7TCAP::TC_ConversationWithPerm: + case SS7TCAP::TC_ConversationWithoutPerm: + if (m_type == SS7TCAP::TC_End || m_type == SS7TCAP::TC_Response) { + params.setParam(s_tcapRequestError,"invalid_update"); + params.setParam("tcap.request.error.currentState",lookup(m_type,SS7TCAP::s_transPrimitives,"Unknown")); + error.setError(SS7TCAPError::Transact_IncorrectTransactionPortion); + return error; + } + else { + m_remoteID = params.getValue(s_tcapRemoteTID); + if (!updateByUser) + populateSCCPAddress(m_localSCCPAddr,m_remoteSCCPAddr,params,updateByUser); + m_type = type; + m_transmit = PendingTransmit; + } + break; + case SS7TCAP::TC_Notice: + case SS7TCAP::TC_P_Abort: + if (updateByUser) { + Debug(tcap(),DebugInfo,"SS7TCAPTransactionANSI::update() [%p], localID=%s - invalid update: trying to update from type=%s to type=%s", + this,m_localID.c_str(),lookup(m_type,SS7TCAP::s_transPrimitives,"Unknown"), + lookup(type,SS7TCAP::s_transPrimitives,"Unknown")); + params.setParam(s_tcapRequestError,"invalid_update"); + params.setParam("tcap.request.error.currentState",lookup(m_type,SS7TCAP::s_transPrimitives,"Unknown")); + error.setError(SS7TCAPError::Transact_IncorrectTransactionPortion); + return error; + } + case SS7TCAP::TC_U_Abort: + if (!updateByUser && String("pAbort") == params.getValue(s_tcapAbortCause)) + m_type = SS7TCAP::TC_P_Abort; + else + m_type = type; + m_transmit = PendingTransmit; + break; + default: + break; + } + + if (updateByUser) { + setState(PackageSent); + m_basicEnd = params.getBoolValue(s_tcapBasicTerm,true); + m_endNow = params.getBoolValue(s_tcapEndNow,false); + } + else + setState(PackageReceived); + if (m_timeout.started()) { + m_timeout.stop(); + XDebug(tcap(),DebugInfo,"SS7TCAPTransactionANSI::update() [%p], localID=%s - timeout timer has been stopped",this,m_localID.c_str()); + } + return error; +} + +SS7TCAPError SS7TCAPTransactionANSI::decodeDialogPortion(NamedList& params, DataBlock& data) +{ + XDebug(tcap(),DebugAll,"SS7TCAPTransactionANSI::decodeDialogPortion() for transaction with localID=%s [%p]", + m_localID.c_str(),this); + + SS7TCAPError error(SS7TCAP::ANSITCAP); + + u_int8_t tag = data[0]; + // dialog is not present + if (tag != SS7TCAPANSI::DialogPortionTag) // 0xf9 + return error; + data.cut(-1); + + // dialog portion is present, decode dialog length + unsigned int len = ASNLib::decodeLength(data); + if (len < 0 || len > data.length()) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + + tag = data[0]; + // check for protocol version + if (data[0] == SS7TCAPANSI::ProtocolVersionTag) { //0xda + data.cut(-1); + // decode protocol version + u_int8_t proto; + len = ASNLib::decodeUINT8(data,&proto,false); + if (len != 1) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + params.setParam(s_tcapProtoVers,String(proto)); + } + + tag = data[0]; + // check for Application Context + if (tag == SS7TCAPANSI::IntApplicationContextTag || tag == SS7TCAPANSI::OIDApplicationContextTag) { // 0xdb , 0xdc + data.cut(-1); + if (tag == SS7TCAPANSI::IntApplicationContextTag) { //0xdb + u_int64_t val = 0; + len = ASNLib::decodeInteger(data,val,sizeof(int),false); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + params.setParam(s_tcapIntAppID,String((int)val)); + } + if (tag == SS7TCAPANSI::OIDApplicationContextTag) { // oxdc + ASNObjId oid; + len = ASNLib::decodeOID(data,&oid,false); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + params.setParam(s_tcapObjAppID,oid.toString()); + } + } + + // check for user information + tag = data[0]; + if (tag == SS7TCAPANSI::UserInformationTag) {// 0xfd + data.cut(-1); + len = ASNLib::decodeLength(data); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + + tag = data[0]; + if (tag != SS7TCAPANSI::ExternalTag) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + data.cut(-1); + + len = ASNLib::decodeLength(data); + if (len < 0 || len > data.length()) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + // direct Reference + tag = data[0]; + if (tag == SS7TCAPANSI::DirectReferenceTag) { // 0x06 + data.cut(-1); + ASNObjId oid; + len = ASNLib::decodeOID(data,&oid,false); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + params.setParam(s_tcapReference,oid.toString()); + } + // data Descriptor + tag = data[0]; + if (tag == SS7TCAPANSI::DataDescriptorTag) { // 0x07 + data.cut(-1); + String str; + int type; + len = ASNLib::decodeString(data,&str,&type,false); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + params.setParam(s_tcapDataDesc,str); + } + // encoding + tag = data[0]; + if (tag == SS7TCAPANSI::SingleASNTypePEncTag || tag == SS7TCAPANSI::SingleASNTypeCEncTag || + tag == SS7TCAPANSI::OctetAlignEncTag || tag == SS7TCAPANSI::ArbitraryEncTag) { + data.cut(-1); + len = ASNLib::decodeLength(data); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + DataBlock d((void*)data.data(0,len),len); + data.cut(-len); + + // put encoding context in hexified form + String dataHexified; + dataHexified.hexify(d.data(),d.length(),' '); + params.setParam(s_tcapEncodingContent,dataHexified); + // put encoding identifier + switch (tag) { + case SS7TCAPANSI::SingleASNTypePEncTag: // 0x80 + params.setParam(s_tcapEncodingType,"single-ASN1-type-primitive"); + break; + case SS7TCAPANSI::SingleASNTypeCEncTag: // 0xa0 + params.setParam(s_tcapEncodingType,"single-ASN1-type-contructor"); + break; + case SS7TCAPANSI::OctetAlignEncTag: // 0x81 + params.setParam(s_tcapEncodingType,"octet-aligned"); + break; + case SS7TCAPANSI::ArbitraryEncTag: // 0x82 + params.setParam(s_tcapEncodingType,"arbitrary"); + break; + default: + break; + } + } + } + + // check for security context + tag = data[0]; + if (tag == SS7TCAPANSI::IntSecurityContextTag || tag == SS7TCAPANSI::OIDSecurityContextTag) { + data.cut(-1); + if (tag == SS7TCAPANSI::IntSecurityContextTag) { //0x80 + int val = 0; + len = ASNLib::decodeINT32(data,&val,false); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + params.setParam(s_tcapIntSecID,String(val)); + } + if (tag == SS7TCAPANSI::OIDSecurityContextTag) { // 0x81 + ASNObjId oid; + len = ASNLib::decodeOID(data,&oid,false); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + params.setParam(s_tcapObjSecID,oid.toString()); + } + } + + // check for Confidentiality information + tag = data[0]; + if (tag == SS7TCAPANSI::ConfidentialityTag) { // 0xa2 + data.cut(-1); + len = ASNLib::decodeLength(data); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + tag = data[0]; + if (tag == SS7TCAPANSI::IntSecurityContextTag || tag == SS7TCAPANSI::OIDSecurityContextTag) { + data.cut(-1); + if (tag == SS7TCAPANSI::IntSecurityContextTag) { //0x80 + int val = 0; + len = ASNLib::decodeINT32(data,&val,false); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + params.setParam(s_tcapIntConfidID,String(val)); + } + if (tag == SS7TCAPANSI::OIDSecurityContextTag) { // 0x81 + ASNObjId oid; + len = ASNLib::decodeOID(data,&oid,false); + if (len < 0) { + error.setError(SS7TCAPError::Dialog_BadlyStructuredDialoguePortion); + return error; + } + params.setParam(s_tcapObjConfidID,oid.toString()); + } + } + } +#ifdef DEBUG + if (s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,tcap(),"SS7TCAPTransactionANSI::decodeDialogPortion() - decoded dialog portion",this,params,data); +#endif + return error; +} + +void SS7TCAPTransactionANSI::encodeDialogPortion(NamedList& params, DataBlock& data) +{ + XDebug(tcap(),DebugAll,"SS7TCAPTransactionANSI::encodeDialogPortion() for transaction with localID=%s [%p]",m_localID.c_str(),this); + + DataBlock dialogData; + int tag; + + // encode confidentiality information + NamedString* val = params.getParam(s_tcapIntConfidID); + NamedString* oidStr = params.getParam(s_tcapObjConfidID); + ASNObjId oid; + + if (!TelEngine::null(val) && !TelEngine::null(oidStr)) { + // parameter error, encoding of this portion skipped + Debug(tcap(),DebugInfo,"SS7TCAPTransactionANSI::encodeDialogPortion() - skipping encoding of Confidentiality Information," + " both IntegerConfidentialityAlgorithmID=%s and ObjectIDConfidentialityID=%s specified, can't pick one", + val->c_str(),oidStr->c_str()); + } + else { + if (!TelEngine::null(val)) { + DataBlock db = ASNLib::encodeInteger(val->toInteger(),false); + db.insert(ASNLib::buildLength(db)); + tag = SS7TCAPANSI::IntSecurityContextTag; + db.insert(DataBlock(&tag,1)); + + dialogData.insert(db); + } + else if (!TelEngine::null(oidStr)) { + oid = *oidStr; + DataBlock db = ASNLib::encodeOID(oid,false); + db.insert(ASNLib::buildLength(db)); + tag = SS7TCAPANSI::OIDSecurityContextTag; + db.insert(DataBlock(&tag,1)); + + dialogData.insert(db); + } + if (dialogData.length()) { + dialogData.insert(ASNLib::buildLength(dialogData)); + tag = SS7TCAPANSI::ConfidentialityTag; + dialogData.insert(DataBlock(&tag,1)); + } + } + // encode security information + val = params.getParam(s_tcapIntSecID); + oidStr = params.getParam(s_tcapObjSecID); + + if (!TelEngine::null(val) && !TelEngine::null(oidStr)) { + // parameter error, encoding of this portion skipped + Debug(tcap(),DebugInfo,"SS7TCAPTransactionANSI::encodeDialogPortion() - skipping encoding of Security Context Information," + " both IntegerSecurityContext=%s and ObjectIDSecurityContext=%s specified, can't pick one", + val->c_str(),oid.toString().c_str()); + } + else if (!TelEngine::null(val)) { + DataBlock db = ASNLib::encodeInteger(val->toInteger(),false); + db.insert(ASNLib::buildLength(db)); + tag = SS7TCAPANSI::IntSecurityContextTag; + db.insert(DataBlock(&tag,1)); + + dialogData.insert(db); + } + else if (!TelEngine::null(oidStr)) { + oid = *oidStr; + DataBlock db = ASNLib::encodeOID(oid,false); + db.insert(ASNLib::buildLength(db)); + tag = SS7TCAPANSI::OIDSecurityContextTag; + db.insert(DataBlock(&tag,1)); + + dialogData.insert(db); + } + + // encode user information + DataBlock userInfo; + val = params.getParam(s_tcapEncodingType); + if (!TelEngine::null(val)) { + if (*val == "single-ASN1-type-primitive") + tag = SS7TCAPANSI::SingleASNTypePEncTag; + else if (*val == "single-ASN1-type-contructor") + tag = SS7TCAPANSI::SingleASNTypeCEncTag; + else if (*val == "octet-aligned") + tag = SS7TCAPANSI::OctetAlignEncTag; + else if (*val == "arbitrary") + tag = SS7TCAPANSI::ArbitraryEncTag; + + val = params.getParam(s_tcapEncodingContent); + if (val) { + DataBlock db; + db.unHexify(val->c_str(),val->length(),' '); + db.insert(ASNLib::buildLength(db)); + db.insert(DataBlock(&tag,1)); + + userInfo.insert(db); + } + } + val = params.getParam(s_tcapDataDesc); + if (!TelEngine::null(val)) { + DataBlock db = ASNLib::encodeString(*val,ASNLib::PRINTABLE_STR,false); + db.insert(ASNLib::buildLength(db)); + tag = SS7TCAPANSI::DataDescriptorTag; + db.insert(DataBlock(&tag,1)); + + userInfo.insert(db); + } + val = params.getParam(s_tcapReference); + if (!TelEngine::null(val)) { + oid = *val; + DataBlock db = ASNLib::encodeOID(oid,false); + db.insert(ASNLib::buildLength(db)); + tag = SS7TCAPANSI::DirectReferenceTag; + db.insert(DataBlock(&tag,1)); + + userInfo.insert(db); + } + + if (userInfo.length()) { + userInfo.insert(ASNLib::buildLength(userInfo)); + tag = SS7TCAPANSI::ExternalTag; + userInfo.insert(DataBlock(&tag,1)); + userInfo.insert(ASNLib::buildLength(userInfo)); + tag = SS7TCAPANSI::UserInformationTag; + userInfo.insert(DataBlock(&tag,1)); + + dialogData.insert(userInfo); + } + + // Aplication context + val = params.getParam(s_tcapIntAppID); + oidStr = params.getParam(s_tcapObjAppID); + if (!TelEngine::null(val) && !TelEngine::null(oidStr)) { + // parameter error, encoding of this portion skipped + Debug(tcap(),DebugInfo,"SS7TCAPTransactionANSI::encodeDialogPortion() - skipping encoding of Application Context Information," + " both IntegerApplicationID=%s and ObjectApplicationID=%s specified, can't pick one",val->c_str(),oid.toString().c_str()); + } + else if (!TelEngine::null(val)) { + DataBlock db = ASNLib::encodeInteger(val->toInteger(),false); + db.insert(ASNLib::buildLength(db)); + tag = SS7TCAPANSI::IntApplicationContextTag; + db.insert(DataBlock(&tag,1)); + + dialogData.insert(db); + } + else if (!TelEngine::null(oidStr)) { + oid = *oidStr; + DataBlock db = ASNLib::encodeOID(oid,false); + db.insert(ASNLib::buildLength(db)); + tag = SS7TCAPANSI::OIDApplicationContextTag; + db.insert(DataBlock(&tag,1)); + + dialogData.insert(db); + } + + val = params.getParam(s_tcapProtoVers); + if (!TelEngine::null(val)) { + u_int8_t proto = val->toInteger(); + DataBlock db = ASNLib::encodeInteger(proto,false); + db.insert(ASNLib::buildLength(db)); + tag = SS7TCAPANSI::ProtocolVersionTag; + db.insert(DataBlock(&tag,1)); + dialogData.insert(db); + } + + if (dialogData.length()) { + dialogData.insert(ASNLib::buildLength(dialogData)); + tag = SS7TCAPANSI::DialogPortionTag; + dialogData.insert(DataBlock(&tag,1)); + } + + data.insert(dialogData); + params.clearParam(s_tcapDialogPrefix,'.'); +#ifdef DEBUG + if (s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,tcap(),"SS7TCAPTransactionANSI::encodeDialogPortion() - encoded dialog portion",this,params,data); +#endif +} + +SS7TCAPError SS7TCAPTransactionANSI::decodePAbort(SS7TCAPTransaction* tr, NamedList& params, DataBlock& data) +{ + u_int8_t tag = data[0]; + SS7TCAPError error(SS7TCAP::ANSITCAP); + if (tag == SS7TCAPANSI::PCauseTag || tag == SS7TCAPANSI::UserAbortPTag || tag == SS7TCAPANSI::UserAbortCTag) { + SS7TCAPError error(SS7TCAP::ANSITCAP); + data.cut(-1); + if (tag == SS7TCAPANSI::PCauseTag) { + u_int8_t pCode = 0; + int len = ASNLib::decodeUINT8(data,&pCode,false); + if (len != 1) { + error.setError(SS7TCAPError::Transact_BadlyStructuredTransaction); + return error; + } + params.setParam(s_tcapAbortCause,"pAbort"); + params.setParam(s_tcapAbortInfo,String(pCode)); + } + else { + int len = ASNLib::decodeLength(data); + if (len < 0) { + error.setError(SS7TCAPError::Transact_BadlyStructuredTransaction); + return error; + } + String str; + str.hexify(data.data(0,len),len,' '); + data.cut(-len); + params.setParam(s_tcapAbortCause,(tag == SS7TCAPANSI::UserAbortPTag ? "userAbortP" : "userAbortC")); + params.setParam(s_tcapAbortInfo,str); + if (tr) + tr->setTransactionType(SS7TCAP::TC_U_Abort); + } +#ifdef DEBUG + if (tr && tr->tcap() && s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,tr->tcap(),"SS7TCAPTransactionANSI::decodePAbort() - decoded Abort info",tr,params,data); +#endif + } + return error; +} + +void SS7TCAPTransactionANSI::encodePAbort(SS7TCAPTransaction* tr, NamedList& params, DataBlock& data) +{ + NamedString* pAbortCause = params.getParam(s_tcapAbortCause); + DataBlock db; + if (!TelEngine::null(pAbortCause)) { + int tag = 0; + if (*pAbortCause == "pAbort") { + tag = SS7TCAPANSI::PCauseTag; + u_int16_t pCode = params.getIntValue(s_tcapAbortInfo); + if (pCode) { + db.append(ASNLib::encodeInteger(pCode,false)); + db.insert(ASNLib::buildLength(db)); + } + } + else if (*pAbortCause == "userAbortP" || *pAbortCause == "userAbortC") { + NamedString* info = params.getParam(s_tcapAbortInfo); + if (!TelEngine::null(info)) + db.unHexify(info->c_str(),info->length(),' '); + db.insert(ASNLib::buildLength(db)); + if (*pAbortCause == "userAbortP") + tag = SS7TCAPANSI::UserAbortPTag; + else + tag = SS7TCAPANSI::UserAbortCTag; + } + if (db.length()) + db.insert(DataBlock(&tag,1)); + } + if (db.length()) { + data.insert(db); + params.clearParam(s_tcapAbortCause); + params.clearParam(s_tcapAbortInfo); + } +#ifdef DEBUG + if (tr && tr->tcap() && s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,tr->tcap(),"SS7TCAPTransactionANSI::encodePAbort() - encoded Abort info",tr,params,data); +#endif +} + +void SS7TCAPTransactionANSI::requestContent(NamedList& params, DataBlock& data) +{ +#ifdef DEBUG + if (s_extendedDbg) + DDebug(tcap(),DebugAll,"SS7TCAPTransactionANSI::requestContent() for transaction with id=%s [%p]",m_localID.c_str(),this); +#endif + if (m_type == SS7TCAP::TC_P_Abort || m_type == SS7TCAP::TC_U_Abort) + encodePAbort(this,params,data); + else + requestComponents(params,data); + encodeDialogPortion(params,data); + transactionData(params); +} + +void SS7TCAPTransactionANSI::updateToEnd() +{ + if (transactionType() == SS7TCAP::TC_QueryWithoutPerm || transactionType() == SS7TCAP::TC_ConversationWithoutPerm) + setTransactionType(SS7TCAP::TC_U_Abort); + else + setTransactionType(SS7TCAP::TC_Response); +} + +SS7TCAPError SS7TCAPTransactionANSI::decodeComponents(NamedList& params, DataBlock& data) +{ + XDebug(tcap(),DebugAll,"SS7TCAPTransactionANSI::decodeComponents() [%p] - data length=%u",this,data.length()); + + SS7TCAPError error(SS7TCAP::ANSITCAP); + if (!data.length()) { + params.setParam(s_tcapCompCount,"0"); + return error; + } + + u_int8_t tag = data[0]; + if (tag != SS7TCAPANSI::ComponentPortionTag) { // 0xe8 + error.setError(SS7TCAPError::General_IncorrectComponentPortion); + return error; + } + data.cut(-1); + + // decode length of component portion + unsigned int len = ASNLib::decodeLength(data); + if (len < 0 || len != data.length()) { // the length of the remaining data should be the same as the decoded length() + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + return error; + } + + unsigned int compCount = 0; + while (data.length()) { + compCount++; + // decode component type + u_int8_t compType = data[0]; + data.cut(-1); + + // verify component length + len = ASNLib::decodeLength(data); + if (len < 0 || len > data.length()) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + break; + } + + // decode component IDs, start with ComponentsIDs identifier + tag = data[0]; + if (tag != SS7TCAPANSI::ComponentsIDsTag) {// 0xcf + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + break; + } + data.cut(-1); + + // obtain component ID(s) + u_int16_t compIDs; + len = ASNLib::decodeUINT16(data,&compIDs,false); + if (len < 0) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + break; + } + + String compParam; + compPrefix(compParam,compCount,false); + // comp IDs shall be decoded according to component type + switch (compType) { + case InvokeLast: + case InvokeNotLast: + if (len == 1) + params.setParam(compParam + "." + s_tcapRemoteCID,String(compIDs)); + else if (len == 2) { + params.setParam(compParam + "." + s_tcapRemoteCID,String(compIDs >> 8)); + params.setParam(compParam + "." + s_tcapLocalCID,String((u_int8_t)compIDs)); + } + else { + params.setParam(compParam + "." + s_tcapRemoteCID,""); + params.setParam(compParam + "." + s_tcapLocalCID,""); + } + break; + case ReturnResultLast: + case ReturnError: + case Reject: + case ReturnResultNotLast: + if (len != 1) + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + else + params.setParam(compParam + "." + s_tcapLocalCID,String(compIDs)); + break; + default: + error.setError(SS7TCAPError::General_UnrecognizedComponentType); + break; + } + const PrimitiveMapping* map = mapCompPrimitivesANSI(-1,compType); + if (!map) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + break; + } + params.setParam(compParam + "." + s_tcapCompType,lookup(map->primitive,SS7TCAP::s_compPrimitives,"Unknown")); + + if (error.error() != SS7TCAPError::NoError) + break; + + // decode Operation Code + tag = data[0]; + if (tag == SS7TCAPANSI::OperationNationalTag || tag == SS7TCAPANSI::OperationPrivateTag) { + data.cut(-1); + + int opCode = 0; + len = ASNLib::decodeINT32(data,&opCode,false); + if (tag == SS7TCAPANSI::OperationNationalTag) { + if (len != 2) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + break; + } + params.setParam(compParam + "." + s_tcapOpCodeType,"national"); + } + if (tag == SS7TCAPANSI::OperationPrivateTag) + params.setParam(compParam + "." + s_tcapOpCodeType,"private"); + params.setParam(compParam + "." + s_tcapOpCode,String(opCode)); + } + + // decode Error Code + tag = data[0]; + if (tag == SS7TCAPANSI::ErrorNationalTag || tag == SS7TCAPANSI::ErrorPrivateTag) { // 0xd3, 0xd4 + data.cut(-1); + + int errCode = 0; + len = ASNLib::decodeINT32(data,&errCode,false); + if (len < 0 || (tag == SS7TCAPANSI::ErrorNationalTag && len != 1)) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + break; + } + if (tag == SS7TCAPANSI::ErrorNationalTag) + params.setParam(compParam + "." + s_tcapErrCodeType,"national"); + else + params.setParam(compParam + "." + s_tcapErrCodeType,"private"); + params.setParam(compParam + "." + s_tcapErrCode,String(errCode)); + } + + // decode Problem + tag = data[0]; + if (tag == SS7TCAPANSI::ProblemCodeTag) { // 0xd5 + data.cut(-1); + u_int16_t problemCode = 0; + len = ASNLib::decodeUINT16(data,&problemCode,false); + if (len != 2) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + break; + } + params.setParam(compParam + "." + s_tcapProblemCode,String(problemCode)); + } + // decode Parameters (Set or Sequence) as payload + tag = data[0]; + String dataHexified = ""; + if (tag == SS7TCAPANSI::ParameterSetTag || tag == SS7TCAPANSI::ParameterSeqTag) { // 0xf2 0x30 + data.cut(-1); + len = ASNLib::decodeLength(data); + if (len < 0 || len > data.length()) { + error.setError(SS7TCAPError::General_BadlyStructuredCompPortion); + break; + } + DataBlock d((void*)data.data(0,len),len); + data.cut(-len); + d.insert(ASNLib::buildLength(d)); + d.insert(DataBlock(&tag,1)); + dataHexified.hexify(d.data(),d.length(),' '); + } + params.setParam(compParam,dataHexified); + } + + params.setParam(s_tcapCompCount,String(compCount)); +#ifdef DEBUG + if (tcap() && s_printMsgs && s_extendedDbg && debugAt(DebugAll)) + dumpData(DebugAll,tcap(),"Finished decoding message",this,params,data); +#endif + return error; +} + +void SS7TCAPTransactionANSI::encodeComponents(NamedList& params, DataBlock& data) +{ + XDebug(tcap(),DebugAll,"SS7TCAPTransactionANS::encodeComponents() for transaction with localID=%s [%p]",m_localID.c_str(),this); + + int componentCount = params.getIntValue(s_tcapCompCount,0); + DataBlock compData; + if (componentCount) { + int index = 0; + + while (index < componentCount) { + index++; + DataBlock codedComp; + // encode parameters + String compParam; + compPrefix(compParam,index,false); + // Component Type + NamedString* value = params.getParam(compParam + "." + s_tcapCompType); + if (TelEngine::null(value)) + continue; + int compPrimitive = lookup(*value,SS7TCAP::s_compPrimitives); + const PrimitiveMapping* map = mapCompPrimitivesANSI(compPrimitive,-1); + if (!map) + continue; + int compType = map->mappedTo; + String payloadHex = params.getValue(compParam,""); + if (!payloadHex.null()) { + DataBlock payload; + payload.unHexify(payloadHex.c_str(),payloadHex.length(),' '); + //payload.insert(ASNLib::buildLength(payload)); + codedComp.insert(payload); + } + + // encode Problem only if Reject + if (compType == Reject) { + value = params.getParam(compParam + "." + s_tcapProblemCode); + if (!TelEngine::null(value)) { + u_int16_t code = value->toInteger(); + DataBlock db = ASNLib::encodeInteger(code,false); + // should check that encoded length is 2 + if (db.length() < 2) { + code = 0; + db.insert(DataBlock(&code,1)); + } + db.insert(ASNLib::buildLength(db)); + int tag = SS7TCAPANSI::ProblemCodeTag; + db.insert(DataBlock(&tag,1)); + codedComp.insert(db); + } + } + + // encode Error Code only if ReturnError + if (compType == ReturnError) { + value = params.getParam(compParam + "." + s_tcapErrCodeType); + if (!TelEngine::null(value)) { + int errCode = params.getIntValue(compParam + "." + s_tcapErrCode,0); + DataBlock db = ASNLib::encodeInteger(errCode,false); + db.insert(ASNLib::buildLength(db)); + + int tag = 0; + if (*value == "national") + tag = SS7TCAPANSI::ErrorNationalTag; + else if (*value == "private") + tag = SS7TCAPANSI::ErrorPrivateTag; + db.insert(DataBlock(&tag,1)); + codedComp.insert(db); + } + } + + // encode Operation Code only if Invoke + if (compType == InvokeLast || + compType == InvokeNotLast) { + value = params.getParam(compParam + "." + s_tcapOpCodeType); + if (!TelEngine::null(value)) { + int opCode = params.getIntValue(compParam + "." + s_tcapOpCode,0); + DataBlock db = ASNLib::encodeInteger(opCode,false); + int tag = 0; + if (*value == "national") { + tag = SS7TCAPANSI::OperationNationalTag; + if (db.length() < 2) { + opCode = 0; + db.insert(DataBlock(&opCode,1)); + } + } + else if (*value == "private") + tag = SS7TCAPANSI::OperationPrivateTag; + db.insert(ASNLib::buildLength(db)); + db.insert(DataBlock(&tag,1)); + codedComp.insert(db); + } + } + NamedString* invID = params.getParam(compParam + "." + s_tcapLocalCID); + NamedString* corrID = params.getParam(compParam + "." + s_tcapRemoteCID); + DataBlock db; + u_int8_t val = 0; + switch (compType) { + case InvokeLast: + case InvokeNotLast: + if (!TelEngine::null(invID)) { + val = invID->toInteger(); + db.append(&val,sizeof(u_int8_t)); + if (!TelEngine::null(corrID)) { + val = corrID->toInteger(); + db.append(&val,sizeof(u_int8_t)); + } + } + else { + if (!TelEngine::null(corrID)) { + val = corrID->toInteger(); + db.append(&val,sizeof(u_int8_t)); + } + } + break; + case ReturnResultLast: + case ReturnError: + case Reject: + case ReturnResultNotLast: + val = corrID->toInteger(); + db.append(&val,sizeof(u_int8_t)); + break; + default: + break; + } + + db.insert(ASNLib::buildLength(db)); + int tag = SS7TCAPANSI::ComponentsIDsTag; + db.insert(DataBlock(&tag,1)); + codedComp.insert(db); + codedComp.insert(ASNLib::buildLength(codedComp)); + codedComp.insert(DataBlock(&compType,1)); + + params.clearParam(compParam,'.'); // clear all params for this component + compData.insert(codedComp); + } + } + + compData.insert(ASNLib::buildLength(compData)); + int tag = SS7TCAPANSI::ComponentPortionTag; + compData.insert(DataBlock(&tag,1)); + + data.insert(compData); + params.clearParam(s_tcapCompPrefix,'.'); +} + +SS7TCAPError SS7TCAPTransactionANSI::handleDialogPortion(NamedList& params, bool byUser) +{ + XDebug(tcap(),DebugAll,"SS7TCAPTransactionANSI::handleDialogPortion() [%p]",this); + + SS7TCAPError err(SS7TCAP::ANSITCAP); + + NamedList dialog(""); + switch (m_type) { + case SS7TCAP::TC_Begin: + case SS7TCAP::TC_QueryWithPerm: + case SS7TCAP::TC_QueryWithoutPerm: + case SS7TCAP::TC_Unidirectional: + if (!byUser) { + int protoVersion = params.getIntValue(s_tcapProtoVers); + if (protoVersion) { // there is a Dialog portion + if ((protoVersion & s_tcapProtoVersion ) != s_tcapProtoVersion) + params.setParam(s_tcapProtoVers,String(s_tcapProtoVersion)); + } + } + else { + dialog.copyParams(params,s_tcapDialogPrefix,'.'); + if (dialog.count()) + params.setParam(s_tcapProtoVers,String(s_tcapProtoVersion)); + } + return err; + case SS7TCAP::TC_End: + case SS7TCAP::TC_Response: + dialog.copyParams(params,s_tcapDialogPrefix,'.'); + if (dialog.count() && m_prevType != SS7TCAP::TC_Begin && m_prevType != SS7TCAP::TC_QueryWithPerm) { + err.setError(SS7TCAPError::Dialog_InconsistentDialoguePortion); + return err; + } + break; + case SS7TCAP::TC_Continue: + case SS7TCAP::TC_ConversationWithPerm: + case SS7TCAP::TC_ConversationWithoutPerm: + dialog.copyParams(params,s_tcapDialogPrefix,'.'); + if (dialog.count() && m_prevType != SS7TCAP::TC_Begin && m_prevType != SS7TCAP::TC_QueryWithPerm + && m_prevType != SS7TCAP::TC_QueryWithoutPerm) { + err.setError(SS7TCAPError::Dialog_InconsistentDialoguePortion); + return err; + } + break; + case SS7TCAP::TC_Notice: + case SS7TCAP::TC_P_Abort: + break; + case SS7TCAP::TC_U_Abort: + break; + default: + break; + } + + return err; +} + +void SS7TCAPTransactionANSI::updateState(bool byUser) +{ + switch (m_type) { + case SS7TCAP::TC_Begin: + case SS7TCAP::TC_QueryWithPerm: + case SS7TCAP::TC_QueryWithoutPerm: + case SS7TCAP::TC_Continue: + case SS7TCAP::TC_ConversationWithPerm: + case SS7TCAP::TC_ConversationWithoutPerm: + (byUser ? setState(SS7TCAPTransaction::PackageSent) : setState(SS7TCAPTransaction::PackageReceived)); + break; + case SS7TCAP::TC_End: + case SS7TCAP::TC_U_Abort: + case SS7TCAP::TC_P_Abort: + case SS7TCAP::TC_Response: + case SS7TCAP::TC_Unidirectional: + setState(Idle); + break; + case SS7TCAP::TC_Notice: + case SS7TCAP::TC_Unknown: + default: + break; + } } /* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/ysig/yatesig.h b/libs/ysig/yatesig.h index cb61d05b..6f181719 100644 --- a/libs/ysig/yatesig.h +++ b/libs/ysig/yatesig.h @@ -122,7 +122,13 @@ class SS7AnsiSccpManagement; // SS7 SCCP management implementation f class SS7ItuSccpManagement; // SS7 SCCP management implementation for ITU class SS7SUA; // SIGTRAN SCCP User Adaptation Layer class SS7ASP; // SS7 ASP implementation +class SS7TCAPMessage; // SS7 TCAP message wrapper +class SS7TCAPError; // SS7 TCAP errors class SS7TCAP; // SS7 TCAP implementation +class SS7TCAPTransaction; // SS7 TCAP transaction base class +class SS7TCAPComponent; // SS7 TCAP component +class SS7TCAPANSI; // SS7 ANSI TCAP implementation +class SS7TCAPTransactionANSI; // SS7 TCAP ANSI Transaction // ISDN class ISDNLayer2; // Abstract ISDN layer 2 (Q.921) message transport class ISDNLayer3; // Abstract ISDN layer 3 (Q.931) message transport @@ -5316,9 +5322,15 @@ private: * An interface to a SS7 Transactional Capabilities Application Part user * @short Abstract SS7 TCAP user interface */ -class YSIG_API TCAPUser +class YSIG_API TCAPUser : public GenObject, public DebugEnabler { + friend class SS7TCAP; public: + TCAPUser(const char* name) + : m_name(name), m_tcap(0) + { + debugName(m_name); + } /** * Destructor, detaches from the TCAP implementation */ @@ -5329,16 +5341,45 @@ public: * @param tcap Pointer to the TCAP to use */ virtual void attach(SS7TCAP* tcap); - + /** + * Receive a TCAP message from TCAP layer + * @param params The message in NamedList form + * @return True or false if the message was processed by this user + */ + virtual bool tcapIndication(NamedList& params); /** * Retrieve the TCAP to which this user is attached * @return Pointer to a SS7 TCAP interface or NULL */ inline SS7TCAP* tcap() const { return m_tcap; } + /** + * Received a management notification from SCCP layer + * @param type SCCP management notification type + * @param params Management notification params + * @return True or false if the notification was handled bu this user + */ + virtual bool managementNotify(SCCP::Type type, NamedList& params); + /** + * Get TCAP user management state + * @return The state of the user + */ + virtual int managementState(); + + virtual const String& toString() const + { return m_name; } + +protected: + inline void setTCAP(SS7TCAP* tcap) + { + Lock l(m_tcapMtx); + m_tcap = tcap; + } private: + String m_name; SS7TCAP* m_tcap; + Mutex m_tcapMtx; }; @@ -10275,20 +10316,1019 @@ protected: ObjList m_sccps; }; +/** + * A TCAP message wrapper, encapsulates the data received from SCCP + * @short TCAP message wrapper + */ +class YSIG_API SS7TCAPMessage : public GenObject +{ +public: + /** + * Constructor + * @param params NamedList reference containing information from the SCCP level + * @param data DataBlock representing the TCAP payload + * @param notice Flag if this is a notification, true if it is, false if it's a message + */ + inline SS7TCAPMessage(NamedList& params, DataBlock& data, bool notice = false) + : m_msgParams(params), m_msgData(data), m_notice(notice) + {} + /** + * Get the SCCP parameters + * @return NamedList reference containing information from the SCCP level + */ + inline NamedList& msgParams() + { return m_msgParams; } + /** + * Get the TCAP message data + * @return DataBlock representing the encoded TCAP message + */ + inline DataBlock& msgData() + { return m_msgData; } + /** + * Is this message a notice or a normal message? + * @return True if message is a notice, false otherwise + */ + inline bool& isNotice() + { return m_notice; } +private: + NamedList m_msgParams; + DataBlock m_msgData; + bool m_notice; +}; + /** * Implementation of SS7 Transactional Capabilities Application Part * @short SS7 TCAP implementation */ -class YSIG_API SS7TCAP : public ASPUser, virtual public SignallingComponent +class YSIG_API SS7TCAP : public SCCPUser { + YCLASS(SS7TCAP,SCCPUser) +public: + /** + * TCAP implementation variant + */ + enum TCAPType { + UnknownTCAP, + ITUTCAP, + ANSITCAP, + }; + /** + * Component handling primitives between TCAP and TCAP user (TC-user) + */ + enum TCAPUserCompActions { + TC_Invoke = 1, // ITU-T Invoke primitive, ANSI InvokeLast - Request/Indication + TC_ResultLast = 2, // ITU-T & ANSI ResultLast primitive - Request/Indication + TC_U_Error = 3, // ITU-T & ANSI ReturnError primitive - Request/Indication + TC_U_Reject = 4, // ITU-T & ANSI Reject primitive - Request/Indication, TC-user rejected the component + TC_R_Reject = 5, // ITU-T & ANSI Reject primitive - Indication, Remote TC-user rejected the component + TC_L_Reject = 6, // ITU-T & ANSI Reject primitive - Indication, local Component Sublayer rejected the component + TC_InvokeNotLast = 7, // ANSI InvokeNotLast primitive - Request/Indication + TC_ResultNotLast = 8, // ITU-T & ANSI ResultNotLast primitive - Request/Indication + TC_L_Cancel = 9, // Local Cancel primitive - Indication, inform TC-user that an operation has timed out + TC_U_Cancel = 10, // User Cancel primitive - Request, TC-user request cancellation of an operation + TC_TimerReset = 11, // Timer Reset - Indication, allow TC-user to refresh an operation timer + }; + /** + * TCAP message primitives + */ + enum TCAPUserTransActions { + TC_Unknown = 0, + TC_Unidirectional = 1, // ITU-T & ANSI - Request/Indication, request Unidirectional message + TC_Begin, // ITU-T - Request/Indication, begin a dialogue + TC_QueryWithPerm, // ANSI - Request/Indication, begin a dialogue with permission to end it + TC_QueryWithoutPerm, // ANSI - Request/Indication, begin a dialogue without permission to end it + TC_Continue, // ITU-T - Request/Indication, continue a dialogue + TC_ConversationWithPerm, // ANSI - Request/Indication, continue a dialogue with permission + TC_ConversationWithoutPerm, // ANSI - Request/Indication, continue a dialogue without permission + TC_End, // ITU-T -Request/Indication, end a dialogue + TC_Response, // ANSI - Request/Indication, end a dialogue + TC_U_Abort, // ITU-T & ANSI - Request/Indication, abort a dialogue per user's request + TC_P_Abort, // ITU-T & ANSI - Indication, notify the abort of a dialogue because of a protocol error + TC_Notice, // ITU-T & ANSI - Indication, notify the TC-user that the network was unable to provide the requested service + }; + /** + * Component Operation Classes + */ + enum TCAPComponentOperationClass { + SuccessOrFailureReport = 1, + FailureOnlyReport = 2, + SuccessOnlyReport = 3, + NoReport = 4, + }; + /** + * Type of message counters + */ + enum TCAPCounter { + IncomingMsgs, + OutgoingMsgs, + DiscardedMsgs, + NormalMsgs, + AbnormalMsgs, + }; + /** + * Constructor + * @param params Parameters for building this TCAP + */ + SS7TCAP(const NamedList& params); + /** + * Destructor + */ + virtual ~SS7TCAP(); + /** + * Configure and initialize the component and any subcomponents it may have + * @param config Optional configuration parameters override + * @return True if the component was initialized properly + */ + virtual bool initialize(const NamedList* config); + /** + * Send a message to SCCP for transport, inherited from SCCPUser + * @param data User data + * @param params SCCP parameters + */ + virtual bool sendData(DataBlock& data, NamedList& params); + /** + * Notification from SCCP that a message has arrived, inherited from SCCPUser + * @param data Received user data + * @param params SCCP parameters + * @return True if this user has processed the message, false otherwise + */ + virtual HandledMSU receivedData(DataBlock& data, NamedList& params); + /** + * Notification from SCCP that a message failed to arrive to its destination, inherited from SCCPUser + * @param data User data sent. + * @param params SCCP parameters + * Note! The data may not contain the full message block previously sent (in case of SCCP segmentation), + * but it must always must contain the first segment + */ + virtual HandledMSU notifyData(DataBlock& data, NamedList& params); + /** + * Notification from SCCP layer about management status + * @param type Type of management notification + * @param params Notification params + */ + bool managementNotify(SCCP::Type type, NamedList& params); /** * Attach a SS7 TCAP user * @param user Pointer to the TCAP user to attach */ void attach(TCAPUser* user); - + /** + * Detach a SS7 TCAP user + * @param user TCAP user to detach + */ + void detach(TCAPUser* user); + /** + * A TCAP user made a request + * @param requestParams NamedList containing all the necessary data for the TCAP request + * @return A SS7TCAPError reporting the status of the request + */ + virtual SS7TCAPError userRequest(NamedList& requestParams); + /** + * Process received SCCP data + * @param sccpData A TCAP message received from SCCP to process + * @return A code specifying if this message was handled + */ + virtual HandledMSU processSCCPData(SS7TCAPMessage* sccpData); + /** + * Report which TCAP implementation is in use + */ + inline TCAPType tcapType() + { return m_tcapType; } + /** + * Set TCAP version + * @param type TCAP version + */ + inline void setTCAPType(TCAPType type) + { m_tcapType = type; } + /** + * Enqueue data received from SCCP as a TCAP message, kept in a processing queue + * @param msg A SS7TCAPMessage pointer containing all data received from SSCP + */ + virtual void enqueue(SS7TCAPMessage* msg); + /** + * Dequeue a TCAP message when ready to process it + * @return A SS7TCAPMessage pointer dequeued from the queue + */ + virtual SS7TCAPMessage* dequeue(); + /** + * Get a new transaction ID + */ + virtual u_int32_t allocTransactionID(); + /** + * Dictionary for TCAP versions + */ + static const TokenDict s_tcapVersion[]; + /** + * Dictionary for component primitives + */ + static const TokenDict s_compPrimitives[]; + /** + * Dictionary for transaction primitives + */ + static const TokenDict s_transPrimitives[]; + /** + * Dictionary for component opearation classes + */ + static const TokenDict s_compOperClasses[]; + /** + * Build a transaction + * @param type Type with which to build the transactions + * @param transactID ID for the transaction + * @param params Parameters for building the transaction + * @param initLocal True if built by user, false if by remote end + * @return A transaction + */ + virtual SS7TCAPTransaction* buildTransaction(SS7TCAP::TCAPUserTransActions type, u_int32_t transactID, NamedList& params, + bool initLocal = true) = 0; + /** + * Find the transaction with the given id + * @param tid Searched local id + * @return A pointer to the transaction or null if not found + */ + SS7TCAPTransaction* getTransaction(const String& tid); + /** + * Remove transaction + * @param tr The transaction to remove + */ + void removeTransaction(SS7TCAPTransaction* tr); + /** + * Method called periodically to do processing and timeout checks + * @param when Time to use as computing base for events and timeouts + */ + virtual void timerTick(const Time& when); + /** + * Send to TCAP users a decode message + * @param params Message in NamedList form + * @return True if the message was handled by a user, false otherwise + */ + virtual bool sendToUser(NamedList& params); + /** + * Build SCCP data + * @param params NamedList containing the parameters to be given to SCCP + * @param tr Transaction for which to build SCCP data + */ + virtual void buildSCCPData(NamedList& params, SS7TCAPTransaction* tr); + /** + * Status of TCAP + * @param status NamedList to fill with status information + */ + virtual void status(NamedList& status); + /** + * Status of TCAP users + * @param status NamedList to fill with user status information + */ + virtual void userStatus(NamedList& status); + /** + * Handle an decoding error + * @param error The encoutered error + * @param params TCAP message parameters which where successfully decoded until the error was encoutered + * @param data Data block containing the rest of the message + * @param tr Transaction to which this message belongs to + * @return Status if the error was handled or not + */ + virtual HandledMSU handleError(SS7TCAPError& error, NamedList& params, DataBlock& data, SS7TCAPTransaction* tr = 0); + /** + * Increment one of the status counters + * @param counterType The type of the counter to increment + */ + inline void incCounter(TCAPCounter counterType) + { + switch (counterType) { + case IncomingMsgs: + m_recvMsgs++; + break; + case OutgoingMsgs: + m_sentMsgs++; + break; + case DiscardedMsgs: + m_discardMsgs++; + break; + case NormalMsgs: + m_normalMsgs++; + break; + case AbnormalMsgs: + m_abnormalMsgs++; + break; + default: + break; + } + } + /** + * Retrieve one of the status counters + * @param counterType The type of the counter to increment + * @return The value of the counter + */ + inline unsigned int count(TCAPCounter counterType) + { + switch (counterType) { + case IncomingMsgs: + return m_recvMsgs; + case OutgoingMsgs: + return m_sentMsgs; + case DiscardedMsgs: + return m_discardMsgs; + case NormalMsgs: + return m_normalMsgs; + case AbnormalMsgs: + return m_abnormalMsgs; + default: + break; + } + return 0; + } + /** + * Get the type of transaction in string form + * @param tr Type of transaction + * @return A string containing the string form of that type of transaction + */ + static inline const char* lookupTransaction(int tr) + { return lookup(tr,s_transPrimitives,"Unknown"); } + /** + * Get the type of transaction from string form + * @param tr Type of transaction in string form + * @return The type of transaction + */ + static inline int lookupTransaction(const char* tr) + { return lookup(tr,s_transPrimitives,TC_Unknown); } + /** + * Get the type of component in string form + * @param comp Type of component + * @return A string containing the string form of that type of component + */ + static inline const char* lookupComponent(int comp) + { return lookup(comp,s_compPrimitives,"Unknown"); } + /** + * Get the type of component from string form + * @param comp Type of component + * @return The type of component + */ + static inline int lookupComponent(const char* comp) + { return lookup(comp,s_compPrimitives,TC_Unknown); } + protected: + virtual SS7TCAPError decodeTransactionPart(NamedList& params, DataBlock& data) = 0; + virtual void encodeTransactionPart(NamedList& params, DataBlock& data) = 0; + // list of TCAP users attached to this TCAP instance ObjList m_users; + Mutex m_usersMtx; + + // list of messages received from sublayer, waiting to be processed + ObjList m_inQueue; + Mutex m_inQueueMtx; + + unsigned int m_SSN; + unsigned int m_defaultRemoteSSN; + SS7PointCode m_defaultRemotePC; + SS7PointCode::Type m_remoteTypePC; + u_int64_t m_trTimeout; + + // list of current TCAP transactions + Mutex m_transactionsMtx; + ObjList m_transactions; + // type of TCAP + TCAPType m_tcapType; + + // counter for allocating transaction IDs + u_int32_t m_idsPool; + + // counters + unsigned int m_recvMsgs; + unsigned int m_sentMsgs; + unsigned int m_discardMsgs; + unsigned int m_normalMsgs; + unsigned int m_abnormalMsgs; +}; + +class SS7TCAPError +{ +public: + enum ErrorType { + // P-AbortCause TransactionProblems + Transact_UnrecognizedPackageType, // named after the ANSI specification, equivalent to ITU-T UnrecongnizedMessageType P-AbortCause + Transact_IncorrectTransactionPortion, // <==> ITU-T incorrectTrasactionPortion P-AbortCause + Transact_BadlyStructuredTransaction, // <==> ITU-T badlyFormattedTransactionPortion P-AbortCause + Transact_UnassignedTransactionID, + Transact_PermissionToReleaseProblem, // HANDLING NOT DEFINED + Transact_ResourceUnavailable, // <==> ITU-T resourceLimitation P-AbortCause + + // P-AbortCause DialogProblem + Dialog_UnrecognizedDialoguePortionID, // ANSI only + Dialog_BadlyStructuredDialoguePortion,// ANSI only + Dialog_MissingDialoguePortion, // ANSI only + Dialog_InconsistentDialoguePortion, // ANSI only + Dialog_Abnormal, // ITU only, indication only + + // GeneralProblem + General_UnrecognizedComponentType, // named after the ANSI specification, equivalent to ITU-T UnrecognizedComponent General Problem + General_IncorrectComponentPortion, // ANSI specification, equivalent to ITU-T MistypedComponent General Problem + General_BadlyStructuredCompPortion, // ANSI specification, equivalent to ITU-T BadlyStructuredComponent General Problem + General_IncorrectComponentCoding, // ANSI specification, no ITU-T equivalent + + // InvokeProblem + Invoke_DuplicateInvokeID, // ANSI & ITU-T specification + Invoke_UnrecognizedOperationCode, // ANSI specification, equivalent to ITU-T UnrecognizedOperation Invoke Problem + Invoke_IncorrectParameter, // ANSI specification, equivalent to ITU-T MistypedParameter Invoke Problem + Invoke_UnrecognizedCorrelationID, // ANSI specification, equivalent to ITU-T UnrecognizedLinkedID Invoke Problem + Invoke_ResourceLimitation, // ITU-T only + Invoke_InitiatingRelease, // ITU-T only + Invoke_LinkedResponseUnexpected, // ITU-T only + Invoke_UnexpectedLinkedOperation, // ITU-T only + + // ReturnResult + Result_UnrecognizedInvokeID, // ITU-T only + Result_UnrecognisedCorrelationID, // ANSI only + Result_UnexpectedReturnResult, // ANSI specification, equivalent to ITU-T ReturnResultUnexpected Result Problem + Result_IncorrectParameter, // ANSI specification, equivalent to ITU-T MistypedParameter Result Problem + + // ReturnError + Error_UnrecognizedInvokeID, // ITU-T only + Error_UnrecognisedCorrelationID, // ANSI only + Error_UnexpectedReturnError, // ANSI only + Error_UnrecognisedError, // ANSI & ITU-T + Error_UnexpectedError, // ANSI & ITU-T + Error_IncorrectParameter, // ANSI specification, equivalent to ITU-T MistypedParameter Return Error Problem + + Discard, + NoError, + }; + /** + * Constructor + * @param tcapType TCAP specification user for this error + */ + SS7TCAPError(SS7TCAP::TCAPType tcapType); + /** + * Constructor + * @param tcapType TCAP specification used for this error + * @param error The error + */ + SS7TCAPError(SS7TCAP::TCAPType tcapType, ErrorType error); + /** + * Destructor + */ + ~SS7TCAPError(); + /** + * Get the error + * @return The TCAP error + */ + inline ErrorType error() + { return m_error; } + /** + * Set the error + * @param error Error to set + */ + inline void setError(ErrorType error) + { m_error = error; } + /** + * Error name + * @return The error name + */ + const String errorName(); + /** + * The full value of the error + * @return 2 byte integer containing the full code of the error + */ + u_int16_t errorCode(); + /** + * Dictionary for error types + */ + static const TokenDict s_errorTypes[]; +private: + SS7TCAP::TCAPType m_tcapType; + ErrorType m_error; +}; + +/** + * Implementation of SS7 Transactional Capabilities Application Part Transaction + * @short SS7 TCAP transaction implementation + */ +class SS7TCAPTransaction : public GenObject, public Mutex +{ +public: + enum TransactionState { + Idle = 0, + PackageSent = 1, + PackageReceived = 2, + Active = 3, + }; + enum TransactionTransmit { + NoTransmit = 0, + PendingTransmit = 256, + Transmitted = 521, + }; + /** + * Constructor + * @param tcap TCAP holding this transaction + * @param type Initiating type for transaction + * @param transactID Transaction ID + * @param params Decoded TCAP parameters for building the transaction + * @param timeout Transaction timeout + * @param initLocal True if the transaction was initiated locally, false if not + */ + SS7TCAPTransaction(SS7TCAP* tcap, SS7TCAP::TCAPUserTransActions type, u_int32_t transactID, NamedList& params, + u_int64_t timeout, bool initLocal = true); + /** + * Destructor + */ + ~SS7TCAPTransaction(); + /** + * Process transaction data and fill the NamedList with the decoded data + * @param params NamedList to fill with decoded data + * @param data Data to decode + * @return A TCAP error encountered whilst decoding + */ + virtual SS7TCAPError handleData(NamedList& params, DataBlock& data) = 0; + /** + * An update request for this transaction + * @param type The type of transaction to which this transaction should be updated + * @param params Update parameter + * @param updateByUser True if the update is made by the local user, false if it's made by the remote end + * @return A TCAP Error + */ + virtual SS7TCAPError update(SS7TCAP::TCAPUserTransActions type, NamedList& params, bool updateByUser = true) = 0; + /** + * Handle TCAP relevant dialog data + * @param params NamedList containing (if present) dialog information + * @param byUser True if the dialog information is provided by the local user, false otherwise + * @return A report error + */ + virtual SS7TCAPError handleDialogPortion(NamedList& params,bool byUser = true) = 0; + /** + * Build a Reject component in answer to an encoutered error during decoding of the component portion + * @param error The encountered error + * @param params Decoded TCAP message parameters + * @param data DataBlock containing the rest of the coded TCAP message + * @return A report error + */ + virtual SS7TCAPError buildComponentError(SS7TCAPError& error, NamedList& params, DataBlock& data); + /** + * Update components + * @param params NamedList reference containing the update information + * @param updateByUser Flag if the update was issued by local user or by remote + * @return A report error + */ + virtual SS7TCAPError handleComponents(NamedList& params, bool updateByUser = true); + /** + * Request encoding for the components of this transaction + * @param params Components parameters to encode + * @param data DataBlock reference in which to insert the encoded components + */ + virtual void requestComponents(NamedList& params, DataBlock& data); + /** + * Fill the NamedList with transaction portion parameters + * @param params NamedList reference to fill with transaction portion parameters + */ + virtual void transactionData(NamedList& params); + /** + * Request content for this transaction + * @param params List of parameters of this tranaction + * @param data Data block to fill with encoded content + */ + virtual void requestContent(NamedList& params, DataBlock& data) = 0; + /** + * Check components for timeouts + */ + virtual void checkComponents(); + /** + * Set the current type of transaction primitive + * @param type The transaction primitive to be set + */ + inline void setTransactionType(SS7TCAP::TCAPUserTransActions type) + { m_type = type; } + /** + * Retrieve the current type of primitive that is set for this transaction + * @return The transaction primitive type + */ + inline SS7TCAP::TCAPUserTransActions transactionType() + { return m_type; } + /** + * Set the state of this transaction, trigger a transmission pending state + * @param state The state to set for the transaction + */ + inline void setState(TransactionState state) + { + m_state = state; + // changing state automatically triggers a change in transmission state (except for Idle) + if (state != Idle) + m_transmit = PendingTransmit; + } + /** + * Retrieve the state of this transaction + * @return The state of this transaction + */ + inline TransactionState transactionState() + { return m_state; } + /** + * Set the transmission state for this transaction + * @param state The transmission state to be set + */ + void setTransmitState(TransactionTransmit state); + /** + * The transmission state for this transaction + * @return The current transmission state + */ + inline TransactionTransmit transmitState() + { return m_transmit; } + /** + * The TCAP to which this transaction belongs + * @return A pointer to the TCAP component + */ + inline SS7TCAP* tcap() + { return m_tcap; } + /** + * Get the ID of the transaction so it can be used for list searches + * @return A reference to the ID + */ + const String& toString() const + { return m_localID; } + /** + * Set the TCAP username to which this transaction belongs + * @param name The name of the user to set + */ + inline void setUserName(const String& name) + { m_userName = name; } + /** + * Return the name of the TCAP user to which this transaction belongs + * @return The name of the user + */ + const String& userName() + { return m_userName; } + /** + * Check if a basic end was set for this transaction + * @return True if basic end was specified by the user, false if prearranged end was specified + */ + inline bool basicEnd() + { return m_basicEnd; } + /** + * Add SCCP Addressing information + * @param fillParams NamedList to fill with addressing information + * @param local True if the information is for the user, otherwise + */ + void addSCCPAddressing(NamedList& fillParams, bool local); + /** + * Check if the flag to end this transaction immediately was set + * @return True if the end flag was set, false otherwise + */ + inline bool endNow() + { return m_endNow; } + /** + * Set the flag to end this transaction immediately + * @param endNow Boolean value to set to the end flag + */ + inline void endNow(bool endNow) + { m_endNow = endNow; } + /** + * Check if the transaction has timed out + * @return True if the transaction timed out, false otherwise + */ + inline bool timedOut() + { return m_timeout.timeout(); } + /** + * Find a component with given id + * @param id Id of component to find + * @return The component with given id or null + */ + SS7TCAPComponent* findComponent(const String& id); + /** + * Update the state of this transaction to end the transaction + */ + virtual void updateToEnd(); + /** + * Update transaction state + * @param byUser True if update is requested by user, false if by remote + */ + inline void updateState(bool byUser = true) + { } + + virtual SS7TCAPError decodeDialogPortion(NamedList& params, DataBlock& data) = 0; + virtual void encodeDialogPortion(NamedList& params, DataBlock& data) = 0; + virtual SS7TCAPError decodeComponents(NamedList& params, DataBlock& data) = 0; + virtual void encodeComponents(NamedList& params, DataBlock& data) = 0; + +protected: + SS7TCAP* m_tcap; + SS7TCAP::TCAPType m_tcapType; + String m_userName; + String m_localID; + String m_remoteID; + SS7TCAP::TCAPUserTransActions m_type; + TransactionState m_state; + TransactionTransmit m_transmit; + + ObjList m_components; + + NamedList m_localSCCPAddr; + NamedList m_remoteSCCPAddr; + + bool m_basicEnd; // basic or prearranged end (specified by user when sending a Response) + bool m_endNow; // delete immediately after sending + SignallingTimer m_timeout; +}; + +class SS7TCAPComponent : public GenObject +{ +public: + /** + * Component state + */ + enum TCAPComponentState { + Idle, + OperationPending, + OperationSent, + WaitForReject, + }; + /** + * Constructor + * @param type TCAP type for which to build this component + * @param trans TCAP transaction to which this component belongs to + * @param params Parameters for building component + * @param index Index in the list of parameters + */ + SS7TCAPComponent(SS7TCAP::TCAPType type, SS7TCAPTransaction* trans, NamedList& params, unsigned int index); + /** + * Destructor + */ + virtual ~SS7TCAPComponent(); + /** + * Update this component's data + * @param params Update parameters + * @param index Index of parameters in the list for the update of this component + */ + virtual void update(NamedList& params, unsigned int index); + /** + * Put the information of the component in a NamedList + * @param index Index for build parameter names + * @param fillIn NamedList to fill with this component's information + */ + virtual void fill(unsigned int index, NamedList& fillIn); + /** + * Build a TCAP Component from a NamedList + * @param type TCAP type of component + * @param tr The transaction to which this component should belong + * @param params Parameters for building the component + * @param index Index in the list of parameters + * @return A pointer to the built SS7TCAPComponent or nil if not all required parameters are present + */ + static SS7TCAPComponent* componentFromNamedList(SS7TCAP::TCAPType type, SS7TCAPTransaction* tr, NamedList& params, unsigned int index); + /** + * Set the transaction to which this component belongs to + * @param transact TCAP transaction + */ + void setTransaction(SS7TCAPTransaction* transact); + /** + * Returns the transaction to which this component belongs to. + */ + SS7TCAPTransaction* transaction(); + /** + * Set the type for this component + * @param type The type of the component + */ + inline void setType(SS7TCAP::TCAPUserCompActions type) + { m_type = type; } + /** + * Get the type of the component + */ + inline SS7TCAP::TCAPUserCompActions type() + { return m_type; } + /** + * Set the Invoke ID for this component + * @param invokeID The invoke ID to assign + */ + virtual void setInvokeID(String invokeID) + { m_id = invokeID; } + /** + * String representation of this component's Invoke ID + * @return String representation of Invoke ID + */ + virtual const String& toString () const + { return m_id; } + /** + * String representation of this component's Correlation ID + * @return String representation of Correlation ID + */ + virtual const String& correlationID() const + { return m_corrID; } + /** + * Check if the component has timed out + * @return True if the component timed out, false otherwise + */ + inline bool timedOut() + { return m_opTimer.timeout(); } + /** + * Set component state + * @param state The state to be set + */ + void setState(TCAPComponentState state); + /** + * Obtain the component state + * @return The component state + */ + inline TCAPComponentState state() + { return m_state; } + /** + * Reset invocation timer on user request + * @param params List of parameters + * @param index Index of this component's parameters in the list + */ + void resetTimer(NamedList& params, unsigned int index); + /** + * Retrieve operation class for this component + * @return The class of the operation + */ + SS7TCAP::TCAPComponentOperationClass operationClass() + { return m_opClass; } + /** + * Dictionary for component states + */ + static const TokenDict s_compStates[]; + +private: + SS7TCAPTransaction* m_transact; + SS7TCAP::TCAPUserCompActions m_type; + TCAPComponentState m_state; + String m_id; + String m_corrID; + SS7TCAP::TCAPComponentOperationClass m_opClass; + SignallingTimer m_opTimer; + SS7TCAPError m_error; +}; + +/** + * Implementation of SS7 Transactional Capabilities Application Part - specification ANSI + * @short ANSI SS7 TCAP implementation + */ +class SS7TCAPANSI : virtual public SS7TCAP +{ + YCLASS(SS7TCAPANSI,SS7TCAP) +public: + enum TCAPTags { + TransactionIDTag = 0xc7, + PCauseTag = 0xd7, + UserAbortPTag = 0xd8 , // Primitive + UserAbortCTag = 0xf8, // Constructor + }; + enum TCAPDialogTags { + DialogPortionTag = 0xf9, + ProtocolVersionTag = 0xda, + IntApplicationContextTag = 0xdb, + OIDApplicationContextTag = 0xdc, + UserInformationTag = 0xfd, + IntSecurityContextTag = 0x80, + OIDSecurityContextTag = 0x81, + ConfidentialityTag = 0xa2, + }; + enum UserInfoTags { + DirectReferenceTag = 0x06, + DataDescriptorTag = 0x07, + ExternalTag = 0x28, + SingleASNTypePEncTag = 0x80, // Primitive Single-ASN1-Type-Encoding + SingleASNTypeCEncTag = 0xa0, // Constructor Single-ASN1-Type-Encoding + OctetAlignEncTag = 0x81, + ArbitraryEncTag = 0x82, + }; + enum ConfidentialityTags { + IntConfidentialContextTag = 0x80, + OIDConfidentialContextTag = 0x81, + }; + enum TCAPComponentTags { + ComponentPortionTag = 0xe8, + ComponentsIDsTag = 0xcf, + OperationNationalTag = 0xd0, + OperationPrivateTag = 0xd1, + ErrorNationalTag = 0xd3, + ErrorPrivateTag = 0xd4, + ProblemCodeTag = 0xd5, + ParameterSetTag = 0xf2, + ParameterSeqTag = 0x30, + }; + /** + * Constructor + * @param params NamedList containing parameters for building TCAP + */ + SS7TCAPANSI(const NamedList& params); + /** + * Destructor + */ + ~SS7TCAPANSI(); + /** + * Build a transaction + * @param type Type with which to build the transactions + * @param transactID ID for the transaction + * @param params Parameters for building the transaction + * @param initLocal True if built by user, false if by remote end + * @return A transaction + */ + virtual SS7TCAPTransaction* buildTransaction(SS7TCAP::TCAPUserTransActions type, u_int32_t transactID, NamedList& params, + bool initLocal = true); +private: + SS7TCAPError decodeTransactionPart(NamedList& params, DataBlock& data); + void encodeTransactionPart(NamedList& params, DataBlock& data); +}; + +/** + * Implementation of SS7 Transactional Capabilities Application Part Transaction - specification ANSI + * @short ANSI SS7 TCAP transaction implementation + */ +class SS7TCAPTransactionANSI : public SS7TCAPTransaction +{ +public: + enum TCAPANSIComponentType { + CompUnknown = 0x0, + Local = 0x1, + InvokeLast = 0xe9, + ReturnResultLast = 0xea, + ReturnError = 0xeb, + Reject = 0xec, + InvokeNotLast = 0xed, + ReturnResultNotLast = 0xee, + }; + enum ANSITransactionType { + Unknown = 0x0, + Unidirectional = 0xe1, + QueryWithPermission = 0xe2, + QueryWithoutPermission = 0xe3, + Response = 0xe4, + ConversationWithPermission = 0xe5, + ConversationWithoutPermission = 0xe6, + Abort = 0xf6, + }; + /** + * Constructor + * @param tcap TCAP holding this transaction + * @param type Initiating type for transaction + * @param transactID Transaction ID + * @param params Decoded TCAP parameters for building the transaction + * @param timeout Transaction timeout + * @param initLocal True if the transaction was initiated locally, false if not + */ + SS7TCAPTransactionANSI(SS7TCAP* tcap, SS7TCAP::TCAPUserTransActions type, u_int32_t transactID, NamedList& params, + u_int64_t timeout, bool initLocal = true); + /** + * Destructor + */ + ~SS7TCAPTransactionANSI(); + /** + * Process transaction data and fill the NamedList with the decoded data + * @param params NamedList to fill with decoded data + * @param data Data to decode + * @return A TCAP error encountered whilst decoding + */ + virtual SS7TCAPError handleData(NamedList& params, DataBlock& data); + /** + * An update request for this transaction + * @param type The type of transaction to which this transaction should be updated + * @param params Update parameter + * @param updateByUser True if the update is made by the local user, false if it's made by the remote end + * @return A TCAP Error + */ + virtual SS7TCAPError update(SS7TCAP::TCAPUserTransActions type, NamedList& params, bool updateByUser = true); + /** + * Handle TCAP relevant dialog data + * @param params NamedList containing (if present) dialog information + * @param byUser True if the dialog information is provided by the local user, false otherwise + * @return A report error + */ + virtual SS7TCAPError handleDialogPortion(NamedList& params, bool byUser = true); + /** + * Encode P-Abort information + * @param tr The transaction on which the abort was signalled + * @param params NamedList reference from which to get the P-Abort information + * @param data DataBlock reference in which to insert the encoded P-Abort information + */ + static void encodePAbort(SS7TCAPTransaction* tr, NamedList& params, DataBlock& data); + /** + * Decode P-Abort TCAP message portion + * @param tr The transaction on which the abort was signalled + * @param params NamedList reference to fill with the decoded P-Abort information + * @param data DataBlock reference from which to decode P-Abort information + */ + static SS7TCAPError decodePAbort(SS7TCAPTransaction* tr, NamedList& params, DataBlock& data); + /** + * Update the state of this transaction to end the transaction + */ + virtual void updateToEnd(); + /** + * Update transaction state + * @param byUser True if update is requested by user, false if by remote + */ + virtual void updateState(bool byUser); + /** + * Request content for this transaction + * @param params List of parameters of this tranaction + * @param data Data block to fill with encoded content + */ + virtual void requestContent(NamedList& params, DataBlock& data); + /** + * Dictionary keeping string versions of transaction types + */ + static const TokenDict s_ansiTransactTypes[]; + +private: + SS7TCAPError decodeDialogPortion(NamedList& params, DataBlock& data); + void encodeDialogPortion(NamedList& params, DataBlock& data); + SS7TCAPError decodeComponents(NamedList& params, DataBlock& data); + void encodeComponents(NamedList& params, DataBlock& data); + + SS7TCAP::TCAPUserTransActions m_prevType; }; // The following classes are ISDN, not SS7, but they use the same signalling diff --git a/modules/server/ysigchan.cpp b/modules/server/ysigchan.cpp index 536117b6..dcd7a1c9 100644 --- a/modules/server/ysigchan.cpp +++ b/modules/server/ysigchan.cpp @@ -46,6 +46,9 @@ class SigTrunkThread; // Get events and check timeout for tru class IsupDecodeHandler; // Handler for "isup.decode" message class IsupEncodeHandler; // Handler for "isup.encode" message class SigNotifier; // Class for handling received notifications +class SigSS7Tcap; // SS7 TCAP - Transaction Capabilities Application Part +class SigTCAPUser; // Default TCAP user +class TCAPMsgHandler; // TCAP Message handler // The signalling channel class SigChannel : public Channel @@ -538,6 +541,56 @@ private: unsigned char m_idleValue; // Idle value for source multiplexer to fill when no data }; +class SigSS7Tcap : public SigTopmost +{ +public: + inline SigSS7Tcap(const char* name) + : SigTopmost(name), m_tcap(0) + { } + // Initialize (create or reload) the TCAP component + // Return false on failure + virtual bool initialize(NamedList& params); + // Return the status of this component + virtual void status(String& retVal); +protected: + virtual void destroyed(); +private: + SS7TCAP* m_tcap; +}; + +class SigTCAPUser : public SigTopmost, public TCAPUser +{ + YCLASS(SigTCAPUser,TCAPUser) +public: + inline SigTCAPUser(const char* name) + : SigTopmost(name), + TCAPUser(name), m_handler(0) + { } + virtual ~SigTCAPUser(); + // { } + // Initialize the TCAP user + // Return false on failure + virtual bool initialize(NamedList& params); + virtual bool tcapRequest(NamedList& params); + virtual bool tcapIndication(NamedList& params); + // Return the status of this component + virtual void status(String& retVal); +protected: + TCAPMsgHandler* m_handler; + String m_tcapName; + virtual void destroyed(); +}; + +class TCAPMsgHandler : public MessageHandler +{ +public: + TCAPMsgHandler(SigTCAPUser* user); + virtual bool received(Message& msg); + virtual void destruct(); +private: + SigTCAPUser* m_user; +}; + // Factory for locally configured components class SigFactory : public SignallingFactory { @@ -567,6 +620,10 @@ public: SigSS7M2PA = 0x21 | SigOnDemand, SigSS7M2UA = 0x22 | SigOnDemand, SigSS7M3UA = 0x23 | SigTopMost, + SigSS7TCAP = 0x24 | SigTopMost, + SigSS7TCAPANSI = 0x25 | SigTopMost, + SigSS7TCAPITU = 0x26 | SigTopMost, + SigTCAPUser = 0x27 | SigTopMost, SigISDNIUAClient = 0x31 | SigDefaults, SigISDNIUAGateway = 0x32 | SigTopMost, SigSS7M2UAClient = 0x33 | SigDefaults, @@ -817,6 +874,9 @@ const TokenDict SigFactory::s_compNames[] = { { "ss7-sccp-itu-mgm", SigSS7ItuSccpManagement }, { "ss7-sccp-ansi-mgm",SigSS7AnsiSccpManagement }, { "ss7-gtt", SigSccpGtt }, + { "ss7-tcap-ansi", SigSS7TCAPANSI }, + { "ss7-tcap-itu", SigSS7TCAPITU }, + { "ss7-tcap-user", SigTCAPUser }, { "isdn-pri-net", SigISDNPN }, { "isdn-bri-net", SigISDNBN }, { "isdn-pri-cpe", SigISDNPC }, @@ -855,6 +915,10 @@ const TokenDict SigFactory::s_compClass[] = { MAKE_CLASS(SS7ItuSccpManagement), MAKE_CLASS(SS7AnsiSccpManagement), MAKE_CLASS(SCCPUserDummy), + MAKE_CLASS(SS7TCAP), + MAKE_CLASS(SS7TCAPANSI), + MAKE_CLASS(SS7TCAPITU), + MAKE_CLASS(TCAPUser), MAKE_CLASS(ISDNPN), MAKE_CLASS(ISDNBN), MAKE_CLASS(ISDNPC), @@ -946,6 +1010,12 @@ SignallingComponent* SigFactory::create(const String& type, const NamedList& nam return new SS7ItuSccpManagement(*config); case SigSS7AnsiSccpManagement: return new SS7AnsiSccpManagement(*config); + case SigSS7TCAP: + if (ty) { + if (*ty == "ss7-tcap-ansi") + return new SS7TCAPANSI(*config); + } + return 0; } return 0; } @@ -2787,6 +2857,13 @@ bool SigDriver::initTopmost(NamedList& sect, int type) case SigFactory::SigSccpGtt: topmost = new SigSccpGtt(sect); break; + case SigFactory::SigSS7TCAPANSI: + case SigFactory::SigSS7TCAPITU: + topmost = new SigSS7Tcap(sect); + break; + case SigFactory::SigTCAPUser: + topmost = new SigTCAPUser(sect); + break; default: return false; } @@ -3882,6 +3959,118 @@ void SigIsdnMonitor::release() XDebug(&plugin,DebugAll,"SigIsdnMonitor('%s'). Released [%p]",name().c_str(),this); } +/** + * SigSS7Tcap + */ +void SigSS7Tcap::destroyed() +{ + DDebug(&plugin,DebugAll,"SigSS7TCAP::destroyed() [%p]",this); + TelEngine::destruct(m_tcap); + SigTopmost::destroyed(); +} + +bool SigSS7Tcap::initialize(NamedList& params) +{ + if (!m_tcap) { + String type = params.getValue("type",""); + m_tcap = YSIGCREATE(SS7TCAP,¶ms); + if (m_tcap) + plugin.engine()->insert(m_tcap); + } + return m_tcap && m_tcap->initialize(¶ms); +} + +void SigSS7Tcap::status(String& retVal) +{ + retVal << "type=" << (m_tcap ? m_tcap->componentType() : ""); + NamedList p(""); + m_tcap->status(p); + retVal << ",totalIncoming=" << p.getValue("totalIncoming","0"); + retVal << ",totalOutgoing=" << p.getValue("totalOutgoing","0"); + retVal << ",totalDiscarded=" << p.getValue("totalDiscarded","0"); + retVal << ",totalNormal=" << p.getValue("totalNormal","0"); + retVal << ",totalAbnormal=" << p.getValue("totalAbnormal","0"); +} + +/** + * SigTCAPUser + */ +SigTCAPUser::~SigTCAPUser() +{ + DDebug(&plugin,DebugAll,"SigTCAPUser::~SigTCAPUser() [%p]",this); + if (!TelEngine::null(m_handler)) + TelEngine::destruct(m_handler); +} + +bool SigTCAPUser::initialize(NamedList& params) +{ + DDebug(&plugin,DebugAll,"SigTCAPUser::initialize() [%p]",this); + m_tcapName = params.getValue("tcap",""); + if (!m_handler) { + m_handler = new TCAPMsgHandler(this); + Engine::install(m_handler); + } + return true; +} + +bool SigTCAPUser::tcapRequest(NamedList& params) +{ + if (!tcap()) { + SignallingComponent* tcap = 0; + if (!(tcap = plugin.engine()->find(m_tcapName,"SS7TCAP",tcap))) { + //TODO createTCAP ? + } + else + attach(YOBJECT(SS7TCAP,tcap)); + } + if (tcap()) { + SS7TCAPError err = tcap()->userRequest(params); + return err.error() != SS7TCAPError::NoError; + } + return false; +} + +bool SigTCAPUser::tcapIndication(NamedList& params) +{ + Message msg("tcap.indication"); + msg.copyParams(params); + return Engine::dispatch(&msg); +} + +void SigTCAPUser::status(String& retVal) +{ +} + +void SigTCAPUser::destroyed() +{ + DDebug(&plugin,DebugAll,"SigTCAPUser::destroyed() [%p]",this); + Engine::uninstall(m_handler); + attach(0); + SigTopmost::destroyed(); +} + +/** + * TCAPMsgHandler + */ +TCAPMsgHandler::TCAPMsgHandler(SigTCAPUser* user) + : MessageHandler("tcap.request"), + m_user(user) +{ + DDebug(&plugin,DebugAll,"TCAPMsgHandler created [%p]",this); +} + +bool TCAPMsgHandler::received(Message& msg) +{ + return (m_user && m_user->tcapRequest(msg)); +} + +void TCAPMsgHandler::destruct() +{ + DDebug(&plugin,DebugAll,"TCAPMsgHandler::destroyed() [%p]",this); + m_user = 0; + MessageHandler::destruct(); +} + /** * SigConsumerMux */