/**
 * sccp.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Yet Another Signalling Stack - implements the support for SS7, ISDN and PSTN
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2004-2014 Null Team
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "yatesig.h"
#include <yatephone.h>

#include<stdlib.h>
#include <string.h>


using namespace TelEngine;

#define MAX_MANDATORY_PARAMS 16

// 227 is maximum data length that can be transported by a UDT message
// with 2 full gt present both numbers with 16 digits (bcd encoded)
#define MAX_UDT_LEN 227

#define MAX_INFO_TIMER 1200000 // Maximum interval for sending SST 20 min
// Maximum length of optional parameters: 6 Segmentation, 3 Importance, 1 EOP
#define MAX_OPT_LEN 10
// Minimum data size in a SCCP message
#define MIN_DATA_SIZE 2

#define MAX_DATA_ITU 3952
#define MAX_DATA_ANSI 3904

static const char* s_userMutexName = "SCCPUserTransport";
static const char* s_sccpMutexName = "SCCPUserList";
static const char* s_managementMutexName = "SCCPManagement";
static const char* s_sccpTranslatorMutex = "SCCPTranslator";
static const char* s_sccpSubsystems = "SccpSubsystems";
static const char* s_sccpRemote = "SccpRemote";

struct SCCPParam {
    // numeric type of the parameter
    SS7MsgSCCP::Parameters type;
    // size in octets, zero for variable
    unsigned char size;
    // SS7 name of the parameter
    const char* name;
    // decoder callback function
    bool (*decoder)(const SS7SCCP*,NamedList&,const SCCPParam*,
	const unsigned char*,unsigned int,const String&);
    // encoder callback function
    unsigned char (*encoder)(const SS7SCCP*,SS7MSU&,unsigned char*,
	const SCCPParam*,const NamedString*,const NamedList*,const String&);
    // table data to be used by the callback
    const void* data;
};

struct MsgParams {
    // type of the message described
    SS7MsgSCCP::Type type;
    // does the message support optional part?
    bool optional;
    // parameters, fixed then variable, separated/terminated by EndOfParameters
    //  using an array is a (moderate) waste of space
    const SS7MsgSCCP::Parameters params[MAX_MANDATORY_PARAMS];
};

static const TokenDict s_return_cause[] = {
    { "No translation for an address of such nature",                   SS7SCCP::NoTranslationAddressNature },
    { "No translation for this specific address",                       SS7SCCP::NoTranslationSpecificAddress },
    { "Subsystem congestion",                                           SS7SCCP::SubsystemCongestion },
    { "Subsystem failure",                                              SS7SCCP::SubsystemFailure },
    { "Unequipped user",                                                SS7SCCP::UnequippedUser },
    { "MTP failure",                                                    SS7SCCP::MtpFailure },
    { "Network Congestion",                                             SS7SCCP::NetworkCongestion },
    { "Unqualified",                                                    SS7SCCP::Unqualified },
    { "Error in message transport",                                     SS7SCCP::ErrorInMessageTransport },
    { "Error in local processing",                                      SS7SCCP::ErrorInLocalProcessing },
    { "Destination can not perform reassembly",                         SS7SCCP::DestinationCanNotPerformReassembly },
    { "SCCP failure",                                                   SS7SCCP::SccpFailure },
    { "Hop counter violation",                                          SS7SCCP::HopCounterViolation },
    { "Segmentation not supported",                                     SS7SCCP::SegmentationNotSupported },
    { "Segmentation failure",                                           SS7SCCP::SegmentationFailure },
    // ANSI only
    { "Message change failure",                                         SS7SCCP::MessageChangeFailure },
    { "Invalid INS routing request",                                    SS7SCCP::InvalidINSRoutingRequest },
    { "Invalid ISNI routing request",                                   SS7SCCP::InvalidISNIRoutingRequest },
    { "Unauthorized message",                                           SS7SCCP::UnauthorizedMessage },
    { "Message incompatibility",                                        SS7SCCP::MessageIncompatibility },
    { "Can not perform ISNI constrained routing",                       SS7SCCP::NotSupportedISNIRouting },
    { "Redundant ISNI constrained routing information",                 SS7SCCP::RedundantISNIConstrainedRouting },
    { "Unable to perform ISNI identification",                          SS7SCCP::ISNIIdentificationFailed },
    { 0, 0 }
};

static const TokenDict s_managementMessages[] = {
    { "SSA",    SCCPManagement::SSA },  // Subsystem-allowed
    { "SSP",    SCCPManagement::SSP },  // Subsystem-prohibited
    { "SST",    SCCPManagement::SST },  // Subsystem-status-test
    { "SOR",    SCCPManagement::SOR },  // Subsystem-out-of-service-request
    { "SOG",    SCCPManagement::SOG },  // Subsystem-out-of-service-grant
    { "SSC",    SCCPManagement::SSC },  // SCCP/Subsystem-congested      (ITU  only)
    { "SBR",    SCCPManagement::SBR },  // Subsystem-backup-routing      (ANSI only)
    { "SNR",    SCCPManagement::SNR },  // Subsystem-normal-routing      (ANSI only)
    { "SRT",    SCCPManagement::SRT },  // Subsystem-routing-status-test (ANSI only)
    { 0, 0 }
};

static const TokenDict s_dict_control[] = {
    { "status",                      SS7SCCP::Status },
    { "full-status",                 SS7SCCP::FullStatus },
    { "enable-extended-monitoring",  SS7SCCP::EnableExtendedMonitoring },
    { "disable-extended-monitoring", SS7SCCP::DisableExtendedMonitoring },
    { "enable-print-messages",       SS7SCCP::EnablePrintMsg },
    { "disable-print-messages",      SS7SCCP::DisablePrintMsg },
    { 0, 0 }
};

const TokenDict SCCPManagement::s_broadcastType[] = {
    { "UserOutOfService",            SCCPManagement::UserOutOfService },
    { "UserInService",               SCCPManagement::UserInService },
    { "SignallingPointInaccessible", SCCPManagement::PCInaccessible },
    { "SignallingPointAccessible",   SCCPManagement::PCAccessible },
    { "RemoteSCCPInaccessible",      SCCPManagement::SccpRemoteInaccessible },
    { "RemoteSCCPAccessible",        SCCPManagement::SccpRemoteAccessible },
    { "SignallingPointCongested",    SCCPManagement::PCCongested },
    { 0, 0 }
};

static const TokenDict s_sccpNotif[] = {
    { "Coordinate Request",            SCCP::CoordinateRequest },          // (User->SCCP)
    { "Coordinate Confirm",            SCCP::CoordinateConfirm },          // (SCCP->User)
    { "Coordinate Indication",         SCCP::CoordinateIndication },       // (SCCP->User)
    { "Coordinate Response",           SCCP::CoordinateResponse },         // (User->SCCP)
    { "Status Indication",             SCCP::StatusIndication },           // (SCCP->User)
    { "Status Request",                SCCP::StatusRequest },              // (User->SCCP)
    { "PointCode Status Indication",   SCCP::PointCodeStatusIndication },  // (SCCP->User)
    { "Trafic Indication",             SCCP::TraficIndication },
    { "Subsystem Status",              SCCP::SubsystemStatus },            // (SCCP->User)
    { 0, 0 }
};

static const TokenDict s_numberingPlan[] = {
    { "unknown",          0x00 },
    { "isdn",             0x01 },
    { "e164",             0x01 },
    { "generic",          0x02 },
    { "data",             0x03 },
    { "x121",             0x03 },
    { "telex",            0x04 },
    { "maritime-mobile",  0x05 },
    { "e210",             0x05 },
    { "e211",             0x05 },
    { "land-mobile",      0x06 },
    { "e212",             0x06 },
    { "isdn-mobile",      0x07 },
    { "e214",             0x07 },
    { "network-specific", 0x0e },
    { 0, 0 }
};

static const TokenDict s_nai[] = {
    { "unknown",                     0x00 },
    { "subscriber",                  0x01 },
    { "national-reserved",           0x02 },
    { "national-significant",        0x03 },
    { "international",               0x04 },
    { 0, 0 }
};

static const TokenDict s_encodingScheme[] = {
    { "unknown",     0x00 },
    { "bcd",         0x01 },
    { "bcd",         0x02 },
    { 0, 0 }
};

static const TokenDict s_ansiSmi[] = {
    { "unknown",       0x00 },
    { "solitary",      0x01 },
    { "duplicated",    0x02 },
    { 0 , 0 }
};

const TokenDict SCCPManagement::s_states[] = {
    { "allowed",        SCCPManagement::Allowed },
    { "prohibited",     SCCPManagement::Prohibited },
    { "wait-for-grant", SCCPManagement::WaitForGrant },
    { "ignore-tests",   SCCPManagement::IgnoreTests },
    { "unknown",        SCCPManagement::Unknown },
    { 0 , 0 }
};

const TokenDict s_messageReturn[] = {
    { "false", 0x00 },
    { "true",  0x08 },
    { "yes",   0x08 },
    { "on",    0x08 },
    { "enable",0x08 },
    { 0, 0 }
};

static bool compareLabel(const SS7Label& l1, const SS7Label& l2)
{
    if (l1.opc() != l2.opc())
	return false;
    if (l1.dpc() != l2.dpc())
	return false;
    return true;
}

// Helper method increments a number stored in a string
static void incrementNS(NamedString* ns)
{
    if (!ns)
	return;
    int counter = ns->toInteger();
    counter++;
    *ns = String(counter);
}

static bool compareNamedList(const NamedList& nl1, const NamedList& nl2)
{
    if (nl1.length() != nl2.length())
	return false;
    NamedIterator iter(nl1);
    while (const NamedString* pr = iter.get()) {
	const NamedString* pr2 = nl2.getParam(pr->name());
	if (!pr2 || (*pr2 != *pr))
	    return false;
    }
    return true;
}

static void getDictValue(NamedList& list, const char* paramName, int val, const TokenDict* dict)
{
    NamedString* ns = new NamedString(paramName);
    *ns = lookup(val,dict,0);
    if (ns->null()) {
	*ns = String(val).c_str();
    }
    list.setParam(ns);
}

static bool decodeRaw(const SS7SCCP* sccp, NamedList& list, const SCCPParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (len < 1)
	return false;
    String raw;
    raw.hexify((void*)buf,len,' ');
    DDebug(sccp,DebugInfo,"decodeRaw decoded %s=%s",param->name,raw.c_str());
    list.addParam(prefix+param->name,raw);
    return true;
}

// Raw decoder for unknown/failed parameter, dumps raw octets
static bool decodeRawParam(const SS7SCCP* sccp, NamedList& list, unsigned char value,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    String name("Param_");
    name << value;
    SCCPParam p;
    p.type = (SS7MsgSCCP::Parameters)value;
    p.size = len;
    p.name = name;
    p.decoder = 0;
    p.encoder = 0;
    p.data = 0;
    return decodeRaw(sccp,list,&p,buf,len,prefix);
};

static bool decodeInt(const SS7SCCP* sccp, NamedList& list, const SCCPParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    unsigned int val = 0;
    int shift = 0;
    while (len--) {
	val |= ((unsigned int)*buf++) << shift;
	shift += 8;
    }
    DDebug(sccp,DebugAll,"decodeInt decoded %s=%s (%u)",param->name,lookup(val,(const TokenDict*)param->data),val);
    SignallingUtils::addKeyword(list,prefix+param->name,(const TokenDict*)param->data,val);
    return true;
}

static bool decodeProtocolClass(const SS7SCCP* sccp, NamedList& list, const SCCPParam* param,
    const unsigned char* buffer, unsigned int len, const String& prefix)
{
    unsigned char protocol = *buffer++;
    unsigned int pClass = protocol & 0x0f;
    if (pClass > 3) {
	Debug(sccp,DebugWarn,"Received Invalid Protocol Class %d",pClass);
	return false;
    }
    if (pClass < 2) // Protocol class 0 | 1 check return option
	getDictValue(list,prefix + "MessageReturn",protocol >> 4,s_messageReturn);
    list.setParam(prefix + param->name, String(pClass));
    return true;
}

static bool decodeCause(const SS7SCCP* sccp, NamedList& list, const SCCPParam* param,
    const unsigned char* buffer, unsigned int len, const String& prefix)
{
    if (len <  1)
	return false;
    unsigned int cause = *buffer++;
    list.setParam(prefix + param->name,String(cause));
    return true;
}

static bool decodeImportance(const SS7SCCP* sccp, NamedList& list, const SCCPParam* param,
    const unsigned char* buffer, unsigned int len, const String& prefix)
{
    if (len < 1)
	return false;
    int importance = *buffer++ & 0x07;
    list.setParam(prefix + "Importance",String(importance));
    return true;
}

static void getDigits(String& num, bool oddNum, const unsigned char* buf, unsigned int len,
    bool ignoreUnk)
{
    static const char digits1[] = "0123456789\0BC\0\0.";
    static const char digits2[] = "0123456789ABCDE.";
    const char* digits = ignoreUnk ? digits1 : digits2;
    for (unsigned int i = 0; i < len; i++) {
	num += digits[buf[i] & 0x0f];
	if (oddNum && ((i+1) == len))
	    break;
	num += digits[buf[i] >> 4];
    }
}

// Decode methods
static bool decodeItuAddress(const SS7SCCP* sccp, NamedList& params,const SCCPParam* param,
	const unsigned char* buffer, unsigned int length, const String& prefix)
{
    unsigned char addressIndicator = *buffer++;
    length--;
    String prName = prefix + param->name;
    while (true) {
	if ((addressIndicator & 0x01) == 0x01) { // Have Pointcode
	    if (length < 2)
		break;
	    int pointcode = *buffer++;
	    pointcode |= (*buffer++ & 0x3f) << 8;
	    params.addParam(prName + ".pointcode",String(pointcode));
	    length -= 2;
	}
	if ((addressIndicator & 0x02) == 0x02) { // Have SSN
	    if (length < 1)
		break;
	    unsigned char ssn = *buffer++;
	    params.addParam(prName + ".ssn",String(ssn));
	    length --;
	}
	params.addParam(prName + ".route", ((addressIndicator & 0x40) == 0x40)
		? YSTRING("ssn") : YSTRING("gt"));
	unsigned char gti = (addressIndicator >> 2) & 0x0f;
	if (!gti) // No Global Title Present
	    return true;
	bool odd = false;
	String tmp;
	String gtName = prName + ".gt";
	if (gti == 0x01) { // GT includes Nature Of Address Indicator
	    if (length < 1) {
		break;
	    }
	    unsigned char nai = *buffer++;
	    length--;
	    getDictValue(params,gtName + ".nature", nai & 0x7f,s_nai);
	    odd = (nai & 0x80) != 0;
	} else if (gti == 0x02) { // GT includes Translation Type
	    if (length < 1)
		break;
	    params.addParam(gtName + ".translation", String((int)*buffer++));
	    length--;
	    tmp.hexify((void*)buffer,length,' ');
	} else if (gti == 0x03) { // GT includes tt, np & es
	    if (length < 2)
		break;
	    params.addParam(gtName+ ".translation", String((int)*buffer++));
	    length--;
	    unsigned char npes = *buffer++;
	    length--;
	    getDictValue(params,gtName + ".plan", npes >> 4,s_numberingPlan);
	    unsigned int es = npes & 0x0f;
	    getDictValue(params,gtName + ".encoding", es,s_encodingScheme);
	    switch (es) {
		case 1:
		    odd = true;
		case 2:
		    break;
		default:
		    tmp.hexify((void*)buffer,length,' ');
	    }
	} else if (gti == 0x04) { // GT includes tt, np, es & nai
	    if (length < 3)
		break;
	    params.addParam(gtName+ ".translation", String((int)*buffer++));
	    length--;
	    unsigned char npes = *buffer++;
	    unsigned char es = npes & 0x0f;
	    length--;
	    getDictValue(params,gtName + ".plan", npes >> 4,s_numberingPlan);
	    getDictValue(params,gtName + ".encoding", es,s_encodingScheme);
	    getDictValue(params,gtName + ".nature", *buffer++ & 0x7f,s_nai);
	    length--;
	    switch (es) {
		case 1:
		    odd = true;
		case 2:
		    break;
		default:
		    tmp.hexify((void*)buffer,length,' ');
	    }
	} else {
	    Debug(sccp,DebugMild, "Unable to decode ITU GT with GTI = %d",gti);
	    return false;
	}
	if (tmp.null())
	    getDigits(tmp,odd,buffer,length,sccp && sccp->ignoreUnknownAddrSignals());
	params.addParam(gtName, tmp);
	return true;
    }
    Debug(sccp,DebugWarn,"Failed to decode ITU address!!! short message length");
    return false;
}

static bool decodeAnsiAddress(const SS7SCCP* sccp, NamedList& params,const SCCPParam* param,
	const unsigned char* buffer, unsigned int length, const String& prefix)
{
    unsigned char addressIndicator = *buffer++;
    length--;
    String prName = prefix + param->name;
    while (true) {
	if ((addressIndicator & 0x01) == 0x01) { // Have SSN
	    if (length < 1)
		break;
	    params.addParam(prName + ".ssn",String(*buffer++));
	    length --;
	}
	if ((addressIndicator & 0x02) == 0x02) { // Have Pointcode
	    if (length < 3)
		break;
	    unsigned int pointcode = *buffer++;
	    pointcode |= (*buffer++ << 8);
	    pointcode |= (*buffer++ << 16);
	    length -= 3;
	    params.addParam(prName + ".pointcode",String(pointcode));
	}
	params.addParam(prName + ".route", ((addressIndicator & 0x40) == 0x40) ?
		    YSTRING("ssn") : YSTRING("gt"));
	unsigned char gti = (addressIndicator >> 2) & 0x0f;
	if (!gti) // No Global Title Present
	    return true;
	bool odd = false;
	String tmp;
	String gtName = prName + ".gt";
	if (gti == 0x01) { // GT includes tt, np & es
	    if (length < 2)
		break;
	    params.addParam(gtName + ".translation", String((int)*buffer++));
	    length--;
	    unsigned char npes = *buffer++;
	    unsigned char es = npes & 0x0f;
	    length--;
	    getDictValue(params,gtName + ".plan", npes >> 4, s_numberingPlan);
	    getDictValue(params,gtName + ".encoding", es,s_encodingScheme);
	    switch (es) {
		case 1:
		    odd = true;
		case 2:
		    break;
		default:
		    tmp.hexify((void*)buffer,length,' ');
	    }
	} else if (gti == 0x02) { // GT includes Translation Type
	    if (length < 1)
		break;
	    params.addParam(gtName + ".translation", String((int)*buffer++));
	    length--;
	    tmp.hexify((void*)buffer,length,' ');
	} else {
	    Debug(sccp,DebugMild, "Unable to decode ANSI GT with GTI = %d",gti);
	    return false;
	}
	if (tmp.null())
	    getDigits(tmp,odd,buffer,length,sccp && sccp->ignoreUnknownAddrSignals());
	params.addParam(gtName, tmp);
	return true;
    }
    Debug(sccp,DebugWarn,"Failed to decode ANSI address!!! short message length");
    return false;
}

static bool decodeAddress(const SS7SCCP* sccp, NamedList& paramsList,const SCCPParam* param,
	const unsigned char* buffer, unsigned int length, const String& prefix)
{
    if (length < 1)
	return false;
    if (sccp->ITU())
	return decodeItuAddress(sccp,paramsList,param,buffer,length,prefix);
    else
	return decodeAnsiAddress(sccp,paramsList,param,buffer,length,prefix);
}

static bool decodeData(const SS7SCCP* sccp, SS7MsgSCCP* msg, const unsigned char* buffer, unsigned int length)
{
    DataBlock* data = new DataBlock((void*)buffer,length, false);
    msg->setData(data);
    buffer += length;
    return true;
}

static bool decodeSegmentation(const SS7SCCP* sccp, NamedList& params,const SCCPParam* param,
	const unsigned char* buffer, unsigned int length, const String& prefix)
{
    if (length < 4) {
	DDebug(sccp,DebugNote,"Failed to decode %s parameter! Reason length to short.",param->name);
	return false;
    }
    unsigned char segInfo = *buffer++;
    String prName = prefix + param->name;
    params.addParam(prName + ".FirstSegment", String((segInfo & 0x80) == 0x80));
    params.addParam(prName + ".ProtocolClass", String((segInfo & 0x40) >> 6));
    params.addParam(prName + ".RemainingSegments", String(segInfo & 0x0f));
    unsigned int segLocalReference = *buffer++;
    segLocalReference |= *buffer++ << 8;
    segLocalReference |= *buffer++ << 16;
    params.addParam(prName + ".SegmentationLocalReference", String(segLocalReference));
    params.addParam(prName ,"true");
    return true;
}


// Encode methods

static unsigned char encodeRaw(const SS7SCCP* sccp, SS7MSU& msu,
    unsigned char* buf, const SCCPParam* param, const NamedString* val,
    const NamedList*, const String&)
{
    if (!(param && val))
	return 0;
    DDebug(sccp,DebugInfo,"encodeRaw encoding %s=%s",param->name,val->c_str());
    DataBlock raw;
    if (!raw.unHexify(val->c_str(),val->length(),' ')) {
	DDebug(sccp,DebugMild,"encodeRaw failed: invalid string");
	return 0;
    }
    if (!raw.length() || raw.length() > 254 ||
	(param->size && param->size != raw.length())) {
	DDebug(sccp,DebugMild,"encodeRaw failed: param size=%u data length=%u",
	    param->size,raw.length());
	return 0;
    }
    if (buf) {
	::memcpy(buf,raw.data(),raw.length());
	return raw.length();
    }
    unsigned char size = (unsigned char)raw.length();
    msu.append(&size,1);
    msu += raw;
    return size;
}

// Encoder for fixed length little-endian integer values
static unsigned char encodeInt(const SS7SCCP* sccp, SS7MSU& msu, unsigned char* buf,
    const SCCPParam* param, const NamedString* val, const NamedList*, const String&)
{
    if (!param)
	return 0;
    unsigned int n = param->size;
    if (!n)
	return 0;
    unsigned int v = 0;
    if (val)
	v = val->toInteger((const TokenDict*)param->data);
    DDebug(sccp,DebugAll,"encodeInt encoding %s=%u on %u octets",param->name,v,n);
    if (!buf) {
	unsigned int l = msu.length();
	DataBlock dummy(0,n+1);
	msu += dummy;
	buf = (unsigned char*)msu.getData(l,n+1);
	*buf++ = n & 0xff;
    }
    while (n--) {
	*buf++ = v & 0xff;
	v >>= 8;
    }
    return param->size;
}

static unsigned char encodeProtocolClass(const SS7SCCP* sccp, SS7MSU& msu,
    unsigned char* buf, const SCCPParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    if (!buf) {
	Debug(sccp,DebugWarn,"Request to encode ProtocolClass in a null buffer!!!");
	return 0;
    }
    unsigned char protocolClass = extra->getIntValue(prefix + "ProtocolClass");
    if (protocolClass > 3) {
	Debug(sccp,DebugWarn,"Invalid ProtocolClass value %d, for encoding",protocolClass);
	return 0;
    }
    if (protocolClass < 2) {
	int errorReturn = extra->getIntValue(prefix + "MessageReturn",s_messageReturn);
	protocolClass |= errorReturn << 4;
    }
    *buf = protocolClass;
    return 1;
}

static DataBlock* setDigits(const char* val)
{
    if (!val)
	return 0;
    unsigned char buf[32];
    unsigned int len = 0;
    bool odd = false;
    while (val && (len < sizeof(buf))) {
	char c = *val++;
	if (!c)
	    break;
	unsigned char n = 0;
	if (('0' <= c) && (c <= '9'))
	    n = c - '0';
	else switch (c) {
	    case 'A':
	    case 'a':
		n = 10;
		break;
	    case 'B':
	    case 'b':
		n = 11;
		break;
	    case 'C':
	    case 'c':
		n = 12;
		break;
	    case 'D':
	    case 'd':
		n = 13;
		break;
	    case 'E':
	    case 'e':
		n = 14;
		break;
	    case 'F':
	    case 'f':
		n = 15;
		break;
	    default:
		continue;
	}
	odd = !odd;
	if (odd)
	    buf[len] = n;
	else
	    buf[len++] |= (n << 4);
    }
    if (odd)
	len++;
    DataBlock* tmp = new DataBlock(buf,len);
    return tmp;
}

static unsigned char encodeItuAddress(const SS7SCCP* sccp, SS7MSU& msu,
    unsigned char* buf, const SCCPParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    unsigned char length = 1;
    unsigned char data[32];
    unsigned char addressIndicator = 0;
    String preName(prefix + param->name);
    bool havePC = extra->getParam(preName + ".pointcode") != 0;
    if (havePC) {
	int pointcode = extra->getIntValue(preName + ".pointcode",0);
	addressIndicator |= 0x01;
	data[++length] = pointcode & 0xff;
	data[++length] = (pointcode >> 8) & 0x3f;
    }
    bool haveSSN = extra->getParam(preName + ".ssn") != 0;
    if (haveSSN) {
	int ssn = extra->getIntValue(preName + ".ssn",0);
	addressIndicator |= 0x02;
	data[++length] = ssn;
    }
    NamedString* route = extra->getParam(preName + ".route");
    if (route && *route == YSTRING("ssn")) { // Marck route on SSN
	if (param->name == YSTRING("CalledPartyAddress") && !haveSSN)
	    Debug(sccp,DebugNote,"Request to route on SSN with no ssn present!");
	addressIndicator |= 0x40;
    }
    NamedString* gtNr = YOBJECT(NamedString,extra->getParam(preName + ".gt"));
    if (!gtNr) { // No Global Title present!!!
	if ((addressIndicator & 0x40) == 0)
	    DDebug(sccp,DebugNote,"RouteIndicator set on global title. But no global title present!!!");
	data[1] = addressIndicator;
	data[0] = length;
	DataBlock tmp(data,length + 1,false);
	msu += tmp;
	tmp.clear(false);
	return data[0];
    }
    NamedString* nature = YOBJECT(NamedString,extra->getParam(preName + ".gt.nature"));
    NamedString* translation = YOBJECT(NamedString,extra->getParam(preName + ".gt.translation"));
    NamedString* plan = YOBJECT(NamedString,extra->getParam(preName + ".gt.plan"));
    NamedString* encoding = YOBJECT(NamedString,extra->getParam(preName + ".gt.encoding"));
    bool odd = false;
    DataBlock* digits = 0;
    if (nature && !translation) { // GT = 0x01
	addressIndicator |= 0x04;
	int nai = nature->toInteger(s_nai);
	odd = (gtNr->length() % 2) ? false : true;
	if (!odd)
	    nai |= 0x80;
	data[++length] = nai & 0xff;
    } else if (translation && !(plan && encoding) && !nature) { // GT = 0x02
	addressIndicator |= 0x08;
	int tt = translation->toInteger();
	data[++length] = tt & 0xff;
	digits = new DataBlock();
	if (!digits->unHexify(*gtNr,gtNr->length(),' ')) {
	    Debug(sccp,DebugInfo,"Setting unknown odd/even number of digits!!");
	    TelEngine::destruct(digits);
	}
    } else if (translation && plan && encoding && !nature) { // GT = 0x03
	addressIndicator |= 0x0c;
	int tt = translation->toInteger();
	data[++length] = tt & 0xff;
	int np = plan->toInteger(s_numberingPlan);
	int es = encoding->toInteger(s_encodingScheme);
	switch (es) {
	    case 1:
	    case 2:
		odd = (gtNr->length() % 2 == 1);
		es = odd ? 1 : 2;
		break;
	    default:
		digits = new DataBlock();
		if (!digits->unHexify(*gtNr,gtNr->length(),' ')) {
		    Debug(sccp,DebugInfo,"Setting unknown odd/even number of digits!!");
		    TelEngine::destruct(digits);
		}
	}
	data[++length] = ((np & 0x0f) << 4) | (es & 0x0f);
    } else if (translation && plan && encoding && nature) { // GT = 0x04
	addressIndicator |= 0x10;
	int tt = translation->toInteger();
	data[++length] = tt & 0xff;
	int np = plan->toInteger(s_numberingPlan);
	int es = encoding->toInteger(s_encodingScheme);
	switch (es) {
	    case 1:
	    case 2:
		odd = (gtNr->length() % 2 == 1);
		es = odd ? 1 : 2;
		break;
	    default:
		digits = new DataBlock();
		if (!digits->unHexify(*gtNr,gtNr->length(),' ')) {
		    Debug(sccp,DebugInfo,"Setting unknown odd/even number of digits!!");
		    TelEngine::destruct(digits);
		}
	}
	data[++length] = ((np & 0x0f) << 4) | (es & 0x0f);
	int nai = nature->toInteger(s_nai);
	data[++length] = nai & 0x7f;
    } else {
	Debug(sccp,DebugWarn,"Can not encode ITU GTI. Unknown GTI value for : nai= %s, Plan & Encoding = %s, TranslationType = %s",
	      nature? "present" : "missing",(plan && encoding)? "present" : "missing",translation ? "present" : "missing");
	return 0;
    }
    data[1] = addressIndicator;
    if (!digits && !(digits = setDigits(*gtNr))) {
	Debug(DebugWarn,"Failed to encode digits!!");
	return 0;
    }
    data[0] = length + digits->length();
    DataBlock tmp(data,length + 1,false);
    msu += tmp;
    msu += *digits;
    tmp.clear(false);
    TelEngine::destruct(digits);
    return data[0];

}

static unsigned char encodeAnsiAddress(const SS7SCCP* sccp, SS7MSU& msu,
    unsigned char* buf, const SCCPParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    unsigned char length = 1;
    unsigned char data[32];
    unsigned char addressIndicator = 0;
    String preName(prefix + param->name);
    bool havePC = extra->getParam(preName + ".pointcode") != 0;
    bool haveSSN = extra->getParam(preName + ".ssn") != 0;
    if (haveSSN) {
	int ssn = extra->getIntValue(preName + ".ssn",0);
	addressIndicator |= 0x01;
	data[++length] = ssn;
    }
    addressIndicator |= 0x80; // Mark the 8 bit from address indicator to national use
    if (havePC) {
	int pointcode = extra->getIntValue(preName + ".pointcode",0);
	addressIndicator |= 0x02;
	data[++length] = pointcode  & 0xff;
	data[++length] = (pointcode >> 8) & 0xff;
	data[++length] = (pointcode >> 16) & 0xff;
    }
    NamedString* route = extra->getParam(preName + ".route");
    if (route && *route == YSTRING("ssn")) { // Marck route on SSN
	if (param->name == YSTRING("CalledPartyAddress") && !haveSSN)
	    Debug(sccp,DebugNote,"Request to route on SSN with no ssn present!");
	addressIndicator |= 0x40;
    }
    NamedString* gtNr = YOBJECT(NamedString,extra->getParam(preName + ".gt"));
    if (!gtNr) { // No Global Title present!!!
	if ((addressIndicator & 0x40) == 0)
	    DDebug(sccp,DebugNote,"RouteIndicator set on global title. But no global title present!!!");
	data[1] = addressIndicator;
	data[0] = length & 0xff;
	DataBlock tmp(data,length + 1,false);
	msu += tmp;
	tmp.clear(false);
	return data[0];
    }
    NamedString* translation = YOBJECT(NamedString,extra->getParam(preName + ".gt.translation"));
    NamedString* plan = YOBJECT(NamedString,extra->getParam(preName + ".gt.plan"));
    NamedString* encoding = YOBJECT(NamedString,extra->getParam(preName + ".gt.encoding"));
    DataBlock* digits = 0;
    bool odd = false;
    if (translation && !(plan && encoding)) { // GT = 0x02
	addressIndicator |= 0x08;
	int tt = translation->toInteger();
	data[++length] = tt & 0xff;
	digits = new DataBlock();
	if (!digits->unHexify(*gtNr,gtNr->length(),' ')) {
	    Debug(sccp,DebugInfo,"Setting unknown odd/even number of digits!!");
	    TelEngine::destruct(digits);
	}
    } else if (translation && plan && encoding) { // GT = 0x01
	addressIndicator |= 0x04;
	int tt = translation->toInteger();
	data[++length] = tt & 0xff;
	int np = plan->toInteger(s_numberingPlan);
	int es = encoding->toInteger(s_encodingScheme);
	switch (es) {
	    case 1:
	    case 2:
		odd = (gtNr->length() % 2 == 1);
		es = odd ? 1 : 2;
		break;
	    default:
		digits = new DataBlock();
		if (!digits->unHexify(*gtNr,gtNr->length(),' ')) {
		    Debug(sccp,DebugInfo,"Setting unknown odd/even number of digits!!");
		    TelEngine::destruct(digits);
		}
	}
	data[++length] = ((np & 0x0f) << 4) | (es & 0x0f);
    } else {
	Debug(sccp,DebugWarn,"Can not encode ANSI GTI. Unknown GTI value for : Plan & Encoding = %s, TranslationType = %s",
	      (plan && encoding)? "present" : "missing",translation ? "present" : "missing");
	return 0;
    }
    data[1] = addressIndicator;
    if (!digits && !(digits = setDigits(*gtNr))) {
	Debug(DebugWarn,"Failed to encode digits!!");
	return 0;
    }
    data[0] = length + digits->length();
    DataBlock tmp(data,length + 1,false);
    msu += tmp;
    msu += *digits;
    tmp.clear(false);
    TelEngine::destruct(digits);
    return data[0];

}

static unsigned char encodeAddress(const SS7SCCP* sccp, SS7MSU& msu,
    unsigned char* buf, const SCCPParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    if (!param || buf || param->size)
	return 0;
    if (sccp->ITU())
	return encodeItuAddress(sccp,msu,buf,param,val,extra,prefix);
    else
	return encodeAnsiAddress(sccp,msu,buf,param,val,extra,prefix);
    return 0;
}

static unsigned char encodeSegmentation(const SS7SCCP* sccp, SS7MSU& msu,
    unsigned char* buf, const SCCPParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    String preName(prefix + param->name);
    unsigned char length = 1;
    unsigned char data[6];
    unsigned char segInfo = 0;
    int leftSgm = extra->getIntValue(preName + ".RemainingSegments",0);
    segInfo |= leftSgm & 0x0f;
    int protocolClass = extra->getIntValue(preName + ".ProtocolClass",0);
    if (protocolClass)
	segInfo |= 0x40;
    bool firstSgm = extra->getBoolValue(preName + ".FirstSegment",false);
    if (firstSgm)
	segInfo |= 0x80;
    data[1] = segInfo;
    unsigned int sgmLocalReference = extra->getIntValue(preName + ".SegmentationLocalReference",0);
    data[++length] = sgmLocalReference & 0xff;
    data[++length] = sgmLocalReference >> 8 & 0xff;
    data[++length] = sgmLocalReference >> 16 & 0xff;

    data[0] = length & 0xff;
    DataBlock tmp(data,length + 1,false);
    msu += tmp;
    tmp.clear(false);
    return data[0];
}

static unsigned char encodeImportance(const SS7SCCP* sccp, SS7MSU& msu,
    unsigned char* buf, const SCCPParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    unsigned char data[6];
    data[0] = 1;
    int importance = extra->getIntValue(prefix + param->name);
    data[1] = importance & 0x07;
    DataBlock tmp(data,2,false);
    msu += tmp;
    tmp.clear(false);
    return data[0];
}

static unsigned int encodeData(const SS7SCCP* sccp, SS7MSU& msu, SS7MsgSCCP* msg)
{
    if (!msg) {
	DDebug(sccp,DebugNote,"Request to encode data for a null message");
	return 0;
    }
    DataBlock* data = msg->getData();
    if (!data) {
	DDebug(sccp,DebugNote,"Request to encode message %s with null data",
	    SS7MsgSCCP::lookup(msg->type()));
	return 0;
    }
    if (data->length() < 2) {
	DDebug(sccp,DebugNote,"Request to encode message %s with short data",
	    SS7MsgSCCP::lookup(msg->type()));
	return 0;
    }
    unsigned int length = data->length();
    unsigned char header[2];
    DataBlock tmp;
    if (msg->isLongDataMessage()) {
	header[0] = length & 0xff;
	header[1] = length >> 8 & 0xff;
	tmp.assign(header,2,false);
    } else {
	header[0] = length & 0xff;
	tmp.assign(header,1,false);
    }
    msu += tmp;
    msu += *data;
    tmp.clear(false);
    return length;
}

static unsigned char encodeCause(const SS7SCCP* sccp, SS7MSU& msu, unsigned char* buf,
    const SCCPParam* param, const NamedString* val, const NamedList*, const String&)
{
    if (!param)
	return 0;
    unsigned int n = param->size;
    if (!n)
	return 0;
    unsigned int v = 0;
    if (val)
	v = val->toInteger();
    DDebug(sccp,DebugAll,"encodeCause encoding %s=%u on %u octets",param->name,v,n);
    if (!buf) {
	unsigned int l = msu.length();
	DataBlock dummy(0,n+1);
	msu += dummy;
	buf = (unsigned char*)msu.getData(l,n+1);
	*buf++ = n & 0xff;
    }
    while (n--) {
	*buf++ = v & 0xff;
	v >>= 8;
    }
    return param->size;
}

#define MAKE_NAME(x) { #x, SS7MsgSCCP::x }
static const TokenDict s_names[] = {
    // this list must be kept in synch with the header
    MAKE_NAME(CR),
    MAKE_NAME(CC),
    MAKE_NAME(CREF),
    MAKE_NAME(RLSD),
    MAKE_NAME(RLC),
    MAKE_NAME(DT1),
    MAKE_NAME(DT2),
    MAKE_NAME(AK),
    MAKE_NAME(UDT),
    MAKE_NAME(UDTS),
    MAKE_NAME(ED),
    MAKE_NAME(EA),
    MAKE_NAME(RSR),
    MAKE_NAME(RSC),
    MAKE_NAME(ERR),
    MAKE_NAME(IT),
    MAKE_NAME(XUDT),
    MAKE_NAME(XUDTS),
    MAKE_NAME(LUDT),
    MAKE_NAME(LUDTS),
    { 0, 0 }
};
#undef MAKE_NAME

#define MAKE_PARAM(p,s,a,d,t) { SS7MsgSCCP::p,s,#p,a,d,t }
static const SCCPParam s_paramDefs[] = {
//             name                           len decoder            encoder            table                  References

    // Standard parameters
    MAKE_PARAM(DestinationLocalReference,      3,decodeInt,          encodeInt,          0),                    // ITU:Q.713 3.2  | Ansi: 1000112.3 3.2
    MAKE_PARAM(SourceLocalReference,           3,decodeInt,          encodeInt,          0),                    // ITU:Q.713 3.3  | Ansi: 1000112.3 3.3
    MAKE_PARAM(CalledPartyAddress,             0,decodeAddress,      encodeAddress,      0),                    // ITU:Q.713 3.4  | Ansi: 1000112.3 3.4
    MAKE_PARAM(CallingPartyAddress,            0,decodeAddress,      encodeAddress,      0),                    // ITU:Q.713 3.5  | Ansi: 1000112.3 3.5
    MAKE_PARAM(ProtocolClass,                  1,decodeProtocolClass,encodeProtocolClass,0),                    // ITU:Q.713 3.6  | Ansi: 1000112.3 3.6
    MAKE_PARAM(Segmenting,                     0,0,                  0,                  0),                    // ITU:Q.713 3.7  | Ansi: 1000112.3 3.7
    MAKE_PARAM(ReceiveSequenceNumber,          0,0,                  0,                  0),                    // ITU:Q.713 3.8  | Ansi: 1000112.3 3.8
    MAKE_PARAM(Sequencing,                     0,0,                  0,                  0),                    // ITU:Q.713 3.9  | Ansi: 1000112.3 3.9
    MAKE_PARAM(Credit,                         0,0,                  0,                  0),                    // ITU:Q.713 3.10 | Ansi: 1000112.3 3.10
    MAKE_PARAM(ReleaseCause,                   1,decodeCause,        encodeCause,        0),                    // ITU:Q.713 3.11 | Ansi: 1000112.3 3.11
    MAKE_PARAM(ReturnCause,                    1,decodeCause,        encodeCause,        0),                    // ITU:Q.713 3.12 | Ansi: 1000112.3 3.12
    MAKE_PARAM(ResetCause,                     1,decodeCause,        encodeCause,        0),                    // ITU:Q.713 3.13 | Ansi: 1000112.3 3.13
    MAKE_PARAM(ErrorCause,                     1,decodeCause,        encodeCause,        0),                    // ITU:Q.713 3.14 | Ansi: 1000112.3 3.14
    MAKE_PARAM(RefusalCause,                   1,decodeCause,        encodeCause,        0),                    // ITU:Q.713 3.15 | Ansi: 1000112.3 3.15
    MAKE_PARAM(Data,                           0,0,                  0,                  0),                    // ITU:Q.713 3.16 | Ansi: 1000112.3 3.16
    MAKE_PARAM(Segmentation,                   4,decodeSegmentation, encodeSegmentation, 0),                    // ITU:Q.713 3.17 | Ansi: 1000112.3 3.18
    MAKE_PARAM(HopCounter,                     1,decodeInt,          encodeInt,          0),                    // ITU:Q.713 3.18 | Ansi: 1000112.3 3.17
    MAKE_PARAM(Importance,                     0,decodeImportance,   encodeImportance,   0),                    // ITU:Q.713 3.19
    MAKE_PARAM(LongData,                       0,0,                  0,                  0),                    // ITU:Q.713 3.20 | Ansi: 1000112.3 3.20
    MAKE_PARAM(MessageTypeInterworking,        0,0,                  0,                  0),                    // Ansi: 1000112.3 3.22
    MAKE_PARAM(INS,                            0,0,                  0,                  0),                    // Ansi: 1000112.3 3.21
    MAKE_PARAM(ISNI,                           0,0,                  0,                  0),                    // Ansi: 1000112.3 3.19
    { SS7MsgSCCP::EndOfParameters, 0, 0, 0, 0, 0 }
};
#undef MAKE_PARAM

// Descriptor of SCCP message
static const MsgParams s_common_params[] = {
    { SS7MsgSCCP::CR, true,
	{
	    SS7MsgSCCP::SourceLocalReference,
	    SS7MsgSCCP::ProtocolClass,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::CalledPartyAddress,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::CC, true,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::SourceLocalReference,
	    SS7MsgSCCP::ProtocolClass,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::CREF, true,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::RefusalCause,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::RLSD, true,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::SourceLocalReference,
	    SS7MsgSCCP::ReleaseCause,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::RLC, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::SourceLocalReference,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::DT1, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::Sequencing,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::Data,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::DT2, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::Sequencing,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::Data,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::AK, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::ReceiveSequenceNumber,
	    SS7MsgSCCP::Credit,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::UDT, false,
	{
	    SS7MsgSCCP::ProtocolClass,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::CalledPartyAddress,
	    SS7MsgSCCP::CallingPartyAddress,
	    SS7MsgSCCP::Data,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::UDTS, false,
	{
	    SS7MsgSCCP::ReturnCause,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::CalledPartyAddress,
	    SS7MsgSCCP::CallingPartyAddress,
	    SS7MsgSCCP::Data,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::ED, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::Data,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::EA, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::RSR, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::SourceLocalReference,
	    SS7MsgSCCP::ResetCause,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::RSC, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::SourceLocalReference,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::ERR, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::ErrorCause,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::IT, false,
	{
	    SS7MsgSCCP::DestinationLocalReference,
	    SS7MsgSCCP::SourceLocalReference,
	    SS7MsgSCCP::ProtocolClass,
	    SS7MsgSCCP::Sequencing,
	    SS7MsgSCCP::Credit,
	SS7MsgSCCP::EndOfParameters,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::XUDT, true,
	{
	    SS7MsgSCCP::ProtocolClass,
	    SS7MsgSCCP::HopCounter,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::CalledPartyAddress,
	    SS7MsgSCCP::CallingPartyAddress,
	    SS7MsgSCCP::Data,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::LUDT, true,
	{
	    SS7MsgSCCP::ProtocolClass,
	    SS7MsgSCCP::HopCounter,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::CalledPartyAddress,
	    SS7MsgSCCP::CallingPartyAddress,
	    SS7MsgSCCP::LongData,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::XUDTS, true,
	{
	    SS7MsgSCCP::ReturnCause,
	    SS7MsgSCCP::HopCounter,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::CalledPartyAddress,
	    SS7MsgSCCP::CallingPartyAddress,
	    SS7MsgSCCP::Data,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::LUDTS, true,
	{
	    SS7MsgSCCP::ReturnCause,
	    SS7MsgSCCP::HopCounter,
	SS7MsgSCCP::EndOfParameters,
	    SS7MsgSCCP::CalledPartyAddress,
	    SS7MsgSCCP::CallingPartyAddress,
	    SS7MsgSCCP::LongData,
	SS7MsgSCCP::EndOfParameters
	}
    },
    { SS7MsgSCCP::Unknown, false, { SS7MsgSCCP::EndOfParameters } }
};


static bool decodeParam(const SS7SCCP* sccp, NamedList& list, const SCCPParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    DDebug(sccp,DebugAll,"decodeParam(%p,%p,%p,%u) type=0x%02x, size=%u, name='%s'",
	&list,param,buf,len,param->type,param->size,param->name);
    if (param->size && (param->size != len))
	return false;
    if (param->decoder)
	return param->decoder(sccp,list,param,buf,len,prefix);
    return decodeRaw(sccp,list,param,buf,len,prefix);
}

// Generic encode helper function for a single mandatory parameter
static unsigned char encodeParam(const SS7SCCP* sccp, SS7MSU& msu,
    const SCCPParam* param, const NamedList* params, ObjList& exclude,
    const String& prefix, unsigned char* buf = 0)
{
    DDebug(sccp,DebugAll,"encodeParam (mand) (%p,%p,%p,%p) type=0x%02x, size=%u, name='%s'",
	&msu,param,params,buf,param->type,param->size,param->name);
    // variable length must not receive fixed buffer
    if (buf && !param->size)
	return 0;
    NamedString* val = params ? params->getParam(prefix+param->name) : 0;
    if (val)
	exclude.append(val)->setDelete(false);
    if (param->encoder)
	return param->encoder(sccp,msu,buf,param,val,params,prefix);
    return encodeRaw(sccp,msu,buf,param,val,params,prefix);
}

// Generic encode helper for a single optional parameter
static unsigned char encodeParam(const SS7SCCP* sccp, SS7MSU& msu,
    const SCCPParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    DDebug(sccp,DebugAll,"encodeParam (opt) (%p,%p,%p,%p) type=0x%02x, size=%u, name='%s'",
	&msu,param,val,extra,param->type,param->size,param->name);
    // add the parameter type now but remember the old length
    unsigned int len = msu.length();
    unsigned char tmp = param->type;
    msu.append(&tmp,1);

    unsigned char size = 0;
    if (param->encoder)
	size = param->encoder(sccp,msu,0,param,val,extra,prefix);
    else
	size = encodeRaw(sccp,msu,0,param,val,extra,prefix);
    if (!size) {
	Debug(sccp,DebugMild,"Unwinding type storage for failed parameter %s",param->name);
	msu.truncate(len);
    }
    return size;
}

// Locate the description for a parameter by type
static const SCCPParam* getParamDesc(SS7MsgSCCP::Parameters type)
{
    const SCCPParam* param = s_paramDefs;
    for (; param->type != SS7MsgSCCP::EndOfParameters; param++) {
	if (param->type == type)
	    return param;
    }
    return 0;
}

// Locate the description for a parameter by name
static const SCCPParam* getParamDesc(const String& name)
{
    const SCCPParam* param = s_paramDefs;
    for (; param->type != SS7MsgSCCP::EndOfParameters; param++) {
	if (name == param->name)
	    return param;
    }
    return 0;
}

// Locate the description table for a message according to protocol type
static const MsgParams* getSccpParams(SS7MsgSCCP::Type msg)
{
    const MsgParams* params = 0;
    for (params = s_common_params; params->type != SS7MsgSCCP::Unknown; params++) {
	if (params->type == msg)
	    return params;
    }
    return 0;
}


const TokenDict* SS7MsgSCCP::names()
{
    return s_names;
}

SS7MsgSCCP::~SS7MsgSCCP()
{
    if (m_data) {
	m_data->clear(false);
	TelEngine::destruct(m_data);
    }
}

void SS7MsgSCCP::toString(String& dest, const SS7Label& label, bool params,
	const void* raw, unsigned int rawLen) const
{
    const char* enclose = "\r\n-----";
    dest = enclose;
    if (raw && rawLen) {
	String tmp;
	tmp.hexify((void*)raw,rawLen,' ');
	dest << "  " << tmp;
    }
    if (params) {
	unsigned int n = m_params.length();
	for (unsigned int i = 0; i < n; i++) {
	    NamedString* s = m_params.getParam(i);
	    if (s)
		dest << "\r\n  " << s->name() << "='" << *s << "'";
	}
    }
    dest << enclose;
}

/**
 * SS7MsgSccpReassemble
 */

