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

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

using namespace TelEngine;

#ifdef DEBUG
#define ISUP_HANDLE_CIC_EVENT_CONTROL
#else
//#define ISUP_HANDLE_CIC_EVENT_CONTROL
#endif

// Maximum number of mandatory parameters including two terminators
#define MAX_MANDATORY_PARAMS 16

// Timer limits and default values
#define ISUP_T7_MINVAL  20000
#define ISUP_T7_DEFVAL  20000
#define ISUP_T7_MAXVAL  30000
#define ISUP_T9_MINVAL  90000
#define ISUP_T9_DEFVAL  0
#define ISUP_T9_MAXVAL  180000
#define ISUP_T27_MINVAL 30000
#define ISUP_T27_DEFVAL 240000
#define ISUP_T27_MAXVAL 300000
#define ISUP_T34_MINVAL 2000
#define ISUP_T34_DEFVAL 3000
#define ISUP_T34_MAXVAL 4000

// Utility: check if 2 cic codes are in valid range, return range if valid, 0 otherwise
static inline int checkValidRange(int code, int extra)
{
    int range = extra - code;
    return (range > -256 && range < 256) ? range : 0;
}

// Adjust range and status data when needed (a new range is used)
static void adjustRangeAndStatus(char* status, unsigned int& code, unsigned int& range,
    int newRange)
{
    if (!(status && newRange))
	return;
    if (newRange > 0) {
	range = (unsigned int)newRange;
	status[0] = '1';
	::memset(status + 1,'0',range);
    }
    else {
	range = (unsigned int)(-newRange);
	code -= range;
	::memset(status,'0',range);
	status[range] = '1';
    }
    range++;
}

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

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


// Nature of Address Indicator
static const TokenDict s_dict_nai[] = {
    { "subscriber",        1 },
    { "unknown",           2 },
    { "national",          3 },
    { "international",     4 },
    { "network-specific",  5 },
    { "national-routing",  6 },
    { "specific-routing",  7 },
    { "routing-with-cdn",  8 },
    { 0, 0 }
};

// Numbering Plan Indicator
static const TokenDict s_dict_numPlan[] = {
    { "unknown",  0 },
    { "isdn",     1 },
    { "data",     3 },
    { "telex",    4 },
    { "private",  5 },
    { "national", 6 },
    { 0, 0 }
};

// Address Presentation
static const TokenDict s_dict_presentation[] = {
    { "allowed",     0 },
    { "restricted",  1 },
    { "unavailable", 2 },
    // aliases for restrict=...
    { "no",    0 },
    { "false", 0 },
    { "yes",   1 },
    { "true",  1 },
    { 0, 0 }
};

// Screening Indicator
static const TokenDict s_dict_screening[] = {
    { "user-provided",        0 },
    { "user-provided-passed", 1 },
    { "user-provided-failed", 2 },
    { "network-provided",     3 },
    // aliases for screened=...
    { "no",    0 },
    { "false", 0 },
    { "yes",   1 },
    { "true",  1 },
    { 0, 0 }
};

// Generic number qualifier
static const TokenDict s_dict_qual[] = {
    { "dialed-digits",        0 },
    { "called-additional",    1 },
    { "caller-failed",        2 },
    { "caller-not-screened",  3 },
    { "terminating",          4 },
    { "connected-additional", 5 },
    { "caller-additional",    6 },
    { "called-original",      7 },
    { "redirecting",          8 },
    { "redirection",          9 },
    { 0, 0 }
};

// Generic name qualifier
static const TokenDict s_dict_qual_name[] = {
    { "caller" ,           0x20 },
    { "called" ,           0x40 },
    { "redirecting" ,      0x60 },
    { "connected" ,        0x80 },
    { 0, 0 }
};

// Redirection Information (Q,763 3.45) bits CBA
static const TokenDict s_dict_redir_main[] = {
    { "none",                     0 },
    { "rerouted",                 1 },
    { "rerouted-restrict-all",    2 },
    { "diverted",                 3 },
    { "diverted-restrict-all",    4 },
    { "rerouted-restrict-number", 5 },
    { "diverted-restrict-number", 6 },
    { 0, 0 }
};

// Redirection Information (Q,763 3.45) bits HGFE or PONM
static const TokenDict s_dict_redir_reason[] = {
    { "busy",      1 },
    { "noanswer",  2 },
    { "always",    3 },
    { "deflected", 4 },
    { "diverted",  5 },
    { "offline",   6 },
    { 0, 0 }
};

// Message Compatibility Information (Q.763 3.33)
static const SignallingFlags s_flags_msgcompat[] = {
    { 0x01, 0x00, "transit" },           // End node / transit exchange
    { 0x01, 0x01, "end-node" },
    { 0x02, 0x02, "release" },           // Release call indicator
    { 0x04, 0x04, "cnf" },               // Pass on set but not possible: Send CNF / RLC
    { 0x08, 0x08, "discard" },           // Discard / pass on message
    { 0x10, 0x00, "nopass-release" },    // Pass on not possible: Release call
    { 0x10, 0x10, "nopass-discard" },    // Pass on not possible: Discard message
    { 0, 0, 0 }
};

// Parameter Compatibility Information (Q.763 3.41)
static const SignallingFlags s_flags_paramcompat[] = {
    { 0x01, 0x00, "transit" },           // End node / transit exchange
    { 0x01, 0x01, "end-node" },
    { 0x02, 0x02, "release" },           // Release call indicator
    { 0x04, 0x04, "cnf" },               // Parameter pass on set but not possible: Send CNF / RLC
    { 0x08, 0x08, "discard-msg" },       // Discard / pass on message
    { 0x18, 0x10, "discard-param" },     // Discard / pass on parameter (if not discarding message)
    { 0x60, 0x00, "nopass-release" },    // No pass on: release call
    { 0x60, 0x20, "nopass-msg" },        // No pass on: discard message
    { 0x60, 0x40, "nopass-param" },      // No pass on: discard parameter
    { 0x60, 0x60, "nopass-release" },    // Reserved, interpreted as 00
    { 0, 0, 0 }
};

// Application Transport Parameter instruction indicators (Q.763 3.82)
static const SignallingFlags s_flags_apt_indicators[] = {
    { 0x01, 0x01, "release" },           // Release call indicator
    { 0x02, 0x02, "cnf" },               // Send CNF notification
    { 0, 0, 0 }
};

// SLS special values on outbound calls
static const TokenDict s_dict_callSls[] = {
    { "auto", SS7ISUP::SlsAuto    }, // Let Layer3 deal with it
    { "last", SS7ISUP::SlsLatest  }, // Last SLS used
    { "cic",  SS7ISUP::SlsCircuit }, // Lower bits of CIC
    { 0, 0 }
};

// Control operations
static const TokenDict s_dict_control[] = {
    { "validate", SS7MsgISUP::CVT },
    { "query", SS7MsgISUP::CQM },
    { "conttest", SS7MsgISUP::CCR },
    { "reset", SS7MsgISUP::RSC },
    { "block", SS7MsgISUP::BLK },
    { "unblock", SS7MsgISUP::UBL },
    { "release", SS7MsgISUP::RLC },
    { "parttest", SS7MsgISUP::UPT },
    { "available", SS7MsgISUP::UPA },
    { "save", SS7MsgISUP::CtrlSave },
#ifdef ISUP_HANDLE_CIC_EVENT_CONTROL
    { "circuitevent", SS7MsgISUP::CtrlCicEvent },
#endif
    { 0, 0 }
};

static const TokenDict s_dict_CRG_process[] = {
    { "confusion", SS7ISUP::Confusion },
    { "ignore",    SS7ISUP::Ignore },
    { "raw",       SS7ISUP::Raw },
    { "parsed",    SS7ISUP::Parsed },
    { 0, 0 }
};

// Build next available parameter name
static void buildName(const NamedList& list, const IsupParam* param, const String& prefix, String& name)
{
    name = prefix + param->name;
    if (!list.getParam(name))
	return;
    // conflict - find a free index
    for (unsigned int i = 1; ; i++) {
	String tmp(name);
	tmp << "." << i;
	if (!list.getParam(tmp)) {
	    name = tmp;
	    break;
	}
    }
}

// Default decoder, dumps raw octets
static bool decodeRaw(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (len < 1)
	return false;
    String raw;
    raw.hexify((void*)buf,len,' ');
    DDebug(isup,DebugInfo,"decodeRaw decoded %s=%s",param->name,raw.c_str());
    String preName;
    buildName(list,param,prefix,preName);
    list.addParam(preName,raw);
    return true;
}

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

// Integer decoder, interprets data as big endian integer
static bool decodeInt(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    unsigned int val = 0;
    while (len--)
	val = (val << 8) | (unsigned int)(*buf++);
    DDebug(isup,DebugAll,"decodeInt decoded %s=%s (%u)",param->name,lookup(val,(const TokenDict*)param->data),val);
    String preName;
    buildName(list,param,prefix,preName);
    SignallingUtils::addKeyword(list,preName,(const TokenDict*)param->data,val);
    return true;
}

// Decoder for ISUP indicators (flags)
static bool decodeFlags(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    const SignallingFlags* flags = (const SignallingFlags*)param->data;
    if (!flags)
	return false;
    String preName;
    buildName(list,param,prefix,preName);
    return SignallingUtils::decodeFlags(isup,list,preName,flags,buf,len);
}

// Utility function - extract just ISUP digits from a parameter
static void getDigits(String& num, unsigned char oddNum, const unsigned char* buf, unsigned int len,
    bool ignoreUnk)
{
    bool odd = (oddNum & 0x80) != 0;
    static const char digits1[] = "0123456789\0BC\0\0.";
    static const char digits2[] = "0123456789ABCDE.";
    const char* digits = ignoreUnk ? digits1 : digits2;
    for (unsigned int i = 0; i < len; i++) {
	num += digits[buf[i] & 0x0f];
	if (odd && ((i+1) == len))
	    break;
	num += digits[buf[i] >> 4];
    }
}

const char* getIsupParamName(unsigned char type);

// Decoder for message or parameter compatibility
// Q.763 3.33/3.41
static bool decodeCompat(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (!len)
	return false;
    String preName;
    buildName(list,param,prefix,preName);
    switch (param->type) {
	case SS7MsgISUP::MessageCompatInformation:
	    SignallingUtils::decodeFlags(isup,list,preName,s_flags_msgcompat,buf,1);
	    if (buf[0] & 0x80) {
		if (len == 1)
		    return true;
		DDebug(isup,DebugMild,
		    "decodeCompat invalid len=%u for %s with first byte having ext bit set",len,param->name);
		break;
	    }
	    return 0 != SignallingUtils::dumpDataExt(isup,list,preName+".more",buf+1,len-1);
	case SS7MsgISUP::ParameterCompatInformation:
	    for (unsigned int i = 0; i < len;) {
		unsigned char val = buf[i++];
		if (i == len) {
		    Debug(isup,DebugMild,"decodeCompat unexpected end of data (len=%u) for %s",len,param->name);
		    return false;
		}
		const char* paramName = getIsupParamName(val);
		String name = preName;
		if (paramName)
		    name << "." << paramName;
		else {
		    Debug(isup,DebugNote,"decodeCompat found unknown parameter %u for %s",val,param->name);
		    name << ".Param_" << (unsigned int)val;
		}
		SignallingUtils::decodeFlags(isup,list,name,s_flags_paramcompat,buf+i,1);
		if (buf[i++] & 0x80)
		    continue;
		unsigned int count = SignallingUtils::dumpDataExt(isup,list,name+".more",buf+i,len-i);
		if (!count)
		    return false;
		i += count;
	    }
	    decodeRaw(isup,list,param,buf,len,prefix);
	    return true;
	default:
	    Debug(isup,DebugStub,"decodeCompat not implemented for %s",param->name);
    }
    return false;
}

// Decoder for various ISUP digit sequences (phone numbers)
static bool decodeDigits(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (len < 2)
	return false;
    unsigned char qualifier = 0;
    if (SS7MsgISUP::GenericNumber == param->type) {
	if (--len < 2)
	    return false;
	qualifier = buf[0];
	buf++;
    }
    unsigned char nai = buf[0] & 0x7f;
    unsigned char plan = (buf[1] >> 4) & 7;
    unsigned char pres = (buf[1] >> 2) & 3;
    unsigned char scrn = buf[1] & 3;
    String tmp;
    getDigits(tmp,buf[0],buf+2,len-2,isup && isup->ignoreUnknownAddrSignals());
    DDebug(isup,DebugAll,"decodeDigits decoded %s='%s' inn/ni=%u nai=%u plan=%u pres=%u scrn=%u",
	param->name,tmp.c_str(),buf[1] >> 7,nai,plan,pres,scrn);
    String preName;
    buildName(list,param,prefix,preName);
    list.addParam(preName,tmp);
    if (SS7MsgISUP::GenericNumber == param->type)
	SignallingUtils::addKeyword(list,preName+".qualifier",s_dict_qual,qualifier);
    SignallingUtils::addKeyword(list,preName+".nature",s_dict_nai,nai);
    SignallingUtils::addKeyword(list,preName+".plan",s_dict_numPlan,plan);
    switch (param->type) {
	case SS7MsgISUP::CalledPartyNumber:
	case SS7MsgISUP::RedirectionNumber:
	case SS7MsgISUP::LocationNumber:
	    tmp = ((buf[1] & 0x80) == 0);
	    list.addParam(preName+".inn",tmp);
	    break;
	case SS7MsgISUP::CallingPartyNumber:
	case SS7MsgISUP::GenericNumber:
	    tmp = ((buf[1] & 0x80) == 0);
	    list.addParam(preName+".complete",tmp);
	    break;
	case SS7MsgISUP::LastDivertingLineIdentity:
	case SS7MsgISUP::PresentationNumber:
	    tmp = ((buf[1] & 0x80) != 0);
	    list.addParam(preName+".pnp",tmp);
	    break;
	default:
	    break;
    }
    switch (param->type) {
	case SS7MsgISUP::CallingPartyNumber:
	case SS7MsgISUP::RedirectingNumber:
	case SS7MsgISUP::OriginalCalledNumber:
	case SS7MsgISUP::LocationNumber:
	case SS7MsgISUP::ConnectedNumber:
	case SS7MsgISUP::GenericNumber:
	case SS7MsgISUP::LastDivertingLineIdentity:
	case SS7MsgISUP::PresentationNumber:
	case SS7MsgISUP::CalledINNumber:
	case SS7MsgISUP::OriginalCalledINNumber:
	    SignallingUtils::addKeyword(list,preName+".restrict",s_dict_presentation,pres);
	default:
	    break;
    }
    switch (param->type) {
	case SS7MsgISUP::CallingPartyNumber:
	case SS7MsgISUP::LocationNumber:
	case SS7MsgISUP::ConnectedNumber:
	case SS7MsgISUP::GenericNumber:
	case SS7MsgISUP::LastDivertingLineIdentity:
	case SS7MsgISUP::PresentationNumber:
	    SignallingUtils::addKeyword(list,preName+".screened",s_dict_screening,scrn);
	default:
	    break;
    }
    return true;
}

// Special decoder for subsequent number
static bool decodeSubseq(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (len < 1)
	return false;
    String tmp;
    getDigits(tmp,buf[0],buf+1,len-1,isup && isup->ignoreUnknownAddrSignals());
    DDebug(isup,DebugAll,"decodeSubseq decoded %s='%s'",param->name,tmp.c_str());
    String preName;
    buildName(list,param,prefix,preName);
    list.addParam(preName,tmp);
    return true;
}

// Decoder for circuit group range and status (Q.763 3.43)
static bool decodeRangeSt(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (len < 1)
	return false;
    String preName;
    buildName(list,param,prefix,preName);
    // 1st octet is the range code (range - 1)
    len--;
    unsigned int range = 1 + *buf++;
    unsigned int octets = (range + 7) / 8;
    if (octets > len) {
	if (len)
	    Debug(isup,DebugMild,"decodeRangeSt truncating range of %u bits to %u octets!",range,len);
	octets = len;
    }
    list.addParam(preName,String(range));

    String map;
    if (len) {
	unsigned char mask = 1;
	unsigned int r = range;
	while (r--) {
	    map += (buf[0] & mask) ? "1" : "0";
	    mask <<= 1;
	    if (!mask) {
		++buf;
		if (!--octets)
		    break;
		mask = 1;
	    }
	}
	list.addParam(preName+".map",map);
    }

    DDebug(isup,DebugAll,"decodeRangeSt decoded %s=%u '%s'",param->name,range,map.c_str());
    return true;
}

// Decoder for generic notification indicators (Q.763 3.25)
static bool decodeNotif(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (len < 1)
	return false;
    String flg;
    for (; len; len--) {
	unsigned char val = *buf++;
	const char* keyword = lookup(val & 0x7f,(const TokenDict*)param->data);
	if (keyword)
	    flg.append(keyword,",");
	else {
	    String tmp(0x7f & (int)val);
	    flg.append(tmp,",");
	}
	if (val & 0x80)
	    break;
    }
    DDebug(isup,DebugAll,"decodeNotif decoded %s='%s'",param->name,flg.c_str());
    String preName;
    buildName(list,param,prefix,preName);
    list.addParam(preName,flg);
    return true;
}

// Decoder for User Service Information
static bool decodeUSI(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    String preName;
    buildName(list,param,prefix,preName);
    return SignallingUtils::decodeCaps(isup,list,buf,len,preName,true);
}

// Decoder for cause indicators
static bool decodeCause(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    String preName;
    buildName(list,param,prefix,preName);
    return SignallingUtils::decodeCause(isup,list,buf,len,preName,true);
}

// Decoder for application transport parameter
static bool decodeAPT(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (len < 4) {
	if (len == 3)
	    Debug(isup,DebugNote,"Received '%s' with no data",param->name);
	return false;
    }
    // Field extension on more then 1 octet is not supported
    if (0 == (buf[0] & buf[1] & buf[2] & 0x80)) {
	Debug(isup,DebugNote,"Received %s with unsupported extension bits set to 0",
	    param->name);
	return false;
    }
    // Segmentation is not supported
    unsigned char si = (buf[2] & 0x40);
    unsigned char segments = (buf[2] & 0x3f);
    if (!si || segments) {
	Debug(isup,DebugNote,"Received unsupported segmented %s (si=%u segments=%u)",
	    param->name,si,segments);
	return false;
    }
    len -= 3;
    // WARNING: HACK - ApplicationTransport does not follow naming convention
    String preName(prefix + param->name);
    String context((int)(buf[0] & 0x7f));
    list.addParam(preName,context);
    preName << "." << context;
    // Application context identifier
    SignallingUtils::dumpData(isup,list,preName,buf + 3,len);
    // Instruction indicators
    unsigned char inds = (buf[1] & 0x7f);
    SignallingUtils::decodeFlags(isup,list,preName + ".indicators",s_flags_apt_indicators,&inds,1);
    return true;
}

// Decoder for generic name
static bool decodeName(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (len < 1)
	return false;
    String val((const char*)buf+1,len-1);
    String preName;
    buildName(list,param,prefix,preName);
    list.addParam(preName,val);
    list.addParam(preName+".available",String::boolText((buf[0] & 0x10) == 0));
    SignallingUtils::addKeyword(list,preName+".qualifier",s_dict_qual_name,buf[0] & 0xe0);
    SignallingUtils::addKeyword(list,preName+".restrict",s_dict_presentation,buf[0] & 0x03);
    DDebug(isup,DebugAll,"decodeName decoded %s='%s'",param->name,val.c_str());
    return true;
}

// Decoder for Redirection information (Q.763 3.45)
static bool decodeRedir(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    if (len < 1)
	return false;
    String preName;
    buildName(list,param,prefix,preName);
    SignallingUtils::addKeyword(list,preName,s_dict_redir_main,buf[0] & 0x07);
    unsigned int reason = buf[0] >> 4;
    if (reason)
	SignallingUtils::addKeyword(list,preName+".reason_original",s_dict_redir_reason,reason);
    if (len > 1) {
	int cnt = buf[1] & 0x07;
	if (cnt)
	    list.addParam(preName+".counter",String(cnt));
	reason = buf[1] >> 4;
	if (reason)
	    SignallingUtils::addKeyword(list,preName+".reason",s_dict_redir_reason,reason);
    }
    return true;
}

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

// Encoder for fixed length ISUP indicators (flags)
static unsigned char encodeFlags(const SS7ISUP* isup, SS7MSU& msu, unsigned char* buf,
    const IsupParam* param, const NamedString* val, const NamedList*, const String&)
{
    if (!param)
	return 0;
    unsigned int n = param->size;
    if (!(n && param->data))
	return 0;
    unsigned int v = 0;
    if (val)
	v = SignallingUtils::encodeFlags(isup,*val,(const SignallingFlags*)param->data,param->name);
    else {
	// locate the defaults
	const SignallingFlags* flags = (const SignallingFlags*)param->data;
	while (flags->mask)
	    flags++;
	v = flags->value;
    }
    DDebug(isup,DebugAll,"encodeFlags encoding %s=0x%x on %u octets",param->name,v,n);
    if (!buf) {
	unsigned int l = msu.length();
	DataBlock dummy(0,n+1);
	msu += dummy;
	buf = (unsigned char*)msu.getData(l,n+1);
	*buf++ = n & 0xff;
    }
    while (n--) {
	*buf++ = v & 0xff;
	v >>= 8;
    }
    return param->size;
}

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

// Utility function - write digit sequences
static unsigned char setDigits(SS7MSU& msu, const char* val, unsigned char nai, int b2 = -1, int b3 = -1, int b0 = -1)
{
    unsigned char buf[32];
    unsigned int len = 1;
    if (b0 >= 0)
	buf[len++] = b0 & 0xff;
    unsigned int naiPos = len++;
    buf[naiPos] = nai & 0x7f;
    if (b2 >= 0) {
	buf[len++] = b2 & 0xff;
	if (b3 >= 0)
	    buf[len++] = b3 & 0xff;
    }
    bool odd = false;
    while (val && (len < sizeof(buf))) {
	char c = *val++;
	if (!c)
	    break;
	unsigned char n = 0;
	if (('0' <= c) && (c <= '9'))
	    n = c - '0';
	else switch (c) {
	    case 'A':
	    case 'a':
		n = 10;
		break;
	    case 'B':
	    case 'b':
	    case '*':
		n = 11;
		break;
	    case 'C':
	    case 'c':
	    case '#':
		n = 12;
		break;
	    case 'D':
	    case 'd':
		n = 13;
		break;
	    case 'E':
	    case 'e':
		n = 14;
		break;
	    case 'F':
	    case 'f':
	    case '.':
		n = 15;
		break;
	    default:
		continue;
	}
	odd = !odd;
	if (odd)
	    buf[len] = n;
	else
	    buf[len++] |= (n << 4);
    }
    if (odd) {
	buf[naiPos] |= 0x80;
	len++;
    }
    buf[0] = (len-1) & 0xff;
    DDebug(DebugAll,"setDigits encoding %u octets (%s)",len,odd ? "odd" : "even");
    DataBlock tmp(buf,len,false);
    msu += tmp;
    tmp.clear(false);
    return buf[0];
}

// Encoder for variable length digit sequences
static unsigned char encodeDigits(const SS7ISUP* isup, SS7MSU& msu,
    unsigned char* buf, const IsupParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    if (!param || buf || param->size)
	return 0;
    unsigned char nai = 2;
    unsigned char plan = 1;
    String preName;
    if (val)
	preName = val->name();
    else
	preName = prefix + param->name;
    int b0 = -1;
    if (SS7MsgISUP::GenericNumber == param->type) {
	b0 = 0;
	if (val && extra)
	    b0 = 0xff & extra->getIntValue(preName+".qualifier",s_dict_qual,0);
    }
    if (val && extra) {
	nai = extra->getIntValue(preName+".nature",s_dict_nai,nai);
	plan = extra->getIntValue(preName+".plan",s_dict_numPlan,plan);
    }
    unsigned char b2 = (plan & 7) << 4;
    switch (param->type) {
	case SS7MsgISUP::CalledPartyNumber:
	case SS7MsgISUP::RedirectionNumber:
	case SS7MsgISUP::LocationNumber:
	    if (val && extra && !extra->getBoolValue(preName+".inn",true))
		b2 |= 0x80;
	    break;
	case SS7MsgISUP::CallingPartyNumber:
	case SS7MsgISUP::GenericNumber:
	    if (val && extra && !extra->getBoolValue(preName+".complete",true))
		b2 |= 0x80;
	    break;
	case SS7MsgISUP::LastDivertingLineIdentity:
	case SS7MsgISUP::PresentationNumber:
	    if (!val || !extra || extra->getBoolValue(preName+".pnp",true))
		b2 |= 0x80;
	    break;
	default:
	    break;
    }
    switch (param->type) {
	case SS7MsgISUP::CallingPartyNumber:
	case SS7MsgISUP::RedirectingNumber:
	case SS7MsgISUP::OriginalCalledNumber:
	case SS7MsgISUP::LocationNumber:
	case SS7MsgISUP::ConnectedNumber:
	case SS7MsgISUP::GenericNumber:
	case SS7MsgISUP::LastDivertingLineIdentity:
	case SS7MsgISUP::PresentationNumber:
	case SS7MsgISUP::CalledINNumber:
	case SS7MsgISUP::OriginalCalledINNumber:
	    if (val && extra)
		b2 |= (extra->getIntValue(preName+".restrict",s_dict_presentation) & 3) << 2;
	default:
	    break;
    }
    switch (param->type) {
	case SS7MsgISUP::CallingPartyNumber:
	case SS7MsgISUP::LocationNumber:
	case SS7MsgISUP::ConnectedNumber:
	case SS7MsgISUP::GenericNumber:
	case SS7MsgISUP::LastDivertingLineIdentity:
	case SS7MsgISUP::PresentationNumber:
	    if (val && extra)
		b2 |= extra->getIntValue(preName+".screened",s_dict_screening) & 3;
	default:
	    break;
    }
    return setDigits(msu,val ? val->c_str() : 0,nai,b2,-1,b0);
}

// Special encoder for subsequent number
static unsigned char encodeSubseq(const SS7ISUP* isup, SS7MSU& msu,
    unsigned char* buf, const IsupParam* param, const NamedString* val,
    const NamedList*, const String&)
{
    return setDigits(msu,val ? val->c_str() : 0,0);
}

// Encoder for circuit group range and status (Q.763 3.43)
static unsigned char encodeRangeSt(const SS7ISUP* isup, SS7MSU& msu,
    unsigned char* buf, const IsupParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    if (!(param && val))
	return 0;
    unsigned char data[34] = {1};
    // 1st octet is the range code (range - 1)
    // Q.763 3.43 Sent range value must be in interval 1..256
    unsigned int range = val->toInteger(0);
    if (range < 1 || range > 256) {
	Debug(isup,DebugNote,"encodeRangeSt invalid range %s=%s",val->name().c_str(),val->safe());
	return 0;
    }
    data[1] = range - 1;
    // Next octets: status bits for the circuits given by range
    NamedString* map = extra->getParam(prefix+param->name+".map");
    if (map && map->length()) {
	// Max status bits is 256. Relevant status bits: range
	unsigned int nBits = map->length();
	if (nBits > 256) {
	    Debug(isup,DebugNote,"encodeRangeSt truncating status bits %u to 256",map->length());
	    nBits = 256;
	}
	unsigned char* src = (unsigned char*)map->c_str();
	unsigned char* dest = data + 1;
	for (unsigned char crtBit = 0; nBits; nBits--, src++) {
	    if (!crtBit) {
		data[0]++;
		*++dest = 0;
	    }
	    if (*src != '0')
		*dest |= (1 << crtBit);
	    crtBit = (crtBit < 7 ? crtBit + 1 : 0);
	}
    }
    // Copy to msu
    DDebug(isup,DebugAll,"encodeRangeSt encoding %s on %u octets",param->name,data[0]);
    DataBlock tmp(data,data[0] + 1,false);
    msu += tmp;
    tmp.clear(false);
    return data[0];
}

// Encoder for generic notification indicators (Q.763 3.25)
static unsigned char encodeNotif(const SS7ISUP* isup, SS7MSU& msu,
    unsigned char* buf, const IsupParam* param, const NamedString* val,
    const NamedList*, const String&)
{
    if (!(param && val) || buf || param->size)
	return 0;
    unsigned char notif[32];
    unsigned int len = 0;
    ObjList* lst = val->split(',',false);
    ObjList* p = lst->skipNull();
    for (; p; p = p->skipNext()) {
	const String* s = static_cast<const String*>(p->get());
	int v = s->toInteger((const TokenDict*)param->data,-1);
	if (v < 0)
	    continue;
	notif[++len] = v & 0x7f;
	if (len >= sizeof(notif)-1)
	    break;
    }
    TelEngine::destruct(lst);
    DDebug(isup,DebugAll,"encodeNotif encoding %s on %u octets",param->name,len);
    if (!len)
	return 0;
    notif[len] |= 0x80;
    notif[0] = len & 0xff;
    DataBlock tmp(notif,len+1,false);
    msu += tmp;
    tmp.clear(false);
    return notif[0];
}

// Encoder for User Service Information (Q.763 3.57, Q.931)
static unsigned char encodeUSI(const SS7ISUP* isup, SS7MSU& msu,
    unsigned char* buf, const IsupParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    if (!param)
	return 0;
    String preName;
    if (val)
	preName = val->name();
    else
	preName = prefix + param->name;
    DataBlock tmp;
    SignallingUtils::encodeCaps(isup,tmp,*extra,preName,true);
    DDebug(isup,DebugAll,"encodeUSI encoding %s on %u octets",param->name,tmp.length());
    if (tmp.length() < 1)
	return 0;
    msu += tmp;
    return tmp.length() - 1;
}