SS7MsgSccpReassemble::SS7MsgSccpReassemble(SS7MsgSCCP* msg, const SS7Label& label,
	    unsigned int timeToLive)
    : SS7MsgSCCP(msg->type()), m_label(label), m_callingPartyAddress(""),
    m_segmentationLocalReference(0), m_timeout(0), m_remainingSegments(0),
    m_firstSgmDataLen(0)
{
    m_callingPartyAddress.copySubParams(msg->params(),
	    YSTRING("CallingPartyAddress."));
    m_segmentationLocalReference = msg->params().getIntValue(
	    YSTRING("Segmentation.SegmentationLocalReference"));
    m_timeout = Time::msecNow() + timeToLive;
    m_remainingSegments = msg->params().getIntValue(
	    YSTRING("Segmentation.RemainingSegments"));
    setData(new DataBlock(*msg->getData()));
    params().copyParams(msg->params());
    m_firstSgmDataLen = getData()->length();
    // Update protocol class
    if (msg->params().getIntValue(
	    YSTRING("Segmentation.ProtocolClass"), -1) > 0)
	params().setParam("ProtocolClass",msg->params().getValue(
		YSTRING("Segmentation.ProtocolClass")));

}

SS7MsgSccpReassemble::~SS7MsgSccpReassemble()
{
    TelEngine::destruct(extractData());
}

bool SS7MsgSccpReassemble::canProcess(const SS7MsgSCCP* msg, const SS7Label& label)
{
    if (!compareLabel(m_label,label))
	return false;
    if (m_segmentationLocalReference !=
	    (u_int32_t)msg->params().getIntValue(YSTRING("Segmentation.SegmentationLocalReference")))
	return false;
    NamedList address("");
    address.copySubParams(msg->params(),YSTRING("CallingPartyAddress."));
    return compareNamedList(address,m_callingPartyAddress);
}

SS7MsgSccpReassemble::Return SS7MsgSccpReassemble::appendSegment(SS7MsgSCCP* msg, const SS7Label& label)
{
    if (!msg)
	return Rejected;
    if (!canProcess(msg,label))
	return Rejected;
    if ((m_remainingSegments - 1) != msg->params().getIntValue(YSTRING("Segmentation.RemainingSegments"),-1)) {
	DDebug("SS7MsgSccpReassemble",DebugNote,"Received out of sequence segment %d : %d",
	       msg->params().getIntValue(YSTRING("Segmentation.RemainingSegments"),-1),m_remainingSegments);
	return Error;
    }
    m_remainingSegments--;
    if (m_firstSgmDataLen < msg->getData()->length()) {
	DDebug("SS7MsgSccpReassemble",DebugNote,"Received data segment bigger than first data segment");
	return Error;
    }
    getData()->append(*msg->getData());
    return m_remainingSegments == 0 ? Finished : Accepted;
}

/**
 * class SCCP
 */

SCCP::SCCP()
    : m_translatorLocker(true,s_sccpTranslatorMutex), m_usersLocker(true,s_sccpMutexName), m_translator(0)
{
}

SCCP::~SCCP()
{
    DDebug(this,DebugAll,"Destroying SCCP [%p]",this);
    // If we have undetached users scream as hard as we can
    if (m_users.skipNull())
	Debug(this,DebugCrit,"Destroying SCCP with %d undetached users!!!",m_users.count());
    if (m_translator)
	Debug(this,DebugCrit,"Destroying SCCP with a valid translator!!!");
}

void SCCP::attach(SCCPUser* user)
{
    if (!user)
	return;
    DDebug(this,DebugAll,"Attaching user (%p)",user);
    Lock lock(m_usersLocker);
    // Detach it if already exists
    detach(user);
    // Append the user
    m_users.append(user)->setDelete(false);
}

void SCCP::detach(SCCPUser* user)
{
    if (!user)
	return;
    Lock lock(m_usersLocker);
    m_users.remove(user,false);
}

void SCCP::attachGTT(GTT* gtt)
{
    Lock lock(m_translatorLocker);
    if (gtt == m_translator)
	return;
    m_translator = gtt;
}

NamedList* SCCP::translateGT(const NamedList& params, const String& prefix, const String& nextPrefix)
{
    Lock lock(m_translatorLocker);
    if (!m_translator) {
	Debug(this,isEndpoint() ? DebugInfo : DebugMild,
	      "Failed to translate Global Title! Reason: No GTT attached to sccp [%p]",this);
	return 0;
    }
    RefPointer<GTT> translator = m_translator;
    if (!translator)
	return 0;
    lock.drop();
    return translator->routeGT(params,prefix,nextPrefix);
}

HandledMSU SCCP::pushMessage(DataBlock& data, NamedList& params, int ssn)
{
    m_usersLocker.lock();
    ListIterator iter(m_users);
    SCCPUser* usr = 0;
    params.setParam("ssn",String(ssn));
    while ((usr = YOBJECT(SCCPUser,iter.get()))) {
	RefPointer<SCCPUser> pointer = usr;
	if (!pointer)
	    continue;
	m_usersLocker.unlock();
	HandledMSU handled = pointer->receivedData(data,params);
	switch (handled) {
	    case HandledMSU::Accepted:
	    case HandledMSU::Failure:
		return handled;
	    case HandledMSU::Rejected:
	    default:
		break; // break switch
	}
	m_usersLocker.lock();
    }
    m_usersLocker.unlock();
    DDebug(this,DebugInfo,"SCCP data message was not processed by any user!");
    return HandledMSU::Unequipped;
}

HandledMSU SCCP::notifyMessage(DataBlock& data, NamedList& params, int ssn)
{
    m_usersLocker.lock();
    ListIterator iter(m_users);
    SCCPUser* usr = 0;
    params.setParam("ssn",String(ssn));
    while ((usr = YOBJECT(SCCPUser,iter.get()))) {
	RefPointer<SCCPUser> pointer = usr;
	if (!pointer)
	    continue;
	m_usersLocker.unlock();
	HandledMSU handled = pointer->notifyData(data,params);
	switch (handled) {
	    case HandledMSU::Accepted:
	    case HandledMSU::Failure:
		return handled;
	    case HandledMSU::Rejected:
	    default:
		break; // break switch
	}
	m_usersLocker.lock();
    }
    m_usersLocker.unlock();
    DDebug(this,DebugAll,"SCCP notify message was not processed by any user!");
    return HandledMSU::Unequipped;
}

bool SCCP::managementMessage(Type type, NamedList& params)
{
    m_usersLocker.lock();
    ListIterator iter(m_users);
    bool ret = false;
    SCCPUser* usr = 0;
    while ((usr = YOBJECT(SCCPUser,iter.get()))) {
	RefPointer<SCCPUser> pointer = usr;
	if (!pointer)
	    continue;
	m_usersLocker.unlock();
	if (pointer->managementNotify(type,params))
	    ret = true;
	m_usersLocker.lock();
    }
    m_usersLocker.unlock();
    return ret;
}

int SCCP::sendMessage(DataBlock& data, const NamedList& params)
{
    Debug(this,DebugStub,"Please implement SCCP sendMessage");
    return false;
}

bool SCCP::managementStatus(Type type, NamedList& params)
{
    DDebug(this,DebugStub,"Please implement SCCP::managementStatus()!!");
    return false;
}

void SCCP::resolveGTParams(SS7MsgSCCP* msg, const NamedList* gtParams)
{
    if (!msg || !gtParams)
	return;
    msg->params().clearParam(YSTRING("CalledPartyAddress"),'.');
    for (unsigned int i = 0;i < gtParams->length();i++) {
	NamedString* val = gtParams->getParam(i);
	if (val && (val->name().startsWith("gt") || val->name() == YSTRING("pointcode") ||
		val->name() == YSTRING("ssn") || val->name() == YSTRING("route")))
	    msg->params().setParam("CalledPartyAddress." + val->name(),*val);
    }
    NamedString* param = 0;
    if ((param = gtParams->getParam(YSTRING("sccp"))))
	msg->params().setParam(param->name(),*param);
    if (!gtParams->hasSubParams(YSTRING("CallingPartyAddress.")))
	return;
    msg->params().clearParam(YSTRING("CallingPartyAddress"),'.');
    msg->params().copySubParams(*gtParams,YSTRING("CallingPartyAddress."),false);
}

/**
 * class SCCPUser
 */

SCCPUser::SCCPUser(const NamedList& config)
    : SignallingComponent(config,&config),
      m_sccp(0), m_sccpMutex(true,s_userMutexName), m_sls(-1)
{
    String tmp;
    config.dump(tmp,"\r\n  ",'\'',true);
    DDebug(DebugAll,"SCCPUser::SCCPUser(%s)",tmp.c_str());
}

SCCPUser::~SCCPUser()
{
    DDebug(this,DebugAll,"Destroying SCCPUser [%p]",this);
}

void SCCPUser::destroyed()
{
    Lock lock(m_sccpMutex);
    if (m_sccp)
	attach(0);
    lock.drop();
    SignallingComponent::destroyed();
}

void SCCPUser::attach(SCCP* sccp)
{
    Lock lock(m_sccpMutex);
    if (!sccp) {
	if (!m_sccp) {
	    DDebug(this,DebugNote,"Request to attach null sccp!!! ");
	    return;
	}
	m_sccp->detach(this);
	TelEngine::destruct(m_sccp);
	return;
    }
    if (m_sccp == sccp) {
	sccp->deref();
	DDebug(this,DebugInfo,"Requesting to attach the same sccp (%p)",m_sccp);
	return;
    }

    SCCP* temp = m_sccp;
    m_sccp = sccp;
    // Do not ref the sccp because we already have an reference
    m_sccp->attach(this);
    // Destruct the old sccp
    if (temp) {
	temp->detach(this);
	TelEngine::destruct(temp);
	temp = 0;
    }
}

bool SCCPUser::initialize(const NamedList* config)
{
    DDebug(this,DebugInfo,"SCCPUser::initialize(%p) [%p]",config,this);
    if (engine()) {
	NamedList params("sccp");
	if (!resolveConfig(YSTRING("sccp"),params,config))
	    params.addParam("local-config","true");
	// NOTE SS7SCCP is created on demand!!!
	// engine ->build method will search for the requested sccc and
	// if it was found will return it with the ref counter incremented
	// if it wasn't found the refcounter will be 1
	// For this behavior SCCPUser attach method will not reference the sccp
	// pointer instead will use the reference of engine build
	if (params.toBoolean(true))
	    attach(YOBJECT(SCCP,engine()->build("SCCP",params,true)));
    } else
	Debug(this,DebugWarn,"SccpUser::initialize() can not attach sccp; null SigEngine!");
    return m_sccp != 0;
}

bool SCCPUser::sendData(DataBlock& data, NamedList& params)
{
    if (!m_sccp) {
	Debug(this,DebugMild,"Can not send data! No Sccp attached!");
	return false;
    }
    bool sequenceControl = params.getBoolValue("sequenceControl",false);
    params.addParam("ProtocolClass",(sequenceControl ? "1" : "0"));
    int sls = params.getIntValue("sls",-1);
    if (sls < 0) {
	// Preserve the sls only if sequence control is requested
	if (sequenceControl)
	    sls = m_sls;
	if (sls < 0)
	    sls = Random::random() & 0xff;
    }
    else
	sls &= 0xff;
    params.setParam("sls", String(sls));
    if (sccp()->sendMessage(data,params) < 0)
	return false;
    m_sls = sls; // Keep the last SLS sent
    return true;
}

bool SCCPUser::sccpNotify(SCCP::Type type, NamedList& params)
{
    if (!m_sccp) {
	Debug(this,DebugMild,"Can not send data! No Sccp attached!");
	return false;
    }
    return sccp()->managementStatus(type,params);
}

HandledMSU SCCPUser::receivedData(DataBlock& data, NamedList& params)
{
    Debug(DebugStub,"Please implement SCCPUser::receivedData(DataBlock& data, const NamedList& params)");
    return 0;
}

HandledMSU SCCPUser::notifyData(DataBlock& data, NamedList& params)
{
    Debug(DebugStub,"Please implement SCCPUser::notifyData(DataBlock& data, const NamedList& params)");
    return 0;
}

bool SCCPUser::managementNotify(SCCP::Type type, NamedList& params)
{
    Debug(this,DebugStub,"Please implement SCCPUser::managementNotify()");
    return false;
}

/**
 * class GTT
 */

GTT::GTT(const NamedList& config)
    : SignallingComponent(config.safe("GTT"),&config,"ss7-gtt"),
      m_sccp(0)
{
}

GTT::~GTT()
{
    if (m_sccp) {
	m_sccp->attachGTT(0);
	TelEngine::destruct(m_sccp);
	m_sccp = 0;
    }
}

bool GTT::initialize(const NamedList* config)
{
    DDebug(this,DebugInfo,"GTT::initialize(%p) [%p]",config,this);
    if (engine()) {
	NamedList params("sccp");
	if (!resolveConfig(YSTRING("sccp"),params,config))
	    params.addParam("local-config","true");
	if (params.toBoolean(true))
	    attach(YOBJECT(SCCP,engine()->build("SCCP",params,true)));
    } else
	Debug(this,DebugWarn,"GTT::initialize() can not attach sccp; null SigEngine");
    return m_sccp != 0;
}

NamedList* GTT::routeGT(const NamedList& gt, const String& prefix, const String& nextPrefix)
{
    Debug(DebugStub,"Please implement NamedList* GTT::routeGT(%s,%s,%s)",
	    gt.c_str(),prefix.c_str(),nextPrefix.c_str());
    return 0;
}

void GTT::attach(SCCP* sccp)
{
    if (!sccp)
	return;
    if (m_sccp == sccp) {
	sccp->deref();
	return;
    }
    SCCP* tmp = m_sccp;
    m_sccp = sccp;
    m_sccp->attachGTT(this);
    if (!tmp)
	return;
    TelEngine::destruct(tmp);
    tmp = 0;
}

void GTT::destroyed()
{
    if (m_sccp) {
	m_sccp->attachGTT(0);
	TelEngine::destruct(m_sccp);
	m_sccp = 0;
    }
    SignallingComponent::destroyed();
}

/**
 * SCCPManagement
 */
SCCPManagement::SCCPManagement(const NamedList& params, SS7PointCode::Type type)
    : SignallingComponent(params,&params,"ss7-sccp-mgm"),
      Mutex(true, s_managementMutexName), m_remoteSccp(),
    m_statusTest(), m_localSubsystems(), m_concerned(), m_pcType(type), m_sccp(0), m_unknownSubsystems("ssn"),
    m_subsystemFailure(0), m_routeFailure(0), m_autoAppend(false), m_printMessages(false)
{
    DDebug(DebugAll,"Creating SCCP management (%p)",this);
    // stat.info timer
    m_testTimeout = params.getIntValue(YSTRING("test-timer"),5000);
    if (m_testTimeout < 5000)
	m_testTimeout = 5000;
    else if (m_testTimeout > 10000)
	m_testTimeout = 10000;
    // coord.chg timer
    m_coordTimeout = params.getIntValue(YSTRING("coord-timer"),1000);
    if (m_coordTimeout < 1000)
	m_coordTimeout = 1000;
    if (m_coordTimeout > 2000)
	m_coordTimeout = 2000;
    m_ignoreStatusTestsInterval = params.getIntValue(YSTRING("ignore-tests"),1000);
    m_printMessages = params.getBoolValue(YSTRING("print-messages"), false);
    m_autoAppend = params.getBoolValue(YSTRING("auto-monitor"),false);
    for (unsigned int i = 0;i < params.length();i++) {
	NamedString* param = params.getParam(i);
	if (!param)
	    continue;
	XDebug(this,DebugAll,"Parsing param %s : %s",param->name().c_str(),param->c_str());
	if (param->name() == YSTRING("remote")) {
	    SccpRemote* rem = new SccpRemote(m_pcType);
	    if (rem->initialize(*param))
		m_remoteSccp.append(rem);
	    else {
		Debug(this,DebugConf,"Failed to initialize remote sccp %s",param->c_str());
		TelEngine::destruct(rem);
	    }
	} else if (param->name() == YSTRING("concerned")) {
	    SccpRemote* rem = new SccpRemote(m_pcType);
	    if (rem->initialize(*param))
		m_concerned.append(rem);
	    else {
		Debug(this,DebugConf,"Failed to initialize concerned sccp %s",param->c_str());
		TelEngine::destruct(rem);
	    }
	}
    }
    NamedString* lsubs = params.getParam(YSTRING("local-subsystems"));
    ObjList* list = lsubs ? lsubs->split(',') : 0;
    if (!list)
	return;
    for (ObjList* o = list->skipNull();o;o = o->skipNext()) {
	String* s = static_cast<String*>(o->get());
	unsigned char ssn = s->toInteger();
	if (ssn < 2)
	    continue;
	m_localSubsystems.append(new SccpLocalSubsystem(ssn,getCoordTimeout(),
		    getIgnoreTestsInterval()));
    }
    TelEngine::destruct(list);
}

SCCPManagement::~SCCPManagement()
{
    DDebug(this,DebugAll,"Destroing SCCPManagement %p",this);
    m_sccp = 0;
}

void SCCPManagement::attach(SS7SCCP* sccp)
{
    Lock lock(this);
    if (!sccp || m_sccp)
	return;
    m_sccp = sccp;
}

bool SCCPManagement::initialize(const NamedList* config)
{
    if (!config) {
	DDebug(this,DebugNote,"Request to initialize sccp management from null conf");
	return true;
    }
    Lock lock(this);
#ifdef DEBUG
    String dst;
    config->dump(dst,"\r\n");
    Debug(this,DebugInfo,"Initializeing SCCPManagement(%p) %s",this,dst.c_str());
#endif
    m_printMessages = config->getBoolValue(YSTRING("print-messages"), m_printMessages);
    return true;
}

void SCCPManagement::pointcodeStatus(SS7Layer3* link, bool operational)
{
    if (!sccp() || !operational) {
	DDebug(this,DebugNote,"Can not process pointcode status sccp(%p) , is up : %s",
	       sccp(), String::boolText(operational));
	return;
    }
    lock();
    for (ObjList* o = m_remoteSccp.skipNull();o;o = o->skipNext()) {
	SccpRemote* rsccp = static_cast<SccpRemote*>(o->get());
	SS7Route::State state = sccp()->network()->getRouteState(m_pcType,rsccp->getPointCode());
	XDebug(this,DebugAll,"Checking route status for remote sccp %s oldState: '%s' newState: '%s'",
	       rsccp->toString().c_str(),stateName(rsccp->getState()),SS7Route::stateName(state));
	if ((int)state != (int)rsccp->getState()) {
	    unlock();
	    manageSccpRemoteStatus(rsccp,state);
	    lock();
	}
    }
    unlock();
}

void SCCPManagement::routeStatus(SS7PointCode::Type type, const SS7PointCode& node, SS7Route::State state)
{
    if (!sccp() || !sccp()->isLayer3Up()) {
	DDebug(this,DebugNote,"Can not process pointcode status sccp(%p) , is up : %s",
	       sccp(), sccp() ? String::boolText(sccp()->isLayer3Up()) : "false");
	return;
    }
    lock();
    for (ObjList* o = m_remoteSccp.skipNull();o;o = o->skipNext()) {
	SccpRemote* rsccp = static_cast<SccpRemote*>(o->get());
#ifdef XDEBUG
	String dest;
	dest << " Local: " << rsccp->getPointCode() << " remote : " << node;
	XDebug (this,DebugNote,"Processing routeStatus %s oldState: '%s' newState: '%s'", dest.c_str(),
		stateName(rsccp->getState()),SS7Route::stateName(state));
#endif
	if (rsccp->getPointCode() != node)
	    continue;
	if ((int)rsccp->getState() == (int)state)
	    break;
	RefPointer<SccpRemote> ref = rsccp;
	if (!ref)
	    continue;
	unlock();
	manageSccpRemoteStatus(rsccp,state);
	return;
    }
    unlock();
}

bool SCCPManagement::handleMessage(int msgType, unsigned char ssn, unsigned char smi, NamedList& params)
{
    int pointcode = params.getIntValue(YSTRING("pointcode"));
    Lock lock(this);
    bool sendMsg = false;
    MsgType msg = SSA;
    switch (msgType) {
	case SSA:
	case SSP:
	{
	    SccpSubsystem* sccpSub = new SccpSubsystem(ssn);
	    SccpRemote* rsccp = new SccpRemote(pointcode,m_pcType);
	    lock.drop();
	    if (ssn == 1 && msgType == SSA)
		manageSccpRemoteStatus(rsccp,SS7Route::Allowed);
	    else if (ssn > 1)
		handleSubsystemStatus(sccpSub, msgType == SSA, rsccp, smi);
	    else
		Debug(this,DebugWarn,"Received Invalid sccp message %s for ssn %d",
		      lookup(msgType,s_managementMessages), ssn);
	    TelEngine::destruct(sccpSub);
	    TelEngine::destruct(rsccp);
	    return true;
	}
	case SST: // Received sst
	{
	    if (ssn == 1)  { // SST is initiated for local sccp send ssa
		sendMsg = true;
		break;
	    }
	    SccpLocalSubsystem* sccps = getLocalSubsystem(ssn);
	    if (sccps) {
		XDebug(this,DebugAll,"Received SST for %d state: %s ignoreTests %s",
		       ssn,stateName(sccps->getState()),String::boolText(sccps->ignoreTests()));
		if (sccps->ignoreTests())
		    return true;
		if (sccps->getState() == SCCPManagement::Allowed) {
		    sendMsg = true;
		    break;
		}
		lock.drop();
		if (!managementMessage(SCCP::SubsystemStatus,params))
		    return true;
		String* status = params.getParam(YSTRING("subsystem-status"));
		if (status && *status == YSTRING("UserInService"))
		    sendMessage(msg,params);
		return true;
	    }
	    if (!sendMsg)
		Debug(this,DebugConf,"Received SST from: '%s' for missing local subsystem %d",
		    params.getValue(YSTRING("RemotePC")),ssn);
	    break;
	}
	case SOR:
	{
	    lock.drop();
	    managementMessage(SCCP::CoordinateIndication,params);
	    return true;
	}
	case SOG:
	    handleSog(ssn,pointcode);
	    return true;
	default:
	    Debug(sccp(),DebugNote,"Received unknown management Message '%s'",
		  lookup(msgType,s_managementMessages));
    }
    lock.drop();
    if (sendMsg)
	sendMessage(msg,params);
    return true;
}

bool SCCPManagement::managementMessage(SCCP::Type type, NamedList& params)
{
    if (!m_sccp)
	return false;
    return m_sccp->managementMessage(type,params);
}

void SCCPManagement::putValue(NamedList& params,int val,const char* name, bool dict)
{
    if (val < 0)
	return;
    if (!dict)
	params.setParam(name,String(val));
    else
	params.setParam(name,lookup(val,s_broadcastType));
}

void SCCPManagement::localBroadcast(SCCP::Type type, int pointcode, int sps,
		int rss, int rl, int ssn, int ss)
{
    if (!m_sccp)
	return;
    NamedList params("lb");
    putValue(params,pointcode,"pointcode");
    putValue(params,rl,"restriction-level");
    putValue(params,ssn,"ssn");
    putValue(params,sps,"signalling-point-status",true);
    putValue(params,ss,"subsystem-status",true);
    putValue(params,rss,"remote-sccp-status",true);
    m_sccp->managementMessage(type,params);
}

SccpLocalSubsystem* SCCPManagement::getLocalSubsystem(unsigned char ssn)
{
    Lock lock(this);
    for (ObjList* o = m_localSubsystems.skipNull();o;o = o->skipNext()) {
	SccpLocalSubsystem* ss = static_cast<SccpLocalSubsystem*>(o->get());
	if (ss && ss->getSSN() == ssn)
	    return ss;
    }
    return 0;
}

bool SCCPManagement::processMessage(SS7MsgSCCP* message)
{
    Debug(DebugStub,"Please implement management message decoder");
    return true;
}