// Encoder for cause indicators
static unsigned char encodeCause(const SS7ISUP* isup, SS7MSU& msu,
    unsigned char* buf, const IsupParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    if (!param)
	return 0;
    String preName;
    if (val)
	preName = val->name();
    else
	preName = prefix + param->name;
    DataBlock tmp;
    SignallingUtils::encodeCause(isup,tmp,*extra,preName,true);
    DDebug(isup,DebugAll,"encodeCause encoding %s on %u octets",param->name,tmp.length());
    if (tmp.length() < 1)
	return 0;
    msu += tmp;
    return tmp.length() - 1;
}

// Encoder for application transport parameter
static unsigned char encodeAPT(const SS7ISUP* isup, SS7MSU& msu,
    unsigned char* buf, const IsupParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    if (!param)
	return 0;
    if (TelEngine::null(val)) {
	if (val)
	    Debug(isup,DebugNote,"Failed to encode empty %s",val->name().c_str());
	return 0;
    }
    int context = val->toInteger(-1);
    if (context < 0 || context > 127) {
	// Assume binary parameter representation
	DataBlock data;
	if (!(data.unHexify(val->c_str(),val->length(),' ') && data.length()) ||
	    data.length() < 4 || data.length() > 254) {
	    Debug(isup,DebugNote,"Failed to encode invalid %s=%s",
		param->name,val->c_str());
	    return 0;
	}
	unsigned char len = data.length();
	msu.append(&len,1);
	msu += data;
	return 1 + data.length();
    }
    // WARNING: HACK - ApplicationTransport does not follow naming convention
    String preName(prefix + param->name);
    preName << "." << context;
    unsigned char hdr[4] = {0,(unsigned char)(0x80 | context),0x80,0xc0};  // c0: extension bit set, new sequence bit set
    // Retrieve data. Make sure all bytes are correct and final length don't
    // overflow our return value
    DataBlock data;
    const String& tmp = extra ? (*extra)[preName] : String::empty();
    if (!(data.unHexify(tmp.c_str(),tmp.length(),' ') && data.length()) ||
	data.length() > (255 - sizeof(hdr))) {
	Debug(isup,DebugNote,"Failed to encode invalid %s=%s",
	    param->name,tmp.c_str());
	return 0;
    }
    String indName(preName + ".indicators");
    const String* inds = extra ? extra->getParam(indName) : 0;
    if (inds) {
	unsigned int v = SignallingUtils::encodeFlags(isup,*inds,s_flags_apt_indicators,indName);
	hdr[2] |= (v & 0x7f);
    }
    else {
	// Set default indicators value: send CNF, no call release
	hdr[2] |= 0x02;
    }
    hdr[0] = data.length() + 3;
    msu.append(hdr,sizeof(hdr));
    msu += data;
    return hdr[0];
}

// Encoder for Generic Name
static unsigned char encodeName(const SS7ISUP* isup, SS7MSU& msu,
    unsigned char* buf, const IsupParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    if (!(param && val) || buf || param->size)
	return 0;
    unsigned int len = val->length() + 1;
    if (len >= 127)
	return 0;
    unsigned char gn[2] = { (unsigned char)len, 3 };
    if (extra) {
	String preName;
	if (val)
	    preName = val->name();
	else
	    preName = prefix + param->name;
	if (!extra->getBoolValue(preName+".available",true))
	    gn[1] |= 0x10;
	gn[1] = (gn[1] & 0x1f) |
	    (extra->getIntValue(preName+".qualifier",s_dict_qual_name,gn[1] & 0xe0) & 0xe0);
	gn[1] = (gn[1] & 0xfc) |
	    (extra->getIntValue(preName+".restrict",s_dict_presentation,gn[1] & 0x03) & 0x03);
    }
    DataBlock tmp(gn,2);
    tmp += *val;
    DDebug(isup,DebugAll,"encodeName encoding %s on %u octets",param->name,tmp.length());
    msu += tmp;
    return len;
}

// Encoder for Redirection information (Q.763 3.45)
static unsigned char encodeRedir(const SS7ISUP* isup, SS7MSU& msu,
    unsigned char* buf, const IsupParam* param, const NamedString* val,
    const NamedList* extra, const String& prefix)
{
    if (!(param && val) || buf || param->size)
	return 0;
    unsigned char ri[3] = { 2, 0, 0 };
    if (extra) {
	String preName;
	if (val)
	    preName = val->name();
	else
	    preName = prefix + param->name;
	ri[1] = (extra->getIntValue(preName,s_dict_redir_main,0) & 0x07) |
	    ((extra->getIntValue(preName+".reason_original",s_dict_redir_reason,0) & 0x0f) << 4);
	ri[2] = (extra->getIntValue(preName+".counter") & 0x07) |
	    ((extra->getIntValue(preName+".reason",s_dict_redir_reason,0) & 0x0f) << 4);
    }
    DataBlock tmp(ri,3,false);
    msu += tmp;
    tmp.clear(false);
    return ri[0];
}

// Nature of Connection Indicators (Q.763 3.35)
static const SignallingFlags s_flags_naci[] = {
    // TODO: add more flags
    { 0x03, 0x00, "0sat" },
    { 0x03, 0x01, "1sat" },
    { 0x03, 0x02, "2sat" },
    { 0x0c, 0x00, "cont-check-none" },      // Continuity check not required
    { 0x0c, 0x04, "cont-check-this" },      // Continuity check required on this circuit
    { 0x0c, 0x08, "cont-check-prev" },      // Continuity check performed on a previous circuit
    { 0x10, 0x10, "echodev" },              // Outgoing half echo control device included
    { 0, 0, 0 }
};

// Forward Call Indicators (Q.763 3.23)
static const SignallingFlags s_flags_fwcallind[] = {
    { 0x0001, 0x0000, "national" },         // National/international call indicator
    { 0x0001, 0x0001, "international" },
    { 0x0006, 0x0000, "e2e-none" },         // End-to-end method indicator (none available: only link-by-link)
    { 0x0006, 0x0002, "e2e-pass" },         //   Pass along method available
    { 0x0006, 0x0004, "e2e-sccp" },         //   SCCP along method available
    { 0x0006, 0x0006, "e2e-pass-sccp" },    //   Pass along and SCCP method available
    { 0x0008, 0x0008, "interworking" },     // Interworking indicator (0: SS7 all the way)
    { 0x0010, 0x0010, "e2e-info" },         // End-to-end information available
    { 0x0020, 0x0020, "isup-path" },        // ISUP indicator (ISUP used all the way)
    { 0x00c0, 0x0000, "isup-pref" },        // ISUP preference indicator: preferred all the way
    { 0x00c0, 0x0040, "isup-notreq" },      //   not required all the way
    { 0x00c0, 0x0080, "isup-req" },         //   required all the way
    { 0x0100, 0x0100, "isdn-orig" },        // Originating from ISDN
    { 0x0600, 0x0000, "sccp-none" },        // SCCP method indicator: no indication
    { 0x0600, 0x0200, "sccp-less" },        //   connectionless method available
    { 0x0600, 0x0400, "sccp-conn" },        //   connection oriented method available
    { 0x0600, 0x0600, "sccp-less-conn" },   //   connectionless and connection oriented methods available
    { 0x1000, 0x1000, "translated" },       // Number Translated (for portability)
    { 0x2000, 0x2000, "qor-routing" },      // QoR routing attempt in progress
    { 0, 0, 0 }
};

// Backward Call Indicators (Q.763 3.5)
static const SignallingFlags s_flags_bkcallind[] = {
    { 0x0003, 0x0001, "no-charge" },        // Charge indicator
    { 0x0003, 0x0002, "charge" },
    { 0x000c, 0x0004, "called-free" },      // Called party's status indicator: subscriber free
    { 0x000c, 0x0008, "called-conn" },      // Called party's status indicator: connect when free
    { 0x0030, 0x0010, "called-ordinary" },  // Called party's category indicator: ordinary subscriber
    { 0x0030, 0x0020, "called-payphone" },  // Called party's category indicator: payphone
    { 0x00c0, 0x0000, "e2e-none" },         // End-to-end method indicator (none available: only link-by-link)
    { 0x00c0, 0x0040, "e2e-pass" },         //   Pass along method available
    { 0x00c0, 0x0080, "e2e-sccp" },         //   SCCP along method available
    { 0x00c0, 0x00c0, "e2e-pass-sccp" },    //   Pass along and SCCP method available
    { 0x0100, 0x0100, "interworking" },     // Interworking indicator (0: SS7 all the way)
    { 0x0200, 0x0200, "e2e-info" },         // End-to-end information available
    { 0x0400, 0x0400, "isup-path" },        // ISUP indicator (ISUP used all the way)
    { 0x0800, 0x0800, "hold-req" },         // Holding indicator: holding requested
    { 0x1000, 0x1000, "isdn-end" },         // Terminating in ISDN
    { 0x2000, 0x2000, "echodev" },          // Incoming half echo control device included
    { 0xc000, 0x0000, "sccp-none" },        // SCCP method indicator: no indication
    { 0xc000, 0x4000, "sccp-less" },        //   connectionless method available
    { 0xc000, 0x8000, "sccp-conn" },        //   connection oriented method available
    { 0xc000, 0xc000, "sccp-less-conn" },   //   connectionless and connection oriented methods available
    { 0, 0, 0 }
};

// Call Diversion Information (Q.763 3.6)
static const SignallingFlags s_flags_calldivinfo[] = {
    { 0x07, 0x01, "presentation-not-allowed" },
    { 0x07, 0x02, "presentation-with-number" },
    { 0x07, 0x03, "presentation-without-number" },
    { 0x78, 0x08, "busy" },
    { 0x78, 0x10, "noanswer" },
    { 0x78, 0x18, "always" },
    { 0x78, 0x20, "deflected-alerting" },
    { 0x78, 0x28, "deflected-immediate" },
    { 0x78, 0x30, "offline" },
    { 0, 0, 0 }
};

// Optional Forward Call Indicators (Q.763 3.38)
static const SignallingFlags s_flags_optfwcallind[] = {
    { 0x03, 0x00, "non-CUG" },
    { 0x03, 0x02, "CUG+out" },
    { 0x03, 0x03, "CUG" },
    { 0x04, 0x04, "segmentation" },         // Additional info will be sent in a segmentation message
    { 0x80, 0x80, "CLIR-requested" },
    { 0, 0, 0 }
};

// Optional Backward Call Indicators (Q.763 3.37)
static const SignallingFlags s_flags_optbkcallind[] = {
    { 0x01, 0x01, "inband" },
    { 0x02, 0x02, "diversion-possible" },
    { 0x04, 0x04, "segmentation" },         // Additional info will be sent in a segmentation message
    { 0x08, 0x08, "MLPP-user" },
    { 0, 0, 0 }
};

// Event Information (Q.763 3.21)
static const SignallingFlags s_flags_eventinfo[] = {
    { 0x7f, 0x01, "ringing" },
    { 0x7f, 0x02, "progress" },
    { 0x7f, 0x03, "inband" },
    { 0x7f, 0x04, "forward-busy" },
    { 0x7f, 0x05, "forward-noanswer" },
    { 0x7f, 0x06, "forward-always" },
    { 0x80, 0x80, "restricted" },
    { 0, 0, 0 }
};

// Continuity Indicators (Q.763 3.18)
static const SignallingFlags s_flags_continuity[] = {
    { 0x01, 0x00, "failed" },
    { 0x01, 0x01, "success" },
    { 0, 0, 0 }
};

// Group Supervision Type Indicator (Q.763 3.13)
static const SignallingFlags s_flags_grptypeind[] = {
    { 0x03, 0x00, "maintenance" },
    { 0x03, 0x01, "hw-failure" },
    { 0x03, 0x02, "national" },
    { 0, 0, 0 }
};

// Access Delivery Information (Q.763 3.2)
static const SignallingFlags s_flags_accdelinfo[] = {
    { 0x01, 0x00, "setup-generated" },   // A Setup message was generated
    { 0x01, 0x01, "no-setup" },          // No Setup message generated
    { 0, 0, 0 }
};

// MCID Request or Response Indicators (Q.763 3.31 and 3.32)
static const SignallingFlags s_flags_mcid[] = {
    { 0x01, 0x01, "MCID" },
    { 0x02, 0x02, "holding" },
    { 0, 0, 0 }
};

// ANSI Circuit Validation Response Indicator
static const SignallingFlags s_flags_ansi_cvri[] = {
    { 0x03, 0x00, "failed" },
    { 0x03, 0x01, "success" },
    { 0, 0, 0 }
};

// ANSI Circuit Group Characteristics Indicator
static const SignallingFlags s_flags_ansi_cgci[] = {
    { 0x03, 0x00, "carrier-unknown" },
    { 0x03, 0x01, "carrier-analog" },
    { 0x03, 0x02, "carrier-digital" },
    { 0x03, 0x03, "carrier-mixed" },
    { 0x0c, 0x00, "seize-none" },
    { 0x0c, 0x04, "seize-odd" },
    { 0x0c, 0x08, "seize-even" },
    { 0x0c, 0x0c, "seize-all" },
    { 0x30, 0x00, "alarm-default" },
    { 0x30, 0x10, "alarm-software" },
    { 0x30, 0x20, "alarm-hardware" },
    { 0xc0, 0x00, "continuity-unknown" },
    { 0xc0, 0x40, "continuity-none" },
    { 0xc0, 0x80, "continuity-statistical" },
    { 0xc0, 0xc0, "continuity-call" },
    { 0, 0, 0 }
};

// National Forward Call Indicators (NICC ND 1007 2001 3.2.1)
static const SignallingFlags s_flags_nfci[] = {
    { 0x0001, 0x0000, "cli-blocked" },      // CLI Blocking Indicator (CBI)
    { 0x0001, 0x0001, "cli-allowed" },
    { 0x0002, 0x0002, "translated" },       // Network translated address indicator
    { 0x0004, 0x0004, "iup-priority" },     // Priority access indicator (IUP)
    { 0x0008, 0x0008, "iup-protected" },    // Protection indicator (IUP)
    { 0, 0, 0 }
};

// Calling Party Category (Q.763 3.11)
static const TokenDict s_dict_callerCat[] = {
    { "unknown",     0 },                // calling party's category is unknown
    { "operator-FR", 1 },                // operator, language French
    { "operator-EN", 2 },                // operator, language English
    { "operator-DE", 3 },                // operator, language German
    { "operator-RU", 4 },                // operator, language Russian
    { "operator-ES", 5 },                // operator, language Spanish
    { "ordinary",   10 },                // ordinary calling subscriber
    { "priority",   11 },                // calling subscriber with priority
    { "data",       12 },                // data call (voice band data)
    { "test",       13 },                // test call
    { "payphone",   15 },                // payphone
    { 0, 0 }
};

// Transmission Medium Requirement (Q.763 3.54)
static const TokenDict s_dict_mediumReq[] = {
    { "speech",          0 },
    { "64kbit",          2 },
    { "3.1khz-audio",    3 },
    { "64kb-preferred",  6 },
    { "2x64kbit",        7 },
    { "384kbit",         8 },
    { "1536kbit",        9 },
    { "1920kbit",       10 },
    { 0, 0 }
};

// Generic Notification Indicator (Q.763 3.25)
static const TokenDict s_dict_notifications[] = {
    { "user-suspended",         0x00 },
    { "user-resumed",           0x01 },
    { "bearer-service-change",  0x02 },
    { "call-completion-delay",  0x04 },
    { "conf-established",       0x42 },
    { "conf-disconnected",      0x43 },
    { "party-added",            0x44 },
    { "isolated",               0x45 },
    { "reattached",             0x46 },
    { "party-isolated",         0x47 },
    { "party-reattached",       0x48 },
    { "party-split",            0x49 },
    { "party-disconnected",     0x4a },
    { "conf-floating",          0x4b },
    { "call-waiting",           0x60 },
    { "call-diversion",         0x68 },
    { "call-transfer-alerting", 0x69 },
    { "call-transfer-active",   0x6a },
    { "remote-hold",            0x79 },
    { "remote-retrieval",       0x7a },
    { "call-diverting",         0x7b },
    { 0, 0 }
};

// Number Portability Forward Information (Q.763 3.101)
static const TokenDict s_dict_portability[] = {
    { "not-queried",       1 },
    { "called-not-ported", 2 },
    { "called-ported",     3 },
    { 0, 0 }
};

// ANSI Originating Line Info
static const TokenDict s_dict_oli[] = {
    { "normal",            0 },
    { "multiparty",        1 },
    { "ani-failure",       2 },
    { "hotel-room-id",     6 },
    { "coinless",          7 },
    { "restricted",        8 },
    { "test-call-1",      10 },
    { "aiod-listed-dn",   20 },
    { "identified-line",  23 },
    { "800-call",         24 },
    { "coin-line",        27 },
    { "restricted-hotel", 68 },
    { "test-call-2",      95 },
    { 0, 0 }
};

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

    // Standard parameters, references to ITU Q.763
    MAKE_PARAM(AccessDeliveryInformation,      1,decodeFlags,   encodeFlags,   s_flags_accdelinfo),   // 3.2
    MAKE_PARAM(AccessTransport,                0,0,             0,             0),                    // 3.3
    MAKE_PARAM(AutomaticCongestionLevel,       1,decodeInt,     encodeInt,     0),                    // 3.4
    MAKE_PARAM(BackwardCallIndicators,         2,decodeFlags,   encodeFlags,   s_flags_bkcallind),    // 3.5
    MAKE_PARAM(CallDiversionInformation,       1,decodeFlags,   encodeFlags,   s_flags_calldivinfo),  // 3.6
    MAKE_PARAM(CallHistoryInformation,         2,decodeInt,     encodeInt,     0),                    // 3.7
    MAKE_PARAM(CallReference,                  0,0,             0,             0),                    // 3.8
    MAKE_PARAM(CalledPartyNumber,              0,decodeDigits,  encodeDigits,  0),                    // 3.9
    MAKE_PARAM(CallingPartyNumber,             0,decodeDigits,  encodeDigits,  0),                    // 3.10
    MAKE_PARAM(CallingPartyCategory,           1,decodeInt,     encodeInt,     s_dict_callerCat),     // 3.11
    MAKE_PARAM(CauseIndicators,                0,decodeCause,   encodeCause,   0),                    // 3.12  Q.850-2.1
    MAKE_PARAM(GroupSupervisionTypeIndicator,  1,decodeFlags,   encodeFlags,   s_flags_grptypeind),   // 3.13
    MAKE_PARAM(CircuitStateIndicator,          0,0,             0,             0),                    // 3.14
    MAKE_PARAM(CUG_InterlockCode,              0,0,             0,             0),                    // 3.15
    MAKE_PARAM(ConnectedNumber,                0,decodeDigits,  encodeDigits,  0),                    // 3.16
    MAKE_PARAM(ConnectionRequest,              0,0,             0,             0),                    // 3.17
    MAKE_PARAM(ContinuityIndicators,           1,decodeFlags,   encodeFlags,   s_flags_continuity),   // 3.18
    MAKE_PARAM(EchoControlInformation,         0,0,             0,             0),                    // 3.19
    MAKE_PARAM(EventInformation,               1,decodeFlags,   encodeFlags,   s_flags_eventinfo),    // 3.21
    MAKE_PARAM(FacilityIndicator,              1,0,             0,             0),                    // 3.22
    MAKE_PARAM(ForwardCallIndicators,          2,decodeFlags,   encodeFlags,   s_flags_fwcallind),    // 3.23
    MAKE_PARAM(GenericDigits,                  0,0,             0,             0),                    // 3.24
    MAKE_PARAM(GenericNotification,            0,decodeNotif,   encodeNotif,   s_dict_notifications), // 3.25
    MAKE_PARAM(GenericNumber,                  0,decodeDigits,  encodeDigits,  0),                    // 3.26
    MAKE_PARAM(GenericReference,               0,0,             0,             0),                    // 3.27
    MAKE_PARAM(InformationIndicators,          2,0,             0,             0),                    // 3.28
    MAKE_PARAM(InformationRequestIndicators,   2,0,             0,             0),                    // 3.29
    MAKE_PARAM(LocationNumber,                 0,decodeDigits,  encodeDigits,  0),                    // 3.30
    MAKE_PARAM(MCID_RequestIndicator,          1,decodeFlags,   encodeFlags,   s_flags_mcid),         // 3.31
    MAKE_PARAM(MCID_ResponseIndicator,         1,decodeFlags,   encodeFlags,   s_flags_mcid),         // 3.32
    MAKE_PARAM(MessageCompatInformation,       0,decodeCompat,  0,             0),                    // 3.33
    MAKE_PARAM(NatureOfConnectionIndicators,   1,decodeFlags,   encodeFlags,   s_flags_naci),         // 3.35
    MAKE_PARAM(NetworkSpecificFacilities,      0,0,             0,             0),                    // 3.36
    MAKE_PARAM(OptionalBackwardCallIndicators, 1,decodeFlags,   encodeFlags,   s_flags_optbkcallind), // 3.37
    MAKE_PARAM(OptionalForwardCallIndicators,  1,decodeFlags,   encodeFlags,   s_flags_optfwcallind), // 3.38
    MAKE_PARAM(OriginalCalledNumber,           0,decodeDigits,  encodeDigits,  0),                    // 3.39
    MAKE_PARAM(OriginationISCPointCode,        0,0,             0,             0),                    // 3.40
    MAKE_PARAM(ParameterCompatInformation,     0,decodeCompat,  0,             0),                    // 3.41
    MAKE_PARAM(PropagationDelayCounter,        2,decodeInt,     encodeInt,     0),                    // 3.42
    MAKE_PARAM(RangeAndStatus,                 0,decodeRangeSt, encodeRangeSt, 0),                    // 3.43
    MAKE_PARAM(RedirectingNumber,              0,decodeDigits,  encodeDigits,  0),                    // 3.44
    MAKE_PARAM(RedirectionInformation,         0,decodeRedir,   encodeRedir,   0),                    // 3.45
    MAKE_PARAM(RedirectionNumber,              0,decodeDigits,  encodeDigits,  0),                    // 3.46
    MAKE_PARAM(RedirectionNumberRestriction,   0,0,             0,             0),                    // 3.47
    MAKE_PARAM(RemoteOperations,               0,0,             0,             0),                    // 3.48
    MAKE_PARAM(ServiceActivation,              0,0,             0,             0),                    // 3.49
    MAKE_PARAM(SignallingPointCode,            0,0,             0,             0),                    // 3.50
    MAKE_PARAM(SubsequentNumber,               0,decodeSubseq,  encodeSubseq,  0),                    // 3.51
    MAKE_PARAM(SuspendResumeIndicators,        1,0,             0,             0),                    // 3.52
    MAKE_PARAM(TransitNetworkSelection,        0,0,             0,             0),                    // 3.53
    MAKE_PARAM(TransmissionMediumRequirement,  1,decodeInt,     encodeInt,     s_dict_mediumReq),     // 3.54
    MAKE_PARAM(TransMediumRequirementPrime,    1,decodeInt,     encodeInt,     s_dict_mediumReq),     // 3.55
    MAKE_PARAM(TransmissionMediumUsed,         1,decodeInt,     encodeInt,     s_dict_mediumReq),     // 3.56
    MAKE_PARAM(UserServiceInformation,         0,decodeUSI,     encodeUSI,     0),                    // 3.57  Q.931-4.5.5
    MAKE_PARAM(UserServiceInformationPrime,    0,0,             0,             0),                    // 3.58
    MAKE_PARAM(UserTeleserviceInformation,     0,0,             0,             0),                    // 3.59
    MAKE_PARAM(UserToUserIndicators,           0,0,             0,             0),                    // 3.60
    MAKE_PARAM(UserToUserInformation,          0,0,             0,             0),                    // 3.61
    MAKE_PARAM(CCSScallIndication,             1,0,             0,             0),                    // 3.63
    MAKE_PARAM(ForwardGVNS,                    0,0,             0,             0),                    // 3.66
    MAKE_PARAM(BackwardGVNS,                   0,0,             0,             0),                    // 3.62
    MAKE_PARAM(CalledINNumber,                 0,decodeDigits,  encodeDigits,  0),                    // 3.73
    MAKE_PARAM(UID_ActionIndicators,           0,0,             0,             0),                    // 3.78
    MAKE_PARAM(UID_CapabilityIndicators,       0,0,             0,             0),                    // 3.79
    MAKE_PARAM(RedirectCapability,             0,0,             0,             0),                    // 3.96
    MAKE_PARAM(RedirectCounter,                0,0,             0,             0),                    // 3.97
    MAKE_PARAM(CCNRpossibleIndicator,          0,0,             0,             0),                    // 3.83
    MAKE_PARAM(PivotRoutingIndicators,         0,0,             0,             0),                    // 3.85
    MAKE_PARAM(CalledDirectoryNumber,          0,0,             0,             0),                    // 3.86
    MAKE_PARAM(OriginalCalledINNumber,         0,0,             0,             0),                    // 3.87
    MAKE_PARAM(CallingGeodeticLocation,        0,0,             0,             0),                    // 3.88
    MAKE_PARAM(HTR_Information,                0,0,             0,             0),                    // 3.89
    MAKE_PARAM(NetworkRoutingNumber,           0,0,             0,             0),                    // 3.90
    MAKE_PARAM(QueryOnReleaseCapability,       0,0,             0,             0),                    // 3.91
    MAKE_PARAM(PivotStatus,                    0,0,             0,             0),                    // 3.92
    MAKE_PARAM(PivotCounter,                   0,0,             0,             0),                    // 3.93
    MAKE_PARAM(PivotRoutingForwardInformation, 0,0,             0,             0),                    // 3.94
    MAKE_PARAM(PivotRoutingBackInformation,    0,0,             0,             0),                    // 3.95
    MAKE_PARAM(RedirectStatus,                 0,0,             0,             0),                    // 3.98
    MAKE_PARAM(RedirectForwardInformation,     0,0,             0,             0),                    // 3.99
    MAKE_PARAM(RedirectBackwardInformation,    0,0,             0,             0),                    // 3.100
    MAKE_PARAM(NumberPortabilityInformation,   0,decodeNotif,   encodeNotif,   s_dict_portability),   // 3.101
    // No references
    MAKE_PARAM(ApplicationTransport,           0,decodeAPT,     encodeAPT,     0),                    // 3.82
    MAKE_PARAM(BusinessGroup,                  0,0,             0,             0),                    //
    MAKE_PARAM(CallModificationIndicators,     0,0,             0,             0),                    //
    MAKE_PARAM(CarrierIdentification,          0,0,             0,             0),                    //
    MAKE_PARAM(CircuitIdentificationName,      0,0,             0,             0),                    //
    MAKE_PARAM(CarrierSelectionInformation,    0,0,             0,             0),                    //
    MAKE_PARAM(ChargeNumber,                   0,0,             0,             0),                    //
    MAKE_PARAM(CircuitAssignmentMap,           0,0,             0,             0),                    //
    MAKE_PARAM(CircuitGroupCharactIndicator,   1,decodeFlags,   encodeFlags,   s_flags_ansi_cgci),    // T1.113 ??
    MAKE_PARAM(CircuitValidationRespIndicator, 1,decodeFlags,   encodeFlags,   s_flags_ansi_cvri),    // T1.113 ??
    MAKE_PARAM(CommonLanguage,                 0,0,             0,             0),                    //
    MAKE_PARAM(CUG_CheckResponseIndicators,    0,0,             0,             0),                    //
    MAKE_PARAM(Egress,                         0,0,             0,             0),                    //
    MAKE_PARAM(FacilityInformationIndicators,  0,0,             0,             0),                    //
    MAKE_PARAM(FreephoneIndicators,            0,0,             0,             0),                    //
    MAKE_PARAM(GenericName,                    0,decodeName,    encodeName,    0),                    //
    MAKE_PARAM(HopCounter,                     1,decodeInt,     encodeInt,     0),                    // 3.80
    MAKE_PARAM(Index,                          0,0,             0,             0),                    //
    MAKE_PARAM(Jurisdiction,                   0,0,             0,             0),                    //
    MAKE_PARAM(MLPP_Precedence,                0,0,             0,             0),                    //
    MAKE_PARAM(NetworkTransport,               0,0,             0,             0),                    //
    MAKE_PARAM(NotificationIndicator,          0,0,             0,             0),                    //
    MAKE_PARAM(OperatorServicesInformation,    0,0,             0,             0),                    //
    MAKE_PARAM(OriginatingLineInformation,     1,decodeInt,     encodeInt,     s_dict_oli),           //
    MAKE_PARAM(OutgoingTrunkGroupNumber,       0,0,             0,             0),                    //
    MAKE_PARAM(Precedence,                     0,0,             0,             0),                    //
    MAKE_PARAM(ServiceCodeIndicator,           0,0,             0,             0),                    //
    MAKE_PARAM(SpecialProcessingRequest,       0,0,             0,             0),                    //
    MAKE_PARAM(TransactionRequest,             0,0,             0,             0),                    //
    // National use (UK-ISUP), references to NICC ND 1007 2001/07
    MAKE_PARAM(NationalForwardCallIndicators,          2,decodeFlags,   encodeFlags,   s_flags_nfci), // 3.2.1
    MAKE_PARAM(NationalForwardCallIndicatorsLinkByLink,0,0,             0,             0),            // 3.2.2
    MAKE_PARAM(PresentationNumber,                     0,decodeDigits,  encodeDigits,  0),            // 3.2.3
    MAKE_PARAM(LastDivertingLineIdentity,              0,decodeDigits,  encodeDigits,  0),            // 3.2.4
    MAKE_PARAM(PartialCLI,                             0,0,             0,             0),            // 3.2.5
    MAKE_PARAM(CalledSubscribersBasicServiceMarks,     0,0,             0,             0),            // 3.2.6
    MAKE_PARAM(CallingSubscribersBasicServiceMarks,    0,0,             0,             0),            // 3.2.7
    MAKE_PARAM(CallingSubscribersOriginatingFacilMarks,0,0,             0,             0),            // 3.2.8
    MAKE_PARAM(CalledSubscribersTerminatingFacilMarks, 0,0,             0,             0),            // 3.2.9
    MAKE_PARAM(NationalInformationRequestIndicators,   0,0,             0,             0),            // 3.2.10
    MAKE_PARAM(NationalInformationIndicators,          0,0,             0,             0),            // 3.2.11
    { SS7MsgISUP::EndOfParameters, 0, 0, 0, 0, 0 }
};
#undef MAKE_PARAM