const TokenDict* SCCPManagement::broadcastType()
{
    return s_broadcastType;
}

void SCCPManagement::notify(SCCP::Type type, NamedList& params)
{
    if (!m_sccp)
	return;
#ifdef DEBUG
    String tmp;
    params.dump(tmp,"\r\n");
    Debug(this,DebugAll,"User notify %s : \r\n%s",lookup(type,s_sccpNotif),tmp.c_str());
#endif
    unsigned char ssn = params.getIntValue(YSTRING("ssn"));
    if (ssn < 2) {
	Debug(this,DebugNote,"Received management notify with invalid ssn %d",ssn);
	return;
    }
    unsigned char smi = params.getIntValue(YSTRING("smi")); // subsystem multiplicity indicator
    if (smi > 3) {
	Debug(this,DebugNote, "Received management notify message with unknown smi: %d , ssn: %d",
		  smi,ssn);
	smi = 0;
    }
    switch (type) {
	case SCCP::CoordinateRequest: // Affected subsystem, subsystem multiplicity indicator
	    handleCoordinateChanged(ssn,smi,params);
	    break;
	case SCCP::CoordinateResponse:// Affected subsystem, subsystem multiplicity indicator
	    params.setParam(YSTRING("pointcode"),String(m_sccp->getPackedPointCode()));
	    sendMessage(SOG,params);
	    break;
	case SCCP::StatusRequest: // Affected subsystem, subsystem multiplicity indicator, user status
	{
	    const char* subsystemStatus = params.getValue(YSTRING("subsystem-status"));
	    int status = lookup(subsystemStatus,broadcastType());
	    if (status != UserOutOfService && status != UserInService) {
		Debug(this,DebugNote,"Reveived subsystem status indication with wrong subsystem status: %s",
			subsystemStatus);
		return;
	    }
	    SccpSubsystem* sub = new SccpSubsystem(ssn);
	    handleSubsystemStatus(sub, status == UserInService, 0, smi);
	    TelEngine::destruct(sub);
	    break;
	}
	default:
	    Debug(this,DebugNote,"Unhandled message '%s' received from attached users!",
		  lookup(type,s_sccpNotif));
    }
}

void SCCPManagement::handleSog(unsigned char ssn, int pointcode)
{
    for (ObjList* ol = m_localSubsystems.skipNull();ol;ol = ol->skipNext()) {
	SccpLocalSubsystem* sls = static_cast<SccpLocalSubsystem*>(ol->get());
	if (sls->receivedSOG(ssn,pointcode))
	    break;
    }
}

void SCCPManagement::handleCoordinateChanged(unsigned char ssn, int smi, const NamedList& params)
{
    Lock lock(this);
    SccpLocalSubsystem* sub = getLocalSubsystem(ssn);
    if (!sub) {
	Debug(this,DebugInfo,"Dinamicaly appending ssn %d to local subsystems list!",ssn);
	sub = new SccpLocalSubsystem(ssn,m_coordTimeout,m_ignoreStatusTestsInterval,smi);
	m_localSubsystems.append(sub);
    }
    sub->ref();
    lock.drop();
    if (sub->getState() == SCCPManagement::Prohibited)
	Debug(this,DebugStub,"Subsystem %d wishes to go oos but is already oos! Logic Bug?",sub->getSSN());
    sub->clearBackups();
    int count = params.getIntValue(YSTRING("backups"));
    for (int i = 0;i < count; i++) {
	String name = "backup.";
	name << i;
	int subsys = params.getIntValue(name + ".ssn", -1);
	int pointcode = params.getIntValue(name + ".pointcode",-1);
	if (pointcode <= 0) {
	    Debug(this,DebugStub,"Coordinate change request to a local subsystem!");
	    continue;
	}
	if (subsys < 2 || pointcode < 0) {
	    Debug(this,DebugMild,"Invalid backup subsystem pc:%d, ssn:%d",pointcode,subsys);
	    continue;
	}
	RemoteBackupSubsystem* bs = new RemoteBackupSubsystem(subsys,pointcode,true);
	sub->appendBackup(bs);
	NamedList data("");
	data.setParam("smi",String(smi));
	data.setParam("ssn",String(subsys));
	data.setParam("pointcode",String(pointcode));
	data.setParam("RemotePC",String(pointcode));
	sendMessage(SOR,data);
    }
    sub->startCoord();
    sub->setState(WaitForGrant);
    TelEngine::destruct(sub);
}

SccpRemote* SCCPManagement::getRemoteSccp(int pointcode)
{
    for (ObjList* o = m_remoteSccp.skipNull();o;o = o->skipNext()) {
	SccpRemote* rsccp = static_cast<SccpRemote*>(o->get());
	if (rsccp->getPackedPointcode() == pointcode)
	    return rsccp;
    }
    return 0;
}

void SCCPManagement::routeFailure(SS7MsgSCCP* msg)
{
    if (!m_sccp)
	return;
    Lock lock(this);
    m_routeFailure++;
    if (!msg || !msg->params().getParam(YSTRING("RemotePC"))) {
	DDebug(this,DebugNote,"Route failure, with no pointcode present!");
	return;
    }
    int pointcode = msg->params().getIntValue(YSTRING("RemotePC"));
    if (pointcode < 1) {
	Debug(this,DebugWarn,"Remote pointcode %d is invalid!",pointcode);
	return;
    }
    if (pointcode == m_sccp->getPackedPointCode())
	return;
    SccpRemote* rsccp = getRemoteSccp(pointcode);
    if (rsccp && rsccp->getState() == SCCPManagement::Prohibited) {
	lock.drop();
	updateTables(rsccp);
	return;
    }
    if (!rsccp) {
	if (m_autoAppend) {
	    Debug(this,DebugNote,"Dynamic appending remote sccp %d to state monitoring list",
		  pointcode);
	    rsccp = new SccpRemote(pointcode,m_pcType);
	    m_remoteSccp.append(rsccp);
	} else
	    Debug(this,DebugMild,
		  "Remote sccp '%d' state is not monitored! Future message routing may not reach target!",
		  pointcode);
    }
    RefPointer<SccpRemote>ref = rsccp;
    lock.drop();
    if (!ref)
	return;
    manageSccpRemoteStatus(rsccp,SS7Route::Prohibited);
}

void SCCPManagement::subsystemFailure(SS7MsgSCCP* msg, const SS7Label& label)
{
    if (!m_sccp) {
	DDebug(this,DebugNote,"Request to process subsystem failure with no sccp attached!");
	return;
    }
    if (!msg || !msg->params().getParam(YSTRING("CalledPartyAddress.ssn"))) {
	DDebug(this,DebugNote,"Subsystem failure! no ssn");
	return;
    }
    int ssn = msg->params().getIntValue(YSTRING("CalledPartyAddress.ssn"),0);
    if (ssn <= 1) {
	DDebug(this,DebugNote,"Subsystem failure, invalid ssn: '%d'",ssn);
	return;
    }
    Lock lock(this);
    // Find local subsystem and change status
    SccpLocalSubsystem* ss = getLocalSubsystem(ssn);
    if (ss)
	ss->setState(SCCPManagement::Prohibited);
    if (m_sccp->extendedMonitoring()) {
	m_subsystemFailure++;
	NamedString* sub = msg->params().getParam(YSTRING("CalledPartyAddress.ssn"));
	if (sub) {
	    NamedString* ssnParam = m_unknownSubsystems.getParam(*sub);
	    if (ssnParam)
		incrementNS(ssnParam);
	    else
		m_unknownSubsystems.setParam(*sub,"1");
	}
    }
    lock.drop();
    notifyConcerned(SSP,ssn,0);
}

void SCCPManagement::subsystemsStatus(String& dest,bool extended)
{
    Lock lock(this);
    if (m_localSubsystems.skipNull()) {
	dest << "Local subsystems state : count: " << m_localSubsystems.count() << "\r\n";
	for (ObjList* o = m_localSubsystems.skipNull();o;o = o->skipNext()) {
	    SccpLocalSubsystem* ss = static_cast<SccpLocalSubsystem*>(o->get());
	    if (!ss)
		continue;
	    ss->dump(dest);
	    dest << "\r\n";
	}
    }
    if (m_subsystemFailure == 0) {
	dest << "\r\nMissing Local Subsystem: " << m_subsystemFailure;
	if (!extended)
	    return;
	for (unsigned int i = 0;i < m_unknownSubsystems.length();i++) {
	    NamedString* ssn = m_unknownSubsystems.getParam(i);
	    if (!ssn)
		continue;
	    dest << "\r\nReceived: " << *ssn << " packets for subsystem : " << ssn->name();
	}
    }
    if (!m_remoteSccp.skipNull())
	return;
    dest << "\r\nRemoteSccp: count: " << m_remoteSccp.count();
    for (ObjList* o = m_remoteSccp.skipNull(); o;o = o->skipNext()) {
	SccpRemote* sr = static_cast<SccpRemote*>(o->get());
	if (!sr)
	    continue;
	sr->dump(dest,true);
    }
}

void SCCPManagement::updateTables(SccpRemote* rsccp, SccpSubsystem* ssn)
{
    if (!rsccp && !ssn) {
	Debug(sccp(),DebugMild,"Request to update tables but no pointcode or ssn present!!");
	return;
    }
    if (!sccp()) {
	DDebug(this,DebugMild,"Request to update tables with no sccp attached");
	return;
    }
    const SS7PointCode* local = rsccp ? &rsccp->getPointCode() : sccp()->getLocalPointCode();
    if (!local) {
	Debug(sccp(),DebugWarn,"Can not update tables, no pointcode present!");
	return;
    }
    NamedList params("sccp.update");
    params.setParam("pointcode",String(local->pack(m_pcType)));
    params.setParam("pc-type",String((int)m_pcType));
    if (rsccp)
	params.setParam("pc-state",stateName(rsccp->getState()));
    params.setParam("component",sccp()->toString());
    if (ssn) {
	params.setParam("subsystem",String(ssn->getSSN()));
	params.setParam("subsystem-state",stateName(ssn->getState()));
    }
    sccp()->updateTables(params);
}

void SCCPManagement::routeStatus(String& dest,bool extended)
{
    dest << "\r\nRouting Status:";
    dest << "\r\nMessages Failed to be routed: " << m_routeFailure;
    if (!extended)
	return;
    // TODO call gtt print unknown translations
}

void SCCPManagement::timerTick(const Time& when)
{
    if (!lock(SignallingEngine::maxLockWait()))
	return;
    ObjList coordt;
    for (ObjList* o = m_localSubsystems.skipNull();o;o = o->skipNext()) {
	SccpLocalSubsystem* ss = static_cast<SccpLocalSubsystem*>(o->get());
	if (!ss)
	    continue;
	if (ss->timeout() && ss->ref())
	    coordt.append(ss);
    }
    // Use another list to append the sst's because the alternative is expensive for timer tick
    // (ListIterator)
    ObjList ssts;
    for (ObjList* o = m_statusTest.skipNull();o;o = o->skipNext()) {
	SubsystemStatusTest* sst = static_cast<SubsystemStatusTest*>(o->get());
	if (!sst->timeout())
	    continue;
	if (sst->ref())
	    ssts.append(sst);
    }
    unlock();
    if (coordt.skipNull())
	for (ObjList* o = coordt.skipNull();o;o = o->skipNext()) {
	    SccpLocalSubsystem* ss = static_cast<SccpLocalSubsystem*>(o->get());
	    ss->manageTimeout(this);
	}
    if (!ssts.skipNull())
	return;
    for (ObjList* o = ssts.skipNull();o;o = o->skipNext()) {
	SubsystemStatusTest* sst = static_cast<SubsystemStatusTest*>(o->get());
	if (!sst)
	    continue;
	if (sst->markAllowed() && sst->getSubsystem()->getSSN() == 1) {
	    manageSccpRemoteStatus(sst->getRemote(),SS7Route::Allowed);
	    continue;
	}
	sst->restartTimer();
	if (!sendSST(sst->getRemote(),sst->getSubsystem()))
	    sst->setAllowed(false);
    }
}

void SCCPManagement::stopSst(SccpRemote* remoteSccp, SccpSubsystem* rSubsystem, SccpSubsystem* less)
{
    if (!remoteSccp)
	return;
    Lock lock(this);
    ListIterator iter(m_statusTest);
    SubsystemStatusTest* sst = 0;
    while ((sst = YOBJECT(SubsystemStatusTest,iter.get()))) {
	if (sst->getRemote()->getPointCode() != remoteSccp->getPointCode())
	    continue;
	if (sst->getSubsystem()) {
	    if (rSubsystem && rSubsystem->getSSN() != sst->getSubsystem()->getSSN())
		continue;
	    if (less && less->getSSN() == sst->getSubsystem()->getSSN())
		continue;
	}
	m_statusTest.remove(sst);
    }
}

bool SCCPManagement::sendSST(SccpRemote* remote, SccpSubsystem* sub)
{
    NamedList params("");
    params.setParam("pointcode",String(remote->getPackedPointcode()));
    params.setParam("RemotePC",String(remote->getPackedPointcode()));
    params.setParam("smi",String(sub->getSmi()));
    params.setParam("ssn",String(sub->getSSN()));
    return sendMessage(SST,params);
}

void SCCPManagement::startSst(SccpRemote* remoteSccp, SccpSubsystem* rSubsystem)
{
    if (!remoteSccp || !rSubsystem)
	return;
    DDebug(this,DebugNote,"Requested to start test for pc : %d  ssn: %d",remoteSccp->getPackedPointcode(),rSubsystem->getSSN());
    Lock lock(this);
    for (ObjList* o = m_statusTest.skipNull();o;o = o->skipNext()) {
	SubsystemStatusTest* sst = static_cast<SubsystemStatusTest*>(o->get());
	if (sst->getRemote()->getPointCode() != remoteSccp->getPointCode())
	    continue;
	if (sst->getSubsystem() && rSubsystem->getSSN() == sst->getSubsystem()->getSSN())
	    return; // We already have the test
    }
    SubsystemStatusTest* sst = new SubsystemStatusTest(m_testTimeout);
    if (!sst->startTest(remoteSccp,rSubsystem)) {
	TelEngine::destruct(sst);
	return;
    }
    m_statusTest.append(sst);
    lock.drop();
    if (!sendSST(remoteSccp,rSubsystem))
	sst->setAllowed(false);
}

void SCCPManagement::mtpEndRestart()
{
    if (!m_sccp)
	return;
    lock();
    ListIterator iter(m_concerned);
    SccpRemote* sr = 0;
    while ((sr = YOBJECT(SccpRemote,iter.get()))) {
	SS7Route::State state = sccp()->network()->getRouteState(m_pcType,sr->getPointCode());
	RefPointer<SccpRemote> ptr = sr;
	unlock();
	if (sr->getState() !=  (SccpStates)state)
	    manageSccpRemoteStatus(sr,state); // Update remote sccp state
	if (state != SS7Route::Allowed) {
	    lock();
	    continue;
	}
	NamedList params("");
	params.setParam("pointcode",String(m_sccp->getPackedPointCode()));
	params.setParam("RemotePC",String(sr->getPackedPointcode()));
	params.setParam("smi","0");
	params.setParam("ssn","1");
	sendMessage(SSA,params);
	lock();
    }
    unlock();
}

void SCCPManagement::notifyConcerned(MsgType msg, unsigned char ssn, int smi)
{
    DDebug(this,DebugAll,"Notify concerned: msg '%s' ssn: '%d', smi: %d",
	   lookup(msg,s_managementMessages),ssn,smi);
    if (!sccp())
	return;
    Lock lock(this);
    ObjList concerned;
    for (ObjList* o = m_concerned.skipNull();o;o = o->skipNext()) {
	SccpRemote* rsccp = static_cast<SccpRemote*>(o->get());
	if (!rsccp || !rsccp->getSubsystem(ssn))
	    continue;
	if (rsccp->ref())
	    concerned.append(rsccp);
    }
    if (!concerned.skipNull()) {
	DDebug(this,DebugNote,"No Concerned pointcode for ssn %d",ssn);
	return;
    }
    NamedList params("");
    params.setParam("ssn",String((int)ssn));
    params.setParam("pointcode",String(sccp()->getPackedPointCode()));
    params.setParam("smi",String(smi));
    lock.drop();
    for (ObjList* o = concerned.skipNull();o; o = o->skipNext()) {
	SccpRemote* rsccp = static_cast<SccpRemote*>(o->get());
	if (!rsccp)
	    continue;
	params.setParam("RemotePC",String(rsccp->getPackedPointcode()));
	sendMessage(msg,params);
    }
}

void SCCPManagement::sccpUnavailable(const SS7PointCode& pointcode, unsigned char cause)
{
#ifdef DEBUG
    String dest;
    dest << pointcode;
    Debug(this,DebugInfo,"Received UPU %s cause : %d",dest.c_str(), cause);
#endif
    Lock lock(this);
    SccpRemote* rsccp = getRemoteSccp(pointcode.pack(m_pcType));
    // Do not process UPU if we do not monitor the remote sccp state
    if (!rsccp)
	return;
    rsccp->setState(SCCPManagement::Prohibited);
    // Stop all subsystem status tests
    ListIterator iter(m_statusTest);
    SubsystemStatusTest* test = 0;
    bool testStarted = false;
    while ((test = YOBJECT(SubsystemStatusTest,iter.get()))) {
	if (!test || !test->getRemote() || pointcode != test->getRemote()->getPointCode())
	    continue;
	// Do not stop test for SSN = 1 if the cause is not Unequipped
	SccpSubsystem* sub = test->getSubsystem();
	if (sub->getSSN() == 1 && cause != HandledMSU::Unequipped) {
	    testStarted = true;
	    continue;
	}
	m_statusTest.remove(test);
    }
    if (!testStarted && cause != HandledMSU::Unequipped) {
	SubsystemStatusTest* sst = new SubsystemStatusTest(m_testTimeout);
	SccpSubsystem* sub = new SccpSubsystem(1);
	if (!sst->startTest(rsccp,new SccpSubsystem(1))) {
	    TelEngine::destruct(sst);
	    TelEngine::destruct(sub);
	    return;
	}
	TelEngine::destruct(sub);
	m_statusTest.append(sst);
	sst->setAllowed(false);
    }
    lock.drop();
    localBroadcast(SCCP::StatusIndication,rsccp->getPackedPointcode(),-1,SccpRemoteInaccessible);
}

void SCCPManagement::printMessage(String& dest, MsgType type, const NamedList& params)
{
    const char* enclose = "\r\n-----";
    dest = enclose;
    dest << "\r\n " << lookup(type,s_managementMessages);
    dest << " pc: " << params.getValue(YSTRING("pointcode")) << ", ";
    dest << "ssn: " << params.getValue(YSTRING("ssn")) << ", ";
    dest << "smi: " << params.getValue(YSTRING("smi"));
    if (type == SSC) {
	dest << ", cl: " << params.getValue(YSTRING("congestion-level"));
    }
    dest << enclose;
}

/**
 * SccpLocalSubsystem
 */

SccpLocalSubsystem::SccpLocalSubsystem(unsigned char ssn, u_int64_t coordInterval, u_int64_t ignoreInterval,unsigned char smi)
    : Mutex(true,s_sccpSubsystems), m_ssn(ssn), m_smi(smi), m_state(SCCPManagement::Allowed),
    m_coordTimer(coordInterval), m_ignoreTestsTimer(ignoreInterval), m_backups(), m_receivedAll(true)
{
    DDebug("SccpSubsystem", DebugAll,"Creating sccp subsystem [%p] with ssn '%d', smi '%d'",this,ssn,smi);
}

SccpLocalSubsystem::~SccpLocalSubsystem()
{
    DDebug("SccpSubsystem", DebugAll,"Destroing sccp subsystem [%p] with ssn '%d'",this,m_ssn);
}

bool SccpLocalSubsystem::timeout()
{
    Lock lock(this);
    if (m_coordTimer.timeout()) {
	m_coordTimer.stop();
	m_receivedAll = true;
	for (ObjList* o = m_backups.skipNull();o;o = o->skipNext()) {
	    RemoteBackupSubsystem* sbs = static_cast<RemoteBackupSubsystem*>(o->get());
	    if (sbs->waitingForGrant())
		m_receivedAll = false;
	}
	if (m_receivedAll)
	    m_ignoreTestsTimer.start();
	return true;
    }
    if (m_ignoreTestsTimer.timeout()) {
	m_state = SCCPManagement::Prohibited;
	m_ignoreTestsTimer.stop();
    }
    return false;
}

void SccpLocalSubsystem::manageTimeout(SCCPManagement* mgm)
{
    if (!mgm)
	return;
    if (m_receivedAll) {
	mgm->localBroadcast(SCCP::CoordinateConfirm,-1,-1,-1,-1,m_ssn,m_smi);
	mgm->notifyConcerned(SCCPManagement::SSP,m_ssn,m_smi);
	m_state = SCCPManagement::IgnoreTests;
	return;
    }
    m_state = SCCPManagement::Allowed;
    /// TODO send local broadcast with request denied!!!
}

void SccpLocalSubsystem::dump(String& dest)
{
    dest << "Subsystem: " << m_ssn << " , smi: " << m_smi;
    dest << ", state: " << SCCPManagement::stateName(m_state) << " ";
}

bool SccpLocalSubsystem::receivedSOG(unsigned char ssn, int pointcode)
{
    Lock lock(this);
    for (ObjList* o = m_backups.skipNull();o;o = o->skipNext()) {
	RemoteBackupSubsystem* sbs = static_cast<RemoteBackupSubsystem*>(o->get());
	if (!sbs->equals(ssn,pointcode))
	    continue;
	sbs->permisionGranted();
	return true;
    }
    return false;
}

void SccpLocalSubsystem::setIgnoreTests(bool ignore)
{
    if (ignore)
	m_ignoreTestsTimer.start();
    else
	m_ignoreTestsTimer.stop();
}

/**
 * SccpRemote
 */

SccpRemote::SccpRemote(const SS7PointCode::Type type)
    : Mutex(true, s_sccpRemote), m_pointcode(type,0), m_pointcodeType(type), m_state(SCCPManagement::Allowed)
{
    DDebug("RemoteSccp",DebugAll,"Creating remote sccp [%p]",this);
}

SccpRemote::SccpRemote(unsigned int pointcode, SS7PointCode::Type pcType)
    : m_pointcode(pcType,pointcode), m_pointcodeType(pcType), m_state(SCCPManagement::Allowed)
{
    DDebug("RemoteSccp",DebugAll,"Creating remote sccp [%p] for pointcode %d",this,pointcode);
}

SccpRemote::~SccpRemote()
{
#ifdef XDEBUG
    String tmp;
    tmp << m_pointcode;
    Debug("RemoteSccp",DebugAll,"Destroying remote sccp [%p], %s",this,tmp.c_str());
#endif
}

bool SccpRemote::initialize(const String& params)
{
    ObjList* o = params.split(':',false);
    if (!o)
	return false;
    String* pointcode = static_cast<String*>(o->get());
    if (!pointcode) {
	TelEngine::destruct(o);
	return false;
    }
    bool pointcodeAssigned = false;
    if (pointcode->find('-') > 0)
	pointcodeAssigned = m_pointcode.assign(*pointcode,m_pointcodeType);
    else
	pointcodeAssigned = m_pointcode.unpack(m_pointcodeType,pointcode->toInteger());
    if (!pointcodeAssigned) {
	TelEngine::destruct(o);
	return false;
    }
    ObjList* subsystems = o->skipNext();
    while (subsystems) {
	String* sub = static_cast<String*>(subsystems->get());
	if (!sub)
	    break;
	subsystems = sub->split(',',false);
	if (!subsystems)
	    break;
	for (ObjList* ob = subsystems->skipNull();ob;ob = ob->skipNext()) {
	    String* subsystem = static_cast<String*>(ob->get());
	    unsigned int ssn = subsystem->toInteger(256);
	    if (ssn > 255) {
		DDebug(DebugConf,"Skipping ssn %d for pointcode %d Value too big!",
		       ssn,m_pointcode.pack(m_pointcodeType));
		continue;
	    }
	    m_subsystems.append(new SccpSubsystem(ssn));
	}
	TelEngine::destruct(subsystems);
	break;
    }
    TelEngine::destruct(o);
    return true;
}

SccpSubsystem* SccpRemote::getSubsystem(int ssn)
{
    Lock lock(this);
    for (ObjList* o = m_subsystems.skipNull();o;o = o->skipNext()) {
	SccpSubsystem* sub = static_cast<SccpSubsystem*>(o->get());
	if (sub && sub->getSSN() == ssn)
	    return sub;
    }
    return 0;
}

void SccpRemote::setState(SCCPManagement::SccpStates state)
{
    if (m_state == state)
	return;
    Lock lock(this);
    m_state = state;
    for (ObjList* o = m_subsystems.skipNull();o;o = o->skipNext()) {
	SccpSubsystem* sub = static_cast<SccpSubsystem*>(o->get());
	sub->setState(state);
    }
}

void SccpRemote::dump(String& dest, bool extended)
{
    Lock lock(this);
    dest << "\r\n----Sccp : " << m_pointcode;
    dest << " (" << m_pointcode.pack(m_pointcodeType) << "," << SS7PointCode::lookup(m_pointcodeType) << ") ";
    dest << "State : " << SCCPManagement::stateName(m_state) << "; ";
    if (extended) {
	dest << "Subsystems : " << m_subsystems.count() << "; ";
	for (ObjList* o = m_subsystems.skipNull();o;o = o->skipNext()) {
	    SccpSubsystem* ss = static_cast<SccpSubsystem*>(o->get());
	    if (!ss)
		continue;
	    ss->dump(dest);
	    dest << " | ";
	}
    }
    dest << "----";
}

bool SccpRemote::changeSubsystemState(int ssn,SCCPManagement::SccpStates newState)
{
    Lock lock(this);
    SccpSubsystem* ss = getSubsystem(ssn);
    if (!ss)
	return true;
    if (ss->getState() == newState)
	return false;
    ss->setState(newState);
    return true;
}

/**
 * SubsystemStatusTest
 */

SubsystemStatusTest::~SubsystemStatusTest()
{
    DDebug("SST",DebugAll,"Stoping SST for pc: '%d' ssn: '%d'", m_remoteSccp ? m_remoteSccp->getPackedPointcode() : 0,
		m_remoteSubsystem ? m_remoteSubsystem->getSSN() : 0);
    if (m_remoteSccp)
	TelEngine::destruct(m_remoteSccp);
    if (m_remoteSubsystem)
	TelEngine::destruct(m_remoteSubsystem);
}

bool SubsystemStatusTest::startTest(SccpRemote* remoteSccp, SccpSubsystem* rSubsystem)
{
    if (!remoteSccp || !remoteSccp->ref())
	return false;
    m_remoteSccp = remoteSccp;
    if (!rSubsystem || !rSubsystem->ref()) {
	TelEngine::destruct(m_remoteSccp);
	return false;
    }
#ifdef DEBUG
    String dump;
    remoteSccp->dump(dump,false);
    Debug("SST",DebugInfo,"Starting subsystem status test for '%s' ssn = '%d' subsystem state : %s",
	  dump.c_str(),rSubsystem->getSSN(),SCCPManagement::stateName(rSubsystem->getState()));
#endif
    m_remoteSubsystem = rSubsystem;
    m_statusInfo.start();
    if (rSubsystem->getSSN() == 1)
	m_markAllowed = true;
    return true;
}

void SubsystemStatusTest::restartTimer()
{
    m_interval *= 2;
    if (m_interval > MAX_INFO_TIMER)
	m_interval = MAX_INFO_TIMER;
    m_statusInfo.fire(Time::msecNow() + m_interval);
}

/**
 * class SS7SCCP
 */
SS7SCCP::SS7SCCP(const NamedList& params)
    : SignallingComponent(params,&params), SS7Layer4(SS7MSU::SCCP|SS7MSU::National,&params), Mutex(true,params),
    m_type(SS7PointCode::Other), m_localPointCode(0), m_management(0), m_hopCounter(15),
    m_msgReturnStatus(""), m_segTimeout(0), m_ignoreUnkDigits(false), m_layer3Up(false),
    m_maxUdtLength(220), m_totalSent(0), m_totalReceived(0), m_errors(0),
    m_totalGTTranslations(0), m_gttFailed(0), m_extendedMonitoring(false), m_mgmName("sccp-mgm"),
    m_printMsg(false), m_extendedDebug(false), m_endpoint(true)
{
    DDebug(this,DebugInfo,"Creating new SS7SCCP [%p]",this);
#ifdef DEBUG
    if (debugAt(DebugAll)) {
	String tmp;
	params.dump(tmp,"\r\n  ",'\'',true);
	Debug(this,DebugAll,"SS7SCCP::SS7SCCP(%p) [%p]%s",
	    &params,this,tmp.c_str());
    }
#endif
    const char* stype = params.getValue(YSTRING("pointcodetype"));
    m_type = SS7PointCode::lookup(stype);
    if (m_type == SS7PointCode::Other) {
	Debug(this,DebugConf,"Invalid point code type '%s'",c_safe(stype));
	return;
    }
    String* lpc = params.getParam(YSTRING("localpointcode"));
    m_localPointCode = new SS7PointCode(0,0,0);
    bool pointcodeAssigned = false;
    if (lpc) {
	 if (lpc->find('-') > 0)
	    pointcodeAssigned = m_localPointCode->assign(*lpc,m_type);
	else
	    pointcodeAssigned = m_localPointCode->unpack(m_type,lpc->toInteger());
    }
    if (!pointcodeAssigned) {
	Debug(this,DebugWarn,"Invalid localpointcode='%s'",lpc ? lpc->c_str() : "null");
	Debug(this,DebugConf,"No local PointCode configured!! GT translations with no local PointCode may lead to undesired behavior");
	TelEngine::destruct(m_localPointCode);
	m_localPointCode = 0;
    }
    int hc = params.getIntValue("hopcounter",15);
    if (hc < 1 || hc > 15)
	hc = 15;
    m_hopCounter = hc;
    m_ignoreUnkDigits = params.getBoolValue(YSTRING("ignore-unknown-digits"),true);
    m_printMsg = params.getBoolValue(YSTRING("print-messages"),false);
    m_extendedDebug = params.getBoolValue(YSTRING("extended-debug"),false);
    m_extendedMonitoring = params.getBoolValue(YSTRING("extended-monitoring"),false);
    m_maxUdtLength = params.getIntValue(YSTRING("max-udt-length"),MAX_UDT_LEN);
    m_segTimeout = params.getIntValue(YSTRING("segmentation-timeout"),10000);
    m_mgmName = params.getValue(YSTRING("management"));
    m_endpoint = params.getBoolValue(YSTRING("endpoint"),true);
    if (m_segTimeout < 5000)
	m_segTimeout = 5000;
    if (m_segTimeout > 20000)
	m_segTimeout = 20000;
    if ((m_type == SS7PointCode::ITU || m_type == SS7PointCode::ANSI) && m_localPointCode) {
	NamedList mgmParams("sccp-mgm");
	if (!resolveConfig(YSTRING("management"),mgmParams,&params))
	    mgmParams.addParam("local-config","true");
	mgmParams.setParam("type",m_type == SS7PointCode::ITU ? "ss7-sccp-itu-mgm" : "ss7-sccp-ansi-mgm");
	if (mgmParams.toBoolean(true)) {
	    if (m_type == SS7PointCode::ITU)
		m_management = YOBJECT(SS7ItuSccpManagement,YSIGCREATE(SCCPManagement,&mgmParams));
	    else if (m_type == SS7PointCode::ANSI)
		m_management = YOBJECT(SS7AnsiSccpManagement,YSIGCREATE(SCCPManagement,&mgmParams));
	}
	if (!m_management)
	    Debug(this,DebugWarn,"Failed to create sccp management!");
	else if (m_management->initialize(&mgmParams))
	    m_management->attach(this);
    } else
	Debug(this,DebugConf,"Created SS7SCCP '%p' without management! No local pointcode pressent!",this);

}

SS7SCCP::~SS7SCCP()
{
    if (m_localPointCode)
	m_localPointCode->destruct();
    DDebug(this,DebugAll,"Destroying SS7SCCP [%p]",this);
}

bool SS7SCCP::initialize(const NamedList* config)
{
#ifdef DEBUG
    String tmp;
    if (config && debugAt(DebugAll))
	config->dump(tmp,"\r\n  ",'\'',true);
    Debug(this,DebugInfo,"SS7SCCP::initialize(%p) [%p]%s",config,this,tmp.c_str());
#endif
    if (config) {
	m_printMsg = config->getBoolValue(YSTRING("print-messages"),m_printMsg);
	m_extendedDebug = config->getBoolValue(YSTRING("extended-debug"),m_extendedDebug);
	m_ignoreUnkDigits = config->getBoolValue(YSTRING("ignore-unknown-digits"),m_ignoreUnkDigits);
	m_maxUdtLength = config->getIntValue(YSTRING("max-udt-length"),m_maxUdtLength);
	m_endpoint = config->getBoolValue(YSTRING("endpoint"),m_endpoint);
	int hc = config->getIntValue("hopcounter",m_hopCounter);
	if (hc < 1 || hc > 15)
	    hc = 15;
	m_hopCounter = hc;
	m_extendedMonitoring = config->getBoolValue(YSTRING("extended-monitoring"),m_extendedMonitoring);
    }
    if (m_management)
	SignallingComponent::insert(m_management);
    return SS7Layer4::initialize(config);
}

void  SS7SCCP::destroyed()
{
    if (m_management)
	TelEngine::destruct(m_management);
    SS7Layer4::destroyed();
}

void SS7SCCP::attach(SS7Layer3* network)
{
    SS7Layer4::attach(network);
    setNetworkUp(network && network->operational());
}

bool SS7SCCP::managementStatus(Type type, NamedList& params)
{
    if (m_management)
	m_management->notify(type,params);
    return false;
}

void SS7SCCP::timerTick(const Time& when)
{
    if (!lock(SignallingEngine::maxLockWait()))
	return;
    for (ObjList* o = m_reassembleList.skipNull();o;) {
        SS7MsgSccpReassemble* usr = YOBJECT(SS7MsgSccpReassemble,o->get());
        if (usr->timeout()) {
            o->remove();
            o = o->skipNull();
        }
        else
            o = o->skipNext();
   }
   unlock();
}

void SS7SCCP::ajustMessageParams(NamedList& params, SS7MsgSCCP::Type type)
{
    if (type == SS7MsgSCCP::UDT || type == SS7MsgSCCP::UDTS)
	return;
    int hopCounter = params.getIntValue(YSTRING("HopCounter"),0);
    if (hopCounter < 1 || hopCounter > 15)
	params.setParam("HopCounter",String(m_hopCounter));
    if (ITU() && params.getParam(YSTRING("Importance"))) {
	int importance = params.getIntValue(YSTRING("Importance"));
	int temp = checkImportanceLevel(type, importance);
	if (importance != temp)
	    params.setParam(YSTRING("Importance"),String(temp));
    }
}

// Called by routing method to send a msu
int SS7SCCP::transmitMessage(SS7MsgSCCP* sccpMsg, bool local)
{
    if (!sccpMsg || !sccpMsg->getData())
	return -1;
    if (unknownPointCodeType()) {
	Debug(this,DebugConf,"SCCP unavailable!! Reason Unknown pointcode type %s",SS7PointCode::lookup(m_type));
	return -1;
    }
    Lock lock(this);
    if (!m_layer3Up) {
	DDebug(this,DebugNote,"Can not send sccp message, L3 is down");
	return -1;
    }

    int dpc = getPointCode(sccpMsg,"CalledPartyAddress","RemotePC",true);
    if (dpc == -2) {
	lock.drop();
	return routeLocal(sccpMsg);
    }
    int opc = getPointCode(sccpMsg,"CallingPartyAddress","LocalPC",false);
    lock.drop();
    if (dpc < 0 || opc < 0) {
	if (m_management)
	    m_management->routeFailure(sccpMsg);
	return -1;
    }
    return sendSCCPMessage(sccpMsg,dpc,opc,local);
}

int SS7SCCP::sendSCCPMessage(SS7MsgSCCP* sccpMsg,int dpc,int opc, bool local)
{
    Lock lock(this);
    int sls = sccpMsg->params().getIntValue(YSTRING("sls"),-1);
    SS7PointCode dest(m_type,dpc);
    SS7PointCode orig(m_type,opc > 0 ? opc : m_localPointCode->pack(m_type));
    // Build the routing label
    SS7Label outLabel(m_type,dest,orig,sls);
    if (sccpMsg->getData()->length() > m_maxUdtLength) {
	lock.drop();
	return segmentMessage(sccpMsg,outLabel,local);
    }
    // Check route indicator
    if (!sccpMsg->params().getParam("CalledPartyAddress.route")) {
	// Set route indicator. If have pointcode and ssn, route on ssn
	if (sccpMsg->params().getParam(YSTRING("RemotePC")) &&
		sccpMsg->params().getIntValue(YSTRING("CalledPartyAddress.ssn"),0) != 0) {
	    sccpMsg->params().setParam("CalledPartyAddress.route","ssn");
	} else
	    sccpMsg->params().setParam("CalledPartyAddress.route","gt");
    }
    // Build the msu
    SS7MSU* msu = buildMSU(sccpMsg,outLabel);
    lock.drop();
    if (!msu)
	return segmentMessage(sccpMsg,outLabel,local);
    printMessage(msu,sccpMsg,outLabel);
    sls = transmitMSU(*msu,outLabel,sls);
#ifdef DEBUG
    if (sls < 0)
	Debug(this,DebugNote,"Failed to transmit message %s. %d",SS7MsgSCCP::lookup(sccpMsg->type()),sls);
#endif
    // CleanUp memory
    TelEngine::destruct(msu);
    return sls;
}

bool SS7SCCP::fillLabelAndReason(String& dest,const SS7Label& label,const SS7MsgSCCP* msg)
{
    dest << " Routing label : " << label;
    if (!isSCLCSMessage(msg->type()))
	return false;
    dest << " Reason: ";
    dest << lookup(msg->params().getIntValue(YSTRING("ReturnCause")),s_return_cause,
	    "Unknown");
    return true;
}

// Obtain a pointcode from called/calling party address
// Return: -1 On Error; -2 If the message should be routed to a local sccp; else  the pointcode
int SS7SCCP::getPointCode(SS7MsgSCCP* msg, const String& prefix, const char* pCode, bool translate)
{
    if (!msg)
	return -1;
    bool havePointCode = false;
    NamedString* pcNs = msg->params().getParam(pCode);
    if (pcNs && pcNs->toInteger(0) > 0)
	havePointCode = true;
    if (!havePointCode) {
	pcNs = msg->params().getParam(prefix + ".pointcode");
	if (pcNs && pcNs->toInteger(0) > 0) {
	    msg->params().setParam(new NamedString(pCode,*pcNs));
	    havePointCode = true;
	}
    }
    if (!havePointCode && translate) { // CalledParyAddress with no pointcode. Check for Global Title
	NamedList* route = translateGT(msg->params(),prefix,
		YSTRING("CallingPartyAddress"));
	m_totalGTTranslations++;
	if (!route) {
	    m_gttFailed++;
	    return -1;
	}
	resolveGTParams(msg,route);
	NamedString* localRouting = route->getParam(YSTRING("sccp"));
	if (localRouting && *localRouting != toString()) {
	    msg->params().copyParam(*route,YSTRING("RemotePC"));
	    TelEngine::destruct(route);
	    return -2;
	}
	bool havePC = route->getParam(pCode) != 0;
	NamedString* trpc = route->getParam(YSTRING("pointcode"));
	if (!trpc && !havePC) {
	    Debug(this,DebugWarn,"The GT has not been translated to a pointcode!!");
	    TelEngine::destruct(route);
	    return -1;
	}
	if (!havePC)
	    msg->params().setParam(pCode,*trpc);
	else
	    msg->params().setParam(pCode,route->getValue(pCode));
	TelEngine::destruct(route);
    } else if (!havePointCode && !translate) { // CallingPartyAddress with no pointcode. Assign sccp pointcode
	if (!m_localPointCode) {
	    Debug(this,DebugWarn,"Can not build routing label. No local pointcode present and no pointcode present in CallingPartyAddress");
	    return -1;
	}
	return m_localPointCode->pack(m_type);
    }
    return msg->params().getIntValue(pCode);
}

int SS7SCCP::routeLocal(SS7MsgSCCP* msg)
{
    if (!msg) {
	Debug(this,DebugWarn,"Failed to route local! Null message!");
	return -1;
    }
    NamedString* sccp = msg->params().getParam(YSTRING("sccp"));
    if (!sccp || *sccp == toString()) {
	Debug(this,DebugStub,
		"Requested to local route sccp message without sccp component!");
	return -1;
    }
    int dpc = msg->params().getIntValue("RemotePC",-1);
    if (dpc < 0)
	dpc = msg->params().getIntValue("CalledPartyAddress.pointcode",-1);
    if (dpc < 0) {
	Debug(this,DebugNote,
		"Unable to route local sccp message! No pointcode present.");
	return -1;
    }
    if (!engine()) {
	Debug(this,DebugMild,
		"Unable to route local sccp message! No engine attached!");
	return -1;
    }
    RefPointer<SS7SCCP> sccpCmp = YOBJECT(SS7SCCP,
		engine()->find(*sccp,YSTRING("SS7SCCP")));
    if (!sccpCmp) {
	Debug(this,DebugNote,
		"Unable to route local sccp message! SCCP component %s not found!",
		sccp->c_str());
	return -1;
    }
    msg->params().clearParam(YSTRING("LocalPC"));
    msg->params().clearParam(YSTRING("CallingPartyAddress.pointcode"));
    return sccpCmp->sendSCCPMessage(msg,dpc,-1,false);
}

int SS7SCCP::checkImportanceLevel(int msgType, int initialImportance)
{
    if ((isSCLCMessage(msgType) && isSCLCSMessage(msgType))) {
	Debug(this,DebugStub,"Check Importance level for a SCOC message!");
	return 0;
    }
    if (isSCLCMessage(msgType)) // Max importance level is 6 and default is 4 for UDT,XUDT and LUDT
	return (initialImportance >= 0 && initialImportance <= 6) ? initialImportance : 4;
    if (isSCLCSMessage(msgType)) // Max importance level is 3 and default is 3 for UDTS,XUDTS and LUDTS
	return (initialImportance >= 0 && initialImportance <= 3) ? initialImportance : 3;
    return initialImportance;
}

void SS7SCCP::checkSCLCOptParams(SS7MsgSCCP* msg)
{
    if (!msg || msg->type() == SS7MsgSCCP::UDT || !isSCLCMessage(msg->type())) // UDT does not have optional parameters
	return;
    if (!ITU()) {
	msg->params().clearParam(YSTRING("Importance"));
	return;
    }
    msg->params().clearParam(YSTRING("ISNI"));
    msg->params().clearParam(YSTRING("INS"));
    msg->params().clearParam(YSTRING("MessageTypeInterworking"));
}

// This method is called to send connectionless data
int SS7SCCP::sendMessage(DataBlock& data, const NamedList& params)
{
    if (unknownPointCodeType()) {
	Debug(this,DebugConf,"SCCP unavailable!! Reason Unknown pointcode type %s",SS7PointCode::lookup(m_type));
	return -1;
    }
#ifdef XDEBUG
    String tmp;
    params.dump(tmp,"\r\n  ",'\'',true);
    Debug(this,DebugAll,"SS7SCCP::sendMessage() [%p]%s",this,tmp.c_str());
#endif
    Lock lock1(this);
    SS7MsgSCCP* sccpMsg = 0;
    // Do not check for data length here! If message data is too long the message
    // change procedure will be initiated in segmentMessage method
    if (params.getParam(YSTRING("Importance")) && m_type == SS7PointCode::ITU) {
	// We have Importance optional parameter. Send XUDT. ITU only
	sccpMsg = new SS7MsgSCCP(SS7MsgSCCP::XUDT);
    } else if ((params.getParam(YSTRING("ISNI")) || params.getParam(YSTRING("INS"))) &&
		 m_type == SS7PointCode::ANSI) {
	// XUDT message ANSI only
	sccpMsg = new SS7MsgSCCP(SS7MsgSCCP::XUDT);
    } else if (params.getParam(YSTRING("HopCounter"))) {
	sccpMsg = new SS7MsgSCCP(SS7MsgSCCP::XUDT);
    } else // In rest send Unit Data Messages
	sccpMsg = new SS7MsgSCCP(SS7MsgSCCP::UDT);

    if (!sccpMsg) {
	Debug(this,DebugWarn,"Failed to create SCCP message!");
	m_errors++;
	return -1;
    }
    sccpMsg->params().copyParams(params); // Copy the parameters to message
    sccpMsg->params().setParam("generated","local");
    if (m_localPointCode)
	sccpMsg->params().setParam("LocalPC",String(getPackedPointCode()));
    ajustMessageParams(sccpMsg->params(),sccpMsg->type());
    if (params.getBoolValue(YSTRING("CallingPartyAddress.pointcode"),false) && m_localPointCode)
	sccpMsg->params().setParam("CallingPartyAddress.pointcode",String(getPackedPointCode()));
    // Avoid sending optional parameters that aren't specified by protocol
    if (sccpMsg->type() == SS7MsgSCCP::XUDT || sccpMsg->type() == SS7MsgSCCP::LUDT)
	checkSCLCOptParams(sccpMsg);
    // Append data to message
    sccpMsg->setData(&data);
    lock1.drop();
    int ret = transmitMessage(sccpMsg,true);
    sccpMsg->removeData();
    TelEngine::destruct(sccpMsg);
    lock();
    if (ret >= 0)
	m_totalSent++;
    else
	m_errors++;
    unlock();
    return ret;
}

// This method approximates the length of sccp address
unsigned int SS7SCCP::getAddressLength(const NamedList& params, const String& prefix)
{
    unsigned int length = 2; // Parameter length + Address information octet
    if (params.getParam(prefix + ".ssn"))
	length++; // One octet for ssn
    if (params.getParam(prefix + ".pointcode"))
	length += ITU() ? 2 : 3; // Pointcode has 2 octets on ITU and 3 on ANSI
    const NamedString* gtNr = YOBJECT(NamedString,params.getParam(prefix + ".gt"));
    if (!gtNr)
	return length;
    DataBlock data;
    if (!data.unHexify(*gtNr,gtNr->length(),' ')) {
	length += gtNr->length() / 2 + gtNr->length() % 2;
    } else
	length += data.length();
    const NamedString* nature = YOBJECT(NamedString,params.getParam(prefix + ".gt.nature"));
    const NamedString* translation = YOBJECT(NamedString,params.getParam(prefix + ".gt.translation"));
    const NamedString* plan = YOBJECT(NamedString,params.getParam(prefix + ".gt.plan"));
    const NamedString* encoding = YOBJECT(NamedString,params.getParam(prefix + ".gt.encoding"));
    if (nature)
	length++;
    if (translation)
	length++;
    if (plan && encoding)
	length++;
    return length;
}

void SS7SCCP::getMaxDataLen(const SS7MsgSCCP* msg, const SS7Label& label,
	unsigned int& udt, unsigned int& xudt, unsigned int& ludt)
{
    if (!network()) {
	Debug(this,DebugConf,"No Network Attached!!!");
	return;
    }

    unsigned int maxLen = network()->getRouteMaxLength(m_type,label.dpc().pack(m_type));
    if (maxLen < MAX_TDM_MSU_SIZE) {
	DDebug(this,DebugInfo,"Received MSU size (%d) lower than maximum TDM!",
	       maxLen);
	maxLen = MAX_TDM_MSU_SIZE;
    }
    bool ludtSupport = maxLen > MAX_TDM_MSU_SIZE; // Maximum MSU size (SIO + Label + data)
    maxLen -= (label.length() + 1); // subtract label length and SIO octet
    // Now max length represents the maximum length of SCCP message
    // Adjust maxLen to represent maximum data in the message.
    unsigned int headerLength = 3; // MsgType + ProtocolClass
    // Memorize pointer start to adjust data size.
    unsigned int pointersStart = headerLength;
    maxLen -= headerLength;
    // We have 3 mandatory variable parameters CallingAddress, CalledAddress,
    // and Data and the pointer to optional parameters + 1 data length
    headerLength += 5;
    headerLength += getAddressLength(msg->params(), "CalledPartyAddress");
    headerLength += getAddressLength(msg->params(), "CallingPartyAddress");
    ludt = 0;
    unsigned int sccpParamsSize = headerLength - pointersStart;
    // 254 = 255 max data length - 1 hopcounter - 1 optional parameters pointer +
    //       1 data length indicator
    if (maxLen > 254 + sccpParamsSize)
	udt = 255;
    else
	udt = maxLen - sccpParamsSize;
    // Append optional parameters length
    sccpParamsSize += MAX_OPT_LEN;

    if (ludtSupport) {
	unsigned int maxSupported  = ITU() ? MAX_DATA_ITU : MAX_DATA_ANSI;
	if (maxLen < maxSupported) {
	    ludt = maxLen - sccpParamsSize;
	    ludt -= 5; // The pointers and data length are on 2 octets
	} else
	    ludt = maxSupported;
    }
    // 254 represents the maximum value that can be stored
    if (maxLen < 254)
	xudt = maxLen - sccpParamsSize;
    // Adjust data length to make sure that the pointer to optional parameters
    // is not bigger than max unsigned char value
    xudt = 254 - sccpParamsSize;
}