const char* getIsupParamName(unsigned char type)
{
   for (unsigned int i = 0; s_paramDefs[i].type; i++)
	if (type == s_paramDefs[i].type)
	    return s_paramDefs[i].name;
   return 0;
}

// Descriptor of ISUP message common across standards
static const MsgParams s_common_params[] = {
    // call progress and release messages
    { SS7MsgISUP::ACM, true,
	{
	    SS7MsgISUP::BackwardCallIndicators,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CON, true,
	{
	    SS7MsgISUP::BackwardCallIndicators,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::ANM, true,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::REL, true,
	{
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::CauseIndicators,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::RLC, true,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::SAM, true,
	{
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::SubsequentNumber,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CPR, true,
	{
	    SS7MsgISUP::EventInformation,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CNF, true,
	{
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::CauseIndicators,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::SUS, true,
	{
	    SS7MsgISUP::SuspendResumeIndicators,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::RES, true,
	{
	    SS7MsgISUP::SuspendResumeIndicators,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::INR, true,
	{
	    SS7MsgISUP::InformationRequestIndicators,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::INF, true,
	{
	    SS7MsgISUP::InformationIndicators,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    // circuit group reset and acknowledgement
    { SS7MsgISUP::GRS, false,
	{
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::RangeAndStatus,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::GRA, false,
	{
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::RangeAndStatus,
	SS7MsgISUP::EndOfParameters
	}
    },
    // circuit group query
    { SS7MsgISUP::CQM, false,
	{
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::RangeAndStatus,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CQR, false,
	{
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::RangeAndStatus,
	    SS7MsgISUP::CircuitStateIndicator,
	SS7MsgISUP::EndOfParameters
	}
    },
    // circuit group blocking, unblocking and acknowledgement
    { SS7MsgISUP::CGB, false,
	{
	    SS7MsgISUP::GroupSupervisionTypeIndicator,
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::RangeAndStatus,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CGA, false,
	{
	    SS7MsgISUP::GroupSupervisionTypeIndicator,
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::RangeAndStatus,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CGU, false,
	{
	    SS7MsgISUP::GroupSupervisionTypeIndicator,
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::RangeAndStatus,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CUA, false,
	{
	    SS7MsgISUP::GroupSupervisionTypeIndicator,
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::RangeAndStatus,
	SS7MsgISUP::EndOfParameters
	}
    },
    // circuit related messages - most without parameters, only CIC
    { SS7MsgISUP::BLK, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::BLA, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::UBL, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::UBA, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CCR, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::LPA, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::OLM, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::RSC, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::UEC, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::COT, false,
	{
	    SS7MsgISUP::ContinuityIndicators,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    // user part test and response
    { SS7MsgISUP::UPT, true,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::UPA, true,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    // application transport
    { SS7MsgISUP::APM, true,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    // facility
    { SS7MsgISUP::FACR, true,
	{
	    SS7MsgISUP::FacilityIndicator,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::FAA, true,
	{
	    SS7MsgISUP::FacilityIndicator,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::FRJ, true,
	{
	    SS7MsgISUP::FacilityIndicator,
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::CauseIndicators,
	SS7MsgISUP::EndOfParameters
	}
    },
    // miscellaneous
    { SS7MsgISUP::USR, true,
	{
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::UserToUserInformation,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::Unknown, false, { SS7MsgISUP::EndOfParameters } }
};

// Descriptor of ITU-T version of ISUP messages
static const MsgParams s_itu_params[] = {
    { SS7MsgISUP::IAM, true,
	{
	    SS7MsgISUP::NatureOfConnectionIndicators,
	    SS7MsgISUP::ForwardCallIndicators,
	    SS7MsgISUP::CallingPartyCategory,
	    SS7MsgISUP::TransmissionMediumRequirement,
	SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::CalledPartyNumber,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::Unknown, false, { SS7MsgISUP::EndOfParameters } }
};

// Descriptor of ANSI version of ISUP messages
static const MsgParams s_ansi_params[] = {
    { SS7MsgISUP::IAM, true,
	{
	    SS7MsgISUP::NatureOfConnectionIndicators,
	    SS7MsgISUP::ForwardCallIndicators,
	    SS7MsgISUP::CallingPartyCategory,
		SS7MsgISUP::EndOfParameters,
	    SS7MsgISUP::UserServiceInformation,
	    SS7MsgISUP::CalledPartyNumber,
		SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::RLC, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::EXM, true,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CVT, false,
	{
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::CVR, true,
	{
	    SS7MsgISUP::CircuitValidationRespIndicator,
	    SS7MsgISUP::CircuitGroupCharactIndicator,
	SS7MsgISUP::EndOfParameters,
	SS7MsgISUP::EndOfParameters
	}
    },
    { SS7MsgISUP::Unknown, false, { SS7MsgISUP::EndOfParameters } }
};

// Descriptor for decoding of compatibility parameters of unsupported messages
//  with only optional parameters (all new messages should be like this)
static const MsgParams s_compatibility = {
    SS7MsgISUP::Unknown, true,
    {
    SS7MsgISUP::EndOfParameters,
    SS7MsgISUP::EndOfParameters
    }
};

// Generic decode helper function for a single parameter
static bool decodeParam(const SS7ISUP* isup, NamedList& list, const IsupParam* param,
    const unsigned char* buf, unsigned int len, const String& prefix)
{
    DDebug(isup,DebugAll,"decodeParam(%p,%p,%p,%u) type=0x%02x, size=%u, name='%s'",
	&list,param,buf,len,param->type,param->size,param->name);
    if (param->size && (param->size != len))
	return false;
    if (param->decoder)
	return param->decoder(isup,list,param,buf,len,prefix);
    return decodeRaw(isup,list,param,buf,len,prefix);
}

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

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

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

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

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

// Locate the description table for a message according to protocol type
static const MsgParams* getIsupParams(SS7PointCode::Type type, SS7MsgISUP::Type msg)
{
    const MsgParams* params = 0;
    switch (type) {
	case SS7PointCode::ITU:
	case SS7PointCode::China:
	case SS7PointCode::Japan:
	case SS7PointCode::Japan5:
	    params = s_itu_params;
	    break;
	case SS7PointCode::ANSI:
	case SS7PointCode::ANSI8:
	    params = s_ansi_params;
	    break;
	default:
	    return 0;
    }
    // search first in specific table
    for (; params->type != SS7MsgISUP::Unknown; params++) {
	if (params->type == msg)
	    return params;
    }
    // then search in common table
    for (params = s_common_params; params->type != SS7MsgISUP::Unknown; params++) {
	if (params->type == msg)
	    return params;
    }
    return 0;
}

// Hexify a list of isup parameter values/names
static void hexifyIsupParams(String& s, const String& list)
{
    if (!list)
	return;
    ObjList* l = list.split(',',false);
    unsigned int len = l->count();
    if (len) {
	unsigned char* buf = new unsigned char[len];
	len = 0;
	for (ObjList* o = l->skipNull(); o; o = o->skipNext()) {
	    String* str = static_cast<String*>(o->get());
	    int val = str->toInteger(-1);
	    if (val < 0) {
		const IsupParam* p = getParamDesc(*str);
		if (p)
		    val = p->type;
	    }
	    if (val >= 0 && val < 256) {
		// avoid duplicates
		for (unsigned int i = 0; i < len; i++) {
		    if ((unsigned char)val == buf[i]) {
			val = -1;
			break;
		    }
		}
		if (val >= 0)
		    buf[len++] = (unsigned char)val;
	    }
	}
	if (len)
	    s.hexify(buf,len,' ');
	delete[] buf;
    }
    TelEngine::destruct(l);
}

// Check if an unhandled messages has only optional parameters
#define MAKE_CASE(x) case SS7MsgISUP::x:
static bool hasOptionalOnly(SS7MsgISUP::Type msg)
{
    switch (msg) {
	MAKE_CASE(IAM)
	MAKE_CASE(SAM)
	MAKE_CASE(INR)
	MAKE_CASE(INF)
	MAKE_CASE(COT)
	MAKE_CASE(ACM)
	MAKE_CASE(CON)
	MAKE_CASE(REL)
	MAKE_CASE(SUS)
	MAKE_CASE(RES)
	MAKE_CASE(CCR)
	MAKE_CASE(RSC)
	MAKE_CASE(BLK)
	MAKE_CASE(UBL)
	MAKE_CASE(BLA)
	MAKE_CASE(UBA)
	MAKE_CASE(GRS)
	MAKE_CASE(CGB)
	MAKE_CASE(CGU)
	MAKE_CASE(CGA)
	MAKE_CASE(CUA)
	MAKE_CASE(FACR)
	MAKE_CASE(FAA)
	MAKE_CASE(FRJ)
	MAKE_CASE(LPA)
	MAKE_CASE(PAM)
	MAKE_CASE(GRA)
	MAKE_CASE(CQM)
	MAKE_CASE(CQR)
	MAKE_CASE(CPR)
	MAKE_CASE(USR)
	MAKE_CASE(UEC)
	MAKE_CASE(CNF)
	MAKE_CASE(OLM)
	    return false;
	default:
	    return true;
    }
}
#undef MAKE_CASE

#define MAKE_NAME(x) { #x, SS7MsgISUP::x }
static const TokenDict s_names[] = {
    // this list must be kept in synch with the header
    MAKE_NAME(IAM),
    MAKE_NAME(SAM),
    MAKE_NAME(INR),
    MAKE_NAME(INF),
    MAKE_NAME(COT),
    MAKE_NAME(ACM),
    MAKE_NAME(CON),
    MAKE_NAME(FOT),
    MAKE_NAME(ANM),
    MAKE_NAME(REL),
    MAKE_NAME(SUS),
    MAKE_NAME(RES),
    MAKE_NAME(RLC),
    MAKE_NAME(CCR),
    MAKE_NAME(RSC),
    MAKE_NAME(BLK),
    MAKE_NAME(UBL),
    MAKE_NAME(BLA),
    MAKE_NAME(UBA),
    MAKE_NAME(GRS),
    MAKE_NAME(CGB),
    MAKE_NAME(CGU),
    MAKE_NAME(CGA),
    MAKE_NAME(CGBA), // alias
    MAKE_NAME(CUA),
    MAKE_NAME(CMR),
    MAKE_NAME(CMC),
    MAKE_NAME(CMRJ),
    MAKE_NAME(FACR),
    MAKE_NAME(FAA),
    MAKE_NAME(FRJ),
    MAKE_NAME(FAD),
    MAKE_NAME(FAI),
    MAKE_NAME(LPA),
    MAKE_NAME(CSVR),
    MAKE_NAME(CSVS),
    MAKE_NAME(DRS),
    MAKE_NAME(PAM),
    MAKE_NAME(GRA),
    MAKE_NAME(CQM),
    MAKE_NAME(CQR),
    MAKE_NAME(CPR),
    MAKE_NAME(CPG),  // alias
    MAKE_NAME(USR),
    MAKE_NAME(UEC),
    MAKE_NAME(UCIC), // alias
    MAKE_NAME(CNF),
    MAKE_NAME(OLM),
    MAKE_NAME(CRG),
    MAKE_NAME(NRM),
    MAKE_NAME(FAC),
    MAKE_NAME(UPT),
    MAKE_NAME(UPA),
    MAKE_NAME(IDR),
    MAKE_NAME(IRS),
    MAKE_NAME(SGM),
    MAKE_NAME(LOP),
    MAKE_NAME(APM),
    MAKE_NAME(PRI),
    MAKE_NAME(SDN),
    MAKE_NAME(CRA),
    MAKE_NAME(CRM),
    MAKE_NAME(CVR),
    MAKE_NAME(CVT),
    MAKE_NAME(EXM),
    { 0, 0 }
};
#undef MAKE_NAME

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

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


/**
 * Helper functions used to transmit responses
 */

// Push down the protocol stack a RLC (Release Complete) message
// @param msg Optional received message to copy release parameters. Ignored if reason is valid
static int transmitRLC(SS7ISUP* isup, unsigned int cic, const SS7Label& label, bool recvLbl,
    const char* reason = 0, const char* diagnostic = 0, const char* location = 0)
{
    SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::RLC,cic);
    if (!TelEngine::null(reason)) {
	m->params().addParam("CauseIndicators",reason);
	m->params().addParam("CauseIndicators.location",location,false);
	m->params().addParam("CauseIndicators.diagnostic",diagnostic,false);
    }
    return isup->transmitMessage(m,label,recvLbl);
}

// Push down the protocol stack a CNF (Confusion) message
static int transmitCNF(SS7ISUP* isup, unsigned int cic, const SS7Label& label, bool recvLbl,
    const char* reason, const char* diagnostic = 0, const char* location = 0)
{
    SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CNF,cic);
    if (reason)
	m->params().addParam("CauseIndicators",reason);
    if (!location)
	location = isup->location();
    m->params().addParam("CauseIndicators.location",location,false);
    m->params().addParam("CauseIndicators.diagnostic",diagnostic,false);
    return isup->transmitMessage(m,label,recvLbl);
}


// Utility used to check for called number completion
static inline bool isCalledIncomplete(const NamedList& l, const String& p = "CalledPartyNumber")
{
    return !l[p].endsWith(".");
}

// Fill call release or cnf flags from message compatibility info
static void getMsgCompat(SS7MsgISUP* msg, bool& release, bool& cnf)
{
    if (!msg)
	return;
    String* msgCompat = msg->params().getParam(YSTRING("MessageCompatInformation"));
    if (msgCompat) {
	ObjList* l = msgCompat->split(',',false);
	// Use a while() to make sure the list is destroyed
	do {
	    release = (0 != l->find("release"));
	    if (release)
		break;
	    // Discard the message (no pass on). Check if CNF should be sent
	    if (l->find("discard")) {
		cnf = (0 != l->find("cnf"));
		break;
	    }
	    // Pass on set: we didn't passed on the message. Check REL/CNF
	    release = (0 != l->find("nopass-release"));
	    cnf = !release && l->find("cnf");
	} while(false);
	TelEngine::destruct(l);
    }
    else
	cnf = true;
}

static void setCallsTerminate(ObjList& lst, bool gracefully, const char* reason = 0,
    const char* diagnostic = 0, const char* location = 0)
{
    for (ObjList* o = lst.skipNull(); o; o = o->skipNext()) {
	SS7ISUPCall* call = static_cast<SS7ISUPCall*>(o->get());
	call->setTerminate(gracefully,reason,diagnostic,location);
    }
}


/**
 * SS7ISUPCall
 */
SS7ISUPCall::SS7ISUPCall(SS7ISUP* controller, SignallingCircuit* cic,
	const SS7PointCode& local, const SS7PointCode& remote, bool outgoing,
	int sls, const char* range, bool testCall)
    : SignallingCall(controller,outgoing),
    m_state(Null),
    m_testCall(testCall),
    m_circuit(cic),
    m_cicRange(range),
    m_terminate(false),
    m_gracefully(true),
    m_circuitChanged(false),
    m_circuitTesting(false),
    m_inbandAvailable(false),
    m_replaceCounter(3),
    m_iamMsg(0),
    m_sgmMsg(0),
    m_relMsg(0),
    m_sentSamDigits(0),
    m_relTimer(300000),                  // Q.764: T5  - 5..15 minutes
    m_iamTimer(ISUP_T7_DEFVAL),          // Setup, Testing: Q.764: T7  - 20..30 seconds
                                         // Releasing: Q.764: T1: 15..60 seconds
    m_sgmRecvTimer(ISUP_T34_DEFVAL),     // Q.764: T34 - 2..4 seconds
    m_contTimer(ISUP_T27_DEFVAL),        // Q.764: T27 - 4 minutes
    m_anmTimer(0)                        // Q.764 T9 Q.118: 1.5 - 3 minutes, not always used
{
    if (!(controller && m_circuit)) {
	Debug(isup(),DebugWarn,
	    "SS7ISUPCall(%u). No call controller or circuit. Terminate [%p]",
	    id(),this);
	setTerminate(true,m_circuit ? "temporary-failure" : "congestion");
	return;
    }
    isup()->setLabel(m_label,local,remote,sls);
    if (isup()->m_t7Interval)
	m_iamTimer.interval(isup()->m_t7Interval);
    if (isup()->m_t9Interval)
	m_anmTimer.interval(isup()->m_t9Interval);
    if (isup()->m_t27Interval)
	m_contTimer.interval(isup()->m_t27Interval);
    if (isup()->m_t34Interval)
	m_sgmRecvTimer.interval(isup()->m_t34Interval);
    m_replaceCounter = isup()->m_replaceCounter;
    if (isup()->debugAt(DebugAll)) {
	String tmp;
	tmp << m_label;
	Debug(isup(),DebugAll,"Call(%u) direction=%s routing-label=%s range=%s [%p]",
	    id(),(outgoing ? "outgoing" : "incoming"),tmp.c_str(),m_cicRange.safe(),this);
    }
}

SS7ISUPCall::~SS7ISUPCall()
{
    TelEngine::destruct(m_iamMsg);
    TelEngine::destruct(m_sgmMsg);
    const char* timeout = 0;
    if (m_relTimer.started())
	timeout = " (release timed out)";
    else if (m_contTimer.started())
	timeout = " (T27 timed out)";
    releaseComplete(true,0,0,0 != timeout);
    Debug(isup(),!timeout ? DebugAll : DebugNote,
	"Call(%u) destroyed with reason='%s'%s [%p]",
	id(),m_reason.safe(),TelEngine::c_safe(timeout),this);
    TelEngine::destruct(m_relMsg);
    if (controller()) {
	if (!timeout)
	    controller()->releaseCircuit(m_circuit);
	else
	    isup()->startCircuitReset(m_circuit,m_relTimer.started() ? "T5" : "T16");
    }
    else
	TelEngine::destruct(m_circuit);
}

// Stop waiting for a SGM (Segmentation) message when another message is
//  received by the controller
void SS7ISUPCall::stopWaitSegment(bool discard)
{
    Lock mylock(this);
    if (!m_sgmMsg)
	return;
    m_sgmRecvTimer.stop();
    if (discard)
	TelEngine::destruct(m_sgmMsg);
}

// Helper functions called in getEvent
inline static bool timeout(SS7ISUP* isup, SS7ISUPCall* call, SignallingTimer& timer,
	const Time& when, const char* req, bool stop = true)
{
    if (!timer.timeout(when.msec()))
	return false;
    if (stop)
	timer.stop();
    Debug(isup,DebugNote,"Call(%u). %s timed out [%p]",call->id(),req,call);
    return true;
}

// Get an event from this call
SignallingEvent* SS7ISUPCall::getEvent(const Time& when)
{
    Lock mylock(this,SignallingEngine::maxLockWait());
    if (m_lastEvent || m_state == Released || !mylock.locked())
	return 0;
    SS7MsgISUP* msg = 0;
    while (true) {
	if (m_terminate) {
	    if (m_state < Releasing && m_state > Null)
		if (m_gracefully)
		    m_lastEvent = release();
		else
		    m_lastEvent = releaseComplete(false,0);
	    else if (m_state == Null || m_state == Released) {
		m_gracefully = false;
		m_lastEvent = releaseComplete(false,0);
	    }
	    m_terminate = false;
	    break;
	}
	// Check if waiting for SGM
	// Stop if: timeout, the controller stopped the timer or received a message other then SGM
	if (m_sgmMsg) {
	    msg = static_cast<SS7MsgISUP*>(dequeue(false));
	    if (!msg && !m_sgmRecvTimer.timeout(when.msec()) && m_sgmRecvTimer.started())
		return 0;
	    msg = ((msg && msg->type() == SS7MsgISUP::SGM) ? static_cast<SS7MsgISUP*>(dequeue()) : 0);
	    processSegmented(msg,m_sgmRecvTimer.timeout(when.msec()));
	    break;
	}
	// Process received messages
	msg = static_cast<SS7MsgISUP*>(dequeue());
	if (msg && validMsgState(false,msg->type(),(msg->params().getParam(YSTRING("BackwardCallIndicators")) != 0)))
	    switch (msg->type()) {
		case SS7MsgISUP::IAM:
		case SS7MsgISUP::CCR:
		case SS7MsgISUP::COT:
		case SS7MsgISUP::ACM:
		case SS7MsgISUP::EXM:
		case SS7MsgISUP::CPR:
		case SS7MsgISUP::ANM:
		case SS7MsgISUP::CON:
		case SS7MsgISUP::CRG:
		    m_sgmMsg = msg;
		    {
			const char* sgmParam = "OptionalBackwardCallIndicators";
			if (msg->type() == SS7MsgISUP::IAM) {
			    copyParamIAM(msg);
			    setOverlapped(isCalledIncomplete(msg->params()));
			    sgmParam = "OptionalForwardCallIndicators";
			}
			// Check segmentation. Keep the message and start timer if segmented
			if (SignallingUtils::hasFlag(msg->params(),sgmParam,"segmentation")) {
			    m_sgmRecvTimer.start(when.msec());
			    return 0;
			}
		    }
		    msg = 0;
		    processSegmented(0,false);
		    break;
		case SS7MsgISUP::SAM:
		    setOverlapped(isCalledIncomplete(msg->params(),"SubsequentNumber"));
		    msg->params().addParam("tone",msg->params().getValue(YSTRING("SubsequentNumber")));
		    msg->params().addParam("dialing",String::boolText(true));
		    m_lastEvent = new SignallingEvent(SignallingEvent::Info,msg,this);
		    break;
		case SS7MsgISUP::RLC:
		    m_gracefully = false;
		    if (m_state < Releasing) {
			setReason(0,msg);
			m_location = isup()->location();
			m_lastEvent = release(0,msg);
		    }
		    else {
		        m_relTimer.stop();
			m_lastEvent = releaseComplete(false,msg);
		    }
		    break;
		case SS7MsgISUP::REL:
		    if (m_state < Releasing) {
		        m_relTimer.stop();
			m_lastEvent = releaseComplete(false,msg);
		    }
		    else
			transmitRLC(isup(),msg->cic(),m_label,false);
		    break;
		case SS7MsgISUP::SGM:
		    DDebug(isup(),DebugInfo,"Call(%u). Received late 'SGM' [%p]",id(),this);
		    break;
		case SS7MsgISUP::SUS:
		    m_lastEvent = new SignallingEvent(SignallingEvent::Suspend,msg,this);
		    break;
		case SS7MsgISUP::RES:
		    m_lastEvent = new SignallingEvent(SignallingEvent::Resume,msg,this);
		    break;
		case SS7MsgISUP::APM:
		    m_lastEvent = new SignallingEvent(SignallingEvent::Generic,msg,this);
		    break;
		default: ;
		    Debug(isup(),DebugStub,"Call(%u). Unhandled '%s' message in getEvent() [%p]",
			id(),msg->name(),this);
	    }
	break;
    }
    if (msg)
	msg->deref();
    // No events: check timeouts
    if (!m_lastEvent) {
	switch (m_state) {
	    case Testing:
	    case Setup:
		if (timeout(isup(),this,m_iamTimer,when,"IAM")) {
		    m_contTimer.stop();
		    if (m_circuitTesting) {
			if (m_iamMsg)
			    setReason("bearer-cap-not-available",0);
			else {
			    setTerminate(true,"bearer-cap-not-available");
			    break;
			}
		    }
		    else
			setReason("timeout",0);
		    m_lastEvent = release();
		    break;
		}
		if (timeout(isup(),this,m_contTimer,when,"T27",false)) {
		    m_gracefully = false;
		    m_lastEvent = releaseComplete(false,0,0,true);
		}
		break;
	    case Releasing:
		if (timeout(isup(),this,m_relTimer,when,"REL",false))
		    m_lastEvent = releaseComplete(false,0,"noresponse",true);
		else if (timeout(isup(),this,m_iamTimer,when,"T1")) {
		    m_iamTimer.stop();
		    m_iamTimer.start(when.msec());
		    transmitREL();
		}
		break;
	    default:
		if (outgoing() && m_anmTimer.started() && m_state >= Accepted &&
		    m_state < Answered && timeout(isup(),this,m_anmTimer,when,"T9")) {
		    setReason("noresponse",0,0,isup()->location());
		    m_lastEvent = release();
		}
	}
    }
    // Reset overlapped if our state is greater then Setup
    if (m_state > Setup)
	setOverlapped(false,false);
    // Check circuit event
    if (!m_lastEvent && m_circuit) {
	SignallingCircuitEvent* cicEvent = m_circuit->getEvent(when);
	if (cicEvent) {
	    if (isup())
		m_lastEvent = isup()->processCircuitEvent(cicEvent,this);
	    TelEngine::destruct(cicEvent);
	}
    }
    if (m_lastEvent)
	XDebug(isup(),DebugNote,"Call(%u). Raising event (%p,'%s') [%p]",
	    id(),m_lastEvent,m_lastEvent->name(),this);

    return m_lastEvent;
}

// Helper that copies all parameters starting with a capital letter
static void copyUpper(NamedList& dest, const NamedList& src)
{
    static const Regexp r("^[A-Z][A-Za-z0-9_.]\\+$");
    unsigned int n = src.length();
    for (unsigned int i = 0; i < n; i++) {
	const NamedString* p = src.getParam(i);
	if (!p || !r.matches(p->name()))
	    continue;
	dest.setParam(p->name(),*p);
    }
}

// Send an event to this call
bool SS7ISUPCall::sendEvent(SignallingEvent* event)
{
    Lock mylock(this);
    if (!event)
	return false;
    if (m_terminate || m_state == Released) {
	mylock.drop();
	delete event;
	return false;
    }
    bool result = false;
    switch (event->type()) {
	case SignallingEvent::NewCall:
	    if (validMsgState(true,SS7MsgISUP::IAM)) {
		if (!event->message()) {
		    DDebug(isup(),DebugNote,
			"Call(%u). No parameters for outgoing call [%p]",id(),this);
		    setTerminate(true,"temporary-failure");
		    break;
		}
		m_iamMsg = new SS7MsgISUP(SS7MsgISUP::IAM,id());
		copyParamIAM(m_iamMsg,true,event->message());
		// Update overlap
		String* called = m_iamMsg->params().getParam(YSTRING("CalledPartyNumber"));
		if (called && (called->length() > isup()->m_maxCalledDigits)) {
		    // Longer than maximum digits allowed - send remainder with SAM
		    m_samDigits = called->substr(isup()->m_maxCalledDigits);
		    *called = called->substr(0,isup()->m_maxCalledDigits);
		    setOverlapped(true);
		}
		else
		    setOverlapped(isCalledIncomplete(m_iamMsg->params()));
		result = transmitIAM();
	    }
	    break;
	case SignallingEvent::Progress:
	case SignallingEvent::Ringing:
	    if (validMsgState(true,SS7MsgISUP::CPR)) {
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CPR,id());
		m->params().addParam("EventInformation",
		    event->type() == SignallingEvent::Ringing ? "ringing": "progress");
		bool inband = m_inbandAvailable;
		if (event->message()) {
		    copyUpper(m->params(),event->message()->params());
		    m_inbandAvailable = m_inbandAvailable ||
			event->message()->params().getBoolValue(YSTRING("earlymedia"));
		    inband = event->message()->params().getBoolValue(YSTRING("send-inband"),m_inbandAvailable);
		}
		if (inband && !outgoing())
		    SignallingUtils::appendFlag(m->params(),"OptionalBackwardCallIndicators","inband");
		m_state = Ringing;
		mylock.drop();
		result = transmitMessage(m);
	    }
	    break;
	case SignallingEvent::Accept:
	    if (validMsgState(true,SS7MsgISUP::ACM)) {
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::ACM,id());
		bool inband = m_inbandAvailable;
		if (event->message()) {
		    copyUpper(m->params(),event->message()->params());
		    m_inbandAvailable = m_inbandAvailable ||
			event->message()->params().getBoolValue(YSTRING("earlymedia"));
		    inband = event->message()->params().getBoolValue(YSTRING("send-inband"),m_inbandAvailable);
		}
		if (inband && !outgoing())
		    SignallingUtils::appendFlag(m->params(),"OptionalBackwardCallIndicators","inband");
		m_state = Accepted;
		mylock.drop();
		result = transmitMessage(m);
	    }
	    break;
	case SignallingEvent::Answer:
	    if (validMsgState(true,SS7MsgISUP::ANM)) {
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::ANM,id());
		if (event->message())
		    copyUpper(m->params(),event->message()->params());
		m_state = Answered;
		mylock.drop();
		result = transmitMessage(m);
	    }
	    break;
	case SignallingEvent::Release:
	    if (validMsgState(true,SS7MsgISUP::REL)) {
		release(event);
		result = true;
	    }
	    break;
	case SignallingEvent::Generic:
	    if (event->message()) {
		const String& oper = event->message()->params()[YSTRING("operation")];
		if (oper == "charge") {
		    if (!validMsgState(true,SS7MsgISUP::CRG))
			break;
		    SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CRG,id());
		    copyUpper(m->params(),event->message()->params());
		    mylock.drop();
		    result = transmitMessage(m);
		    break;
		}
		if (oper != "transport")
		    break;
		if (!validMsgState(true,SS7MsgISUP::APM))
		    break;
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::APM,id());
		copyUpper(m->params(),event->message()->params());
		mylock.drop();
		result = transmitMessage(m);
	    }
	    break;
	case SignallingEvent::Suspend:
	    if (event->message()) {
		if (!validMsgState(true,SS7MsgISUP::SUS))
		    break;
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::SUS,id());
		copyUpper(m->params(),event->message()->params());
		mylock.drop();
		result = transmitMessage(m);
	    }
	    break;
	case SignallingEvent::Resume:
	    if (event->message()) {
		if (!validMsgState(true,SS7MsgISUP::RES))
		    break;
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::RES,id());
		copyUpper(m->params(),event->message()->params());
		mylock.drop();
		result = transmitMessage(m);
	    }
	    break;
	case SignallingEvent::Info:
	    if (validMsgState(true,SS7MsgISUP::SAM)) {
		mylock.drop();
		transmitSAM(event->message()->params().getValue(YSTRING("tone")));
		result = true;
		break;
	    }
	//case SignallingEvent::Message:
	//case SignallingEvent::Transfer:
	case SignallingEvent::Charge:
	    if (event->message()) {
		if (!validMsgState(true,SS7MsgISUP::CRG))
		    break;
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CRG,id());
		copyUpper(m->params(),event->message()->params());
		mylock.drop();
		result = transmitMessage(m);
	    }
	    break;
	default:
	    DDebug(isup(),DebugStub,
		"Call(%u). sendEvent not implemented for '%s' [%p]",
		id(),event->name(),this);
    }
    // Reset overlapped if our state is greater then Setup
    if (m_state > Setup)
	setOverlapped(false,false);
    XDebug(isup(),DebugAll,"Call(%u). Event (%p,'%s') sent. Result: %s [%p]",
	id(),event,event->name(),String::boolText(result),this);
    mylock.drop();
    delete event;
    return result;
}

// Get reserved circuit or this object
void* SS7ISUPCall::getObject(const String& name) const
{
    if (name == YSTRING("SignallingCircuit"))
	return m_circuit;
    if (name == YSTRING("SS7ISUPCall"))
	return (void*)this;
    return SignallingCall::getObject(name);
}

// Check if the circuit can be replaced
// Returns true unless the counter is already zero
bool SS7ISUPCall::canReplaceCircuit()
{
    if (m_replaceCounter <= 0)
	return false;
    m_replaceCounter--;
    return true;
}

// Replace the circuit reserved for this call. Release the already reserved circuit.
// Retransmit the initial IAM request on success.
// On failure set the termination flag and release the new circuit if valid
bool SS7ISUPCall::replaceCircuit(SignallingCircuit* circuit, SS7MsgISUP* msg)
{
    Lock mylock(this);
    clearQueue();
    if (m_state > Setup || !circuit || !outgoing()) {
        Debug(isup(),DebugNote,"Call(%u). Failed to replace circuit [%p]",id(),this);
	m_iamTimer.stop();
	if (controller()) {
	    controller()->releaseCircuit(m_circuit);
	    controller()->releaseCircuit(circuit);
	}
	setTerminate(false,"congestion");
	TelEngine::destruct(msg);
	return false;
    }
    transmitMessage(msg);
    unsigned int oldId = id();
    if (controller())
	controller()->releaseCircuit(m_circuit);
    m_circuit = circuit;
    Debug(isup(),DebugNote,"Call(%u). Circuit replaced by %u [%p]",oldId,id(),this);
    m_circuitChanged = true;
    return transmitIAM();
}

// Stop timers. Send a RLC (Release Complete) message if it should terminate gracefully
// Decrease the object's refence count and generate a Release event if not final
// @param final True if called from destructor
// @param msg Received message with parameters if any
// @param reason Optional release reason
SignallingEvent* SS7ISUPCall::releaseComplete(bool final, SS7MsgISUP* msg, const char* reason,
    bool timeout)
{
    if (timeout)
	m_gracefully = false;
    m_iamTimer.stop();
    setReason(reason,msg);
    stopWaitSegment(true);
    if (m_state == Released)
	return 0;
    if (isup() && m_gracefully) {
	int sls = transmitRLC(isup(),id(),m_label,false);
	if (sls != -1 && m_label.sls() == 255)
	    m_label.setSls(sls);
    }
    m_state = Released;
    if (final)
	return 0;
    // Return event and decrease reference counter
    bool create = (msg == 0);
    if (create)
	msg = new SS7MsgISUP(SS7MsgISUP::RLC,id());
    if (m_circuit)
	m_circuit->disconnect();
    msg->params().setParam("reason",m_reason);
    SignallingEvent* event = new SignallingEvent(SignallingEvent::Release,msg,this);
    // deref() msg if created here. If received, it will be deref()'d in getEvent()
    if (create)
	msg->deref();
    deref();
    DDebug(isup(),DebugInfo,"Call(%u). Released with reason '%s' [%p]",
	id(),m_reason.safe(),this);
    return event;
}

// Helper function to copy parameters
inline void param(NamedList& dest, NamedList& src, const String& destParam,
	const String& srcParam, const char* defVal)
{
    const char* val = src.getValue(srcParam,src.getValue(destParam,defVal));
    if ((val != defVal) || !dest.getParam(destParam))
	dest.setParam(destParam,val);
}

// Initialize/set IAM message parameters
// @param msg Valid ISUP message
// @param outgoing Message direction: true for outgoing
// @param sigMsg Valid signalling message with parameters if outgoing
bool SS7ISUPCall::copyParamIAM(SS7MsgISUP* msg, bool outgoing, SignallingMessage* sigMsg)
{
    NamedList& dest = msg->params();
    if (outgoing) {
	NamedList& src = sigMsg->params();
	copyUpper(dest,src);
	param(dest,src,"CalledPartyNumber","called","");
	param(dest,src,"CalledPartyNumber.inn","inn",String::boolText(isup()->m_inn));
	param(dest,src,"CalledPartyNumber.nature","callednumtype",isup()->m_numType);
	param(dest,src,"CalledPartyNumber.plan","callednumplan",isup()->m_numPlan);
	param(dest,src,"CallingPartyCategory","callercategory",isup()->m_callerCat);
	param(dest,src,"CallingPartyNumber","caller","");
	param(dest,src,"CallingPartyNumber.nature","callernumtype",isup()->m_numType);
	param(dest,src,"CallingPartyNumber.plan","callernumplan",isup()->m_numPlan);
	param(dest,src,"CallingPartyNumber.restrict","callerpres",isup()->m_numPresentation);
	param(dest,src,"CallingPartyNumber.screened","callerscreening",isup()->m_numScreening);
	param(dest,src,"CallingPartyNumber.complete","complete","true");
	m_format = src.getValue(YSTRING("format"),isup()->format());
	dest.setParam("UserServiceInformation",m_format);
	return true;
    }
    // Incoming call
    m_format = dest.getValue(YSTRING("UserServiceInformation"),isup()->format());
    dest.setParam("format",m_format);
    dest.setParam("caller",dest.getValue(YSTRING("CallingPartyNumber")));
    //dest.setParam("callername",dest.getValue(""));
    dest.setParam("callernumtype",dest.getValue(YSTRING("CallingPartyNumber.nature")));
    dest.setParam("callernumplan",dest.getValue(YSTRING("CallingPartyNumber.plan")));
    dest.setParam("callerpres",dest.getValue(YSTRING("CallingPartyNumber.restrict")));
    dest.setParam("callerscreening",dest.getValue(YSTRING("CallingPartyNumber.screened")));
    dest.setParam("called",dest.getValue(YSTRING("CalledPartyNumber")));
    dest.setParam("callednumtype",dest.getValue(YSTRING("CalledPartyNumber.nature")));
    dest.setParam("callednumplan",dest.getValue(YSTRING("CalledPartyNumber.plan")));
    dest.setParam("inn",dest.getValue(YSTRING("CalledPartyNumber.inn")));
    if (m_label.sls() != 0xff)
	dest.setParam("sls",String((unsigned int)m_label.sls()));
    return true;
}

// If already releasing, set termination flag. Otherwise, send REL (Release) message
// @param event Event with the parameters. 0 if release is started on unspecified interworking
SignallingEvent* SS7ISUPCall::release(SignallingEvent* event, SS7MsgISUP* msg)
{
    m_iamTimer.stop();
    if (event)
	setReason(0,event->message());
    else
	setReason("interworking",0);
    stopWaitSegment(true);
    XDebug(isup(),DebugAll,"Call(%u). Releasing call with reason '%s' [%p]",
	id(),m_reason.safe(),this);
    if (!isup() || m_state >= Releasing) {
	m_terminate = true;
	return 0;
    }
    m_iamTimer.interval(isup() ? isup()->m_t1Interval : 1);
    m_relTimer.interval(isup() ? isup()->m_t5Interval : 1);
    m_iamTimer.start();
    m_relTimer.start();
    m_state = Releasing;
    transmitREL((event && event->message()) ? &(event->message()->params()) : 0);
    if (event)
	return 0;
    bool create = (msg == 0);
    if (create)
	msg = new SS7MsgISUP(SS7MsgISUP::REL,id());
    msg->params().setParam("reason",m_reason);
    SignallingEvent* ev = new SignallingEvent(SignallingEvent::Release,msg,this);
    // deref() msg if created here. If received, it will be deref()'d in getEvent()
    if (create)
        TelEngine::destruct(msg);
    return ev;
}

// Set termination reason from received text or message
void SS7ISUPCall::setReason(const char* reason, SignallingMessage* msg,
    const char* diagnostic, const char* location)
{
    if (!m_reason.null())
	return;
    if (reason) {
	m_reason = reason;
	m_diagnostic = diagnostic;
	m_location = location;
    }
    else if (msg) {
	m_reason = msg->params().getValue(YSTRING("CauseIndicators"),msg->params().getValue(YSTRING("reason")));
	m_diagnostic = msg->params().getValue(YSTRING("CauseIndicators.diagnostic"),diagnostic);
	m_location = msg->params().getValue(YSTRING("CauseIndicators.location"),location);
    }
}

// Accept send/receive messages in current state based on call direction
bool SS7ISUPCall::validMsgState(bool send, SS7MsgISUP::Type type, bool hasBkwCallInd)
{
    bool handled = true;
    switch (type) {
	case SS7MsgISUP::CCR:    // Continuity check
	    if (m_state == Testing && send == outgoing())
		return true;
	    // fall through
	case SS7MsgISUP::IAM:    // Initial address
	    if (m_state != Null || send != outgoing())
		break;
	    return true;
	case SS7MsgISUP::COT:    // Continuity
	    if (m_state != Testing || send != outgoing())
		break;
	    return true;
	case SS7MsgISUP::ACM:    // Address complete
	case SS7MsgISUP::EXM:    // Exit Message (ANSI)
	    if (m_state != Setup || send == outgoing())
		break;
	    return true;
	case SS7MsgISUP::CPR:    // Call progress
	    if (m_state < (hasBkwCallInd ? Setup : Accepted) || m_state >= Releasing)
		break;
	    return true;
	case SS7MsgISUP::CON:    // Connect
	    // CON can be sent/received on not accepted calls
	    if (m_state == Setup && send != outgoing())
		return true;
	case SS7MsgISUP::ANM:    // Answer
	    if (m_state < (hasBkwCallInd ? Setup : Accepted) || m_state >= Answered || send == outgoing())
		break;
	    return true;
	case SS7MsgISUP::SAM:    // Subsequent address
	    if (m_state != Setup || !m_overlap || send != outgoing())
		break;
	    return true;
	case SS7MsgISUP::REL:    // Release
	    if (send && m_state >= Releasing)
		break;
	    // fall through
	case SS7MsgISUP::RLC:    // Release complete
	case SS7MsgISUP::CRG:    // Charging
	    if (m_state == Null || m_state == Released)
		break;
	    return true;
	case SS7MsgISUP::SUS:    // Suspend
	case SS7MsgISUP::RES:    // Resume
	    if (m_state != Answered)
		break;
	    return true;
	case SS7MsgISUP::SGM:    // Segmentation
	case SS7MsgISUP::APM:    // Application Transport
	    return true;
	default:
	    handled = false;
    }
    Debug(isup(),handled?DebugNote:DebugStub,
	"Call(%u). Can't %s %smessage '%s' in state %u [%p]",
	id(),send?"send":"accept",handled?"":"unhandled ",
	SS7MsgISUP::lookup(type,""),m_state,this);
    return false;
}

// Connect or test the reserved circuit. Return false if it fails.
// Return true if this call is a signalling only one
bool SS7ISUPCall::connectCircuit(const char* special)
{
    bool ok = signalOnly();
    if (TelEngine::null(special))
	special = 0;
    if (m_circuit && !ok) {
	u_int64_t t = Time::msecNow();
	if (special) {
	    m_circuit->updateFormat(m_format,0);
	    ok = m_circuit->setParam("special_mode",special) &&
		m_circuit->status(SignallingCircuit::Special);
	}
	else
	    ok = m_circuit->connected() || m_circuit->connect(m_format);
	t = Time::msecNow() - t;
	if (t > 100) {
	    int level = DebugInfo;
	    if (t > 300)
		level = DebugMild;
	    else if (t > 200)
		level = DebugNote;
	    Debug(isup(),level,"Call(%u). Spent %u ms connecting circuit [%p]",
		id(),(unsigned int)t,this);
	}
#ifdef DEBUG
	else
	    Debug(isup(),DebugAll,"Call(%u). Spent %u ms connecting circuit [%p]",
		id(),(unsigned int)t,this);
#endif
    }
    if (!ok)
	Debug(isup(),DebugMild,"Call(%u). Circuit %s failed (format='%s')%s [%p]",
	    id(),(special ? special : "connect"),
	    m_format.safe(),(m_circuit ? "" : ". No circuit"),this);

    if (m_sgmMsg) {
	if (m_circuitChanged) {
	    m_sgmMsg->params().setParam("circuit-change","true");
	    m_circuitChanged = false;
	}
	m_sgmMsg->params().setParam("format",m_format);
    }
    return ok;
}

// Transmit the IAM message. Start IAM timer if not started
bool SS7ISUPCall::transmitIAM()
{
    if (!m_iamTimer.started())
	m_iamTimer.start();
    if (!m_iamMsg)
	return false;
    if (needsTesting(m_iamMsg)) {
	if (m_circuitTesting && !(isup() && isup()->m_continuity)) {
	    Debug(isup(),DebugWarn,"Call(%u). Continuity check requested but not configured [%p]",
		id(),this);
	    return false;
	}
	m_state = Testing;
	if (m_circuitTesting && !connectCircuit("test:" + isup()->m_continuity))
	    return false;
	Debug(isup(),DebugNote,"Call(%u). %s continuity check [%p]",
	    id(),(m_circuitTesting ? "Executing" : "Forwarding"),this);
    }
    else
	m_state = Setup;
    m_iamMsg->m_cic = id();
    m_iamMsg->ref();
    // Reset SAM digits: this might be a re-send
    m_sentSamDigits = 0;
    bool ok = transmitMessage(m_iamMsg);
    if (ok && m_overlap)
	transmitSAM();
    return ok;
}

// Transmit SAM digits
bool SS7ISUPCall::transmitSAM(const char* extra)
{
    if (!m_overlap)
	return false;
    m_samDigits << extra;
    while (m_samDigits.length() > m_sentSamDigits) {
	unsigned int send = m_samDigits.length() - m_sentSamDigits;
	if (send > isup()->m_maxCalledDigits)
	    send = isup()->m_maxCalledDigits;
	SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::SAM,id());
	String number = m_samDigits.substr(m_sentSamDigits,send);
	m->params().addParam("SubsequentNumber",number);
	bool complete = !isCalledIncomplete(m->params(),"SubsequentNumber");
	bool ok = transmitMessage(m);
	if (ok) {
	    m_sentSamDigits += send;
	    if (complete) {
		if (m_samDigits.length() > m_sentSamDigits)
		    Debug(isup(),DebugNote,
			"Call(%u). Completed number sending remaining='%s' [%p]",
			id(),m_samDigits.substr(m_sentSamDigits).c_str(),this);
		// Reset overlap sending
		setOverlapped(false);
		break;
	    }
	}
	else {
	    Debug(isup(),DebugNote,"Call(%u). Failed to send SAM with '%s' [%p]",
		id(),number.c_str(),this);
	    complete = false;
	    break;
	}
    }
    return true;
}

// (Re)transmit REL. Create and populate the message if needed
// Remember sls
bool SS7ISUPCall::transmitREL(const NamedList* params)
{
    if (!isup())
	return false;
    if (!m_relMsg) {
	m_relMsg = new SS7MsgISUP(SS7MsgISUP::REL,id());
	if (m_reason)
	    m_relMsg->params().addParam("CauseIndicators",m_reason);
	m_relMsg->params().addParam("CauseIndicators.diagnostic",m_diagnostic,false);
	m_relMsg->params().addParam("CauseIndicators.location",m_location,false);
	if (params)
	    copyUpper(m_relMsg->params(),*params);
    }
    // transmitMessage will dereference message so make sure we preserve it
    m_relMsg->ref();
    int sls = isup()->transmitMessage(m_relMsg,m_label,false);
    if (sls != -1 && m_label.sls() == 255)
	m_label.setSls(sls);
    return sls != -1;
}

bool SS7ISUPCall::needsTesting(const SS7MsgISUP* msg)
{
    if ((m_state >= Testing) || !msg)
	return false;
    const String* naci = msg->params().getParam(YSTRING("NatureOfConnectionIndicators"));
    if (!naci)
	return false;
    ObjList* list = naci->split(',',false);
    m_circuitTesting = (0 != list->find("cont-check-this"));
    bool checkIt = m_circuitTesting || (0 != list->find("cont-check-prev"));
    TelEngine::destruct(list);
    return checkIt;
}

// Stop waiting for a SGM (Segmentation) message. Copy parameters to
// the pending segmented message if sgm is valid.
// Change call state and set m_lastEvent
SignallingEvent* SS7ISUPCall::processSegmented(SS7MsgISUP* sgm, bool timeout)
{
    if (sgm)
	if (sgm->type() == SS7MsgISUP::SGM) {
	    // Copy parameters from SGM as defined in Q.763 - Table 49
	    // (Segmentation message) and Q.764 - 2.1.12 (re-assembly)
	    #define COPY_PARAM(param) \
		m_sgmMsg->params().copyParam(sgm->params(),param); \
		m_sgmMsg->params().copyParam(sgm->params(),param,'.');
	    COPY_PARAM(YSTRING("AccessTranport"))
	    COPY_PARAM(YSTRING("UserToUserInformation"))
	    COPY_PARAM(YSTRING("MessageCompatInformation"))
	    COPY_PARAM(YSTRING("GenericDigits"))
	    COPY_PARAM(YSTRING("GenericNotification"))
	    COPY_PARAM(YSTRING("GenericNumber"))
	    #undef COPY_PARAM
	}
	else
	    Debug(isup(),DebugStub,"Call(%u). stopWaitSegment() called with non-SGM message !!! [%p]",
		id(),this);
    else if (timeout)
	Debug(isup(),DebugMild,"Call(%u). Segment waiting message '%s' timed out [%p]",
	    id(),m_sgmMsg->name(),this);
    m_sgmRecvTimer.stop();
    // Raise event, connect the reserved circuit, change call state
    m_iamTimer.stop();
    switch (m_sgmMsg->type()) {
	case SS7MsgISUP::COT:
	    {
		const String* cont = m_sgmMsg->params().getParam(YSTRING("ContinuityIndicators"));
		bool ok = cont && (*cont == YSTRING("success"));
		if (ok) {
		    Debug(isup(),DebugNote,"Call(%u). Continuity check succeeded [%p]",
			id(),this);
		    m_circuitTesting = false;
		}
		else {
		    Debug(isup(),DebugWarn,"Call(%u). Continuity check failed [%p]",
			id(),this);
		    m_contTimer.start();
		    break;
		}
		if (!(ok && m_iamMsg)) {
		    m_lastEvent = new SignallingEvent(SignallingEvent::Info,m_sgmMsg,this);
		    break;
		}
	    }
	    TelEngine::destruct(m_sgmMsg);
	    m_sgmMsg = m_iamMsg;
	    m_iamMsg = 0;
	    // intentionally fall through
	case SS7MsgISUP::IAM:
	    if (needsTesting(m_sgmMsg)) {
		m_state = Testing;
		if (m_circuitTesting && !(isup() && isup()->m_continuity)) {
		    Debug(isup(),DebugWarn,"Call(%u). Continuity check requested but not configured [%p]",
			id(),this);
		    setTerminate(true,"service-not-implemented",0,isup()->location());
		    break;
		}
		if (m_circuitTesting && !connectCircuit(isup()->m_continuity)) {
		    setTerminate(true,"bearer-cap-not-available",0,isup()->location());
		    break;
		}
		Debug(isup(),DebugNote,"Call(%u). Waiting for continuity check [%p]",
		    id(),this);
		// Save message for later
		m_iamMsg = m_sgmMsg;
		m_sgmMsg = 0;
		return 0;
	    }
	    m_state = Setup;
	    if (!connectCircuit() && isup() &&
		(isup()->mediaRequired() >= SignallingCallControl::MediaAlways)) {
		setTerminate(true,"bearer-cap-not-available",0,isup()->location());
		break;
	    }
	    m_sgmMsg->params().setParam("overlapped",String::boolText(m_overlap));
	    m_lastEvent = new SignallingEvent(SignallingEvent::NewCall,m_sgmMsg,this);
	    break;
	case SS7MsgISUP::CCR:
	    if (m_state < Testing) {
		m_state = Testing;
		if (!(isup() && isup()->m_continuity)) {
		    Debug(isup(),DebugWarn,"Call(%u). Continuity check requested but not configured [%p]",
			id(),this);
		    setTerminate(true,"service-not-implemented",0,isup()->location());
		    break;
		}
		m_circuitTesting = true;
		if (!connectCircuit(isup()->m_continuity)) {
		    setTerminate(true,"bearer-cap-not-available",0,isup()->location());
		    break;
		}
		Debug(isup(),DebugNote,"Call(%u). Continuity test only [%p]",
		    id(),this);
	    }
	    else if (!m_circuitTesting) {
		setTerminate(true,"wrong-state-message",0,isup()->location());
		break;
	    }
	    m_contTimer.stop();
	    m_iamTimer.start();
	    if (isup()->m_confirmCCR)
		transmitMessage(new SS7MsgISUP(SS7MsgISUP::LPA,id()));
	    break;
	case SS7MsgISUP::ACM:
	    m_state = Accepted;
	    if (!connectCircuit() && isup() &&
		(isup()->mediaRequired() >= SignallingCallControl::MediaAlways)) {
		setReason("bearer-cap-not-available",0,0,isup()->location());
		m_lastEvent = release();
		break;
	    }
	    m_lastEvent = 0;
	    m_inbandAvailable = m_inbandAvailable ||
		SignallingUtils::hasFlag(m_sgmMsg->params(),"OptionalBackwardCallIndicators","inband");
	    if (isup() && isup()->m_earlyAcm) {
		// If the called party is known free report ringing
		// If it may become free or there is inband audio report progress
		bool ring = SignallingUtils::hasFlag(m_sgmMsg->params(),"BackwardCallIndicators","called-free");
		if (m_inbandAvailable || ring || SignallingUtils::hasFlag(m_sgmMsg->params(),"BackwardCallIndicators","called-conn")) {
		    m_sgmMsg->params().setParam("earlymedia",String::boolText(m_inbandAvailable));
		    m_lastEvent = new SignallingEvent(ring ? SignallingEvent::Ringing : SignallingEvent::Progress,m_sgmMsg,this);
		}
	    }
	    if (!m_lastEvent) {
		m_sgmMsg->params().setParam("earlymedia",String::boolText(m_inbandAvailable));
		m_lastEvent = new SignallingEvent(SignallingEvent::Accept,m_sgmMsg,this);
	    }
	    // intentionally fall through
	case SS7MsgISUP::EXM:
	    // Start T9 timer
	    if (m_anmTimer.interval() && !m_anmTimer.started())
		m_anmTimer.start();
	    break;
	case SS7MsgISUP::CPR:
	    m_state = Ringing;
	    if (!connectCircuit() && isup() &&
		(isup()->mediaRequired() >= SignallingCallControl::MediaRinging)) {
		setTerminate(true,"bearer-cap-not-available",0,isup()->location());
		break;
	    }
	    m_inbandAvailable = m_inbandAvailable ||
		SignallingUtils::hasFlag(m_sgmMsg->params(),"OptionalBackwardCallIndicators","inband") ||
		SignallingUtils::hasFlag(m_sgmMsg->params(),"EventInformation","inband");
	    m_sgmMsg->params().setParam("earlymedia",String::boolText(m_inbandAvailable));
	    m_lastEvent = new SignallingEvent(
		SignallingUtils::hasFlag(m_sgmMsg->params(),"EventInformation","ringing")
	        ? SignallingEvent::Ringing : SignallingEvent::Progress,
	        m_sgmMsg,this);
	    break;
	case SS7MsgISUP::ANM:
	case SS7MsgISUP::CON:
	    m_state = Answered;
	    m_anmTimer.stop();
	    if (!connectCircuit() && isup() &&
		(isup()->mediaRequired() >= SignallingCallControl::MediaAnswered)) {
		setTerminate(true,"bearer-cap-not-available",0,isup()->location());
		break;
	    }
	    m_lastEvent = new SignallingEvent(SignallingEvent::Answer,m_sgmMsg,this);
	    break;
	case SS7MsgISUP::CRG:
	    m_lastEvent = new SignallingEvent(SignallingEvent::Charge,m_sgmMsg,this);
	    break;
	default:
	    Debug(isup(),DebugStub,"Call(%u). Segment waiting message is '%s' [%p]",
		id(),m_sgmMsg->name(),this);
    }
    TelEngine::destruct(m_sgmMsg);
    return m_lastEvent;
}

// Transmit message. Set routing label's link if not already set
bool SS7ISUPCall::transmitMessage(SS7MsgISUP* msg)
{
    if (!msg || !isup()) {
	TelEngine::destruct(msg);
	return false;
    }
    DDebug(isup(),DebugAll,"Call(%u). Transmitting messsage (%s,%p) [%p]",
	id(),msg->name(),msg,this);
    int sls = isup()->transmitMessage(msg,m_label,false);
    if (sls == -1)
	return false;
    if (m_label.sls() == 255)
	m_label.setSls(sls);
    return true;
}

SS7ISUP* SS7ISUPCall::isup() const
{
    return static_cast<SS7ISUP*>(SignallingCall::controller());
}

// Set overlapped flag. Output a debug message
void SS7ISUPCall::setOverlapped(bool on, bool numberComplete)
{
    if (m_overlap == on)
	return;
    m_overlap = on;
    const char* reason = on ? "" : (numberComplete ? " (number complete)" : " (state changed)");
    Debug(isup(),DebugAll,"Call(%u). Overlapped dialing is %s%s [%p]",
	id(),String::boolText(on),reason,this);
}


/**
 * SS7ISUP
 */
SS7ISUP::SS7ISUP(const NamedList& params, unsigned char sio)
    : SignallingComponent(params.safe("SS7ISUP"),&params,"ss7-isup"),
      SignallingCallControl(params,"isup."),
      SS7Layer4(sio,&params),
      m_cicLen(2),
      m_type(SS7PointCode::Other),
      m_defPoint(0),
      m_remotePoint(0),
      m_sls(255),
      m_earlyAcm(true),
      m_inn(false),
      m_defaultSls(SlsLatest),
      m_maxCalledDigits(16),
      m_confirmCCR(true),
      m_dropOnUnknown(true),
      m_ignoreGRSSingle(false),
      m_ignoreCGBSingle(false),
      m_ignoreCGUSingle(false),
      m_duplicateCGB(false),
      m_ignoreUnkDigits(true),
      m_l3LinkUp(false),
      m_chargeProcessType(Confusion),
      m_t1Interval(15000),               // Q.764 T1 15..60 seconds
      m_t5Interval(300000),              // Q.764 T5 5..15 minutes
      m_t7Interval(ISUP_T7_DEFVAL),      // Q.764 T7 20..30 seconds
      m_t9Interval(0),                   // Q.764 T9 Q.118 1.5 - 3 minutes, not always used
      m_t12Interval(20000),              // Q.764 T12 (BLK) 15..60 seconds
      m_t13Interval(300000),             // Q.764 T13 (BLK global) 5..15 minutes
      m_t14Interval(20000),              // Q.764 T14 (UBL) 15..60 seconds
      m_t15Interval(300000),             // Q.764 T15 (UBL global) 5..15 minutes
      m_t16Interval(20000),              // Q.764 T16 (RSC) 15..60 seconds
      m_t17Interval(300000),             // Q.764 T17 5..15 minutes
      m_t18Interval(20000),              // Q.764 T18 (CGB) 15..60 seconds
      m_t19Interval(300000),             // Q.764 T19 (CGB global) 5..15 minutes
      m_t20Interval(20000),              // Q.764 T20 (CGU) 15..60 seconds
      m_t21Interval(300000),             // Q.764 T21 (CGU global) 5..15 minutes
      m_t27Interval(ISUP_T27_DEFVAL),    // Q.764 T27 4 minutes
      m_t34Interval(ISUP_T34_DEFVAL),    // Q.764 T34 2..4 seconds
      m_uptTimer(0),
      m_userPartAvail(true),
      m_uptMessage(SS7MsgISUP::UPT),
      m_uptCicCode(0),
      m_cicWarnLevel(DebugMild),
      m_replaceCounter(3),
      m_rscTimer(0),
      m_rscCic(0),
      m_rscSpeedup(0),
      m_lockTimer(2000),
      m_lockGroup(true),
      m_printMsg(false),
      m_extendedDebug(false)
{
#ifdef DEBUG
    if (debugAt(DebugAll)) {
	String tmp;
	params.dump(tmp,"\r\n  ",'\'',true);
	Debug(this,DebugAll,"SS7ISUP::SS7ISUP(%p) [%p]%s",
	    &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,DebugWarn,"Invalid point code type '%s'",c_safe(stype));
	return;
    }
    if (m_type == SS7PointCode::ITU)
	m_defaultSls = SlsCircuit;

    m_format = params.getValue(YSTRING("format"));
    if (-1 == lookup(m_format,SignallingUtils::dict(1,0),-1))
	switch (m_type) {
	    case SS7PointCode::ANSI:
	    case SS7PointCode::ANSI8:
	    case SS7PointCode::Japan:
	    case SS7PointCode::Japan5:
		m_format = "mulaw";
		break;
	    default:
		m_format = "alaw";
	}

    const char* rpc = params.getValue(YSTRING("remotepointcode"));
    m_remotePoint = new SS7PointCode(0,0,0);
    if (!(m_remotePoint->assign(rpc,m_type) && m_remotePoint->pack(m_type))) {
	Debug(this,DebugMild,"Invalid remotepointcode='%s'",rpc);
	TelEngine::destruct(m_remotePoint);
    }

    m_lockGroup = params.getBoolValue(YSTRING("lockgroup"),m_lockGroup);
    m_earlyAcm = params.getBoolValue(YSTRING("earlyacm"),m_earlyAcm);
    m_inn = params.getBoolValue(YSTRING("inn"),m_inn);
    m_numPlan = params.getValue(YSTRING("numplan"));
    if (-1 == lookup(m_numPlan,s_dict_numPlan,-1))
	m_numPlan = "unknown";
    m_numType = params.getValue(YSTRING("numtype"));
    if (-1 == lookup(m_numType,s_dict_nai,-1))
	m_numType = "unknown";
    m_numPresentation = params.getValue(YSTRING("presentation"));
    if (-1 == lookup(m_numPresentation,s_dict_presentation,-1))
	m_numPresentation = "allowed";
    m_numScreening = params.getValue(YSTRING("screening"));
    if (-1 == lookup(m_numScreening,s_dict_screening,-1))
	m_numScreening = "user-provided";
    m_callerCat = params.getValue(YSTRING("callercategory"));
    if (-1 == lookup(m_callerCat,s_dict_callerCat,-1))
	m_callerCat = "ordinary";

    m_rscTimer.interval(params,"channelsync",60,300,true,true);
    m_rscInterval = m_rscTimer.interval();

    // Remote user part test
    m_uptTimer.interval(params,"userparttest",10,60,true,true);
    if (m_uptTimer.interval())
	m_userPartAvail = false;
    else
	m_lockTimer.start();

    // Timers
    m_t7Interval = SignallingTimer::getInterval(params,"t7",ISUP_T7_MINVAL,ISUP_T7_DEFVAL,ISUP_T7_MAXVAL,false);
    m_t9Interval = SignallingTimer::getInterval(params,"t9",ISUP_T9_MINVAL,ISUP_T9_DEFVAL,ISUP_T9_MAXVAL,true);
    m_t27Interval = SignallingTimer::getInterval(params,"t27",ISUP_T27_MINVAL,ISUP_T27_DEFVAL,ISUP_T27_MAXVAL,false);
    m_t34Interval = SignallingTimer::getInterval(params,"t34",ISUP_T34_MINVAL,ISUP_T34_DEFVAL,ISUP_T34_MAXVAL,false);

    m_continuity = params.getValue(YSTRING("continuity"));
    m_confirmCCR = params.getBoolValue(YSTRING("confirm_ccr"),true);
    m_dropOnUnknown = params.getBoolValue(YSTRING("drop_unknown"),true);
    m_ignoreGRSSingle = params.getBoolValue(YSTRING("ignore-grs-single"));
    m_ignoreCGBSingle = params.getBoolValue(YSTRING("ignore-cgb-single"));
    m_ignoreCGUSingle = params.getBoolValue(YSTRING("ignore-cgu-single"));
    m_duplicateCGB = params.getBoolValue(YSTRING("duplicate-cgb"),
	(SS7PointCode::ANSI == m_type || SS7PointCode::ANSI8 == m_type));
    m_chargeProcessType = (ChargeProcess)params.getIntValue(YSTRING("charge-process"),s_dict_CRG_process,m_chargeProcessType);
    int testMsg = params.getIntValue(YSTRING("parttestmsg"),s_names,SS7MsgISUP::UPT);
    switch (testMsg) {
	case SS7MsgISUP::CVT:
	    if (SS7PointCode::ANSI != m_type && SS7PointCode::ANSI8 != m_type)
		break;
	    // fall through
	case SS7MsgISUP::RSC:
	case SS7MsgISUP::UBL:
	case SS7MsgISUP::UPT:
	    m_uptMessage = (SS7MsgISUP::Type)testMsg;
    }
    m_replaceCounter = params.getIntValue(YSTRING("max_replaces"),3,0,31);
    m_ignoreUnkDigits = params.getBoolValue(YSTRING("ignore-unknown-digits"),true);
    m_defaultSls = params.getIntValue(YSTRING("sls"),s_dict_callSls,m_defaultSls);
    m_maxCalledDigits = params.getIntValue(YSTRING("maxcalleddigits"),m_maxCalledDigits);
    if (m_maxCalledDigits < 1)
	m_maxCalledDigits = 16;

    setDebug(params.getBoolValue(YSTRING("print-messages"),false),
	params.getBoolValue(YSTRING("extended-debug"),false));

    if (debugAt(DebugInfo)) {
	String s;
	s << "pointcode-type=" << stype;
	s << " format=" << m_format;
	s << " plan/type/pres/screen=" << m_numPlan << "/" << m_numType << "/"
	    << m_numPresentation << "/" << m_numScreening;
	s << " caller-category=" << m_callerCat;
	s << " remote-pointcode=";
	if (m_remotePoint)
	    s << *m_remotePoint;
	else
	    s << "missing";
	s << " SIF/SSF=" << (unsigned int)sif() << "/" << (unsigned int)ssf();
	s << " lockcircuits=" << params.getValue(YSTRING("lockcircuits"));
	s << " userpartavail=" << String::boolText(m_userPartAvail);
	s << " lockgroup=" << String::boolText(m_lockGroup);
	s << " mediareq=" << lookup(m_mediaRequired,s_mediaRequired);
	const char* sls = lookup(m_defaultSls,s_dict_callSls);
	s << " outboundsls=";
	if (sls)
	    s << sls;
	else
	    s << m_defaultSls;
	if (m_continuity)
	    s << " continuity=" << m_continuity;
	Debug(this,DebugInfo,"ISUP Call Controller %s [%p]",s.c_str(),this);
    }
}

SS7ISUP::~SS7ISUP()
{
    cleanup();
    if (m_remotePoint)
	m_remotePoint->destruct();
    Debug(this,DebugInfo,"ISUP Call Controller destroyed [%p]",this);
}

bool SS7ISUP::initialize(const NamedList* config)
{
#ifdef DEBUG
    String tmp;
    if (config && debugAt(DebugAll))
	config->dump(tmp,"\r\n  ",'\'',true);
    Debug(this,DebugInfo,"SS7ISUP::initialize(%p) [%p]%s",config,this,tmp.c_str());
#endif
    if (config) {
	debugLevel(config->getIntValue(YSTRING("debuglevel_isup"),
	    config->getIntValue(YSTRING("debuglevel"),-1)));
	setDebug(config->getBoolValue(YSTRING("print-messages"),false),
	    config->getBoolValue(YSTRING("extended-debug"),false));
	m_lockGroup = config->getBoolValue(YSTRING("lockgroup"),m_lockGroup);
	m_earlyAcm = config->getBoolValue(YSTRING("earlyacm"),m_earlyAcm);
	m_continuity = config->getValue(YSTRING("continuity"),m_continuity);
	m_confirmCCR = config->getBoolValue(YSTRING("confirm_ccr"),true);
	m_dropOnUnknown = config->getBoolValue(YSTRING("drop_unknown"),true);
	m_ignoreGRSSingle = config->getBoolValue(YSTRING("ignore-grs-single"));
	m_ignoreCGBSingle = config->getBoolValue(YSTRING("ignore-cgb-single"));
	m_ignoreCGUSingle = config->getBoolValue(YSTRING("ignore-cgu-single"));
	m_duplicateCGB = config->getBoolValue(YSTRING("duplicate-cgb"),
	    (SS7PointCode::ANSI == m_type || SS7PointCode::ANSI8 == m_type));
	int testMsg = config->getIntValue(YSTRING("parttestmsg"),s_names,SS7MsgISUP::UPT);
	switch (testMsg) {
	    case SS7MsgISUP::CVT:
		if (SS7PointCode::ANSI != m_type && SS7PointCode::ANSI8 != m_type)
		    break;
		// fall through
	    case SS7MsgISUP::RSC:
	    case SS7MsgISUP::UBL:
	    case SS7MsgISUP::UPT:
		m_uptMessage = (SS7MsgISUP::Type)testMsg;
	}
	m_replaceCounter = config->getIntValue(YSTRING("max_replaces"),3,0,31);
        m_ignoreUnkDigits = config->getBoolValue(YSTRING("ignore-unknown-digits"),true);
	m_defaultSls = config->getIntValue(YSTRING("sls"),s_dict_callSls,m_defaultSls);
	m_chargeProcessType = (ChargeProcess)config->getIntValue(YSTRING("charge-process"),s_dict_CRG_process,m_chargeProcessType);
	m_mediaRequired = (MediaRequired)config->getIntValue(YSTRING("needmedia"),
	    s_mediaRequired,m_mediaRequired);
        // Timers
	m_t7Interval = SignallingTimer::getInterval(*config,"t7",ISUP_T7_MINVAL,ISUP_T7_DEFVAL,ISUP_T7_MAXVAL,false);
	m_t9Interval = SignallingTimer::getInterval(*config,"t9",ISUP_T9_MINVAL,ISUP_T9_DEFVAL,ISUP_T9_MAXVAL,true);
	m_t27Interval = SignallingTimer::getInterval(*config,"t27",ISUP_T27_MINVAL,ISUP_T27_DEFVAL,ISUP_T27_MAXVAL,false);
	m_t34Interval = SignallingTimer::getInterval(*config,"t34",ISUP_T34_MINVAL,ISUP_T34_DEFVAL,ISUP_T34_MAXVAL,false);
    }
    m_cicWarnLevel = DebugMild;
    return SS7Layer4::initialize(config);
}

const char* SS7ISUP::statusName() const
{
    if (exiting())
	return "Exiting";
    if (!m_l3LinkUp)
	return "Layer 3 down";
    if (!m_userPartAvail)
	return "Remote unavailable";
    if (!m_defPoint)
	return "No local PC set";
    if (!m_remotePoint)
	return "No remote PC set";
    return "Operational";
}

void SS7ISUP::attach(SS7Layer3* network)
{
    SS7Layer4::attach(network);
    m_l3LinkUp = network && network->operational();
}

// Append a point code to the list of point codes serviced by this controller
// Set default point code
bool SS7ISUP::setPointCode(SS7PointCode* pc, bool def)
{
    if (!(pc && pc->pack(m_type)))
	return false;
    Lock mylock(this);
    // Force default if we are not having one or the list is empty
    def = def || !m_defPoint || !m_pointCodes.skipNull();
    // Force not default if the received point code is the same as the default one
    if (def && m_defPoint && *m_defPoint == *pc)
	def = false;
    SS7PointCode* p = hasPointCode(*pc);
    if (def)
	m_defPoint = p ? p : pc;
    String tmp;
    tmp << (def ? *m_defPoint : *pc);
    if (!p) {
	m_pointCodes.append(pc);
	DDebug(this,DebugAll,"Added new point code '%s'%s",tmp.safe(),def?". Set to default":"");
    }
    else {
	TelEngine::destruct(pc);
	if (def)
	    Debug(this,DebugAll,"Set default point code '%s'",tmp.safe());
    }
    return true;
}

// Add all point codes described in a parameter list
unsigned int SS7ISUP::setPointCode(const NamedList& params)
{
    unsigned int count = 0;
    unsigned int n = params.length();
    bool hadDef = false;
    for (unsigned int i= 0; i < n; i++) {
	NamedString* ns = params.getParam(i);
	if (!ns)
	    continue;
	bool defPc = false;
	if (ns->name() == YSTRING("defaultpointcode"))
	    defPc = true;
	else if (ns->name() != YSTRING("pointcode"))
	    continue;
	SS7PointCode* pc = new SS7PointCode(0,0,0);
	if (pc->assign(*ns,m_type) && setPointCode(pc,defPc && !hadDef)) {
	    count++;
	    if (defPc) {
		if (hadDef)
		    Debug(this,DebugMild,"Added point code '%s' as non-default",ns->safe());
		else
		    hadDef = true;
	    }
	}
	else {
	    Debug(this,DebugWarn,"Invalid '%s'='%s' in parameters '%s'",
		ns->name().c_str(),ns->safe(),params.safe());
	    TelEngine::destruct(pc);
	}
    }
    return count;
}

// Check if the given point code is serviced by this controller
SS7PointCode* SS7ISUP::hasPointCode(const SS7PointCode& pc)
{
    Lock mylock(this);
    for (ObjList* o = m_pointCodes.skipNull(); o; o = o->skipNext()) {
	SS7PointCode* p = static_cast<SS7PointCode*>(o->get());
	if (*p == pc)
	    return p;
    }
    return 0;
}

SS7MSU* SS7ISUP::createMSU(SS7MsgISUP::Type type, unsigned char ssf,
    const SS7Label& label, unsigned int cic, const NamedList* params) const
{
    return buildMSU(type,sif() | (ssf & 0xf0),label,cic,params);
}

// Make an outgoing call
SignallingCall* SS7ISUP::call(SignallingMessage* msg, String& reason)
{
    if (!msg) {
	reason = "noconn";
	return 0;
    }
    if (exiting() || !m_l3LinkUp) {
	Debug(this,DebugInfo,"Denying outgoing call request, reason: %s.",
	    exiting() ? "exiting" : "L3 down");
	TelEngine::destruct(msg);
	reason = "net-out-of-order";
	return 0;
    }
    if (!m_userPartAvail) {
	Debug(this,DebugNote,"Remote User Part is unavailable");
	TelEngine::destruct(msg);
	reason = "noconn";
	return 0;
    }
    SS7PointCode dest;
    SignallingCircuit* cic = 0;
    const char* range = msg->params().getValue(YSTRING("circuits"));
    reason.clear();
    Lock mylock(this);
    // Check
    while (true) {
	if (!m_defPoint) {
 	    Debug(this,DebugNote,"Source point code is missing");
	    reason = "noconn";
	    break;
	}
	String pc = msg->params().getValue(YSTRING("calledpointcode"));
	if (!(dest.assign(pc,m_type) && dest.pack(m_type))) {
	    if (!m_remotePoint) {
		Debug(this,DebugNote,
		    "Destination point code is missing (calledpointcode=%s)",pc.safe());
		reason = "noconn";
		break;
	    }
	    dest = *m_remotePoint;
	}
	for (int attempts = 3; attempts; attempts--) {
	    if (!reserveCircuit(cic,range,SignallingCircuit::LockLockedBusy)) {
		Debug(this,DebugNote,"Can't reserve circuit");
		break;
	    }
	    SS7ISUPCall* call2 = findCall(cic->code());
	    if (!call2)
		break;
	    Debug(this,DebugWarn,"Circuit %u is already used by call %p",
		cic->code(),call2);
	    TelEngine::destruct(cic);
	}
	if (!cic)
	    reason = "congestion";
	break;
    }
    SS7ISUPCall* call = 0;
    if (reason.null()) {
	String* cicParams = msg->params().getParam(YSTRING("circuit_parameters"));
	if (cicParams) {
	    NamedList* p = YOBJECT(NamedList,cicParams);
	    if (p)
		cic->setParams(*p);
	}
	int sls = msg->params().getIntValue(YSTRING("sls"),s_dict_callSls,m_defaultSls);
	switch (sls) {
	    case SlsCircuit:
		if (cic) {
		    sls = cic->code();
		    break;
		}
		// fall through
	    case SlsLatest:
		sls = m_sls;
		break;
	}
	call = new SS7ISUPCall(this,cic,*m_defPoint,dest,true,sls,range);
	call->ref();
	m_calls.append(call);
	SignallingEvent* event = new SignallingEvent(SignallingEvent::NewCall,msg,call);
	// (re)start RSC timer if not currently reseting
	if (!m_rscCic && m_rscTimer.interval())
	    m_rscTimer.start();
	// Drop lock and send the event
	mylock.drop();
	if (!event->sendEvent()) {
	    call->setTerminate(false,"failure");
	    TelEngine::destruct(call);
	    reason = "failure";
	}
    }
    TelEngine::destruct(msg);
    return call;
}

// Converts an ISUP message to a Message Signal Unit and push it down the protocol stack
// The given message is consumed
int SS7ISUP::transmitMessage(SS7MsgISUP* msg, const SS7Label& label, bool recvLbl, int sls)
{
    if (!msg)
	return -1;
    const SS7Label* p = &label;
    SS7Label tmp;
    if (recvLbl) {
	switch (sls) {
	    case SlsCircuit:
		sls = msg->cic();
		break;
	    case SlsLatest:
		sls = m_sls;
		break;
	    case SlsDefault:
		sls = label.sls();
		break;
	}
	tmp.assign(label.type(),label.opc(),label.dpc(),sls,label.spare());
	p = &tmp;
    }

    lock();
    SS7MSU* msu = createMSU(msg->type(),ssf(),*p,msg->cic(),&msg->params());

    if (m_printMsg && debugAt(DebugInfo)) {
	String tmp;
	void* data = 0;
	unsigned int len = 0;
	if (m_extendedDebug && msu) {
	    unsigned int offs = 2 + label.length() + m_cicLen;
	    data = msu->getData(offs);
	    len = data ? msu->length() - offs : 0;
	}
	msg->toString(tmp,*p,debugAt(DebugAll),data,len);
	Debug(this,DebugInfo,"Sending message (%p)%s",msg,tmp.c_str());
    }
    else if (debugAt(DebugAll)) {
	String tmp;
	tmp << *p;
	Debug(this,DebugAll,"Sending message '%s' cic=%u label=%s",
	    msg->name(),msg->cic(),tmp.c_str());
    }

    sls = -1;
    if (msu && m_l3LinkUp) {
	unlock();
	sls = transmitMSU(*msu,*p,p->sls());
	lock();
	if ((m_sls == 255) && (sls != -1))
	    m_sls = (unsigned char)sls;
    }
    unlock();
#ifdef XDEBUG
    if (sls == -1)
	Debug(this,DebugMild,"Failed to send message (%p): '%s'",msg,msg->name());
#endif
    TelEngine::destruct(msu);
    TelEngine::destruct(msg);
    return sls;
}

void SS7ISUP::cleanup(const char* reason)
{
    ObjList terminate;
    lock();
    for (ObjList* o = m_calls.skipNull(); o; o = o->skipNext()) {
	SS7ISUPCall* call = static_cast<SS7ISUPCall*>(o->get());
	if (call->ref())
	    terminate.append(call);
    }
    releaseCircuit(m_rscCic);
    m_rscTimer.stop();
    unlock();
    setCallsTerminate(terminate,true,reason);
    clearCalls();
}

// Remove all links with other layers. Disposes the memory
void SS7ISUP::destroyed()
{
    lock();
    clearCalls();
    unlock();
    SignallingCallControl::attach(0);
    SS7Layer4::destroyed();
}

// Utility: find a pending (un)block message for a given circuit
static bool findPendingMsgTimerLock(ObjList& list, unsigned int code)
{
    for (ObjList* o = list.skipNull(); o; o = o->skipNext()) {
	SignallingMessageTimer* m = static_cast<SignallingMessageTimer*>(o->get());
	SS7MsgISUP* msg = static_cast<SS7MsgISUP*>(m->message());
	if (!msg || code < msg->cic())
	    continue;
	if (msg->type() == SS7MsgISUP::BLK || msg->type() == SS7MsgISUP::UBL) {
	    if (msg->cic() == code)
		return true;
	    continue;
	}
	if (msg->type() != SS7MsgISUP::CGB && msg->type() != SS7MsgISUP::CGU)
	    continue;
	const String& map = msg->params()[YSTRING("RangeAndStatus.map")];
	if (map[code - msg->cic()] == '1')
	    return true;
    }
    return false;
}

void SS7ISUP::timerTick(const Time& when)
{
    Lock mylock(this,SignallingEngine::maxLockWait());
    if (!(mylock.locked() && m_l3LinkUp && circuits()))
	return;

    // Test remote user part
    if (m_remotePoint && !m_userPartAvail && m_uptTimer.interval()) {
	if (m_uptTimer.started()) {
	    if (!m_uptTimer.timeout(when.msec()))
		return;
	    DDebug(this,DebugNote,"%s timed out. Retransmitting",lookup(m_uptMessage,s_names));
	}
	ObjList* o = circuits()->circuits().skipNull();
	SignallingCircuit* cic = o ? static_cast<SignallingCircuit*>(o->get()) : 0;
	m_uptCicCode = cic ? cic->code() : 1;
	SS7MsgISUP* msg = new SS7MsgISUP(m_uptMessage,m_uptCicCode);
	SS7Label label(m_type,*m_remotePoint,*m_defPoint,
	    (m_defaultSls == SlsCircuit) ? m_uptCicCode : m_sls);
	m_uptTimer.start(when.msec());
	mylock.drop();
	transmitMessage(msg,label,false);
	return;
    }

    // Blocking/unblocking circuits
    if (m_lockTimer.timeout(when.msec())) {
	DDebug(this,DebugAll,"Re-checking local lock sending");
	m_lockTimer.stop();
	mylock.drop();
	sendLocalLock(when);
	return;
    }

    // Pending messages
    ObjList reInsert;
    ObjList sendMsgs;
    ObjList rsc;
    while (true) {
	SignallingMessageTimer* m = m_pending.timeout(when);
	if (!m)
	    break;
	SS7MsgISUP* msg = static_cast<SS7MsgISUP*>(m->message());
	if (!msg) {
	    TelEngine::destruct(m);
	    continue;
	}
	if (msg->type() != SS7MsgISUP::RSC &&
	    msg->type() != SS7MsgISUP::REL &&
	    msg->type() != SS7MsgISUP::CGB &&
	    msg->type() != SS7MsgISUP::CGU &&
	    msg->type() != SS7MsgISUP::BLK &&
	    msg->type() != SS7MsgISUP::UBL) {
	    Debug(this,DebugStub,"Unhandled pending message '%s'",msg->name());
	    TelEngine::destruct(m);
	    continue;
	}
	// Global timer timed out: set retransmission timer from it
	if (m->global().timeout(when.msec())) {
	    if (msg->type() != SS7MsgISUP::REL) {
		m->interval(m->global().interval());
		m->global().stop();
		m->global().interval(0);
		msg->params().setParam("isup_alert_maint",String::boolText(true));
	    }
	    else {
		Debug(this,DebugNote,"Pending operation '%s' cic=%u timed out",
		    msg->name(),msg->cic());
		SignallingCircuit* c = circuits() ? circuits()->find(msg->cic()) : 0;
		TelEngine::destruct(m);
		if (c && c->ref())
		    rsc.append(c)->setDelete(false);
		continue;
	    }
	}
	// Check if message is still in use
	if (msg->type() == SS7MsgISUP::CGB || msg->type() == SS7MsgISUP::CGU) {
	    String* map = msg->params().getParam(YSTRING("RangeAndStatus.map"));
	    bool ok = !TelEngine::null(map);
	    String removedCics;
	    if (ok) {
		unsigned int nCics = 0;
		int flg = 0;
		int flgReset = 0;
		if ((msg->params()[YSTRING("GroupSupervisionTypeIndicator")] == YSTRING("hw-failure"))) {
		    flg = SignallingCircuit::LockLocalHWFail;
		    flgReset = SignallingCircuit::LockingHWFail;
		}
		else {
		    flg = SignallingCircuit::LockLocalMaint;
		    flgReset = SignallingCircuit::LockingMaint;
		}
		int on = (msg->type() == SS7MsgISUP::CGB) ? flg : 0;
		char* s = (char*)map->c_str();
		for (unsigned int i = 0; i < map->length(); i++) {
		    if (s[i] == '0')
			continue;
		    unsigned int code = msg->cic() + i;
		    SignallingCircuit* cic = circuits()->find(code);
		    if (cic && (on == cic->locked(flg))) {
			nCics++;
			continue;
		    }
		    // Don't reset locking flag if there is another operation in progress
		    if (cic && !(findPendingMsgTimerLock(m_pending,code) ||
			findPendingMsgTimerLock(reInsert,code)) && cic->locked(flgReset)) {
			cic->resetLock(flgReset);
			Debug(this,DebugNote,"Pending %s reset flag=0x%x cic=%u current=0x%x",
			    msg->name(),flgReset,code,cic->locked());;
		    }
		    s[i] = '0';
		    removedCics.append(String(code),",");
		}
		if (nCics)
		    msg->params().setParam("RangeAndStatus",String(nCics));
		else
		    ok = false;
	    }
	    if (!ok) {
		Debug(this,DebugNote,"Removed empty pending operation '%s' cic=%u",
		    msg->name(),msg->cic());
		TelEngine::destruct(m);
		continue;
	    }
	    if (removedCics)
		Debug(this,DebugAll,"Removed cics=%s from pending operation '%s' map cic=%u",
		    removedCics.c_str(),msg->name(),msg->cic());
	}
	else if (msg->type() == SS7MsgISUP::BLK || msg->type() == SS7MsgISUP::UBL) {
	    // We set the following param when sending BLK/UBL for HW fail reason
	    bool maint = !msg->params().getBoolValue(YSTRING("isup_pending_block_hwfail"));
	    int flg = maint ? SignallingCircuit::LockLocalMaint : SignallingCircuit::LockLocalHWFail;
	    int on = (msg->type() == SS7MsgISUP::BLK) ? flg : 0;
	    SignallingCircuit* cic = circuits()->find(msg->cic());
	    if (!cic || on != cic->locked(flg)) {
		flg = maint ? SignallingCircuit::LockingMaint : SignallingCircuit::LockingHWFail;
		// Don't reset locking flag if there is another operation in progress
		if (cic && !(findPendingMsgTimerLock(m_pending,msg->cic()) ||
		    findPendingMsgTimerLock(reInsert,msg->cic())) && cic->locked(flg)) {
		    cic->resetLock(flg);
		    Debug(this,DebugNote,"Pending %s reset flag=0x%x cic=%u current=0x%x",
			msg->name(),flg,cic->code(),cic->locked());
		}
		Debug(this,DebugNote,"Removed empty pending operation '%s' cic=%u",
		    msg->name(),msg->cic());
		TelEngine::destruct(m);
		continue;
	    }
	}
	bool alert = msg->params().getBoolValue(YSTRING("isup_alert_maint"));
	const char* reason = msg->params().getValue(YSTRING("isup_pending_reason"),"");
	Debug(this,!alert ? DebugAll : DebugMild,
	    "Pending operation '%s' cic=%u reason='%s' timed out",
	    msg->name(),msg->cic(),reason);
	if (alert) {
	    // TODO: alert maintenance
	}
	msg->ref();
	reInsert.append(m)->setDelete(false);
	sendMsgs.append(msg)->setDelete(false);
    }
    // Re-insert
    ObjList* o = reInsert.skipNull();
    ObjList* oRsc = rsc.skipNull();
    if (o || oRsc) {
	for (; o; o = o->skipNext())
	    m_pending.add(static_cast<SignallingMessageTimer*>(o->get()),when);
	mylock.drop();
	transmitMessages(sendMsgs);
	for (; oRsc; oRsc = oRsc->skipNext()) {
	    SignallingCircuit* c = static_cast<SignallingCircuit*>(oRsc->get());
	    c->resetLock(SignallingCircuit::Resetting);
	    startCircuitReset(c,"T5");
	}
	return;
    }

    // Circuit reset disabled ?
    if (!m_rscTimer.interval())
	return;
    if (m_rscTimer.started()) {
	if (!m_rscTimer.timeout(when.msec()))
	    return;
	m_rscTimer.stop();
	if (m_rscCic) {
	    Debug(this,DebugMild,"Circuit reset timed out for cic=%u",m_rscCic->code());
	    m_rscCic->resetLock(SignallingCircuit::Resetting);
	    releaseCircuit(m_rscCic);
	    return;
	}
    }
    if (m_rscSpeedup && !--m_rscSpeedup) {
	Debug(this,DebugNote,"Reset interval back to %u ms",m_rscInterval);
	m_rscTimer.interval(m_rscInterval);
    }
    m_rscTimer.start(when.msec());
    // Pick the next circuit to reset. Ignore circuits locally locked or busy
    if (m_defPoint && m_remotePoint &&
	reserveCircuit(m_rscCic,0,SignallingCircuit::LockLocal | SignallingCircuit::LockBusy)) {
	// Avoid already resetting cic
	if (!findPendingMessage(SS7MsgISUP::RSC,m_rscCic->code())) {
	    m_rscCic->setLock(SignallingCircuit::Resetting);
	    SS7MsgISUP* msg = new SS7MsgISUP(SS7MsgISUP::RSC,m_rscCic->code());
	    SS7Label label(m_type,*m_remotePoint,*m_defPoint,
		(m_defaultSls == SlsCircuit) ? m_rscCic->code() : m_sls);
	    DDebug(this,DebugNote,"Periodic restart on cic=%u",m_rscCic->code());
	    mylock.drop();
	    transmitMessage(msg,label,false);
	}
	else
	    releaseCircuit(m_rscCic);
    }
}

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

    if (ret) {
	if (oper && (cmd < 0))
	    return false;
	String part = params.getValue(YSTRING("partword"));
	if (cmp) {
	    if (toString() != cmp)
		return false;
	    for (const TokenDict* d = s_dict_control; d->token; d++)
		Module::itemComplete(*ret,d->token,part);
	    return true;
	}
	return Module::itemComplete(*ret,toString(),part);
    }

    if (!(cmp && toString() == cmp))
	return false;
    Lock mylock(this);
    if (!m_remotePoint)
	return TelEngine::controlReturn(&params,false);
    unsigned int code1 = 1;
    if (circuits()) {
	ObjList* o = circuits()->circuits().skipNull();
	if (o) {
	    SignallingCircuit* cic = static_cast<SignallingCircuit*>(o->get());
	    if (cic)
		code1 = cic->code();
	}
    }
    switch (cmd) {
	case SS7MsgISUP::UPT:
	case SS7MsgISUP::CVT:
	    {
		unsigned int code = params.getIntValue(YSTRING("circuit"),code1);
		SS7MsgISUP* msg = new SS7MsgISUP((SS7MsgISUP::Type)cmd,code);
		SS7Label label(m_type,*m_remotePoint,*m_defPoint,m_sls);
		mylock.drop();
		transmitMessage(msg,label,false);
	    }
	    return TelEngine::controlReturn(&params,true);
	case SS7MsgISUP::CQM:
	    {
		unsigned int code = params.getIntValue(YSTRING("circuit"),code1);
		unsigned int range = params.getIntValue(YSTRING("range"),1);
		SS7MsgISUP* msg = new SS7MsgISUP(SS7MsgISUP::CQM,code);
		msg->params().addParam("RangeAndStatus",String(range));
		SS7Label label(m_type,*m_remotePoint,*m_defPoint,m_sls);
		mylock.drop();
		transmitMessage(msg,label,false);
	    }
	    return TelEngine::controlReturn(&params,true);
	case SS7MsgISUP::CCR:
	    {
		unsigned int code = params.getIntValue(YSTRING("circuit"),code1);
		// TODO: create a test call, not just send CCR
		SS7MsgISUP* msg = 0;
		const String& ok = params[YSTRING("success")];
		if (ok.isBoolean()) {
		    msg = new SS7MsgISUP(SS7MsgISUP::COT,code);
		    msg->params().addParam("ContinuityIndicators",
			ok.toBoolean() ? "success" : "failed");
		}
		else
		    msg = new SS7MsgISUP(SS7MsgISUP::CCR,code);
		SS7Label label(m_type,*m_remotePoint,*m_defPoint,m_sls);
		mylock.drop();
		transmitMessage(msg,label,false);
	    }
	    return TelEngine::controlReturn(&params,true);
	case SS7MsgISUP::RSC:
	    if (0 == (m_rscSpeedup = circuits() ? circuits()->count() : 0))
		return TelEngine::controlReturn(&params,false);
	    // Temporarily speed up reset interval to 10s or as provided
	    m_rscTimer.interval(params,"interval",2,10,false,true);
	    Debug(this,DebugNote,"Fast reset of %u circuits every %u ms",
		m_rscSpeedup,(unsigned int)m_rscTimer.interval());
	    if (m_rscTimer.started())
		m_rscTimer.start(Time::msecNow());
	    return TelEngine::controlReturn(&params,true);
	case SS7MsgISUP::BLK:
	case SS7MsgISUP::UBL:
	    return TelEngine::controlReturn(&params,handleCicBlockCommand(params,cmd == SS7MsgISUP::BLK));
	case SS7MsgISUP::RLC:
	    {
		int code = params.getIntValue(YSTRING("circuit"));
		if (code <= 0)
		    return TelEngine::controlReturn(&params,false);
		SignallingMessageTimer* pending = findPendingMessage(SS7MsgISUP::RSC,code,true);
		if (pending) {
		    resetCircuit((unsigned int)code,false,false);
		    TelEngine::destruct(pending);
		    SS7Label label(m_type,*m_remotePoint,*m_defPoint,m_sls);
		    mylock.drop();
		    transmitRLC(this,code,label,false);
		}
		else {
		    RefPointer<SS7ISUPCall> call;
		    findCall(code,call);
		    if (!call)
			return TelEngine::controlReturn(&params,false);
		    mylock.drop();
		    call->setTerminate(true,params.getValue(YSTRING("reason"),"normal"));
		}
	    }
	    return TelEngine::controlReturn(&params,true);
	case SS7MsgISUP::UPA:
	    if (!m_userPartAvail) {
		const char* oldStat = statusName();
		m_uptTimer.stop();
		m_userPartAvail = true;
		m_lockTimer.start();
		if (statusName() != oldStat) {
		    NamedList params("");
		    params.addParam("from",toString());
		    params.addParam("type","trunk");
		    params.addParam("operational",String::boolText(m_l3LinkUp));
		    params.addParam("available",String::boolText(m_userPartAvail));
		    params.addParam("text",statusName());
		    engine()->notify(this,params);
		}
	    }
	    return TelEngine::controlReturn(&params,true);
	case SS7MsgISUP::CtrlSave:
	    setVerify(true,true);
	    return TelEngine::controlReturn(&params,true);
#ifdef ISUP_HANDLE_CIC_EVENT_CONTROL
	case SS7MsgISUP::CtrlCicEvent:
	    return TelEngine::controlReturn(&params,handleCicEventCommand(params));
#endif
    }
    mylock.drop();
    return SignallingComponent::control(params);
}

// Process a notification generated by the attached network layer
void SS7ISUP::notify(SS7Layer3* link, int sls)
{
    if (!(link && network()))
	return;
    Lock mylock(this);
    SS7Route::State state = m_remotePoint ?
	network()->getRouteState(m_type,*m_remotePoint) : SS7Route::Unknown;
    bool linkTmp = m_l3LinkUp;
    bool partAvail = m_userPartAvail;
    const char* oldStat = statusName();
    // Copy linkset operational state
    m_l3LinkUp = network()->operational();
    // Reset remote user part's availability state if supported
    // Force UPT re-send
    if (m_uptTimer.interval() && (!m_l3LinkUp || (SS7Route::Prohibited == state))) {
	m_uptTimer.stop();
	m_userPartAvail = false;
    }
    Debug(this,DebugInfo,
	"L3 '%s' sls=%d is %soperational.%s Route is %s. Remote User Part is %savailable",
	link->toString().safe(),sls,
	(link->operational() ? "" : "not "),
	(network() == link ? "" : (m_l3LinkUp ? " L3 is up." : " L3 is down.")),
	SS7Route::stateName(state),
	(m_userPartAvail ? "" : "un"));
    if (linkTmp != m_l3LinkUp || partAvail != m_userPartAvail) {
	NamedList params("");
	params.addParam("from",toString());
	params.addParam("type","trunk");
	params.addParam("operational",String::boolText(m_l3LinkUp));
	params.addParam("available",String::boolText(m_userPartAvail));
	params.addParam("link",link->toString());
	if (statusName() != oldStat)
	    params.addParam("text",statusName());
	engine()->notify(this,params);
    }
}

SS7MSU* SS7ISUP::buildMSU(SS7MsgISUP::Type type, unsigned char sio,
    const SS7Label& label, unsigned int cic, const NamedList* params) const
{
    // Special treatment for charge message
    // Check if it is in raw format
    if (type == SS7MsgISUP::CRG && params && params->getParam(YSTRING("Charge")))
	return encodeRawMessage(type,sio,label,cic,(*params)[YSTRING("Charge")]);

    if (type == SS7MsgISUP::PAM && params)
	return encodeRawMessage(type,sio,label,cic,(*params)[YSTRING("PassAlong")]);
    // see what mandatory parameters we should put in this message
    const MsgParams* msgParams = getIsupParams(label.type(),type);
    if (!msgParams) {
	if (!hasOptionalOnly(type)) {
	    const char* name = SS7MsgISUP::lookup(type);
	    if (name)
		Debug(this,DebugWarn,"No parameter table for ISUP MSU type %s [%p]",name,this);
	    else
		Debug(this,DebugWarn,"Cannot create ISUP MSU type 0x%02x [%p]",type,this);
	    return 0;
	}
	msgParams = &s_compatibility;
    }
    unsigned int len = m_cicLen + 1;

    const SS7MsgISUP::Parameters* plist = msgParams->params;
    SS7MsgISUP::Parameters ptype;
    // first add the length of mandatory fixed parameters
    while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) {
	const IsupParam* param = getParamDesc(ptype);
	if (!param) {
	    // this is fatal as we don't know the length
	    Debug(this,DebugCrit,"Missing description of fixed ISUP parameter 0x%02x [%p]",ptype,this);
	    return 0;
	}
	if (!param->size) {
	    Debug(this,DebugCrit,"Invalid (variable) description of fixed ISUP parameter 0x%02x [%p]",ptype,this);
	    return 0;
	}
	len += param->size;
    }
    // initialize the pointer array offset just past the mandatory fixed part
    unsigned int ptr = label.length() + 1 + len;
    // then add one pointer octet to each mandatory variable parameter
    while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) {
	const IsupParam* param = getParamDesc(ptype);
	if (!param) {
	    // this is fatal as we won't be able to populate later
	    Debug(this,DebugCrit,"Missing description of variable ISUP parameter 0x%02x [%p]",ptype,this);
	    return 0;
	}
	if (param->size)
	    Debug(this,DebugMild,"Invalid (fixed) description of variable ISUP parameter 0x%02x [%p]",ptype,this);
	len++;
    }
    // finally add a pointer to the optional part only if supported by type
    if (msgParams->optional)
	len++;
    SS7MSU* msu = new SS7MSU(sio,label,0,len);
    unsigned char* d = msu->getData(label.length()+1,len);
    unsigned int i = m_cicLen;
    while (i--) {
	*d++ = cic & 0xff;
	cic >>= 8;
    }
    *d++ = type;
#ifdef XDEBUG
    if (params && debugAt(DebugAll)) {
	String tmp;
	params->dump(tmp,"\r\n  ",'\'',true);
	Debug(this,DebugAll,"SS7ISUP::buildMSU params:%s",
	    tmp.c_str());
    }
#endif
    ObjList exclude;
    plist = msgParams->params;
    String prefix = params->getValue(YSTRING("message-prefix"));
    // first populate with mandatory fixed parameters
    while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) {
	const IsupParam* param = getParamDesc(ptype);
	if (!param) {
	    Debug(this,DebugFail,"Stage 2: no description of fixed ISUP parameter 0x%02x [%p]",ptype,this);
	    continue;
	}
	if (!param->size) {
	    Debug(this,DebugFail,"Stage 2: Invalid (variable) description of fixed ISUP parameter %s [%p]",param->name,this);
	    continue;
	}
	if (!encodeParam(this,*msu,param,params,exclude,prefix,d))
	    Debug(this,DebugCrit,"Could not encode fixed ISUP parameter %s [%p]",param->name,this);
	d += param->size;
    }
    // now populate with mandatory variable parameters
    for (; (ptype = *plist++) != SS7MsgISUP::EndOfParameters; ptr++) {
	const IsupParam* param = getParamDesc(ptype);
	if (!param) {
	    Debug(this,DebugFail,"Stage 2: no description of variable ISUP parameter 0x%02x [%p]",ptype,this);
	    continue;
	}
	if (param->size) {
	    Debug(this,DebugFail,"Stage 2: Invalid (fixed) description of variable ISUP parameter %s [%p]",param->name,this);
	    continue;
	}
	// remember the offset this parameter will actually get stored
	len = msu->length();
	unsigned char size = encodeParam(this,*msu,param,params,exclude,prefix);
	d = msu->getData(0,len+1);
	if (!(size && d)) {
	    Debug(this,DebugCrit,"Could not encode variable ISUP parameter %s [%p]",param->name,this);
	    continue;
	}
	if ((d[len] != size) || (msu->length() != (len+1+size))) {
	    Debug(this,DebugCrit,"Invalid encoding variable ISUP parameter %s (len=%u size=%u stor=%u) [%p]",
		param->name,len,size,d[len],this);
	    continue;
	}
	// store pointer to parameter
	d[ptr] = len - ptr;
    }
    if (msgParams->optional && params) {
	// remember the offset past last mandatory == first optional parameter
	len = msu->length();
	// optional parameters are possible - try to set anything left in the message
	unsigned int n = params->length();
	for (unsigned int i = 0; i < n; i++) {
	    NamedString* ns = params->getParam(i);
	    if (!ns || exclude.find(ns))
		continue;
	    if (prefix && !ns->name().startsWith(prefix))
		continue;
	    String tmp(ns->name());
	    tmp >> prefix.c_str();
	    static const Regexp s_suffix("\\.[0-9]\\+$");
	    if (tmp.matches(s_suffix)) {
		tmp.assign(tmp,tmp.matchOffset());
		// WARNING: HACK - ApplicationTransport does not follow naming convention
		if (tmp == YSTRING("ApplicationTransport"))
		    continue;
	    }
	    const IsupParam* param = getParamDesc(tmp);
	    unsigned char size = 0;
	    if (param)
		size = encodeParam(this,*msu,param,ns,params,prefix);
	    else if (tmp.startSkip("Param_",false)) {
		int val = tmp.toInteger(-1);
		if (val >= 0 && val <= 255) {
		    IsupParam p;
		    p.name = tmp;
		    p.type = (SS7MsgISUP::Parameters)val;
		    p.size = 0;
		    p.encoder = 0;
		    size = encodeParam(this,*msu,&p,ns,params,prefix);
		}
	    }
	    if (!size)
		continue;
	    if (len) {
		d = msu->getData(0,len+1);
		d[ptr] = len - ptr;
		len = 0;
	    }
	}
	if (!len) {
	    // we stored some optional parameters so we need to put the terminator
	    DataBlock tmp(0,1);
	    *msu += tmp;
	}
    }
    return msu;
}

SS7MSU* SS7ISUP::encodeRawMessage(SS7MsgISUP::Type type, unsigned char sio,
    const SS7Label& label, unsigned int cic, const String& param) const
{
    DataBlock raw;
    if (!raw.unHexify(param.c_str(),param.length(),' ')) {
	DDebug(this,DebugMild,"Encode raw charge failed: invalid string");
	return 0;
    }
    if (raw.length() > 254) {
	DDebug(this,DebugMild,"Encode raw charge failed: data length=%u",
		raw.length());
	return 0;
    }
    SS7MSU* msu = new SS7MSU(sio,label,0,m_cicLen + 1);
    unsigned char* d = msu->getData(label.length()+1,m_cicLen + 1);
    unsigned int i = m_cicLen;
    while (i--) {
	*d++ = cic & 0xff;
	cic >>= 8;
    }
    *d++ = type;
    *msu += raw;
    return msu;
}



// Decode a buffer to a list of parameters
bool SS7ISUP::decodeMessage(NamedList& msg,
    SS7MsgISUP::Type msgType, SS7PointCode::Type pcType,
    const unsigned char* paramPtr, unsigned int paramLen)
{
    String msgTypeName((int)msgType);
    const char* msgName = SS7MsgISUP::lookup(msgType,msgTypeName);
#ifdef XDEBUG
    String tmp;
    tmp.hexify((void*)paramPtr,paramLen,' ');
    Debug(this,DebugAll,"Decoding msg=%s len=%u: %s [%p]",
	msgName,paramLen,tmp.c_str(),this);
#else
    DDebug(this,DebugAll,"Decoding msg=%s len=%u [%p]",
	msgName,paramLen,this);
#endif

    // see what parameters we expect for this message
    const MsgParams* params = getIsupParams(pcType,msgType);
    if (!params) {
	if (hasOptionalOnly(msgType)) {
	    Debug(this,DebugNote,"Unsupported message %s, decoding compatibility [%p]",msgName,this);
	    params = &s_compatibility;
	}
	else if (msgType != SS7MsgISUP::PAM) {
	    Debug(this,DebugWarn,"Unsupported message %s or point code type [%p]",msgName,this);
	    return false;
	}
	else if (!paramLen) {
	    // PAM message must have at least 1 byte for message type
	    Debug(this,DebugNote,"Empty %s [%p]",msgName,this);
	    return false;
	}
    }

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

    // Add protocol and message type
    if (!msg.getValue(prefix+"protocol-type")) {
	switch (pcType) {
	    case SS7PointCode::ITU:
		msg.setParam(prefix+"protocol-type","itu-t");
		break;
	    case SS7PointCode::ANSI:
	    case SS7PointCode::ANSI8:
		msg.setParam(prefix+"protocol-type","ansi");
		break;
	    case SS7PointCode::China:
		msg.setParam(prefix+"protocol-type","chn");
		msg.setParam(prefix+"protocol-basetype","chn");
		break;
	    case SS7PointCode::Japan:
	    case SS7PointCode::Japan5:
		msg.setParam(prefix+"protocol-type","ttc");
		msg.setParam(prefix+"protocol-basetype","ttc93+");
		break;
	    default: ;
	}
    }
    msg.addParam(prefix+"message-type",msgName);

    // Special decoder for PAM
    if (msgType == SS7MsgISUP::PAM) {
        String raw;
	raw.hexify((void*)paramPtr,paramLen,' ');
        msg.addParam(prefix + "PassAlong",raw);
	return true;
    }

    // Decode raw CRG if specified
    if (msgType == SS7MsgISUP::CRG && getChargeProcessType() != Parsed) {
	String raw;
	raw.hexify((void*)paramPtr,paramLen,' ');
	msg.addParam(prefix + "Charge",raw);
	return true;
    }

    String unsupported;
    const SS7MsgISUP::Parameters* plist = params->params;
    SS7MsgISUP::Parameters ptype;
    // first decode any mandatory fixed parameters the message should have
    while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) {
	const IsupParam* param = getParamDesc(ptype);
	if (!param) {
	    // this is fatal as we don't know the length
	    Debug(this,DebugCrit,"Missing description of fixed ISUP parameter 0x%02x [%p]",ptype,this);
	    return false;
	}
	if (!param->size) {
	    Debug(this,DebugCrit,"Invalid (variable) description of fixed ISUP parameter %s [%p]",param->name,this);
	    return false;
	}
	if (paramLen < param->size) {
	    Debug(this,DebugWarn,"Truncated ISUP message! [%p]",this);
	    return false;
	}
	if (!decodeParam(this,msg,param,paramPtr,param->size,prefix)) {
	    Debug(this,DebugWarn,"Could not decode fixed ISUP parameter %s [%p]",param->name,this);
	    decodeRaw(this,msg,param,paramPtr,param->size,prefix);
	    SignallingUtils::appendFlag(unsupported,param->name);
	}
	paramPtr += param->size;
	paramLen -= param->size;
    } // while ((ptype = *plist++)...
    bool mustWarn = true;
    // next decode any mandatory variable parameters the message should have
    while ((ptype = *plist++) != SS7MsgISUP::EndOfParameters) {
	mustWarn = false;
	const IsupParam* param = getParamDesc(ptype);
	if (!param) {
	    // we could skip over unknown mandatory variable length but it's still bad
	    Debug(this,DebugCrit,"Missing description of variable ISUP parameter 0x%02x [%p]",ptype,this);
	    return false;
	}
	if (param->size)
	    Debug(this,DebugMild,"Invalid (fixed) description of variable ISUP parameter %s [%p]",param->name,this);
	unsigned int offs = paramPtr[0];
	if ((offs < 1) || (offs >= paramLen)) {
	    Debug(this,DebugWarn,"Invalid offset %u (len=%u) ISUP parameter %s [%p]",
		offs,paramLen,param->name,this);
	    return false;
	}
	unsigned int size = paramPtr[offs];
	if ((size < 1) || (offs+size >= paramLen)) {
	    Debug(this,DebugWarn,"Invalid size %u (ofs=%u, len=%u) ISUP parameter %s [%p]",
		size,offs,paramLen,param->name,this);
	    return false;
	}
	if (!decodeParam(this,msg,param,paramPtr+offs+1,size,prefix)) {
	    Debug(this,DebugWarn,"Could not decode variable ISUP parameter %s (size=%u) [%p]",
		param->name,size,this);
	    decodeRaw(this,msg,param,paramPtr+offs+1,size,prefix);
	    SignallingUtils::appendFlag(unsupported,param->name);
	}
	paramPtr++;
	paramLen--;
    } // while ((ptype = *plist++)...
    // now decode the optional parameters if the message supports them
    if (params->optional) {
	unsigned int offs = paramLen ? paramPtr[0] : 0;
	if (offs >= paramLen) {
	    if (paramLen) {
		Debug(this,DebugWarn,"Invalid ISUP optional offset %u (len=%u) [%p]",
		    offs,paramLen,this);
		return false;
	    }
	    Debug(this,DebugMild,"ISUP message %s lacking optional parameters [%p]",
		msgName,this);
	}
	else if (offs) {
	    mustWarn = true;
	    // advance pointer past mandatory parameters
	    paramPtr += offs;
	    paramLen -= offs;
	    while (paramLen) {
		ptype = (SS7MsgISUP::Parameters)(*paramPtr++);
		paramLen--;
		if (ptype == SS7MsgISUP::EndOfParameters)
		    break;
		if (paramLen < 2) {
		    Debug(this,DebugWarn,"Only %u octets while decoding optional ISUP parameter 0x%02x [%p]",
			paramLen,ptype,this);
		    return false;
		}
		unsigned int size = *paramPtr++;
		paramLen--;
		if ((size < 1) || (size >= paramLen)) {
		    Debug(this,DebugWarn,"Invalid size %u (len=%u) ISUP optional parameter 0x%02x [%p]",
			size,paramLen,ptype,this);
		    return false;
		}
		const IsupParam* param = getParamDesc(ptype);
		if (!param) {
		    Debug(this,DebugMild,"Unknown optional ISUP parameter 0x%02x (size=%u) [%p]",ptype,size,this);
		    decodeRawParam(this,msg,ptype,paramPtr,size,prefix);
		    SignallingUtils::appendFlag(unsupported,"Param_" + String((unsigned int)ptype));
		}
		else if (!decodeParam(this,msg,param,paramPtr,size,prefix)) {
		    Debug(this,DebugWarn,"Could not decode optional ISUP parameter %s (size=%u) [%p]",param->name,size,this);
		    decodeRaw(this,msg,param,paramPtr,size,prefix);
		    SignallingUtils::appendFlag(unsupported,param->name);
		}
		paramPtr += size;
		paramLen -= size;
	    } // while (paramLen)
	} // else if (offs)
	else
	    paramLen = 0;
    }
    if (unsupported)
	msg.addParam(prefix + "parameters-unsupported",unsupported);
    String release,cnf,npRelease;
    String pCompat(prefix + "ParameterCompatInformation.");
    unsigned int n = msg.length();
    for (unsigned int i = 0; i < n; i++) {
	NamedString* ns = msg.getParam(i);
	if (!(ns && ns->name().startsWith(pCompat) && !ns->name().endsWith(".more")))
	    continue;
	ObjList* l = ns->split(',',false);
	for (ObjList* ol = l->skipNull(); ol; ol = ol->skipNext()) {
	    String* s = static_cast<String*>(ol->get());
	    if (*s == YSTRING("release")) {
		SignallingUtils::appendFlag(release,ns->name().substr(pCompat.length()));
		break;
	    }
	    if (*s == YSTRING("cnf"))
		SignallingUtils::appendFlag(cnf,ns->name().substr(pCompat.length()));
	    if (*s == YSTRING("nopass-release"))
		SignallingUtils::appendFlag(npRelease,ns->name().substr(pCompat.length()));
	}
	TelEngine::destruct(l);
    }
    if (release)
	msg.setParam(prefix + "parameters-unhandled-release",release);
    if (cnf)
	msg.setParam(prefix + "parameters-unhandled-cnf",cnf);
    if (npRelease)
	msg.setParam(prefix + "parameters-nopass-release",npRelease);
    if (paramLen && mustWarn)
	Debug(this,DebugWarn,"Got %u garbage octets after message type 0x%02x [%p]",
	    paramLen,msgType,this);
    return true;
}

// Encode an ISUP list of parameters to a buffer
bool SS7ISUP::encodeMessage(DataBlock& buf, SS7MsgISUP::Type msgType, SS7PointCode::Type pcType,
    const NamedList& params, unsigned int* cic)
{
    unsigned int circuit = cic ? *cic : 0;
    SS7Label label(pcType,1,1,1);

    SS7MSU* msu = buildMSU(msgType,1,label,circuit,&params);
    if (!msu)
	return false;
    unsigned int start = 1 + label.length() + (cic ? 0 : m_cicLen);
    buf.assign(((char*)msu->data()) + start,msu->length() - start);
    TelEngine::destruct(msu);
    return true;
}

// Process parameter compatibility lists
// Terminate an existing call or send CNF
// Return true if any parameter compatibility was handled
bool SS7ISUP::processParamCompat(const NamedList& list, unsigned int cic, bool* callReleased)
{
    if (!cic)
	return true;
    const String& prefix = list[YSTRING("message-prefix")];
    // Release call params
    String relCall = list[prefix + "parameters-unhandled-release"];
    relCall.append(list[prefix + "parameters-nopass-release"],",");
    if (relCall) {
	Lock lock(this);
	SS7ISUPCall* call = findCall(cic);
	Debug(this,DebugNote,
	    "Terminating call (%p) on cic=%u: unknown/unhandled params='%s' [%p]",
	    call,cic,relCall.c_str(),this);
	String diagnostic;
	hexifyIsupParams(diagnostic,relCall);
	if (call) {
	    lock.drop();
	    call->setTerminate(true,"unknown-ie",diagnostic,m_location);
	}
	else if (m_remotePoint) {
	    // No call: make sure the circuit is released at remote party
	    SS7Label label(m_type,*m_remotePoint,*m_defPoint,
		(m_defaultSls == SlsCircuit) ? cic : m_sls);
	    lock.drop();
	    transmitRLC(this,cic,label,false,"unknown-ie",diagnostic,m_location);
	}
	if (callReleased)
	    *callReleased = true;
	return true;
    }
    // Send CNF params
    const String& cnf = list[prefix + "parameters-unhandled-cnf"];
    if (!cnf)
	return false;
    DDebug(this,DebugAll,"processParamCompat() cic=%u sending CNF for '%s' [%p]",
	cic,cnf.c_str(),this);
    String diagnostic;
    hexifyIsupParams(diagnostic,cnf);
    if (diagnostic && m_remotePoint) {
	SS7Label label(m_type,*m_remotePoint,*m_defPoint,
	    (m_defaultSls == SlsCircuit) ? cic : m_sls);
	transmitCNF(this,cic,label,false,"unknown-ie",diagnostic,m_location);
    }
    return !diagnostic.null();
}

HandledMSU SS7ISUP::receivedMSU(const SS7MSU& msu, const SS7Label& label, SS7Layer3* network, int sls)
{
    if (msu.getSIF() != sif() || !hasPointCode(label.dpc()) || !handlesRemotePC(label.opc()))
	return HandledMSU::Rejected;
    // we should have at least 2 bytes CIC and 1 byte message type
    const unsigned char* s = msu.getData(label.length()+1,3);
    if (!s) {
	Debug(this,DebugNote,"Got short MSU");
	return false;
    }
    unsigned int len = msu.length()-label.length()-1;
    unsigned int cic = s[0] | (s[1] << 8);
    SS7MsgISUP::Type type = (SS7MsgISUP::Type)s[2];
    String name = SS7MsgISUP::lookup(type);
    if (!name) {
        String tmp;
	tmp.hexify((void*)s,len,' ');
	Debug(this,DebugMild,"Received unknown ISUP type 0x%02x, cic=%u, length %u: %s",
	    type,cic,len,tmp.c_str());
	name = (int)type;
    }
    if (!(circuits() && circuits()->find(cic))) {
	Debug(this,m_cicWarnLevel,"Received ISUP type 0x%02x (%s) for unknown cic=%u",
	    type,name.c_str(),cic);
	m_cicWarnLevel = DebugAll;
	return HandledMSU::NoCircuit;
    }
    bool ok = processMSU(type,cic,s+3,len-3,label,network,sls);
    if (!ok && debugAt(DebugMild)) {
	String tmp;
	tmp.hexify((void*)s,len,' ');
	Debug(this,DebugMild,"Unhandled ISUP type %s, cic=%u, length %u: %s",
	    name.c_str(),cic,len,tmp.c_str());
    }
    return ok;
}

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

    SS7MsgISUP* msg = new SS7MsgISUP(type,cic);
    if (!SS7MsgISUP::lookup(type)) {
	String tmp;
	tmp.hexify(&type,1);
	msg->params().assign("Message_" + tmp);
    }
    if (!decodeMessage(msg->params(),type,label.type(),paramPtr,paramLen)) {
	TelEngine::destruct(msg);
	return false;
    }

    if (m_printMsg && debugAt(DebugInfo)) {
	String tmp;
	msg->toString(tmp,label,debugAt(DebugAll),
	    m_extendedDebug ? paramPtr : 0,paramLen);
	Debug(this,DebugInfo,"Received message (%p)%s",msg,tmp.c_str());
    }
    else if (debugAt(DebugAll)) {
	String tmp;
	tmp << label;
	Debug(this,DebugAll,"Received message '%s' cic=%u label=%s",
	    msg->name(),msg->cic(),tmp.c_str());
    }

    // TODO: check parameters-unsupported vs. ParameterCompatInformation

    // Check if we expected some response to UPT
    // Ignore
    if (!m_userPartAvail && m_uptTimer.started()) {
	m_uptTimer.stop();
	const char* oldStat = statusName();
	m_userPartAvail = true;
	m_lockTimer.start();
	Debug(this,DebugInfo,"Remote user part is available");
	if (statusName() != oldStat) {
	    NamedList params("");
	    params.addParam("from",toString());
	    params.addParam("type","trunk");
	    params.addParam("operational",String::boolText(m_l3LinkUp));
	    params.addParam("available",String::boolText(m_userPartAvail));
	    params.addParam("text",statusName());
	    engine()->notify(this,params);
	}
	if (msg->cic() == m_uptCicCode &&
	    (msg->type() == SS7MsgISUP::UPA ||
	     msg->type() == SS7MsgISUP::CVR ||
	     msg->type() == SS7MsgISUP::CNF ||
	     msg->type() == SS7MsgISUP::UEC)) {
	    m_uptCicCode = 0;
	    TelEngine::destruct(msg);
	    return true;
	}
    }

    switch (msg->type()) {
	case SS7MsgISUP::IAM:
	case SS7MsgISUP::SAM:
	case SS7MsgISUP::ACM:
	case SS7MsgISUP::EXM:
	case SS7MsgISUP::CPR:
	case SS7MsgISUP::ANM:
	case SS7MsgISUP::CON:
	case SS7MsgISUP::REL:
	case SS7MsgISUP::SGM:
	case SS7MsgISUP::CCR:
	case SS7MsgISUP::COT:
	case SS7MsgISUP::APM:
	case SS7MsgISUP::SUS:
	case SS7MsgISUP::RES:
	    processCallMsg(msg,label,sls);
	    break;
	case SS7MsgISUP::CRG:
	    switch (getChargeProcessType()) {
		case Confusion:
		    processControllerMsg(msg,label,sls);
		case Ignore:
		    break;
		default:
		    processCallMsg(msg,label,sls);
	    }
	    break;
	case SS7MsgISUP::RLC:
	    if (m_rscCic && m_rscCic->code() == msg->cic())
		processControllerMsg(msg,label,sls);
	    else {
		SignallingMessageTimer* m = findPendingMessage(SS7MsgISUP::RSC,msg->cic(),true);
		if (m) {
		    DDebug(this,DebugAll,"RSC confirmed for pending cic=%u",msg->cic());
		    resetCircuit(msg->cic(),false,false);
		    TelEngine::destruct(m);
		}
		else
		    processCallMsg(msg,label,sls);
	    }
	    break;
	default:
	    processControllerMsg(msg,label,sls);
    }

    TelEngine::destruct(msg);
    return true;
}

// MTP notification that remote user part is unavailable
void SS7ISUP::receivedUPU(SS7PointCode::Type type, const SS7PointCode node,
    SS7MSU::Services part, unsigned char cause, const SS7Label& label, int sls)
{
    if (part != sif() || !handlesRemotePC(node))
	return;
    if (!(m_userPartAvail && m_uptTimer.interval()))
	return;
    const char* oldStat = statusName();
    Debug(this,DebugNote,"Remote User Part is unavailable (received UPU)");
    m_userPartAvail = false;
    m_uptTimer.start();
    if (statusName() != oldStat) {
	NamedList params("");
	params.addParam("from",toString());
	params.addParam("type","trunk");
	params.addParam("operational",String::boolText(m_l3LinkUp));
	params.addParam("available",String::boolText(m_userPartAvail));
	params.addParam("text",statusName());
	engine()->notify(this,params);
    }
}

// Process an event received from a non-reserved circuit
SignallingEvent* SS7ISUP::processCircuitEvent(SignallingCircuitEvent*& event,
    SignallingCall* call)
{
    if (!event)
	return 0;
    SignallingEvent* ev = 0;
    switch (event->type()) {
	case SignallingCircuitEvent::Alarm:
	case SignallingCircuitEvent::NoAlarm:
	    if (event->circuit()) {
		lock();
		bool block = (event->type() == SignallingCircuitEvent::Alarm);
		bool blocked = (0 != event->circuit()->locked(SignallingCircuit::LockLocalHWFail));
		// Avoid notifying the same state
		if (block != blocked) {
		    event->circuit()->hwLock(block,false,true,true);
		    if (!m_lockTimer.started())
			m_lockTimer.start();
		    if (block)
			cicHwBlocked(event->circuit()->code(),String("1"));
		}
		unlock();
		ev = new SignallingEvent(event,call);
	    }
	    break;
	case SignallingCircuitEvent::Dtmf:
	    if (event->getValue(YSTRING("tone"))) {
		SignallingMessage* msg = new SignallingMessage(event->c_str());
		msg->params().addParam("tone",event->getValue(YSTRING("tone")));
		msg->params().addParam("inband",event->getValue(YSTRING("inband"),String::boolText(true)));
		ev = new SignallingEvent(SignallingEvent::Info,msg,call);
		TelEngine::destruct(msg);
	    }
	    break;
	default:
	    ev = new SignallingEvent(event,call);
    }
    TelEngine::destruct(event);
    return ev;
}

// Initiate a circuit reset
bool SS7ISUP::startCircuitReset(SignallingCircuit*& cic, const String& timer)
{
    if (!cic)
	return false;
    bool ok = false;
    do {
	Lock lock(this);
	// Check if the circuit can be reset
	// Do nothing on locally locked circuit: this would clear our lock
	// state at remote side. See Q.764 2.9.3.1
	if (cic->locked(SignallingCircuit::LockLocal)) {
	    Debug(this,DebugNote,
		"Failed to start reset on locally locked circuit (cic=%u timer=%s) [%p]",
		cic->code(),timer.c_str(),this);
	    ok = SignallingCallControl::releaseCircuit(cic);
	    break;
	}
	// Check if there is any management operation in progress on the cic
	if (cic->locked(SignallingCircuit::LockBusy))
	    break;
	bool relTimeout = (timer == "T5");
	Debug(this,!relTimeout ? DebugAll : DebugNote,
	    "Starting circuit %u reset on timer %s [%p]",
	    cic->code(),timer.c_str(),this);
	// TODO: alert maintenance if T5 timer expired
	SignallingMessageTimer* m = 0;
	if (relTimeout)
	    m = new SignallingMessageTimer(m_t17Interval);
	else
	    m = new SignallingMessageTimer(m_t16Interval,m_t17Interval);
	m = m_pending.add(m);
	if (m) {
	    cic->setLock(SignallingCircuit::Resetting);
	    SS7MsgISUP* msg = new SS7MsgISUP(SS7MsgISUP::RSC,cic->code());
	    msg->params().addParam("isup_pending_reason",timer,false);
	    if (relTimeout)
		msg->params().addParam("isup_alert_maint",String::boolText(true));
	    msg->ref();
	    m->message(msg);
	    lock.drop();
	    ok = true;
	    SS7Label label;
	    if (setLabel(label,msg->cic()))
		transmitMessage(msg,label,false);
	}
	else {
	    Debug(this,DebugNote,
		"Failed to add circuit %u reset to pending messages timer=%s [%p]",
		cic->code(),timer.c_str(),this);
	    ok = SignallingCallControl::releaseCircuit(cic);
	}
    } while (false);
    TelEngine::destruct(cic);
    return ok;
}

// Process call related messages
void SS7ISUP::processCallMsg(SS7MsgISUP* msg, const SS7Label& label, int sls)
{
    // Find a call for this message, create a new one or drop the message
    RefPointer<SS7ISUPCall> call;
    findCall(msg->cic(),call);
    const char* reason = 0;
    while (true) {
	#define DROP_MSG(res) { reason = res; break; }
	// Avoid cic == 0
	if (!msg->cic())
	    DROP_MSG("invalid CIC")
	// non IAM message. Drop it if there is no call for it
	if ((msg->type() != SS7MsgISUP::IAM) && (msg->type() != SS7MsgISUP::CCR)) {
	    if (!call) {
		if (msg->type() == SS7MsgISUP::REL)
		    DROP_MSG("no call")
		if (msg->type() != SS7MsgISUP::RLC) {
		    // Initiate circuit reset
		    SignallingCircuit* cic = 0;
		    String s(msg->cic());
		    if (reserveCircuit(cic,0,SignallingCircuit::LockLockedBusy,&s))
			startCircuitReset(cic,"T16");
		}
		return;
	    }
	    break;
	}
	// IAM or CCR message
	SignallingCircuit* circuit = 0;
	// Check collision
	if (call) {
	    // If existing call is an incoming one, drop the message (retransmission ?)
	    if (!call->outgoing()) {
		if (msg->type() == SS7MsgISUP::CCR)
		    break;
		else
		    DROP_MSG("retransmission")
	    }
	    Debug(this,DebugNote,"Incoming call %u collide with existing outgoing",msg->cic());
	    // *** See Q.764 2.9.1.4
	    // Drop the request if the outgoing call already received some response or
	    // the destination point code is greater then the originating and the CIC is even
	    if (call->state() > SS7ISUPCall::Setup)
		DROP_MSG("collision - outgoing call responded")
	    // The greater point code should have the even circuit
	    unsigned int dpc = label.dpc().pack(label.type());
	    unsigned int opc = label.opc().pack(label.type());
	    bool controlling = (dpc > opc);
	    bool even = (0 == (msg->cic() % 2));
	    if (controlling == even)
		DROP_MSG("collision - we control the CIC")
	    // Accept the incoming request. Change the call's circuit
	    reserveCircuit(circuit,call->cicRange(),SignallingCircuit::LockLockedBusy);
	    call->replaceCircuit(circuit);
	    circuit = 0;
	    call = 0;
	}
	int flags = SignallingCircuit::LockLockedBusy;
	// Q.764 2.8.2 - accept test calls even if the remote side is blocked
	// Q.764 2.8.2.3 (xiv) - unblock remote side of the circuit for non-test calls
	if ((msg->type() == SS7MsgISUP::CCR) ||
	    (msg->params()[YSTRING("CallingPartyCategory")] == YSTRING("test"))) {
	    Debug(this,DebugInfo,"Received test call on circuit %u",msg->cic());
	    flags = 0;
	}
	else {
	    circuit = circuits() ? circuits()->find(msg->cic()) : 0;
	    if (circuit && circuit->locked(SignallingCircuit::LockRemote)) {
		Debug(this,DebugNote,"Unblocking remote circuit %u on IAM request",msg->cic());
		circuit->hwLock(false,true,0!=circuit->locked(SignallingCircuit::LockRemoteHWFail),false);
		circuit->maintLock(false,true,0!=circuit->locked(SignallingCircuit::LockRemoteMaint),false);
		m_verifyEvent = true;
	    }
	    circuit = 0;
	}
	String s(msg->cic());
	if (reserveCircuit(circuit,0,flags,&s,true)) {
	    call = new SS7ISUPCall(this,circuit,label.dpc(),label.opc(),false,label.sls(),
		0,msg->type() == SS7MsgISUP::CCR);
	    m_calls.append(call);
	    break;
	}
	// Congestion: send REL
	SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::REL,msg->cic());
	m->params().addParam("CauseIndicators","congestion");
	transmitMessage(m,label,true);
	DROP_MSG("can't reserve circuit")
	#undef DROP_MSG
    }
    if (!reason) {
	msg->ref();
	call->enqueue(msg);
    }
    else {
	if (msg->type() != SS7MsgISUP::IAM && msg->type() != SS7MsgISUP::RLC)
	    transmitRLC(this,msg->cic(),label,true);
	if (msg->type() != SS7MsgISUP::RLC)
	    Debug(this,DebugNote,"'%s' with cic=%u: %s",msg->name(),msg->cic(),reason);
    }
}

unsigned int getRangeAndStatus(NamedList& nl, unsigned int minRange, unsigned int maxRange,
    unsigned int maxMap = 0, String** map = 0, unsigned int nCicsMax = 0)
{
    unsigned int range = nl.getIntValue(YSTRING("RangeAndStatus"));
    if (range < minRange || range > maxRange)
	return 0;
    if (!maxMap)
	return range;
    NamedString* ns = nl.getParam(YSTRING("RangeAndStatus.map"));
    if (!ns || ns->length() > maxMap || ns->length() < range)
	return 0;
    if (map) {
	if (nCicsMax) {
	    // Check the number of bits set to 1 (circuits affected)
	    for (unsigned int i = 0; i < ns->length(); i++) {
		if ((*ns)[i] != '1')
		    continue;
		if (!nCicsMax)
                   return 0;
		nCicsMax--;
           }
	}
	*map = ns;
    }
    return range;
}

// Retrieve maintenance/hwfail type indicator
// Return false if invalid
static bool getGrpTypeInd(SS7ISUP* isup, SS7MsgISUP* msg, bool& hwFail, NamedString** ns = 0)
{
    if (!msg)
	return false;
    NamedString* s = msg->params().getParam(YSTRING("GroupSupervisionTypeIndicator"));
    if (s) {
	if (ns)
	    *ns = s;
	hwFail = (*s == YSTRING("hw-failure"));
	if (hwFail || (*s == YSTRING("maintenance")))
	    return true;
    }
    Debug(isup,DebugNote,"%s with unknown/unsupported GroupSupervisionTypeIndicator=%s [%p]",
	msg->name(),TelEngine::c_safe(s),isup);
    return false;
}

// Utility: set invalid-ie reason and diagnostic
static inline void setInvalidIE(unsigned char ie, const char*& reason, String& diagnostic)
{
    reason = "invalid-ie";
    diagnostic.hexify(&ie,1);
}

// Process controller related messages
// Q.764 2.1.12: stop waiting for SGM if message is not: COT,BLK,BLA,UBL,UBA,CGB,CGA,CGU,CUA,CQM,CQR
void SS7ISUP::processControllerMsg(SS7MsgISUP* msg, const SS7Label& label, int sls)
{
    const char* reason = 0;
    String diagnostic;
    bool impl = true;
    bool stopSGM = false;

    // TODO: Check if segmentation should stop for all affected circuits received withing range (CGB,GRS, ...)

    switch (msg->type()) {
	case SS7MsgISUP::CNF: // Confusion
	    // TODO: check if this message was received in response to RSC, UBL, UBK, CGB, CGU
	    Debug(this,DebugNote,"%s with cic=%u cause='%s' diagnostic='%s'",
		msg->name(),msg->cic(),
		msg->params().getValue(YSTRING("CauseIndicators")),
		msg->params().getValue(YSTRING("CauseIndicators.diagnostic")));
	    stopSGM = true;
	    break;
	case SS7MsgISUP::RLC: // Release Complete
	    // Response to RSC: reset local lock flags. Release m_rscCic
	    resetCircuit(msg->cic(),false,false);
	    break;
	case SS7MsgISUP::RSC: // Reset Circuit
	    if (resetCircuit(msg->cic(),true,true)) {
		// Send BLK on previously blocked cic: Q.764 2.9.3.1 c)
		lock();
		SignallingCircuit* cic = circuits() ? circuits()->find(msg->cic()) : 0;
		SS7MsgISUP* m = 0;
		if (cic && cic->locked(SignallingCircuit::LockLocalMaint) &&
		    !cic->locked(SignallingCircuit::LockingMaint))
		    m = buildCicBlock(cic,true,true);
		unlock();
		if (m)
		    transmitMessage(m,label,true);
		transmitRLC(this,msg->cic(),label,true);
	    }
	    else
		reason = "unknown-channel";
	    stopSGM = true;
	    break;
	case SS7MsgISUP::GRS: // Circuit Group Reset
	    stopSGM = true;
	    {
		// Q.763 3.43 min=1 max=31
		unsigned int n = getRangeAndStatus(msg->params(),1,31);
		if (!n) {
		    Debug(this,DebugNote,"%s with invalid range %s",msg->name(),
			msg->params().getValue(YSTRING("RangeAndStatus")));
		    break;
		}
		else if (n == 1 && m_ignoreGRSSingle) {
		    Debug(this,DebugAll,"Ignoring %s with range 1",msg->name());
		    break;
		}
		String map('0',n);
		char* d = (char*)map.c_str();
		for (unsigned int i = 0; i < n; i++)
		    if (!resetCircuit(msg->cic()+i,true,true))
			d[i] = '1';
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::GRA,msg->cic());
		m->params().addParam("RangeAndStatus",String(n));
		m->params().addParam("RangeAndStatus.map",map);
		transmitMessage(m,label,true);
	    }
	    break;
	case SS7MsgISUP::UBL: // Unblocking
	    if (blockCircuit(msg->cic(),false,true,false,true,true))
		transmitMessage(new SS7MsgISUP(SS7MsgISUP::UBA,msg->cic()),label,true);
	    else
		reason = "unknown-channel";
	    break;
	case SS7MsgISUP::BLK: // Blocking
	    if (blockCircuit(msg->cic(),true,true,false,true,true)) {
		transmitMessage(new SS7MsgISUP(SS7MsgISUP::BLA,msg->cic()),label,true);
		// Replace circuit for outgoing call in initial state
		replaceCircuit(msg->cic(),String('1'));
	    }
	    else
		reason = "unknown-channel";
	    break;
	case SS7MsgISUP::UBA: // Unblocking Acknowledgement
	case SS7MsgISUP::BLA: // Blocking Acknowledgement
	    {
		bool block = (msg->type() == SS7MsgISUP::BLA);
		SS7MsgISUP::Type type = block ? SS7MsgISUP::BLK : SS7MsgISUP::UBL;
		SignallingMessageTimer* t = findPendingMessage(type,msg->cic(),true);
		if (t) {
		    SS7MsgISUP* m = static_cast<SS7MsgISUP*>(t->message());
		    bool hw = m && m->params().getBoolValue(YSTRING("isup_pending_block_hwfail"));
		    DDebug(this,m ? DebugAll : DebugNote,"%s confirmed for pending cic=%u",
			  block ? "BLK" : "UBL",msg->cic());
		    TelEngine::destruct(t);
		    blockCircuit(msg->cic(),block,false,hw,true,false,true);
		    sendLocalLock();
		}
		else
		    reason = "wrong-state-message";
	    }
	    break;
	case SS7MsgISUP::CGA: // Circuit Group Blocking Acknowledgement
	case SS7MsgISUP::CUA: // Circuit Group Unblocking Acknowledgement
	    // Q.763 3.43 range can be 1..256. Max bits set to 1 should be 32
	    // Bit: 0-no indication 1-block/unblock
	    {
		bool hwFail = false;
		NamedString* grpSuperType = 0;
		if (!getGrpTypeInd(this,msg,hwFail,&grpSuperType))
		    break;
		String* srcMap = 0;
		unsigned int nCics = getRangeAndStatus(msg->params(),1,256,256,&srcMap,32);
		if (!nCics) {
		    Debug(this,DebugNote,"%s (%s) cic=%u with invalid range %s or map=%s",
			msg->name(),grpSuperType->c_str(),msg->cic(),
			msg->params().getValue(YSTRING("RangeAndStatus")),
			msg->params().getValue(YSTRING("RangeAndStatus.map")));
		    break;
		}
		bool block = (msg->type() == SS7MsgISUP::CGA);
		lock();
		// Check for correct response: same msg type, circuit code, type indicator, circuit map
		SS7MsgISUP::Type type = block ? SS7MsgISUP::CGB : SS7MsgISUP::CGU;
		SignallingMessageTimer* t = findPendingMessage(type,msg->cic(),
		    grpSuperType->name(),*grpSuperType);
		if (!t) {
		    Debug(this,DebugNote,"%s (%s) cic=%u: no request for it in our queue",
			msg->name(),grpSuperType->c_str(),msg->cic());
		    unlock();
		    break;
		}
		SS7MsgISUP* m = static_cast<SS7MsgISUP*>(t->message());
		String map;
		while (m) {
		    // Check map
		    map = m->params()[YSTRING("RangeAndStatus.map")];
		    if (!map)
			break;
		    if (map.length() != nCics) {
			map.clear();
			break;
		    }
		    for (unsigned int i = 0; i < map.length(); i++)
			if (map[i] == '0' && (*srcMap)[i] != '0') {
			    map.clear();
			    break;
			}
		    break;
		}
		if (map) {
		    DDebug(this,DebugAll,"%s (%s) confirmed for pending cic=%u",
			m->name(),grpSuperType->c_str(),msg->cic());
		    m_pending.remove(t);
		}
		unlock();
		if (!map) {
		    Debug(this,DebugNote,"%s (%s) cic=%u with unnacceptable range %s or map=%s",
			msg->name(),grpSuperType->c_str(),msg->cic(),
			msg->params().getValue(YSTRING("RangeAndStatus")),
			msg->params().getValue(YSTRING("RangeAndStatus.map")));
		    break;
		}
		for (unsigned int i = 0; i < map.length(); i++)
		    if (map[i] != '0')
			blockCircuit(msg->cic()+i,block,false,hwFail,true,false,true);
		sendLocalLock();
	    }
	    break;
	case SS7MsgISUP::CGB: // Circuit Group Blocking
	case SS7MsgISUP::CGU: // Circuit Group Unblocking
	    // Q.763 3.43 range can be 1..256. Max bits set to 1 should be 32
	    // Bit: 0-no indication 1-block/unblock
	    {
		bool hwFail = false;
		if (!getGrpTypeInd(this,msg,hwFail))
		    break;
	        bool block = (msg->type() == SS7MsgISUP::CGB);
		String* srcMap = 0;
		unsigned int nCics = getRangeAndStatus(msg->params(),1,256,256,&srcMap,32);
		if (!nCics) {
		    Debug(this,DebugNote,"%s with invalid range %s or map=%s",msg->name(),
			msg->params().getValue(YSTRING("RangeAndStatus")),
			msg->params().getValue(YSTRING("RangeAndStatus.map")));
		    break;
		}
		else if (nCics == 1 && ((block && m_ignoreCGBSingle) || (!block && m_ignoreCGUSingle))) {
		    Debug(this,DebugAll,"Ignoring %s with range 1",msg->name());
		    break;
		}
		String map('0',srcMap->length());
		char* d = (char*)map.c_str();
		for (unsigned int i = 0; i < srcMap->length(); i++)
		    if (srcMap->at(i) != '0' && blockCircuit(msg->cic()+i,block,true,hwFail,true,true))
			d[i] = '1';
		SS7MsgISUP* m = new SS7MsgISUP(block?SS7MsgISUP::CGA:SS7MsgISUP::CUA,msg->cic());
		m->params().copyParam(msg->params(),"GroupSupervisionTypeIndicator");
		m->params().addParam("RangeAndStatus",String(nCics));
		m->params().addParam("RangeAndStatus.map",map);
		transmitMessage(m,label,true);
		// Replace circuits for outgoing calls in initial state
		// Terminate all others when blocking for hw failure
		if (block) {
		    if (hwFail)
			cicHwBlocked(msg->cic(),map);
		    else
			replaceCircuit(msg->cic(),map);
		}
	    }
	    break;
	case SS7MsgISUP::UEC: // Unequipped CIC (national use)
	    Debug(this,DebugWarn,"%s for cic=%u. Circuit is unequipped on remote side",
		msg->name(),msg->cic());
	    blockCircuit(msg->cic(),true,true,false,true,true);
	    break;
	case SS7MsgISUP::UPT: // User Part Test
	    transmitMessage(new SS7MsgISUP(SS7MsgISUP::UPA,msg->cic()),label,true);
	    break;
	case SS7MsgISUP::UPA: // User Part Available
	    if (m_uptCicCode && m_uptCicCode == msg->cic()) {
		DDebug(this,DebugInfo,"Received valid %s",msg->name());
		m_uptCicCode = 0;
	    }
#ifdef DEBUG
	    else
		Debug(this,DebugMild,"Received unexpected %s",msg->name());
#endif
	    break;
	case SS7MsgISUP::GRA: // Circuit Group Reset Acknowledgement
	    // TODO: stop receiving segments
	    reason = "wrong-state-message";
	    break;
	case SS7MsgISUP::CVT: // Circuit Validation Test (ANSI)
	    if (circuits() && circuits()->find(msg->cic())) {
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CVR,msg->cic());
		m->params().addParam("CircuitValidationRespIndicator","success");
		transmitMessage(m,label,true);
	    }
	    else
		reason = "unknown-channel";
	    break;
	case SS7MsgISUP::CQM: // Circuit Group Query (national use)
	    if (circuits()) {
		// Q.763 3.43 min=1 max=31
		unsigned int n = getRangeAndStatus(msg->params(),1,31);
		if (!n) {
		    reason = "invalid-ie";
		    break;
		}
		DataBlock si(0,n);
		for (unsigned int i = 0; i < n; i++) {
		    unsigned char* state = si.data(i);
		    if (!state)
			break;
		    SignallingCircuit* circuit = circuits()->find(msg->cic()+i);
		    if (circuit && (circuit->status() != SignallingCircuit::Missing)) {
			switch (circuit->locked(SignallingCircuit::LockLocalMaint | SignallingCircuit::LockRemoteMaint)) {
			    case SignallingCircuit::LockLocalMaint:
				*state = 0x01; // locally maint blocked
				break;
			    case SignallingCircuit::LockRemoteMaint:
				*state = 0x02; // remote maint blocked
				break;
			    case SignallingCircuit::LockLocalMaint | SignallingCircuit::LockRemoteMaint:
				*state = 0x03; // locally and remote maint blocked
				break;
			}
			switch (circuit->locked(SignallingCircuit::LockLocalHWFail | SignallingCircuit::LockRemoteHWFail)) {
			    case SignallingCircuit::LockLocalHWFail:
				*state |= 0x1c; // locally hw blocked
				continue;
			    case SignallingCircuit::LockRemoteHWFail:
				*state |= 0x2c; // locally hw blocked
				continue;
			    case SignallingCircuit::LockLocalHWFail | SignallingCircuit::LockRemoteHWFail:
				*state |= 0x3c; // locally and remotely hw blocked
				continue;
			}
			if (circuit->connected())
			    *state |= 0x04; // incoming busy
			else if (!circuit->available())
			    *state |= 0x08; // outgoing busy
			else
			    *state |= 0x0c; // idle
		    }
		    else
			*state = 0x03; // Unequipped
		}
		String tmp;
		tmp.hexify(si.data(),si.length(),' ');
		DDebug(this,DebugInfo,"Sending CQR (%u+%u): %s",msg->cic(),n,tmp.c_str());
		SS7MsgISUP* m = new SS7MsgISUP(SS7MsgISUP::CQR,msg->cic());
		m->params().addParam("RangeAndStatus",String(n));
		m->params().addParam("CircuitStateIndicator",tmp);
		transmitMessage(m,label,true);
	    }
	    else
		reason = "unknown-channel";
	    break;
	case SS7MsgISUP::CQR: // Circuit Group Query Response (national use)
	case SS7MsgISUP::CVR: // Circuit Validation Response (ANSI)
	case SS7MsgISUP::LPA: // Loopback Acknowledge (national use)
	    // Known but not implemented responses, just ignore them
	    impl = false;
	    break;
	default:
	    impl = false;
	    // Q.764 2.9.5.1: call in Setup state:
	    // incoming: drop it and reset cic
	    // outgoing: repeat IAM and reset cic
	    lock();
	    SS7ISUPCall* call = findCall(msg->cic());
	    if (call)
		call->ref();
	    unlock();
	    if (m_dropOnUnknown && call && call->earlyState() && msg->type() != SS7MsgISUP::CRG) {
		Debug(this,DebugNote,
		    "Received unexpected message for call %u (%p) in initial state",
		    msg->cic(),call);
		if (call->outgoing())
		    replaceCircuit(msg->cic(),String("1"),false);
		else {
		    call->setTerminate(false,"normal",0,m_location);
		    SignallingCircuit* c = call->m_circuit;
		    if (c && c->ref())
			startCircuitReset(c,String::empty());
		}
	    }
	    else {
		bool cnf = false;
		bool release = false;
		getMsgCompat(msg,release,cnf);
		if (cnf || release) {
		    reason = "unknown-message";
		    unsigned char type = msg->type();
		    diagnostic.hexify(&type,1);
		    if (release) {
			if (call)
			    call->setTerminate(true,reason,diagnostic,m_location);
			else
			    transmitRLC(this,msg->cic(),label,true,reason,diagnostic,m_location);
			// Avoid sending CNF
			reason = 0;
		    }
		}
	    }
	    TelEngine::destruct(call);
    }
    if (stopSGM) {
	RefPointer<SS7ISUPCall> call;
	findCall(msg->cic(),call);
	if (call)
	    call->stopWaitSegment(false);
	call = 0;
    }
    if (reason || !impl) {
	Debug(this,impl?DebugNote:DebugStub,"'%s' with cic=%u: %s",
	    msg->name(),msg->cic(),(reason ? reason : "Not implemented, ignoring"));
	if (reason)
	    transmitCNF(this,msg->cic(),label,true,reason,diagnostic);
    }
}

// Replace a call's circuit if checkCall is true
// Release currently reseting circuit if the code match
// Clear lock flags
// See Q.764 2.9.3.1
bool SS7ISUP::resetCircuit(unsigned int cic, bool remote, bool checkCall)
{
    SignallingCircuit* circuit = circuits() ? circuits()->find(cic) : 0;
    if (!circuit)
	return false;
    DDebug(this,DebugAll,"Reseting circuit %u",cic);
    if (checkCall) {
	RefPointer<SS7ISUPCall> call;
	findCall(cic,call);
	if (call) {
	    if (call->outgoing() && call->state() == SS7ISUPCall::Setup) {
	        SignallingCircuit* newCircuit = 0;
		reserveCircuit(newCircuit,call->cicRange(),SignallingCircuit::LockLockedBusy);
		call->replaceCircuit(newCircuit);
	    }
	    else
		call->setTerminate(false,"normal");
	}
    }
    // Remove remote lock flags (Q.764 2.9.3.1)
    if (remote && circuit->locked(SignallingCircuit::LockRemote)) {
	Debug(this,DebugNote,"Unblocking remote circuit %u on reset request",cic);
	circuit->hwLock(false,true,0!=circuit->locked(SignallingCircuit::LockRemoteHWFail),false);
	circuit->maintLock(false,true,0!=circuit->locked(SignallingCircuit::LockRemoteMaint),false);
	m_verifyEvent = true;
    }
    // Remove pending RSC/REL. Reset 'Resetting' flag'
    SignallingMessageTimer* m = findPendingMessage(SS7MsgISUP::RSC,cic,true);
    if (!m)
	m = findPendingMessage(SS7MsgISUP::REL,cic,true);
    if (m) {
	Debug(this,DebugAll,"Pending %s`cic=%u removed",m->message()->name(),cic);
	TelEngine::destruct(m);
    }
    circuit->resetLock(SignallingCircuit::Resetting);
    if (m_rscCic && m_rscCic->code() == cic)
	releaseCircuit(m_rscCic);
    else
	circuit->status(SignallingCircuit::Idle);
    return true;
}

// Block/unblock a circuit
// See Q.764 2.8.2
bool SS7ISUP::blockCircuit(unsigned int cic, bool block, bool remote, bool hwFail,
	bool changed, bool changedState, bool resetLocking)
{
    XDebug(this,DebugAll,"blockCircuit(%u,%u,%u,%u,%u,%u,%u)",
	cic,block,remote,hwFail,changed,changedState,resetLocking);
    SignallingCircuit* circuit = circuits() ? circuits()->find(cic) : 0;
    if (!circuit)
	return false;

    bool something = false;
    if (hwFail)
	something = circuit->hwLock(block,remote,changed,changedState);
    else
	something = circuit->maintLock(block,remote,changed,changedState);
    if (resetLocking && !remote)
	circuit->resetLock(hwFail ? SignallingCircuit::LockingHWFail : SignallingCircuit::LockingMaint);

    if (something) {
	Debug(this,DebugNote,"%s %s side of circuit %u. Current flags 0x%x",
	    (block ? "Blocked" : "Unblocked"),
	    (remote ? "remote" : "local"),
	    cic,circuit->locked(-1));
	m_verifyEvent = true;
    }
    return true;
}

SS7ISUPCall* SS7ISUP::findCall(unsigned int cic)
{
    for (ObjList* o = m_calls.skipNull(); o; o = o->skipNext()) {
	SS7ISUPCall* call = static_cast<SS7ISUPCall*>(o->get());
	if (call->id() == cic)
	    return call;
    }
    return 0;
}

// Utility used in sendLocalLock()
// Check if a circuit has lock change flag set and can be locked (not busy)
static inline bool canLock(SignallingCircuit* cic, bool hw)
{
    if (hw)
	return cic->locked(SignallingCircuit::LockLocalHWFailChg) &&
            !cic->locked(SignallingCircuit::LockingHWFail | SignallingCircuit::Resetting);
    return cic->locked(SignallingCircuit::LockLocalMaintChg) &&
	!cic->locked(SignallingCircuit::LockingMaint | SignallingCircuit::Resetting);
}

// Utility: check if a circuit needs lock and not currently locking
static inline void checkNeedLock(SignallingCircuit* cic, bool& needLock)
{
    if (needLock)
	return;
    needLock = cic->locked(SignallingCircuit::LockLocalChg) &&
        !cic->locked(SignallingCircuit::LockingHWFail | SignallingCircuit::LockingMaint);
}

// Send blocking/unblocking messages
// Return false if no request was sent
bool SS7ISUP::sendLocalLock(const Time& when)
{
    Lock lock(this);
    if (!circuits())
	return false;
    bool needLock = false;
    ObjList msgs;
    while (true) {
	bool hwReq = false;
	bool lockReq = false;
	unsigned int code = 0;
	int locking = 0;
	// Peek a starting circuit whose local state changed
	ObjList* o = circuits()->circuits().skipNull();
	SignallingCircuitSpan* span = 0;
	for (; o; o = o->skipNext()) {
	    SignallingCircuit* cic = static_cast<SignallingCircuit*>(o->get());
	    if (canLock(cic,true)) {
		hwReq = true;
		lockReq = (0 != cic->locked(SignallingCircuit::LockLocalHWFail));
		locking = SignallingCircuit::LockingHWFail;
	    }
	    else if (canLock(cic,false)) {
		hwReq = false;
		lockReq = (0 != cic->locked(SignallingCircuit::LockLocalMaint));
		locking = SignallingCircuit::LockingMaint;
	    }
	    else {
		checkNeedLock(cic,needLock);
		continue;
	    }
	    code = cic->code();
	    span = cic->span();
	    cic->setLock(locking);
	    o = o->skipNext();
	    break;
	}
	if (!code)
	    break;
	// If remote doesn't support group block/unblock just send BLK/UBL
	if (!m_lockGroup)
	    o = 0;
	// Check if we can pick a range of circuits within the same span
	//  with the same operation to do
	// Q.763 3.43: range can be 2..256. Bit: 0-no indication 1-block/unblock.
	// Max bits set to 1 must be 32
	char d[256];
	d[0] = '1';
	unsigned int cics = 1;
	unsigned int lockRange = 1;
	ObjList* cicPos = o;
	int newRange = 0;
	int flag = hwReq ? SignallingCircuit::LockLocalHWFail : SignallingCircuit::LockLocalMaint;
	for (; o && cics < 32 && lockRange < 256; o = o->skipNext()) {
	    SignallingCircuit* cic = static_cast<SignallingCircuit*>(o->get());
	    // Presume all circuits belonging to the same span to follow each other in the list
	    if (span != cic->span())
		break;
	    // Make sure the circuit codes are sequential
	    if ((code + lockRange) != cic->code()) {
		if (!newRange)
		    newRange = checkValidRange(code,cic->code());
		checkNeedLock(cic,needLock);
		continue;
	    }
	    // Add circuit to map. Skip busy circuits
	    // Circuit must have the same lock type and flags as the base circuit's
	    if (canLock(cic,hwReq) && (lockReq == (0 != cic->locked(flag)))) {
		cic->setLock(locking);
		d[lockRange] = '1';
		cics++;
	    }
	    else {
		checkNeedLock(cic,needLock);
		d[lockRange] = '0';
	    }
	    lockRange++;
	}
	if (cics == 1) {
	    if (lockRange > 1) {
		// Shorten the range: be nice to remote party, no need to check the whole map
		if (hwReq)
		    lockRange = 2;
		else
		    lockRange = 1;
	    }
	    else if (m_lockGroup && hwReq) {
		if (!newRange) {
		    // Bad luck: check for a code before the circuit
		    for (o = circuits()->circuits().skipNull(); o && o != cicPos; o = o->skipNext()) {
			SignallingCircuit* cic = static_cast<SignallingCircuit*>(o->get());
			if (span != cic->span())
			    continue;
			newRange = checkValidRange(code,cic->code());
			if (newRange)
			    break;
		    }
		}
		if (newRange)
		    adjustRangeAndStatus(d,code,lockRange,newRange);
		else
		    Debug(this,DebugNote,
			"Failed to pick a second circuit to group HW %sblock cic=%u [%p]",
			lockReq ? "" : "un",code,this);
	    }
	}
	else {
	    // Shorten range
	    unsigned int last = lockRange;
	    while (d[--last] == '0')
		lockRange--;
	}
	// Build and send the message
	// Don't send individual circuit blocking for HW failure (they are supposed
	//  to be sent for maintenance reason)
	String map(d,lockRange);
	SS7MsgISUP* msg = 0;
	SignallingMessageTimer* t = 0;
	if (m_lockGroup && (map.length() > 1 || hwReq)) {
	    msg = new SS7MsgISUP((lockReq ? SS7MsgISUP::CGB : SS7MsgISUP::CGU),code);
	    msg->params().addParam("GroupSupervisionTypeIndicator",
		(hwReq ? "hw-failure" : "maintenance"));
	    msg->params().addParam("RangeAndStatus",String(map.length()));
	    msg->params().addParam("RangeAndStatus.map",map);
	    if (lockReq)
		t = new SignallingMessageTimer(m_t18Interval,m_t19Interval);
	    else
		t = new SignallingMessageTimer(m_t20Interval,m_t21Interval);
	}
	else {
	    msg = new SS7MsgISUP(lockReq ? SS7MsgISUP::BLK : SS7MsgISUP::UBL,code);
	    // Remember HW/maintenance flag
	    if (hwReq)
		msg->params().addParam("isup_pending_block_hwfail",String::boolText(true));
	    if (lockReq)
		t = new SignallingMessageTimer(m_t12Interval,m_t13Interval);
	    else
		t = new SignallingMessageTimer(m_t14Interval,m_t15Interval);
	}
	t->message(msg);
	m_pending.add(t);
	msg->ref();
	msgs.append(msg)->setDelete(false);
    }
    // Restart timer if we still have cics needing lock
    DDebug(this,DebugAll,"%s circuit locking timer",needLock ? "Starting" : "Stopping");
    if (needLock)
	m_lockTimer.start(when.msec());
    else
	m_lockTimer.stop();
    lock.drop();
    return transmitMessages(msgs);
}

// Fill label from local/remote point codes
bool SS7ISUP::setLabel(SS7Label& label, unsigned int cic)
{
    Lock lock(this);
    if (!(m_remotePoint && m_defPoint))
	return false;
    label.assign(m_type,*m_remotePoint,*m_defPoint,
	(m_defaultSls == SlsCircuit) ? cic : m_sls);
    return true;
}

// Retrieve a pending message
SignallingMessageTimer* SS7ISUP::findPendingMessage(SS7MsgISUP::Type type, unsigned int cic,
    bool remove)
{
    Lock lock(this);
    for (ObjList* o = m_pending.skipNull(); o; o = o->skipNext()) {
	SignallingMessageTimer* m = static_cast<SignallingMessageTimer*>(o->get());
	SS7MsgISUP* msg = static_cast<SS7MsgISUP*>(m->message());
	if (msg && msg->type() == type && msg->cic() == cic) {
	    if (remove)
		o->remove(false);
	    return m;
	}
    }
    return 0;
}

// Retrieve a pending message with given parameter
SignallingMessageTimer* SS7ISUP::findPendingMessage(SS7MsgISUP::Type type, unsigned int cic,
    const String& param, const String& value, bool remove)
{
    Lock lock(this);
    for (ObjList* o = m_pending.skipNull(); o; o = o->skipNext()) {
	SignallingMessageTimer* m = static_cast<SignallingMessageTimer*>(o->get());
	SS7MsgISUP* msg = static_cast<SS7MsgISUP*>(m->message());
	if (msg && msg->type() == type && msg->cic() == cic && msg->params()[param] == value) {
	    if (remove)
		o->remove(false);
	    return m;
	}
    }
    return 0;
}

// Transmit a list of messages. Return true if at least 1 message was sent
bool SS7ISUP::transmitMessages(ObjList& list)
{
    ObjList* o = list.skipNull();
    if (!o)
	return false;
    for (; o; o = o->skipNext()) {
	SS7MsgISUP* msg = static_cast<SS7MsgISUP*>(o->get());
    	SS7Label label;
	setLabel(label,msg->cic());
	if (m_duplicateCGB && (msg->type() == SS7MsgISUP::CGB)) {
	    // ANSI needs the CGB duplicated
	    msg->ref();
	    transmitMessage(msg,label,false);
	}
	transmitMessage(msg,label,false);
    }
    return true;
}

// Utility: check if a circuit exists and can be start an (un)block operation
static const char* checkBlockCic(SignallingCircuit* cic, bool block, bool maint,
    bool force)
{
    if (!cic)
	return "not found";
    int flg = cic->locked(maint ? SignallingCircuit::LockLocalMaint :
	SignallingCircuit::LockLocalHWFail);
    if ((block == (0 != flg)) && !force)
	return "already in the same state";
    flg = maint ? SignallingCircuit::LockingMaint : SignallingCircuit::LockingHWFail;
    if (cic->locked(flg | SignallingCircuit::Resetting) && !force)
	return "busy locking or resetting";
    return 0;
}

// Handle circuit(s) (un)block command
bool SS7ISUP::handleCicBlockCommand(const NamedList& p, bool block)
{
    if (!circuits())
	return false;
    SS7MsgISUP* msg = 0;
    SS7MsgISUP::Type remove = SS7MsgISUP::Unknown;
    bool force = p.getBoolValue(YSTRING("force"));
    String* param = p.getParam(YSTRING("circuit"));
    bool remote = p.getBoolValue(YSTRING("remote"));
    Lock mylock(this);
    if (param) {
	if (remote) {
	    unsigned int code = param->toInteger();
	    return handleCicBlockRemoteCommand(p,&code,1,block);
	}
	SignallingCircuit* cic = circuits()->find(param->toInteger());
	msg = buildCicBlock(cic,block,force);
	if (!msg)
	    return false;
	if (force)
	    remove = block ? SS7MsgISUP::UBL : SS7MsgISUP::BLK;
    }
    else {
	// NOTE: we assume the circuits belongs to the same span for local (un)block
	param = p.getParam(YSTRING("circuits"));
	if (TelEngine::null(param)) {
	    Debug(this,DebugNote,"Circuit '%s' missing circuit(s)",
		p.getValue(YSTRING("operation")));
	    return false;
	}
	// Parse the range
	unsigned int count = 0;
	unsigned int* cics = SignallingUtils::parseUIntArray(*param,1,0xffffffff,count,true);
	if (!cics) {
	    // Allow '*' (all circuits) for remote
	    if (!(remote && *param == YSTRING("*"))) {
		SignallingCircuitRange* range = circuits()->findRange(*param);
		if (range)
		    cics = range->copyRange(count);
	    }
	    else {
		String tmp;
		circuits()->getCicList(tmp);
		SignallingCircuitRange* range = new SignallingCircuitRange(tmp);
		cics = range->copyRange(count);
		TelEngine::destruct(range);
	    }
	    if (!cics) {
		Debug(this,DebugNote,"Circuit group '%s': invalid circuits=%s",
		    p.getValue(YSTRING("operation")),param->c_str());
		return false;
	    }
	}
	if (remote) {
	    bool ok = handleCicBlockRemoteCommand(p,cics,count,block);
	    delete[] cics;
	    return ok;
	}
	if (count > 32) {
	    Debug(this,DebugNote,"Circuit group '%s': too many circuits %u (max=32)",
		p.getValue(YSTRING("operation")),count);
	    delete[] cics;
	    return false;
	}
	// Check if all circuits can be (un)blocked
	ObjList list;
	bool maint = !p.getBoolValue(YSTRING("hwfail"));
	for (unsigned int i = 0; i < count; i++) {
	    SignallingCircuit* c = circuits()->find(cics[i]);
	    const char* reason = checkBlockCic(c,block,maint,force);
	    if (reason) {
		Debug(this,DebugNote,"Circuit group '%s' range=%s failed for cic=%u: %s",
		    p.getValue(YSTRING("operation")),param->c_str(),cics[i],reason);
		delete[] cics;
		return false;
	    }
	    list.append(c)->setDelete(false);
	}
	// Retrieve the code: the lowest circuit code
	unsigned int code = cics[0];
	for (unsigned int i = 1; i < count; i++)
	    if (cics[i] < code)
		code = cics[i];
	// Build the range. Fail if falling outside maximum range
	char d[256];
	::memset(d,'0',256);
	d[0] = '1';
	unsigned int lockRange = 1;
	unsigned int nCics = 0;
	for (; nCics < count; nCics++) {
	    if (code == cics[nCics])
		continue;
	    unsigned int pos = cics[nCics] - code;
	    if (pos > 255)
		break;
	    d[pos++] = '1';
	    if (pos > lockRange)
		lockRange = pos;
	}
	delete[] cics;
	if (nCics != count) {
	    Debug(this,DebugNote,"Circuit group '%s': invalid circuit map=%s",
		p.getValue(YSTRING("operation")),param->c_str());
	    return false;
	}
	if (nCics == 1) {
	    // Try to pick another circuit for map
	    SignallingCircuit* cic = static_cast<SignallingCircuit*>(list.skipNull()->get());
	    int newRange = 0;
	    for (ObjList* o = circuits()->circuits().skipNull(); o ; o = o->skipNext()) {
		SignallingCircuit* c = static_cast<SignallingCircuit*>(o->get());
		if (c->span() != cic->span() || c == cic)
		    continue;
		newRange = checkValidRange(cic->code(),c->code());
		if (newRange)
		    break;
	    }
	    if (!newRange) {
		Debug(this,DebugNote,
		    "Circuit group '%s': failed to pick another circuit to send group command",
		    p.getValue(YSTRING("operation")));
		return false;
	    }
	    adjustRangeAndStatus(d,code,lockRange,newRange);
	}
	// Ok: block circuits and send the request
	int flg = maint ? SignallingCircuit::LockingMaint : SignallingCircuit::LockingHWFail;
	for (ObjList* o = list.skipNull(); o ; o = o->skipNext()) {
	    SignallingCircuit* c = static_cast<SignallingCircuit*>(o->get());
	    blockCircuit(c->code(),block,false,!maint,true,true);
	    c->setLock(flg);
	}
	String map(d,lockRange);
        msg = new SS7MsgISUP(block ? SS7MsgISUP::CGB : SS7MsgISUP::CGU,code);
	msg->params().addParam("GroupSupervisionTypeIndicator",
	    (maint ? "maintenance" : "hw-failure"));
	msg->params().addParam("RangeAndStatus",String(map.length()));
	msg->params().addParam("RangeAndStatus.map",map);
	SignallingMessageTimer* t = 0;
	if (block)
	    t = new SignallingMessageTimer(m_t18Interval,m_t19Interval);
	else
	    t = new SignallingMessageTimer(m_t20Interval,m_t21Interval);
        t->message(msg);
	m_pending.add(t);
	msg->ref();
	if (force)
	    remove = block ? SS7MsgISUP::CGU : SS7MsgISUP::CGB;
    }
    if (SS7MsgISUP::Unknown != remove) {
	bool removed = false;
	SignallingMessageTimer* pending = 0;
	if (remove != SS7MsgISUP::CGB && remove != SS7MsgISUP::CGU) {
	    while (0 != (pending = findPendingMessage(remove,msg->cic(),true))) {
		TelEngine::destruct(pending);
		removed = true;
	    }
	}
	else {
	    NamedString* ns = msg->params().getParam(YSTRING("GroupSupervisionTypeIndicator"));
	    while (ns &&
		0 != (pending = findPendingMessage(remove,msg->cic(),ns->name(),*ns,true))) {
		TelEngine::destruct(pending);
		removed = true;
	    }
	}
	if (removed)
	    Debug(this,DebugNote,"Removed pending operation '%s' cic=%u",
		SS7MsgISUP::lookup(remove),msg->cic());
    }
    SS7Label label;
    setLabel(label,msg->cic());
    mylock.drop();
    if (m_duplicateCGB && (msg->type() == SS7MsgISUP::CGB)) {
	// ANSI needs the CGB duplicated
	msg->ref();
	transmitMessage(msg,label,false);
    }
    transmitMessage(msg,label,false);
    return true;
}

// Handle remote circuit(s) (un)block command
bool SS7ISUP::handleCicBlockRemoteCommand(const NamedList& p, unsigned int* cics,
    unsigned int count, bool block)
{
    if (!(cics && count))
	return false;
    bool hwFail = p.getBoolValue(YSTRING("hwfail"));
    if (debugAt(DebugNote)) {
	String s;
	for (unsigned int i = 0; i < count; i++)
	    s.append(String(cics[i]),",");
	Debug(this,DebugNote,"Circuit remote '%s' command: hwfail=%s circuits=%s [%p]",
	    p.getValue(YSTRING("operation")),String::boolText(hwFail),s.c_str(),this);
    }
    bool found = false;
    for (unsigned int i = 0; i < count; i++) {
	if (blockCircuit(cics[i],block,true,hwFail,true,true))
	    found = true;
	else
	    Debug(this,DebugNote,"Circuit remote '%s' command: cic %u not found [%p]",
		p.getValue(YSTRING("operation")),cics[i],this);
    }
    if (found)
	m_verifyEvent = true;
    return found;
}

// Handle circuit(s) event generation command
bool SS7ISUP::handleCicEventCommand(const NamedList& p)
{
    if (!circuits())
	return false;
    int evType = p.getIntValue(YSTRING("type"));
    if (evType <= 0) {
	Debug(this,DebugNote,"Control '%s': invalid type '%s'",
	    p.getValue(YSTRING("operation")),p.getValue(YSTRING("type")));
	return false;
    }
    ObjList cics;
    String* param = p.getParam(YSTRING("circuit"));
    if (param) {
	SignallingCircuit* cic = circuits()->find(param->toInteger());
	if (!cic) {
	    Debug(this,DebugNote,"Control '%s' circuit %s not found",
		p.getValue(YSTRING("operation")),param->c_str());
	    return false;
	}
	cics.append(cic)->setDelete(false);
    }
    else {
	param = p.getParam(YSTRING("circuits"));
	if (TelEngine::null(param)) {
	    Debug(this,DebugNote,"Control '%s' missing circuit(s)",
		p.getValue(YSTRING("operation")));
	    return false;
	}
	// Parse the range
	unsigned int count = 0;
	unsigned int* cList = SignallingUtils::parseUIntArray(*param,1,0xffffffff,count,true);
	if (!cList) {
	    Debug(this,DebugNote,"Control '%s' invalid circuits=%s",
		p.getValue(YSTRING("operation")),param->c_str());
	    return false;
	}
	for (unsigned int i = 0; i < count; i++) {
	    SignallingCircuit* cic = circuits()->find(cList[i]);
	    if (cic) {
		cics.append(cic)->setDelete(false);
		continue;
	    }
	    Debug(this,DebugNote,"Control '%s' circuit %u not found",
		p.getValue(YSTRING("operation")),cList[i]);
	    cics.clear();
	    break;
	}
	delete[] cList;
    }
    ObjList* o = cics.skipNull();
    if (!o)
	return false;
    for (; o; o = o->skipNext()) {
	SignallingCircuit* cic = static_cast<SignallingCircuit*>(o->get());
	SignallingCircuitEvent* cicEvent = new SignallingCircuitEvent(cic,
	    (SignallingCircuitEvent::Type)evType);
	cicEvent->copyParams(p);
	SignallingEvent* ev = processCircuitEvent(cicEvent,0);
	TelEngine::destruct(cicEvent);
	if (ev)
	    delete ev;
    }
    return true;
}

// Try to start single circuit (un)blocking. Set a pending operation on success
// @param force True to ignore resetting/(un)blocking flags of the circuit
// Return built message to be sent on success
SS7MsgISUP* SS7ISUP::buildCicBlock(SignallingCircuit* cic, bool block, bool force)
{
    const char* reason = checkBlockCic(cic,block,true,force);
    if (reason) {
	Debug(this,DebugNote,"Failed to start circuit %sblocking for %u: %s",
	    block ? "" : "un",cic ? cic->code() : 0,reason);
	return 0;
    }
    blockCircuit(cic->code(),block,false,false,true,true);
    cic->setLock(SignallingCircuit::LockingMaint);
    SS7MsgISUP* m = new SS7MsgISUP(block ? SS7MsgISUP::BLK : SS7MsgISUP::UBL,cic->code());
    SignallingMessageTimer* t = 0;
    if (block)
	t = new SignallingMessageTimer(m_t12Interval,m_t13Interval);
    else
        t = new SignallingMessageTimer(m_t14Interval,m_t15Interval);
    t->message(m);
    m_pending.add(t);
    m->ref();
    return m;
}

// Replace circuit for outgoing calls in Setup state
void SS7ISUP::replaceCircuit(unsigned int cic, const String& map, bool rel)
{
    ObjList calls;
    lock();
    for (unsigned int i = 0; i < map.length(); i++) {
	if (map[i] != '1')
	    continue;
        // Replace circuit for call (Q.764 2.8.2.1)
	SS7ISUPCall* call = findCall(cic + i);
	if (call && call->outgoing() && call->state() == SS7ISUPCall::Setup &&
	    call->ref())
	    calls.append(call);
    }
    unlock();
    for (ObjList* o = calls.skipNull(); o; o = o->skipNext()) {
	SS7ISUPCall* call = static_cast<SS7ISUPCall*>(o->get());
	Debug(this,DebugInfo,"Replacing remotely blocked cic=%u for existing call",call->id());
	SignallingCircuit* newCircuit = 0;
	if (call->canReplaceCircuit())
	    reserveCircuit(newCircuit,call->cicRange(),SignallingCircuit::LockLockedBusy);
	if (!newCircuit) {
	    call->setTerminate(rel,"congestion",0,m_location);
	    if (!rel) {
		SignallingCircuit* c = call->m_circuit;
		if (c && c->ref())
		    startCircuitReset(c,String::empty());
	    }
	    continue;
	}
	lock();
	SignallingCircuit* c = circuits()->find(call->id());
	SS7MsgISUP* m = 0;
	if (c && !c->locked(SignallingCircuit::Resetting)) {
	    c->setLock(SignallingCircuit::Resetting);
	    m = new SS7MsgISUP(rel ? SS7MsgISUP::REL : SS7MsgISUP::RSC,call->id());
	    if (rel) {
		m->params().addParam("CauseIndicators","normal");
		m->params().addParam("CauseIndicators.location",m_location,false);
	    }
	    m->ref();
	}
	unlock();
	call->replaceCircuit(newCircuit,m);
	if (m) {
	    SignallingMessageTimer* t = 0;
	    if (rel)
		t = new SignallingMessageTimer(m_t1Interval,m_t5Interval);
	    else
		t = new SignallingMessageTimer(m_t16Interval,m_t17Interval);
	    t->message(m);
	    m_pending.add(t);
	}
    }
}

// Handle circuit hw-fail block
// Replace cics for outgoing calls. Terminate incoming
void SS7ISUP::cicHwBlocked(unsigned int cic, const String& map)
{
    Debug(this,DebugNote,"Circuit(s) in HW failure cic=%u map=%s",cic,map.c_str());
    replaceCircuit(cic,map,true);
    ObjList terminate;
    lock();
    for (unsigned int i = 0; i < map.length(); i++) {
	if (map[i] != '1')
	    continue;
	SS7ISUPCall* call = findCall(cic + i);
	// We've made retransmit attempt for outgoing
	bool processed = !call || (call->outgoing() && call->state() == SS7ISUPCall::Setup);
	if (!processed && call->ref())
	    terminate.append(call);
    }
    unlock();
    setCallsTerminate(terminate,true,"normal",0,m_location);
}


/**
 * SS7BICC
 */
SS7BICC::SS7BICC(const NamedList& params, unsigned char sio)
    : SignallingComponent(params.safe("SS7BICC"),&params,"ss7-bicc"),
      SS7ISUP(params,sio)
{
    m_cicLen = 4;
    Debug(this,DebugInfo,"BICC Call Controller [%p]",this);
}

SS7BICC::~SS7BICC()
{
    cleanup();
    Debug(this,DebugInfo,"BICC Call Controller destroyed [%p]",this);
}

HandledMSU SS7BICC::receivedMSU(const SS7MSU& msu, const SS7Label& label, SS7Layer3* network, int sls)
{
    if (msu.getSIF() != sif() || !hasPointCode(label.dpc()) || !handlesRemotePC(label.opc()))
	return HandledMSU::Rejected;
    // we should have at least 4 bytes CIC and 1 byte message type
    const unsigned char* s = msu.getData(label.length()+1,5);
    if (!s)
	return false;
    unsigned int len = msu.length()-label.length()-1;
    unsigned int cic = s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24);
    SS7MsgISUP::Type type = (SS7MsgISUP::Type)s[4];
    const char* name = SS7MsgISUP::lookup(type);
    if (name) {
	bool ok = processMSU(type,cic,s+5,len-5,label,network,sls);
	String tmp;
	tmp.hexify((void*)s,len,' ');
	Debug(this,ok ? DebugInfo : DebugMild,"Unhandled BICC type %s, cic=%u, length %u: %s",
	    name,cic,len,tmp.c_str());
	return ok;
    }
    String tmp;
    tmp.hexify((void*)s,len,' ');
    Debug(this,DebugMild,"Received unknown BICC type 0x%02x, cic=%u, length %u: %s",
	type,cic,len,tmp.c_str());
    return false;
}

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