void SS7SCCP::printMessage(const SS7MSU* msu, const SS7MsgSCCP* sccpMsg, const
	SS7Label& label) {

    if (m_printMsg && debugAt(DebugInfo)) {
	String tmp;
	const void* data = 0;
	unsigned int len = 0;
	if (m_extendedDebug && msu) {
	    unsigned int offs = label.length() + 4;
	    data = msu->getData(offs);
	    len = data ? msu->length() - offs : 0;
	}
	String tmp1;
	fillLabelAndReason(tmp1,label,sccpMsg);
	sccpMsg->toString(tmp,label,debugAt(DebugAll),data,len);
	Debug(this,DebugInfo,"Sending message (%p) '%s' %s %s",sccpMsg,
		SS7MsgSCCP::lookup(sccpMsg->type()),tmp1.c_str(),tmp.c_str());
    } else if (debugAt(DebugAll)) {
	String tmp;
	bool debug = fillLabelAndReason(tmp,label,sccpMsg);
	Debug(this,debug ? DebugInfo : DebugAll,"Sending message '%s' %s",
		sccpMsg->name(),tmp.c_str());
    }
}

ObjList* SS7SCCP::getDataSegments(unsigned int dataLength,
	unsigned int maxSegmentSize)
{
    DDebug(DebugAll,"getDataSegments(%u,%u)",dataLength,maxSegmentSize);
    ObjList* segments = new ObjList();
    // The first sccp segment must be the largest
    int segmentSize = maxSegmentSize - 1;
    int dataLeft = dataLength;
    unsigned int totalSent = 0;
    int sgSize = maxSegmentSize;
    if (dataLength - maxSegmentSize <= MIN_DATA_SIZE)
	sgSize = maxSegmentSize - MIN_DATA_SIZE;
    segments->append(new SS7SCCPDataSegment(0,sgSize));
    dataLeft -= sgSize;
    totalSent += sgSize;
    while (dataLeft > 0) {
	sgSize = 0;
	if ((dataLeft - segmentSize) > MIN_DATA_SIZE) { // Make sure that the left segment is longer than 2
	    sgSize = segmentSize;
	} else if (dataLeft > segmentSize) {
	    sgSize = segmentSize - MIN_DATA_SIZE;
	} else {
	    sgSize = dataLeft;
	}
	XDebug(this,DebugAll,"Creating new data segment total send %d, segment size %d",
	       totalSent,sgSize);
	segments->append(new SS7SCCPDataSegment(totalSent,sgSize));
	dataLeft -= sgSize;
	totalSent += sgSize;
    }
    return segments;
}

SS7SCCPDataSegment* getAndRemoveDataSegment(ObjList* obj)
{
    if (!obj)
	return 0;
    ObjList* o = obj->skipNull();
    if (!o)
	return 0;
    SS7SCCPDataSegment* sgm = static_cast<SS7SCCPDataSegment*>(o->get());
    obj->remove(sgm,false);
    return sgm;
}

int SS7SCCP::segmentMessage(SS7MsgSCCP* origMsg, const SS7Label& label, bool local)
{
    if (!origMsg)
	return -1;
    unsigned int udtLength = 0;
    unsigned int xudtLength = 0;
    unsigned int ludtLength = 0;
    getMaxDataLen(origMsg,label,udtLength,xudtLength,ludtLength);
    unsigned int dataLen = 0;

    DDebug(this,DebugInfo, "Got max data len : udt (%d) : xudt (%d) ludt (%d)",
	   udtLength,xudtLength,ludtLength);
    if (udtLength < 2 && xudtLength < 2 && ludtLength < 2)
	return -1;
    int sls = origMsg->params().getIntValue(YSTRING("sls"),-1);
    DataBlock* data = origMsg->getData();
    if (!data)
	return -1;
    // Verify if we should bother to send the message
    if (data->length() > (ITU() ? MAX_DATA_ITU : MAX_DATA_ANSI)) {
	Debug(this,DebugNote,
	      "Unable to send SCCP message! Data length (%d) is too long",
	      data->length());
	return -1;
    }

    SS7MsgSCCP::Type msgType = origMsg->type();
    if (data->length() <= udtLength && origMsg->canBeUDT()) {
	msgType = isSCLCMessage(msgType) ? SS7MsgSCCP::UDT : SS7MsgSCCP::UDTS;
	dataLen = udtLength;
    } else if (data->length() <= xudtLength) {
	msgType = isSCLCMessage(msgType) ? SS7MsgSCCP::XUDT : SS7MsgSCCP::XUDTS;
	dataLen = xudtLength;
    } else if (data->length() <= ludtLength) {
	msgType = isSCLCMessage(msgType) ? SS7MsgSCCP::LUDT : SS7MsgSCCP::LUDTS;
	dataLen = ludtLength;
    } else { // Segmentation is needed!!!
	if (ludtLength > 2) { // send ludt
	    msgType = isSCLCMessage(msgType) ? SS7MsgSCCP::LUDT : SS7MsgSCCP::LUDTS;
	    dataLen = ludtLength;
	} else if (xudtLength > 2) { // Send Ludt
	    msgType = isSCLCMessage(msgType) ? SS7MsgSCCP::XUDT : SS7MsgSCCP::XUDTS;
	    dataLen = xudtLength;
	} else {
	    Debug(this,DebugWarn,
		  "Unable to segment message!! Invalid data len params! XUDT data len = %d, LUDT data len = %d",
		  xudtLength,ludtLength);
	}
    }
    origMsg->updateType(msgType);
    origMsg->params().clearParam(YSTRING("Segmentation"),'.');
    // Send the message if it fits in a single message
    if (data->length() <= dataLen) {
	Lock lock(this);
	ajustMessageParams(origMsg->params(),origMsg->type());
	SS7MSU* msu = buildMSU(origMsg,label,false);
	if (!msu) {
	    Debug(this,DebugWarn,"Failed to build MSU from sccpMessage %s",
		SS7MsgSCCP::lookup(origMsg->type()));
	    return -1;
	}
	printMessage(msu,origMsg,label);
	lock.drop();
	sls = transmitMSU(*msu,label,sls);
#ifdef DEBUG
	if (sls < 0)
	    Debug(this,DebugNote,"Failed to transmit message %s. %d",
		  SS7MsgSCCP::lookup(origMsg->type()),sls);
#endif
	// CleanUp memory
	TelEngine::destruct(msu);
	return sls;
    }
    // Verify if we should bother to segment the message
    if ((data->length() > 16 * (dataLen - 1)) && !isSCLCSMessage(msgType)) {
	Debug(DebugNote,
	      "Unable to segment SCCP message! Data length (%d) excedes max data allowed (%d)",
	      data->length(),(16 * (dataLen - 1)));
	return -1;
    }

    // Start segmentation process
    lock();
    ObjList* listSegments = getDataSegments(data->length(),dataLen);

    // Build message params
    NamedList msgData("");
    msgData.copyParams(origMsg->params());
    ajustMessageParams(msgData,msgType);

    // Set segmentation local reference for this message
    msgData.setParam("Segmentation","");
    if (!msgData.getParam(YSTRING("Segmentation.SegmentationLocalReference")))
	msgData.setParam("Segmentation.SegmentationLocalReference",String((u_int32_t)Random::random()));
    int segments = listSegments->count();
    msgData.setParam("Segmentation.ProtocolClass",msgData.getValue(YSTRING("ProtocolClass")));
    if (isSCLCMessage(msgType))
	msgData.setParam("ProtocolClass","1"); // Segmentation is using in sequence delivery option
    bool msgReturn = msgData.getBoolValue(YSTRING("MessageReturn"),false);
    sls = msgData.getIntValue(YSTRING("sls"),-1);

    // Transmit first segment
    SS7MsgSCCP* msg = new SS7MsgSCCP(msgType);
    msg->params().copyParams(msgData);
    DataBlock temp;
    SS7SCCPDataSegment* sg = getAndRemoveDataSegment(listSegments);
    if (!sg) {
	Debug(DebugStub,"Unable to extract first data segment!!!");
	TelEngine::destruct(msg);
	TelEngine::destruct(listSegments);
	return -1;
    }
    sg->fillSegment(temp,*data);
    msg->params().setParam("Segmentation.RemainingSegments",
	    String(isSCLCMessage(msgType) ? --segments : 0));
    msg->params().setParam("Segmentation.FirstSegment","true");
    msg->setData(&temp);
    SS7MSU* msu = buildMSU(msg,label,false);
    msg->removeData();
    temp.clear(false);
    if (!msu) {
	Debug(this,DebugWarn,"Failed to build MSU from sccpMessage %s",
		SS7MsgSCCP::lookup(msgType));
	TelEngine::destruct(msg);
	TelEngine::destruct(listSegments);
	return -1;
    }
    printMessage(msu,msg,label);
    unlock();
    sls = transmitMSU(*msu,label,sls);
#ifdef DEBUG
    if (sls < 0)
	Debug(this,DebugNote,"Failed to transmit message %s. %d",
		SS7MsgSCCP::lookup(msgType),sls);
#endif
    TelEngine::destruct(msu);
    TelEngine::destruct(msg);
    TelEngine::destruct(sg);
    if (sls < 0) {
	if (msgReturn && !local)
	    returnMessage(origMsg,MtpFailure);
	Debug(this,DebugNote,"Failed to transmit first segment of message");
	TelEngine::destruct(listSegments);
	return sls;
    }
    if (isSCLCSMessage(msgType)) {
	TelEngine::destruct(listSegments);
	return sls;
    }
    lock();
    msgData.setParam("Segmentation.FirstSegment","false");
    // Set message return option only for the first segment
    msgData.setParam("MessageReturn","false");
    while ((sg = getAndRemoveDataSegment(listSegments))) {
	msg = new SS7MsgSCCP(msgType);
	msg->params().copyParams(msgData);
	sg->fillSegment(temp,*data);
	TelEngine::destruct(sg);
	msg->params().setParam("Segmentation.RemainingSegments",String(--segments));
	msg->setData(&temp);
	SS7MSU* msu = buildMSU(msg,label,false);
	msg->removeData();
	temp.clear(false);
	if (!msu) {
	    Debug(this,DebugWarn,"Failed to build MSU from sccpMessage %s",
		    SS7MsgSCCP::lookup(msgType));
	    TelEngine::destruct(msg);
	    TelEngine::destruct(listSegments);
	    return -1;
	}
	printMessage(msu,msg,label);
	unlock();
	sls = transmitMSU(*msu,label,sls);
#ifdef DEBUG
	if (sls < 0)
	    Debug(this,DebugNote,"Failed to transmit message %s. %d",
		    SS7MsgSCCP::lookup(msgType),sls);
#endif
	TelEngine::destruct(msg);
	TelEngine::destruct(msu);
	if (sls < 0) {
	    if (msgReturn && !local)
		returnMessage(origMsg,MtpFailure);
	    Debug(this,DebugNote,"Failed to transmit segment of %s message remaining segments %d",
		  SS7MsgSCCP::lookup(msgType),segments);
	    return sls;
	}
	lock();
    }
    if (segments != 0)
	Debug(this,DebugStub,"Bug in segment message!! RemainingSegments %d",segments);
    TelEngine::destruct(listSegments);
    unlock();
    return sls;
}

SS7MsgSccpReassemble::Return SS7SCCP::reassembleSegment(SS7MsgSCCP* segment,
	    const SS7Label& label, SS7MsgSCCP*& msg)
{
    if (segment->params().getBoolValue(YSTRING("Segmentation.FirstSegment"))) {
	for (ObjList* o = m_reassembleList.skipNull(); o; o = o->skipNext()) {
	    SS7MsgSccpReassemble* reass = static_cast <SS7MsgSccpReassemble*>(o->get());
	    if (!reass || !reass->canProcess(segment,label))
		continue;
	    m_reassembleList.remove(reass);
	    DDebug(this,DebugNote,"Duplicate first segment received!");
	    return SS7MsgSccpReassemble::Error;
	}
	SS7MsgSccpReassemble* reass = new SS7MsgSccpReassemble(segment,label,m_segTimeout);
	m_reassembleList.append(reass);
	return SS7MsgSccpReassemble::Accepted;
    }

    SS7MsgSccpReassemble::Return ret = SS7MsgSccpReassemble::Rejected;
    for (ObjList* o = m_reassembleList.skipNull(); o; o = o->skipNext()) {
	SS7MsgSccpReassemble* reass = static_cast <SS7MsgSccpReassemble*>(o->get());
	if (!reass)
	    continue;
	ret = reass->appendSegment(segment,label);
	if (ret == SS7MsgSccpReassemble::Rejected)
	    continue;
	if (ret == SS7MsgSccpReassemble::Error) {
	    m_reassembleList.remove(reass,false);
	    msg = reass;
	    return ret;
	}
	if (ret == SS7MsgSccpReassemble::Finished) {
	    m_reassembleList.remove(reass,false);
	    msg = reass;
	}
	return ret;
    }
    return ret;
}

SS7MSU* SS7SCCP::buildMSU(SS7MsgSCCP* msg, const SS7Label& label, bool checkLength) const
{
    // see what mandatory parameters we should put in this message
    const MsgParams* msgParams = getSccpParams(msg->type());
    if (!msgParams) {
	const char* name = SS7MsgSCCP::lookup(msg->type());
	if (name)
	    Debug(this,DebugWarn,"No parameter table for SCCP MSU type %s [%p]",name,this);
	else
	    Debug(this,DebugWarn,"Cannot create SCCP MSU type 0x%02x [%p]",msg->type(),this);
	return 0;
    }
    unsigned int len = 1;

    const SS7MsgSCCP::Parameters* plist = msgParams->params;
    SS7MsgSCCP::Parameters ptype;
    // first add the length of mandatory fixed parameters
    while ((ptype = *plist++) != SS7MsgSCCP::EndOfParameters) {
	const SCCPParam* param = getParamDesc(ptype);
	if (!param) {
	    // this is fatal as we don't know the length
	    Debug(this,DebugCrit,"Missing description of fixed SCCP parameter 0x%02x [%p]",ptype,this);
	    return 0;
	}
	if (!param->size) {
	    Debug(this,DebugCrit,"Invalid (variable) description of fixed SCCP parameter 0x%02x [%p]",ptype,this);
	    return 0;
	}
	len += param->size;
    }
    bool ludt = msg->isLongDataMessage();
    int pointerLen = ludt ? 2 : 1;
    // initialize the pointer array offset just past the mandatory fixed part
    unsigned int ptr = label.length() + 1 + len;
    // then add one pointer octet to each mandatory variable parameter
    while ((ptype = *plist++) != SS7MsgSCCP::EndOfParameters) {
	const SCCPParam* param = getParamDesc(ptype);
	if (!param) {
	    // this is fatal as we won't be able to populate later
	    Debug(this,DebugCrit,"Missing description of variable SCCP parameter 0x%02x [%p]",ptype,this);
	    return 0;
	}
	if (param->size)
	    Debug(this,DebugMild,"Invalid (fixed) description of variable SCCP parameter 0x%02x [%p]",ptype,this);
	len += pointerLen;
    }
    // finally add a pointer to the optional part only if supported by type
    if (msgParams->optional)
	len += pointerLen;
    SS7MSU* msu = new SS7MSU(sio(),label,0,len);
    unsigned char* d = msu->getData(label.length()+1,len);
    *d++ = msg->type();
    ObjList exclude;
    plist = msgParams->params;
    String prefix = msg->params().getValue(YSTRING("message-prefix"));
    // first populate with mandatory fixed parameters
    while ((ptype = *plist++) != SS7MsgSCCP::EndOfParameters) {
	const SCCPParam* param = getParamDesc(ptype);
	if (!param) {
	    Debug(this,DebugFail,"Stage 2: no description of fixed SCCP parameter 0x%02x [%p]",ptype,this);
	    continue;
	}
	if (!param->size) {
	    Debug(this,DebugFail,"Stage 2: Invalid (variable) description of fixed SCCP parameter %s [%p]",param->name,this);
	    continue;
	}
	if (!encodeParam(this,*msu,param,&msg->params(),exclude,prefix,d))
	    Debug(this,DebugCrit,"Could not encode fixed SCCP parameter %s [%p]",param->name,this);
	d += param->size;
    }
    // now populate with mandatory variable parameters
    for (; (ptype = *plist++) != SS7MsgSCCP::EndOfParameters; ptr += pointerLen) {
	const SCCPParam* param = getParamDesc(ptype);
	if (!param) {
	    Debug(this,DebugFail,"Stage 2: no description of variable SCCP parameter 0x%02x [%p]",ptype,this);
	    continue;
	}
	if (param->size) {
	    Debug(this,DebugFail,"Stage 2: Invalid (fixed) description of variable SCCP parameter %s [%p]",param->name,this);
	    continue;
	}
	// remember the offset this parameter will actually get stored
	len = msu->length();
	unsigned int size = 0;
	if (ptype == SS7MsgSCCP::Data || ptype == SS7MsgSCCP::LongData) {
	    size = encodeData(this,*msu,msg);
	    if (ptype == SS7MsgSCCP::Data) {
		// Data parameter is the last of variable mandatory parameters
		// Check if the pointer to variable part may be bigger than 255
		// (max unsigned char value)
		if (checkLength && ((len + size + MAX_OPT_LEN) > 254)) {
		    TelEngine::destruct(msu);
		    return 0;
		}
	    }
	} else
	    size = encodeParam(this,*msu,param,&msg->params(),exclude,prefix);
	d = msu->getData(0,len+1);
	if (!(size && d)) {
	    Debug(this,DebugCrit,"Could not encode variable SCCP parameter %s [%p]",param->name,this);
	    continue;
	}
	if (ptype != SS7MsgSCCP::LongData && ((d[len] != size) || (msu->length() != (len + size + 1)))) {
	    Debug(this,DebugCrit,"Invalid encoding variable SCCP parameter %s (len=%u size=%u stor=%u msuLength = %u) [%p]",
		param->name,len,size,d[len],msu->length(),this);
	    continue;
	}
	// store pointer to parameter
	unsigned int storedLength = len - ptr;
	if (!ludt) {
	    d[ptr] = storedLength;
	    continue;
	}
	storedLength --;
	d[ptr] = storedLength & 0xff;
	d[ptr+1] = storedLength >> 8;
    }
    if (msgParams->optional) {
	// remember the offset past last mandatory == first optional parameter
	len = msu->length();
	// optional parameters are possible - try to set anything left in the message
	unsigned int n = msg->params().length();
	for (unsigned int i = 0; i < n; i++) {
	    NamedString* ns = msg->params().getParam(i);
	    if (!ns || exclude.find(ns))
		continue;
	    if (prefix && !ns->name().startsWith(prefix))
		continue;
	    String tmp(ns->name());
	    tmp >> prefix.c_str();
	    const SCCPParam* param = getParamDesc(tmp);
	    unsigned char size = 0;
	    if (param)
		size = encodeParam(this,*msu,param,ns,&msg->params(),prefix);
	    else if (tmp.startSkip("Param_",false)) {
		int val = tmp.toInteger(-1);
		if (val >= 0 && val <= 255) {
		    SCCPParam p;
		    p.name = tmp;
		    p.type = (SS7MsgSCCP::Parameters)val;
		    p.size = 0;
		    p.encoder = 0;
		    size = encodeParam(this,*msu,&p,ns,&msg->params(),prefix);
		}
	    }
	    if (!size)
		continue;
	    if (len) {
		d = msu->getData(0,len+1);
		unsigned int storedLength = len - ptr;
		if (ludt) {
		    storedLength --;
		    d[ptr] = storedLength & 0xff;
		    d[ptr+1] = storedLength >> 8;
		} else {
		    // Do not try to set the pointer to optional parameters
		    // if is bigger than max unsigned char value because will
		    // result in a malformed packet!
		    if (storedLength > 255) {
			Debug(this,checkLength ? DebugAll : DebugStub,
			      "Build MSU the pointer to optional parameters is bigger than 255!!!! %d",
			      storedLength);
			TelEngine::destruct(msu);
			return 0;
		    }
		    d[ptr] = storedLength;
		}
		len = 0;
	    }
	}
	if (!len) {
	    // we stored some optional parameters so we need to put the terminator
	    DataBlock tmp(0,1);
	    *msu += tmp;
	}
    }
    return msu;
}

HandledMSU SS7SCCP::receivedMSU(const SS7MSU& msu, const SS7Label& label, SS7Layer3* network, int sls)
{
    if (msu.getSIF() != sif()) // SCCP message?
	return HandledMSU::Rejected;
    Lock lock(this);
    if (unknownPointCodeType()) {
	DDebug(this,DebugNote,"Rejecting MSU! Reason Unknown pointcode type");
	lock.drop();
	return HandledMSU::Rejected;
    }
    if (m_localPointCode && *m_localPointCode != label.dpc()) { // Is the msu for us?
	lock.drop();
	return HandledMSU::Rejected;
    }
    lock.drop();
    const unsigned char* s = msu.getData(label.length() + 1,1);
    if (!s) {
	Debug(this,DebugNote,"Got short MSU");
	return false;
    }
    unsigned int len = msu.length()-label.length()-1;
    SS7MsgSCCP::Type type = (SS7MsgSCCP::Type)s[0];
    String name = SS7MsgSCCP::lookup(type);
    if (!name) {
        String tmp;
	tmp.hexify((void*)s,len,' ');
	DDebug(this,DebugMild,"Received unknown SCCP type 0x%02x, length %u: %s",
	    type,len,tmp.c_str());
	return false;
    }
    bool ok = processMSU(type,s+1,len-1,label,network,sls);
    if (!ok && debugAt(DebugMild)) {
	String tmp;
	tmp.hexify((void*)s,len,' ');
	Debug(this,DebugMild,"Unhandled SCCP message %s,  length %u: %s",
	    name.c_str(),len,tmp.c_str());
    }
    return ok;
}

bool SS7SCCP::processMSU(SS7MsgSCCP::Type type, const unsigned char* paramPtr,
	unsigned int paramLen, const SS7Label& label, SS7Layer3* network, int sls)
{
    XDebug(this,DebugAll,"SS7SCCP::processMSU(%u,%p,%u,%p,%p,%d) [%p]",
	type,paramPtr,paramLen,&label,network,sls,this);

    Lock lock(this);
    SS7MsgSCCP* msg = new SS7MsgSCCP(type);
    if (!decodeMessage(msg,label.type(),paramPtr,paramLen)) {
	DDebug(this,DebugNote,"Failed to decode SCCP message!");
	m_errors++;
	TelEngine::destruct(msg);
	return false;
    }
    msg->params().setParam("LocalPC",String(label.dpc().pack(m_type)));
    msg->params().setParam("RemotePC",String(label.opc().pack(m_type)));
    msg->params().setParam("generated","remote");
    // Set the sls in case of STP routing for sequence control
    msg->params().setParam("sls",String(label.sls()));
    if (m_printMsg && debugAt(DebugInfo)) {
	String tmp;
	msg->toString(tmp,label,debugAt(DebugAll),
	    m_extendedDebug ? paramPtr : 0,paramLen);
	String tmp1;
	fillLabelAndReason(tmp1,label,msg);
	Debug(this,DebugInfo,"Received message (%p) '%s' %s %s",
	      msg,SS7MsgSCCP::lookup(msg->type()),tmp1.c_str(),tmp.c_str());
    } else if (debugAt(DebugAll)) {
	String tmp;
	bool debug = fillLabelAndReason(tmp,label,msg);
	Debug(this,debug ? DebugInfo : DebugAll,"Received message '%s' %s",
	    msg->name(),tmp.c_str());
    }
    // Form here something will happened with the message!
    // return true
    m_totalReceived++;
    int protocolClass = msg->params().getIntValue(YSTRING("ProtocolClass"), -1);
    if (isSCOCMsg(msg->type())) {
	Debug(DebugWarn,"Received Connection oriented message!!");
	if (msg->type() != SS7MsgSCCP::CR) { // Received Connection Oriented message other than Connect Request
	    // Drop the message
	    DDebug(this,DebugNote,"Received message %s without a connection!",SS7MsgSCCP::lookup(msg->type()));
	    TelEngine::destruct(msg);
	    return true;
	}
	// Send Connection Refused
	SS7MsgSCCP* ref = new SS7MsgSCCP(SS7MsgSCCP::CREF);
	ref->params().setParam("DestinationLocalReference",msg->params().getValue(YSTRING("SourceLocalReference")));
	ref->params().setParam("RefusalCause",String(0x13)); // Unequipped user
	SS7Label outLabel(label.type(),label.opc(),label.dpc(),label.sls());
	SS7MSU* msu = buildMSU(ref,outLabel);
	if (!msu)
	    Debug(this,DebugWarn,"Failed to build msu from sccpMessage %s",
		SS7MsgSCCP::lookup(ref->type()));
	lock.drop();
	if (msu) {
	    transmitMSU(*msu,outLabel,outLabel.sls());
	    TelEngine::destruct(msu);
	}
	TelEngine::destruct(ref);
	TelEngine::destruct(msg);
	return true;
    }
    // If the Calling party address does not contain route information
    // Set OPC as Calling Party Address pointcode
    if (((protocolClass == 0 || protocolClass == 1) &&
	    isSCLCMessage(msg->type())) || isSCLCSMessage(msg->type())) { // ConnectionLess message
	lock.drop();
	routeSCLCMessage(msg,label);
    } else {
	Debug(this,DebugMild,"Received bad message! Inconsistence between msg type %s and protocol class %d",
		SS7MsgSCCP::lookup(msg->type()),protocolClass);
    }
    TelEngine::destruct(msg);
    return true;
}

// Process a SCCP message! return false if an error was detected
bool SS7SCCP::routeSCLCMessage(SS7MsgSCCP*& msg, const SS7Label& label)
{
    Lock lock(this);
    if (!msg) {
	Debug(this,DebugWarn,"Request to route null sccp message");
	m_errors++;
	return false;
    }
    if (msg->params().getParam(YSTRING("Segmentation"))) {
	// Verify if we had received Segmentation parameter with only one segment
	// and let it pass trough
	// The reassamblation of XUTDS and LUDTS is optional but, for code flow
	// purpose, we are manageing it.
	if (msg->params().getIntValue(YSTRING("Segmentation.RemainingSegments"),0) != 0 ||
		!msg->params().getBoolValue(YSTRING("Segmentation.FirstSegment"),true)) {
	    // We have segmentation parameter with multiple segments
	    SS7MsgSCCP* finishead = 0;
	    int ret = reassembleSegment(msg,label,finishead);
	    if (ret == SS7MsgSccpReassemble::Accepted ||
		    ret == SS7MsgSccpReassemble::Rejected)
		return true;
	    if (ret == SS7MsgSccpReassemble::Error) {
		// For XUDTS and LUDTS messages the message return should allways
		// be false
		if (finishead && finishead->params().getBoolValue(YSTRING("MessageReturn"),false))
		    returnMessage(finishead,SegmentationFailure);
		if (finishead)
		    TelEngine::destruct(finishead);
		m_errors++;
		return true;
	    }
	    if (!finishead) {
		Debug(this,DebugStub,"Sccp Message finishead to reassemble but the message was not returned");
		return true;
	    }
	    TelEngine::destruct(msg);
	    msg = finishead;
	}
    }
    int errorCode = -1;
    NamedString* route = msg->params().getParam(YSTRING("CalledPartyAddress.route"));
    bool msgReturn = msg->params().getBoolValue(YSTRING("MessageReturn"),false);
    bool informManagement = false;
    while (route && *route != YSTRING("ssn")) {
	if (!msg->params().getParam(YSTRING("CalledPartyAddress.gt"))) {
	    if (m_endpoint && msg->params().getParam(YSTRING("CalledPartyAddress.ssn")))
		break; // If we are endpoint and we have a ssn try to process the message
	    Debug(this,DebugInfo,"Message requested to be routed on gt but no gt present!");
	    break;
	}
	NamedList* gtRoute = translateGT(msg->params(),"CalledPartyAddress",
		"CallingPartyAddress");
	m_totalGTTranslations++;
	if (!gtRoute) {
	    if (m_endpoint && msg->params().getParam(YSTRING("CalledPartyAddress.ssn")))
		break; // If we are endpoint and we have a ssn try to process the message
	    m_gttFailed++;
	    errorCode = NoTranslationSpecificAddress;
	    Debug(this,DebugInfo,"No Gt Found for : %s, or all routes are down!",
		  msg->params().getValue(YSTRING("CalledPartyAddress.gt")));
	    break;
	}
	resolveGTParams(msg,gtRoute);
	NamedString* localRouting = gtRoute->getParam(YSTRING("sccp"));
	if (localRouting && *localRouting != toString()) {
	    msg->params().copyParam(*gtRoute,YSTRING("RemotePC"));
	    TelEngine::destruct(gtRoute);
	    lock.drop();
	    return routeLocal(msg) >= 0;
	}
	bool haveRemotePC = gtRoute->getParam(YSTRING("RemotePC")) != 0;
	if (!gtRoute->getParam(YSTRING("pointcode")) && !haveRemotePC) {
	    if (m_endpoint) {
		// If we have an ssn try to process the message
		if (msg->params().getParam(YSTRING("CalledPartyAddress.ssn")))
		    break;
		if (gtRoute->getParam(YSTRING("ssn"))) {
		    msg->params().setParam("CalledPartyAddress.ssn",gtRoute->getValue(YSTRING("ssn")));
		    break;
		}
	    }
	    Debug(this,DebugWarn,"The GT has not been translated to a pointcode!!");
	    TelEngine::destruct(gtRoute);
	    errorCode = NoTranslationAddressNature;
	    break;
	}
	msg->params().clearParam(YSTRING("CalledPartyAddress"),'.');
	for (unsigned int i = 0;i < gtRoute->length();i++) {
	    NamedString* val = gtRoute->getParam(i);
    	    if (val && (val->name().startsWith("gt") || val->name() == YSTRING("pointcode") ||
		    val->name() == YSTRING("ssn") || val->name() == YSTRING("route")))
		msg->params().setParam("CalledPartyAddress." + val->name(),*val);
	}
	int pointcode = haveRemotePC ? gtRoute->getIntValue(YSTRING("RemotePC")) :
	    msg->params().getIntValue(YSTRING("CalledPartyAddress.pointcode"));

	TelEngine::destruct(gtRoute);
	if (msg->params().getIntValue(YSTRING("CalledPartyAddress.ssn"),-1) == 1) {
	    Debug(this,DebugNote,"GT Routing Warn!! Message %s global title translated for management!",
		  SS7MsgSCCP::lookup(msg->type()));
	    m_errors++;
	    return false; // Management message with global title translation
	}
	if (!m_localPointCode)
	    Debug(this,DebugConf,
		  "No local PointCode configured!! GT translations with no local PointCode may lead to undesired behavior");
	if (msg->params().getParam(YSTRING("HopCounter"))) {
	    int hopcounter = msg->params().getIntValue(YSTRING("HopCounter"));
	    hopcounter --;
	    if (hopcounter <= 0) {
		errorCode = HopCounterViolation;
		break;
	    }
	    msg->params().setParam("HopCounter",String(hopcounter));
	}
	// If from the translated gt resulted a pointcode other then ours forward the message
	if (pointcode > 0 && m_localPointCode && (unsigned int)pointcode != m_localPointCode->pack(m_type)) {
	    msg->params().setParam("RemotePC",String(pointcode));
	    lock.drop();
	    if (transmitMessage(msg) >= 0)
		return true;
	    informManagement = true;
	    errorCode = MtpFailure;
	}
	break;
    }
    if (errorCode >= 0) {
	m_errors++;
	lock.drop();
	if (informManagement && m_management)
	    m_management->routeFailure(msg);
	if (msgReturn)
	    returnMessage(msg,errorCode);
	else
	    Debug(this,DebugInfo,"Dropping message %s. Reason: %s",
		    SS7MsgSCCP::lookup(msg->type()),lookup(errorCode,s_return_cause));
	return false;
    }
    int ssn = msg->params().getIntValue(YSTRING("CalledPartyAddress.ssn"),-1);
    errorCode = SccpFailure;
    while (ssn > 0) {
	if (ssn == 0) {
	    Debug(this,DebugNote,"Requested user with ssn 0!");
	    errorCode = UnequippedUser;
	    break;
	}
	if (ssn == 1) { // Local Management message ?
	    while (true) {
		int protocolClass = msg->params().getIntValue(YSTRING("ProtocolClass"));
		// SCCP management messages need to have protocol class 0 with no special options
		if (protocolClass != 0 || msgReturn)
		    break;
		// Remote SSN must be management SSN (1)
		if (msg->params().getIntValue(YSTRING("CallingPartyAddress.ssn"),-1) != 1)
		    break;
		if (m_management) {
		    lock.drop();
		    return m_management->processMessage(msg);
		}
		break;
	    }
#ifdef DEBUG
	    String tmp;
	    msg->params().dump(tmp,"\r\n  ",'\'',true);
	    Debug(this,DebugNote,"Received invalid SCCPManagement message! %s",tmp.c_str());
#endif
	    m_errors++;
	    return false;
	}
	// If we are here that means that the message is for local processing!
	switch (msg->type()) {
	    case SS7MsgSCCP::XUDT:
	    case SS7MsgSCCP::LUDT:
	    case SS7MsgSCCP::UDT:
		lock.drop();
		{
		    int ret = pushMessage(*msg->getData(),msg->params(),ssn);
		    if (ret == HandledMSU::Accepted)
			return true;
		    if (m_management)
			m_management->subsystemFailure(msg,label);
		    errorCode = (ret == HandledMSU::Unequipped) ? UnequippedUser : SubsystemFailure;
		}
		break;
	    case SS7MsgSCCP::XUDTS:
	    case SS7MsgSCCP::LUDTS:
	    case SS7MsgSCCP::UDTS:
		if (m_extendedMonitoring)
		    archiveMessage(msg);
		DDebug(this,DebugAll,"Received service message %s. Reason: %s",
			SS7MsgSCCP::lookup(msg->type()),lookup(msg->params().getIntValue(YSTRING("ReturnCause")),s_return_cause));
		msg->params().setParam("location","remote");
		lock.drop();
		notifyMessage(*msg->getData(),msg->params(),ssn);
		// Do not bother to verify the return code, because there is nothing that we can do for service messages
		return true;
	    default:
		Debug(this,DebugWarn,"Received unknown SCLC msg type %d",msg->type());
		errorCode = ErrorInLocalProcessing;
		break;
	}
	break;
    }
    m_errors++;
    lock.drop();
    if (msgReturn)
	returnMessage(msg,errorCode);
    else
	Debug(this,DebugInfo,"Dropping message %s. Reason: %s",
	      SS7MsgSCCP::lookup(msg->type()),lookup(errorCode,s_return_cause));
    return false;
}

void SS7SCCP::returnMessage(SS7MsgSCCP* message, int error)
{
    DDebug(this,DebugInfo,"Returning message %s! reason : %s",SS7MsgSCCP::lookup(message->type()),lookup(error,s_return_cause));
    if (!message) {
	DDebug(this,DebugNote,"Message return method called for a null message!!");
	return;
    }
    if (!message->getData()) {
	DDebug(this,DebugWarn,"Message Return initiated with no data parameter");
	return;
    }
    SS7MsgSCCP* msg = 0;
    switch (message->type()) {
	case SS7MsgSCCP::UDT:
	    msg = new SS7MsgSCCP(SS7MsgSCCP::UDTS);
	    break;
	case SS7MsgSCCP::XUDT:
	    msg = new SS7MsgSCCP(SS7MsgSCCP::XUDTS);
	    break;
	case SS7MsgSCCP::LUDT:
	    msg = new SS7MsgSCCP(SS7MsgSCCP::LUDTS);
	    break;
	default:
	    DDebug(this,DebugInfo,"Message return procedure initiated for wrong message type %s",
			SS7MsgSCCP::lookup(msg->type()));
	    return;
    }
    if (!msg) {
	Debug(this,DebugStub,"Implementation bug!! null SCCP message");
	return;
    }
    msg->params().copyParams(message->params());
    switchAddresses(message->params(),msg->params());
    msg->params().setParam("ReturnCause",String(error));
    msg->setData(message->getData());
    msg->params().clearParam(YSTRING("ProtocolClass"),'.');
    msg->params().clearParam(YSTRING("Segmentation"),'.');
    msg->params().clearParam(YSTRING("MessageReturn"),'.');
    if (msg->params().getParam(YSTRING("Importance")))
	msg->params().setParam("Importance","3"); // Default value for service messages
    if (msg->params().getParam(YSTRING("HopCounter")))
	msg->params().setParam("HopCounter",String(m_hopCounter));
    transmitMessage(msg,true);
    msg->removeData();
    TelEngine::destruct(msg);
}

void SS7SCCP::switchAddresses(const NamedList& source, NamedList& dest)
{
    // First remove the called and calling party address from dest
    dest.clearParam(YSTRING("CalledPartyAddress"),'.');
    dest.clearParam(YSTRING("CallingPartyAddress"),'.');
    dest.clearParam(YSTRING("LocalPC"));
    dest.clearParam(YSTRING("RemotePC"));
    if (source.getParam(YSTRING("LocalPC")))
	dest.setParam("LocalPC",source.getValue(YSTRING("LocalPC")));
    // Do not set RemotePC because the message can fail after a gt was performed
    // and than RemotePC represents message destination pc rather then
    // originating pc. Obtain return address from CallingPartyAddress
    // Copy the params
    for (unsigned int i = 0;i < source.length();i++) {
	NamedString* param = source.getParam(i);
	if (!param || !param->name().startsWith("Call"))
	    continue;
	String name = param->name();
	if (name.startSkip(YSTRING("CalledPartyAddress"),false))
	    dest.setParam(new NamedString("CallingPartyAddress" + name,*param));
	if (name.startSkip(YSTRING("CallingPartyAddress"),false))
	    dest.setParam(new NamedString("CalledPartyAddress" + name,*param));
    }
}

bool SS7SCCP::decodeMessage(SS7MsgSCCP* msg, SS7PointCode::Type pcType,
    const unsigned char* paramPtr, unsigned int paramLen)
{
    if (!msg)
	return false;
    String msgTypeName((int)msg->type());
    const char* msgName = SS7MsgSCCP::lookup(msg->type(),msgTypeName);
#ifdef XDEBUG
    String tmp;
    tmp.hexify((void*)paramPtr,paramLen,' ');
    Debug(this,DebugAll,"Decoding msg=%s len=%u: %s [%p]",
	msgName,paramLen,tmp.c_str(),this);
#else
    DDebug(this,DebugAll,"Decoding msg=%s len=%u [%p]",
	msgName,paramLen,this);
#endif

    // see what parameters we expect for this message
    const MsgParams* params = getSccpParams(msg->type());
    if (!params) {
	Debug(this,DebugWarn,"Parameters list could not be found for message %s [%p]",msgName,this);
	return false;
    }

    // Get parameter prefix
    String prefix = msg->params().getValue(YSTRING("message-prefix"));

    // Add protocol and message type
    switch (pcType) {
	case SS7PointCode::ITU:
	    msg->params().addParam(prefix+"protocol-type","itu-t");
	    break;
	case SS7PointCode::ANSI:
	case SS7PointCode::ANSI8:
	    msg->params().addParam(prefix+"protocol-type","ansi");
	    break;
	default: ;
    }
    msg->params().addParam(prefix+"message-type",msgName);

    String unsupported;
    const SS7MsgSCCP::Parameters* plist = params->params;
    SS7MsgSCCP::Parameters ptype;
    // first decode any mandatory fixed parameters the message should have
    while ((ptype = *plist++) != SS7MsgSCCP::EndOfParameters) {
	const SCCPParam* param = getParamDesc(ptype);
	if (!param) {
	    // this is fatal as we don't know the length
	    Debug(this,DebugCrit,"Missing description of fixed SCCP parameter 0x%02x [%p]",ptype,this);
	    return false;
	}
	if (!param->size) {
	    Debug(this,DebugCrit,"Invalid (variable) description of fixed SCCP parameter %s [%p]",param->name,this);
	    return false;
	}
	if (paramLen < param->size) {
	    Debug(this,DebugWarn,"Truncated SCCP message! [%p]",this);
	    return false;
	}
	DDebug(this,DebugAll,"Decoding fixed SCCP Param %s",param->name);
	if (!decodeParam(this,msg->params(),param,paramPtr,param->size,prefix)) {
	    Debug(this,DebugWarn,"Could not decode fixed SCCP parameter %s [%p]",param->name,this);
	    decodeRaw(this,msg->params(),param,paramPtr,param->size,prefix);
	    unsupported.append(param->name,",");
	}
	paramPtr += param->size;
	paramLen -= param->size;
    } // while ((ptype = *plist++)...
    bool mustWarn = true;
    bool ludt = msg->isLongDataMessage();
    // next decode any mandatory variable parameters the message should have
    while ((ptype = *plist++) != SS7MsgSCCP::EndOfParameters) {
	mustWarn = false;
	const SCCPParam* param = getParamDesc(ptype);
	if (!param) {
	    // we could skip over unknown mandatory variable length but it's still bad
	    Debug(this,DebugCrit,"Missing description of variable SCCP parameter 0x%02x [%p]",ptype,this);
	    return false;
	}
	if (param->size)
	    Debug(this,DebugMild,"Invalid (fixed) description of variable SCCP parameter %s [%p]",param->name,this);
	if (!paramPtr || paramLen <= 0) {
	    Debug(this,DebugWarn,
		  "Unexpected end of stream!! Expecting to decode variabile parameter %s but there is no data left!!!",
		   param->name);
	    return false;
	}
	unsigned int offs = paramPtr[0];
	if (ludt) {
	    offs |= (paramPtr[1] << 8);
	    paramPtr++;
	    paramLen--;
	}
	if ((offs < 1) || (offs >= paramLen)) {
	    Debug(this,DebugWarn,"Invalid offset %u (len=%u) SCCP parameter %s [%p]",
		offs,paramLen,param->name,this);
	    return false;
	}
	unsigned int size = paramPtr[offs];
	if (ptype == SS7MsgSCCP::LongData) {
	    size |= (paramPtr[++offs] << 8);
	    size --;
	}
	if ((size < 1) || (offs+size >= paramLen)) {
	    Debug(this,DebugWarn,"Invalid size %u (ofs=%u, len=%u) SCCP parameter %s [%p]",
		size,offs,paramLen,param->name,this);
	    return false;
	}
	bool decoded = false;
	if (ptype == SS7MsgSCCP::Data || ptype == SS7MsgSCCP::LongData) {
	    if (!decodeData(this,msg,paramPtr+offs+1,size)) {
		Debug(this,DebugWarn,"Could not decode data SCCP parameter %s (size=%u) [%p]",
			param->name,size,this);
		decodeRaw(this,msg->params(),param,paramPtr+offs+1,size,prefix);
	    }
	    decoded = true;
	}
	if (!decoded && !decodeParam(this,msg->params(),param,paramPtr+offs+1,size,prefix)) {
	    Debug(this,DebugWarn,"Could not decode variable SCCP parameter %s (size=%u) [%p]",
		param->name,size,this);
	    decodeRaw(this,msg->params(),param,paramPtr+offs+1,size,prefix);
	    unsupported.append(param->name,",");
	}
	paramPtr++;
	paramLen--;
    } // while ((ptype = *plist++)...
    // now decode the optional parameters if the message supports them
    if (params->optional) {
	unsigned int offs = 0;
	if (paramLen) {
	    if (ludt && paramLen > 1) {
		offs = paramPtr[0] | (paramPtr[1] << 8);
		paramPtr++;
		paramLen--;
	    } else if (!ludt)
		offs = paramPtr[0];
	}
	if (offs >= paramLen) {
	    if (paramLen) {
		Debug(this,DebugWarn,"Invalid SCCP optional offset %u (len=%u) [%p]",
		    offs,paramLen,this);
		return false;
	    }
	    Debug(this,DebugMild,"SCCP message %s lacking optional parameters [%p]",
		msgName,this);
	}
	else if (offs) {
	    mustWarn = true;
	    // advance pointer past mandatory parameters
	    paramPtr += offs;
	    paramLen -= offs;
	    while (paramLen) {
		ptype = (SS7MsgSCCP::Parameters)(*paramPtr++);
		paramLen--;
		if (ptype == SS7MsgSCCP::EndOfParameters)
		    break;
		if (paramLen < 2) {
		    Debug(this,DebugWarn,"Only %u octets while decoding optional SCCP parameter 0x%02x [%p]",
			paramLen,ptype,this);
		    return false;
		}
		unsigned int size = *paramPtr++;
		paramLen--;
		if ((size < 1) || (size >= paramLen)) {
		    Debug(this,DebugWarn,"Invalid size %u (len=%u) SCCP optional parameter 0x%02x [%p]",
			size,paramLen,ptype,this);
		    return false;
		}
		const SCCPParam* param = getParamDesc(ptype);
		if (!param) {
		    Debug(this,DebugMild,"Unknown optional SCCP parameter 0x%02x (size=%u) [%p]",ptype,size,this);
		    decodeRawParam(this,msg->params(),ptype,paramPtr,size,prefix);
		    unsupported.append(String((unsigned int)ptype),",");
		}
		else if (!decodeParam(this,msg->params(),param,paramPtr,size,prefix)) {
		    Debug(this,DebugWarn,"Could not decode optional SCCP parameter %s (size=%u) [%p]",param->name,size,this);
		    decodeRaw(this,msg->params(),param,paramPtr,size,prefix);
		    unsupported.append(param->name,",");
		}
		paramPtr += size;
		paramLen -= size;
	    } // while (paramLen)
	} // else if (offs)
	else
	    paramLen = 0;
    }
    if (unsupported)
	msg->params().addParam(prefix + "parameters-unsupported",unsupported);
    if (paramLen && mustWarn)
	Debug(this,DebugWarn,"Got %u garbage octets after message type 0x%02x [%p]",
	    paramLen,msg->type(),this);
    return true;
}


void SS7SCCP::receivedUPU(SS7PointCode::Type type, const SS7PointCode node,
	SS7MSU::Services part, unsigned char cause, const SS7Label& label, int sls)
{
    if (part != sif() || !m_management) // not SCCP
	return;
    m_management->sccpUnavailable(node,cause);
}

bool SS7SCCP::control(NamedList& params)
{
    String* ret = params.getParam(YSTRING("completion"));
    const String* oper = params.getParam(YSTRING("operation"));
    const char* cmp = params.getValue(YSTRING("component"));
    int cmd = oper ? oper->toInteger(s_dict_control,-1) : -1;

    if (ret) {
	if (oper && (cmd < 0))
	    return false;
	String part = params.getValue(YSTRING("partword"));
	if (cmp) {
	    if (toString() != cmp)
		return false;
	    for (const TokenDict* d = s_dict_control; d->token; d++)
		Module::itemComplete(*ret,d->token,part);
	    return true;
	}
	return Module::itemComplete(*ret,toString(),part);
    }
    if (toString() != cmp)
	return false;
    Lock lock(this);
    switch (cmd) {
	case Status:
	    printStatus(false);
	    return TelEngine::controlReturn(&params,true);
	case FullStatus:
	    if (m_extendedMonitoring)
		printStatus(true);
	    else
		Output("Extended monitoring disabled!! Full Status unavailable!");
	    return TelEngine::controlReturn(&params,true);
	case EnableExtendedMonitoring:
	    m_extendedMonitoring = true;
	    return TelEngine::controlReturn(&params,true);
	case DisableExtendedMonitoring:
	    m_extendedMonitoring = false;
	    return TelEngine::controlReturn(&params,true);
	case EnablePrintMsg:
	    m_printMsg = true;
	    return TelEngine::controlReturn(&params,true);
	case DisablePrintMsg:
	    m_printMsg = false;
	    return TelEngine::controlReturn(&params,true);
    }
    return TelEngine::controlReturn(&params,false);
}

void SS7SCCP::printStatus(bool extended)
{
    String dest = "";
    dumpArchive(dest,extended);
    if (!m_management)
	return;
    m_management->subsystemsStatus(dest);
    m_management->routeStatus(dest);
    Output("SCCP '%s' [%p] Time: " FMT64 " Status:%s",debugName(),this,Time::msecNow(),dest.c_str());
}

void SS7SCCP::notify(SS7Layer3* link, int sls)
{
    if (!(link && network()))
	return;
    setNetworkUp(network()->operational());
    if (m_management)
	m_management->pointcodeStatus(link,network()->operational());
}

void SS7SCCP::setNetworkUp(bool operational)
{
    if (m_layer3Up == operational)
	return;
    m_layer3Up = operational;
    if (!m_management)
	return;
    DDebug(this,DebugInfo,"L3 is %s %p",operational ? "operational" : "down", m_management);
    if (m_layer3Up)
	m_management->mtpEndRestart();
    else
	m_management->stopSSTs();

}

void SS7SCCP::routeStatusChanged(SS7PointCode::Type type, const SS7PointCode& node, SS7Route::State state)
{
#ifdef DEBUG
    String dump;
    dump << node;
    DDebug(this,DebugAll,"Route status changed %s %s %p",dump.c_str(),SS7Route::stateName(state),m_management);
#endif
    state = network()->getRouteState(type,node);
    if (m_management)
	m_management->routeStatus(type,node,state);
}

void SS7SCCP::archiveMessage(SS7MsgSCCP* msg)
{
    if (!msg)
	return;
    const char* type = SS7MsgSCCP::lookup(msg->type());
    NamedString* msgType = m_msgReturnStatus.getParam(type);
    if (msgType)
	incrementNS(msgType);
    else
	m_msgReturnStatus.addParam(type,"1");
    const char* code = msg->params().getValue(YSTRING("ReturnCode"));
    NamedString* retCode = m_msgReturnStatus.getParam(code);
    if (retCode)
	incrementNS(retCode);
    else
	m_msgReturnStatus.addParam(code,"1");
}

void SS7SCCP::dumpArchive(String& msg, bool extended)
{
    msg << "\r\nMessages Sent :          " << m_totalSent;
    msg << "\r\nMessages Received :      " << m_totalReceived;
    msg << "\r\nGT Translations :        " << m_totalGTTranslations;
    msg << "\r\nErrors :                 " << m_errors;
    msg << "\r\nGT Translations failed : " << m_gttFailed;
    NamedString* udts = m_msgReturnStatus.getParam(SS7MsgSCCP::lookup(SS7MsgSCCP::UDTS));
    if (udts)
	msg << "\r\n" << udts->name() << " : " << *udts;
    NamedString* xudts = m_msgReturnStatus.getParam(SS7MsgSCCP::lookup(SS7MsgSCCP::XUDTS));
    if (xudts)
	msg << "\r\n" << xudts->name() << " : " << *xudts;
    NamedString* ludts = m_msgReturnStatus.getParam(SS7MsgSCCP::lookup(SS7MsgSCCP::LUDTS));
    if (ludts)
	msg << "\r\n" << ludts->name() << " : " << *ludts;
    if (!extended)
	return;
    msg << "\r\n Error Causes:";
    for (unsigned int i = 0;i < m_msgReturnStatus.length();i++) {
	NamedString* param = m_msgReturnStatus.getParam(i);
	if (!param || param == udts || param == xudts || param == ludts)
	    continue;
	const char* error = lookup(param->name().toInteger(),s_return_cause);
	if (!error)
	    continue;
	msg << "\r\nCount: " << *param << " Error: " << error;
    }
}

bool SS7SCCP::isSCOCMsg(int msgType)
{
    switch (msgType) {
	case SS7MsgSCCP::CR:
	case SS7MsgSCCP::CC:
	case SS7MsgSCCP::CREF:
	case SS7MsgSCCP::RLSD:
	case SS7MsgSCCP::RLC:
	case SS7MsgSCCP::DT1:
	case SS7MsgSCCP::DT2:
	case SS7MsgSCCP::AK:
	case SS7MsgSCCP::ED:
	case SS7MsgSCCP::EA:
	case SS7MsgSCCP::RSR:
	case SS7MsgSCCP::RSC:
	case SS7MsgSCCP::ERR:
	case SS7MsgSCCP::IT:
	    return true;
	default:
	    return false;
    }
    return false;
}

/**
 * SS7ItuSCCPManagement
 */

SS7ItuSccpManagement::SS7ItuSccpManagement(const NamedList& params)
    : SCCPManagement(params,SS7PointCode::ITU)
{
    DDebug(this,DebugAll,"Creating SS7ItuSccpManagement(%s) %p",params.c_str(),this);
}

bool SS7ItuSccpManagement::processMessage(SS7MsgSCCP* message)
{
    if (!sccp())
	return false;
    DataBlock* data = message->getData();
    if (!data) {
	Debug(sccp(),DebugNote,"Request to process Itu management message with no data!");
	return false;
    }
    if (data->length() < 5) {
	Debug(sccp(),DebugNote,"Received short management message!");
	return false;
    }
    const unsigned char* paramsPtr = (const unsigned char*)data->data();
    unsigned char msg = *paramsPtr++;
    const char* msgType = lookup(msg,s_managementMessages);
    if (!msgType) {
	Debug(sccp(),DebugNote,"Received unknown management message! 0x%x",msg);
	return false;
    }
    if (msg > SSC) {
	Debug(sccp(),DebugNote,"Received unknown ITU management message! 0x%x",msg);
	return false;
    }
    // After msg type is SSN
    message->params().setParam("ssn",String((int)*paramsPtr++));
    // Pointcode 2 o
    int pointcode = *paramsPtr++;
    pointcode |= (*paramsPtr++ & 0x3f) << 8;
    message->params().setParam("pointcode",String(pointcode));
    // Subsystem Multiplicity Indicator
    message->params().setParam("smi",String(*paramsPtr++ & 0x03));
    // If message type is SSC decode congestion level
    if (msg == SSC) {
	if (!paramsPtr) {
	    Debug(sccp(),DebugNote,"Failed to decode SSC congestion level parameter! Reason short message.");
	    return false;
	}
	message->params().setParam("congestion-level",String(*paramsPtr & 0x0f));
    }
    if (printMessagess()) {
	String dest;
	printMessage(dest, (MsgType)msg,message->params());
	Debug(this,DebugInfo,"Received message %s",dest.c_str());
    }
    return handleMessage(msg,message->params());
}

bool SS7ItuSccpManagement::sendMessage(SCCPManagement::MsgType msgType, const NamedList& params)
{
    if (!sccp())
	return false;
    if (printMessagess()) {
	String dest;
	printMessage(dest, msgType,params);
	Debug(this,DebugInfo,"Sending message %s",dest.c_str());
    }
    unsigned char ssn = params.getIntValue(YSTRING("ssn"));
    int pointcode = params.getIntValue(YSTRING("pointcode"));
    int smi = params.getIntValue(YSTRING("smi"));
    int dataLen = msgType == SSC ? 6 : 5;
    DataBlock data(0,dataLen);
    unsigned char * d = (unsigned char*)data.data();
    d[0] = msgType;
    d[1] = ssn;
    d[2] = pointcode & 0xff;
    d[3] = (pointcode >> 8) & 0x3f;
    d[4] = smi & 0x03;
    if (msgType == SSC)
	d[5] = params.getIntValue(YSTRING("congestion-level"),0) & 0x0f;
    int localPC = sccp()->getPackedPointCode();
    SS7MsgSCCP* msg = new SS7MsgSCCP(SS7MsgSCCP::UDT);
    const char* remotePC = params.getValue(YSTRING("RemotePC"));
    msg->params().setParam("ProtocolClass","0");
    msg->params().setParam("CalledPartyAddress.ssn","1");
    msg->params().setParam("CalledPartyAddress.pointcode",remotePC);
    msg->params().setParam("CalledPartyAddress.route","ssn");
    msg->params().setParam("CallingPartyAddress.ssn","1");
    msg->params().setParam("CallingPartyAddress.route","ssn");
    msg->params().setParam("CallingPartyAddress.pointcode",String(localPC));
    msg->params().setParam("LocalPC",String(localPC));
    msg->params().setParam("RemotePC",remotePC);
    msg->setData(&data);
    bool ret = sccp()->transmitMessage(msg) >= 0;
    if (!ret)
	Debug(this,DebugNote,"Failed to send management message %s to remote %s",
	      lookup(msgType,s_managementMessages),params.getValue(YSTRING("RemotePC")));
    msg->extractData();
    TelEngine::destruct(msg);
    return ret;
}

void SS7ItuSccpManagement::manageSccpRemoteStatus(SccpRemote* rsccp, SS7Route::State newState)
{
    if (!rsccp)
	return;
#ifdef XDEBUG
    String pc;
    rsccp->dump(pc,false);
    XDebug(this,DebugInfo,"Remote sccp '%s' status changed, new state: %s",pc.c_str(),SS7Route::stateName(newState));
#endif
    switch (newState) {
	case SS7Route::Congestion:
	    Debug(sccp(),DebugStub,"Please implement SCCPManagement Congestion");
	    break;
	case SS7Route::Allowed:
	{
	    // Set state should set the state to all subsystems
	    rsccp->setState(SCCPManagement::Allowed);
	    updateTables(rsccp);
	    rsccp->resetCongestion();
	    // Discontinue the Subsystem Status Test for SSN = 1
	    SccpSubsystem* ss = new SccpSubsystem(1);
	    stopSst(rsccp,ss);
	    TelEngine::destruct(ss);
	    localBroadcast(SCCP::PointCodeStatusIndication,rsccp->getPackedPointcode(),PCAccessible,-1,0);
	    localBroadcast(SCCP::PointCodeStatusIndication,rsccp->getPackedPointcode(),-1,SccpRemoteAccessible,0);
	    break;
	}
	case SS7Route::Prohibited:
	{
	    rsccp->setState(SCCPManagement::Prohibited);
	    updateTables(rsccp);
	    // Discontinue all tests for the remote sccp
	    SccpSubsystem* ss = new SccpSubsystem(1);
	    stopSst(rsccp,0,ss); // Stop all sst except management
	    // Do not start SST if the route is down the message will fail to be
	    // sent. The status will be changed to allowed when the route is up
	    TelEngine::destruct(ss);
	    localBroadcast(SCCP::PointCodeStatusIndication,rsccp->getPackedPointcode(),PCInaccessible,-1,0);
	    localBroadcast(SCCP::PointCodeStatusIndication,rsccp->getPackedPointcode(),-1,SccpRemoteInaccessible,0);
	    break;
	}
	case SS7Route::Unknown:
	    rsccp->setState(SCCPManagement::Unknown);
	    break;
	default:
	    DDebug(this,DebugNote,"Unhandled remote sccp status '%s'",SS7Route::stateName(newState));
    }
}

bool SS7ItuSccpManagement::handleMessage(int msgType, NamedList& params)
{
    unsigned char ssn = params.getIntValue(YSTRING("ssn"));
    if (ssn == 0) {
	Debug(this,DebugNote,"Received management message '%s' with invalid ssn '%d'",
	      lookup(msgType,s_managementMessages),ssn);
	return false;
    }
    unsigned char smi = params.getIntValue(YSTRING("smi")); // subsystem multiplicity indicator
    if (smi != 0) {
	if (smi > 3) {
	    Debug(this,DebugWarn, "Received management message '%s' with unknown smi: '%d' , ssn: '%d'",
		  lookup(msgType,s_managementMessages),smi,ssn);
	    smi = 0;
	} else
	    DDebug(this,DebugNote,"Received management message '%s' with national smi: %d",
		   lookup(msgType,s_managementMessages),smi);
    }
    switch (msgType) {
	case SSC:
	    Debug(this,DebugStub,"Please implement subsystem congested!");
	    break;
	default:
	    return SCCPManagement::handleMessage(msgType,ssn,smi,params);
    }
    return true;
}

void SS7ItuSccpManagement::handleSubsystemStatus(SccpSubsystem* subsystem, bool allowed, SccpRemote* remote, int smi)
{
    if (!subsystem) {
	Debug(sccp(),DebugWarn,"Request to handle subsystem status with no subsystem!");
	return;
    }
    SCCPManagement::SccpStates ssnState = allowed ? SCCPManagement::Allowed : SCCPManagement::Prohibited;
    subsystem->setState(ssnState);
    DDebug(this,DebugInfo,"Handle subsystem status for pc: '%d' ssn: '%d' status %s", remote ? remote->getPackedPointcode() : 0,
		subsystem ? subsystem->getSSN() : 0, stateName(ssnState));
    Lock lock(this);
    bool localSubsystem = false;
    // Change the status of the subsystem
    if (!remote || remote->getPointCode() == *sccp()->getLocalPointCode()) { // LocalSubsystem
	SccpLocalSubsystem* subs = getLocalSubsystem(subsystem->getSSN());
	if (subs) {
	    if (subs->getState() == ssnState) // Same state? do nothing
		return;
	    subs->resetTimers();
	    subs->setState(ssnState);
	} else // Append dynamically
	    m_localSubsystems.append(new SccpLocalSubsystem(subsystem->getSSN(),getCoordTimeout(),
		    getIgnoreTestsInterval()));
	localSubsystem = true;
    } else {
	SccpRemote* rsccp = getRemoteSccp(remote->getPackedPointcode());
	if (rsccp && !rsccp->changeSubsystemState(subsystem->getSSN(),ssnState))
	    return;
    }
    // Stop all subsystem status tests
    if (!localSubsystem && allowed)
	stopSst(remote,subsystem);
    else if (!localSubsystem) // Initiate subsystem status test
	startSst(remote,subsystem);
    lock.drop();
    // update translation tables
    if (!localSubsystem)
	updateTables(remote,subsystem);
    // Local Broadcast user in/out of service
    NamedList params("");
    if (!localSubsystem)
	params.setParam("pointcode",String(remote->getPackedPointcode()));
    params.setParam("ssn",String(subsystem->getSSN()));
    params.setParam("subsystem-status",lookup(allowed ? UserInService : UserOutOfService,broadcastType()));
    managementMessage(SCCP::StatusIndication,params);
    // Send broadcast for all concerned signalling points
    ///TODO for now we send only for local interested subsystems
    if (!localSubsystem)
	return;
    notifyConcerned(allowed ? SSA : SSP, subsystem->getSSN(),smi);
}

/**
 * SS7AnsiSCCPManagement
 */
SS7AnsiSccpManagement::~SS7AnsiSccpManagement()
{
    DDebug(this,DebugAll,"Destroing Ansi Sccp Management(%p)",this);
}

bool SS7AnsiSccpManagement::processMessage(SS7MsgSCCP* message)
{
    if (!sccp()) {
	return false;
    }
    DataBlock* data = message->getData();
    if (!data) {
	DDebug(sccp(),DebugNote,"Request to process Ansi management message with no data!");
	return false;
    }
    if (data->length() < 6) {
	DDebug(sccp(),DebugNote,"Received short Ansi management message! %d",data->length());
	return false;
    }
    const unsigned char* paramsPtr = (const unsigned char*)data->data();
    unsigned char msg = *paramsPtr++;
    const char* msgType = lookup(msg,s_managementMessages);
    if (!msgType) {
	DDebug(sccp(),DebugNote,"Received unknown management message! 0x%x",msg);
	return false;
    }
    if (msg > 0x05 && msg < 0xfd) {
	DDebug(sccp(),DebugNote,"Received unknown Ansi management message! 0x%x",msg);
	return false;
    }
    // After msg type is SSN
    message->params().setParam("ssn",String((int)*paramsPtr++));
    // Pointcode 2 o
    unsigned int pointcode = *paramsPtr++;
    pointcode |= (*paramsPtr++ << 8);
    pointcode |= (*paramsPtr++ << 16);
    message->params().setParam("pointcode",String(pointcode));
    // Subsystem Multiplicity Indicator
    message->params().setParam("SMI",String(*paramsPtr++ & 0x03));

    if (printMessagess()) {
	String dest;
	printMessage(dest, (MsgType)msg,message->params());
	Debug(this,DebugInfo,"Received message %s",dest.c_str());
    }
    return handleMessage(msg,message->params());
}

bool SS7AnsiSccpManagement::sendMessage(SCCPManagement::MsgType msgType, const NamedList& params)
{
    if (!sccp())
	return false;
    if (printMessagess()) {
	String dest;
	printMessage(dest, msgType,params);
	Debug(this,DebugInfo,"Sending message %s",dest.c_str());
    }
    unsigned char ssn = params.getIntValue(YSTRING("ssn"));
    int pointcode = params.getIntValue(YSTRING("pointcode"));
    int smi = params.getIntValue(YSTRING("smi"));
    DataBlock data(0,6);
    unsigned char * d = (unsigned char*)data.data();
    d[0] = msgType;
    d[1] = ssn;
    d[2] = pointcode & 0xff;
    d[3] = (pointcode >> 8) & 0xff;
    d[4] = (pointcode >> 16) & 0xff;
    d[5] = smi & 0x03;
    int localPC = sccp()->getPackedPointCode();
    SS7MsgSCCP* msg = new SS7MsgSCCP(SS7MsgSCCP::UDT);
    const char* remotePC = params.getValue(YSTRING("RemotePC"));
    msg->params().setParam("ProtocolClass","0");
    msg->params().setParam("CalledPartyAddress.ssn","1");
    msg->params().setParam("CalledPartyAddress.pointcode",remotePC);
    msg->params().setParam("CalledPartyAddress.route","ssn");
    msg->params().setParam("CallingPartyAddress.ssn","1");
    msg->params().setParam("CallingPartyAddress.route","ssn");
    msg->params().setParam("CallingPartyAddress.pointcode",String(localPC));
    msg->params().setParam("LocalPC",String(localPC));
    msg->params().setParam("RemotePC",remotePC);
    msg->setData(&data);
    bool ret = sccp()->transmitMessage(msg) >= 0;
    if (!ret)
	Debug(this,DebugNote,"Failed to send management message %s to remote %s",
	      lookup(msgType,s_managementMessages),params.getValue(YSTRING("RemotePC")));
    msg->extractData();
    TelEngine::destruct(msg);
    return ret;
}

bool SS7AnsiSccpManagement::handleMessage(int msgType, NamedList& params)
{
    unsigned char ssn = params.getIntValue(YSTRING("ssn"));
    if (ssn == 0) {
	Debug(this,DebugNote,"Received management message '%s' with invalid ssn '%d'",
	      lookup(msgType,s_managementMessages),ssn);
	return false;
    }
    unsigned char smi = params.getIntValue(YSTRING("smi")); // subsystem multiplicity indicator
    if (!lookup(smi,s_ansiSmi)) {
	Debug(this,DebugWarn, "Received management message '%s' with invalid smi: '%d' , ssn: '%d'",
		  lookup(msgType,s_managementMessages),smi,ssn);
	smi = 0;
    }
    switch (msgType) {
	case SBR:
	case SNR:
	case SRT:
	    Debug(this,DebugStub,"Please implement %s message handling!",lookup(msgType,s_managementMessages));
	    break;
	default:
	    return SCCPManagement::handleMessage(msgType,ssn,smi,params);
    }
    return true;
}

void SS7AnsiSccpManagement::manageSccpRemoteStatus(SccpRemote* rsccp, SS7Route::State newState)
{
    if (!rsccp)
	return;
#ifdef XDEBUG
    String pc;
    rsccp->dump(pc,false);
    XDebug(this,DebugInfo,"Remote sccp '%s' status changed, new state: %s",pc.c_str(),SS7Route::stateName(newState));
#endif
    switch (newState) {
	case SS7Route::Congestion:
	    Debug(sccp(),DebugStub,"Please implement SCCPManagement Congestion");
	    break;
	case SS7Route::Allowed:
	{
	    // Set state should set the state to all subsystems
	    rsccp->setState(SCCPManagement::Allowed);
	    rsccp->resetCongestion();
	    localBroadcast(SCCP::PointCodeStatusIndication,rsccp->getPackedPointcode(),
		    PCAccessible,-1,0);
	    // Discontinue all subsystem status tests
	    stopSst(rsccp);
	    localBroadcast(SCCP::PointCodeStatusIndication,rsccp->getPackedPointcode(),
		    -1,SccpRemoteAccessible,0);
	    updateTables(rsccp);
	    rsccp->lock();
	    ListIterator ssns(rsccp->getSubsystems());
	    rsccp->unlock();
	    SccpSubsystem* ss = 0;
	    while ((ss = YOBJECT(SccpSubsystem,ssns.get())))
		localBroadcast(SCCP::StatusIndication,-1,-1,-1,-1,ss->getSSN(),UserInService);
	    break;
	}
	case SS7Route::Prohibited:
	{
	    rsccp->setState(SCCPManagement::Prohibited);
	    localBroadcast(SCCP::PointCodeStatusIndication,rsccp->getPackedPointcode(),
		    PCInaccessible,-1,0);
	    SccpSubsystem* ss = new SccpSubsystem(1);
	    stopSst(rsccp,ss);
	    TelEngine::destruct(ss);
	    updateTables(rsccp);
	    localBroadcast(SCCP::PointCodeStatusIndication,rsccp->getPackedPointcode(),
		    -1,SccpRemoteInaccessible,0);
	    rsccp->lock();
	    ListIterator ssns(rsccp->getSubsystems());
	    rsccp->unlock();
	    SccpSubsystem* ss1 = 0;
	    while ((ss1 = YOBJECT(SccpSubsystem,ssns.get())))
		localBroadcast(SCCP::StatusIndication,-1,-1,-1,-1,ss1->getSSN(),UserOutOfService);
	    break;
	}
	case SS7Route::Unknown:
	    rsccp->setState(SCCPManagement::Unknown);
	    break;
	default:
	    DDebug(this,DebugNote,"Unhandled remote sccp status '%s'",SS7Route::stateName(newState));
    }
}

void SS7AnsiSccpManagement::handleSubsystemStatus(SccpSubsystem* subsystem, bool allowed, SccpRemote* remote, int smi)
{
    if (!subsystem || subsystem->getSSN() <= 0) {
	Debug(sccp(),DebugWarn,"Request to handle subsystem status with no subsystem!");
	return;
    }
    SCCPManagement::SccpStates ssnState = allowed ? SCCPManagement::Allowed : SCCPManagement::Prohibited;
    subsystem->setState(ssnState);
    DDebug(this,DebugInfo,"Handle subsystem status for pc: '%d' ssn: '%d' status %s", remote ? remote->getPackedPointcode() : 0,
		subsystem ? subsystem->getSSN() : 0, stateName(ssnState));
    Lock lock(this);
    bool localSubsystem = false;
    // Change the status of the subsystem
    if (!remote || remote->getPointCode() == *sccp()->getLocalPointCode()) { // LocalSubsystem
	SccpLocalSubsystem* subs = getLocalSubsystem(subsystem->getSSN());
	if (subs) {
	    if (subs->getState() == ssnState) // Same state? do nothing
		return;
	    subs->resetTimers();
	    subs->setState(ssnState);
	} else // Append dynamically
	    m_localSubsystems.append(new SccpLocalSubsystem(subsystem->getSSN(),getCoordTimeout(),
		    getIgnoreTestsInterval()));
	localSubsystem = true;
    } else {
	SccpRemote* rsccp = getRemoteSccp(remote->getPackedPointcode());
	if (rsccp && !rsccp->changeSubsystemState(subsystem->getSSN(),ssnState))
	    return;
    }
    // Stop all subsystem status tests
    if (!localSubsystem && allowed)
	stopSst(remote,subsystem);
    else if (!localSubsystem) // Initiate subsystem status test
	startSst(remote,subsystem);
    lock.drop();
    // update translation tables
    if (!localSubsystem)
	updateTables(remote,subsystem);
    // Local Broadcast user in/out of service
    localBroadcast(SCCP::StatusIndication,localSubsystem ? -1 :remote->getPackedPointcode(),
	    -1,-1,-1,subsystem->getSSN(), allowed ? UserInService : UserOutOfService);
    // Send broadcast for all concerned signalling points
    ///TODO for now we send only for local interested subsystems
    if (!localSubsystem)
	return;
    notifyConcerned(allowed ? SSA : SSP, subsystem->getSSN(),smi);
}

/* vi: set ts=8 sw=4 sts=4 noet: */
