/**
 * engine.cpp
 * Yet Another MGCP Stack
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * 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 <yatemgcp.h>

#include <string.h>

namespace TelEngine {

// Engine process, receive or check timeouts
class MGCPPrivateThread : public Thread, public GenObject
{
public:
    enum Action {
	Process = 1,
	Receive = 2,
    };
    // Create a thread to process or receive data for the engine
    MGCPPrivateThread(MGCPEngine* engine, bool process, Thread::Priority priority);
    virtual ~MGCPPrivateThread();
    virtual void run();
private:
    MGCPEngine* m_engine;
    SocketAddr m_addr;
    Action m_action;
};

};

using namespace TelEngine;

#define MAX_TRANS_ID 999999999           // Maximum length for transaction identifier

// Some default values. Time values are given in miliseconds
#define RECV_BUF_LEN 1500                // Receive buffer length
#define TR_RETRANS_INTERVAL 250
#define TR_RETRANS_INTERVAL_MIN 100
#define TR_RETRANS_COUNT 3
#define TR_RETRANS_COUNT_MIN 1
#define TR_EXTRA_TIME 30000
#define TR_EXTRA_TIME_MIN 10000


/**
 * MGCPPrivateThread
 */
MGCPPrivateThread::MGCPPrivateThread(MGCPEngine* engine, bool process,
	Thread::Priority priority)
    : Thread(process?"MGCP Process":"MGCP Receive",priority),
    m_engine(engine),
    m_addr(AF_INET),
    m_action(process?Process:Receive)
{
    DDebug(m_engine,DebugInfo,"MGCPPrivateThread::MGCPPrivateThread() [%p]",this);
    if (m_engine)
	m_engine->appendThread(this);
}

MGCPPrivateThread::~MGCPPrivateThread()
{
    DDebug(m_engine,DebugInfo,"MGCPPrivateThread::~MGCPPrivateThread() [%p]",this);
    if (m_engine)
	m_engine->removeThread(this);
}

void MGCPPrivateThread::run()
{
    DDebug(m_engine,DebugInfo,"%s started [%p]",currentName(),this);
    if (!m_engine)
	return;
    switch (m_action) {
	case Process:
	    m_engine->runProcess();
	    break;
	case Receive:
	    m_engine->runReceive(m_addr);
	    break;
    }
}


/**
 * MGCPEngine
 */
MGCPEngine::MGCPEngine(bool gateway, const char* name, const NamedList* params)
    : Mutex(true,"MGCPEngine"),
    m_iterator(m_transactions),
    m_gateway(gateway),
    m_initialized(false),
    m_nextId(1),
    m_address(AF_INET),
    m_maxRecvPacket(RECV_BUF_LEN),
    m_recvBuf(0),
    m_allowUnkCmd(false),
    m_retransInterval(TR_RETRANS_INTERVAL * 1000),
    m_retransCount(TR_RETRANS_COUNT),
    m_extraTime(TR_EXTRA_TIME * 1000),
    m_parseParamToLower(true),
    m_provisional(true),
    m_ackRequest(true)
{
    debugName((name && *name) ? name : (gateway ? "mgcp_gw" : "mgcp_ca"));

    DDebug(this,DebugAll,"MGCPEngine::MGCPEngine(). Gateway: %s [%p]",
	String::boolText(gateway),this);
    // Add known commands
    for (int i = 0; mgcp_commands[i].token; i++)
	m_knownCommands.append(new String(mgcp_commands[i].token));
    // Init
    if (params)
	initialize(*params);
}

MGCPEngine::~MGCPEngine()
{
    cleanup(false);
    if (m_recvBuf)
	delete[] m_recvBuf;
    DDebug(this,DebugAll,"MGCPEngine::~MGCPEngine()");
}

// Initialize this engine
void MGCPEngine::initialize(const NamedList& params)
{
    int level = params.getIntValue(YSTRING("debuglevel"));
    if (level)
	debugLevel(level);

    m_allowUnkCmd = params.getBoolValue(YSTRING("allow_unknown_cmd"),false);
    int val = params.getIntValue(YSTRING("retrans_interval"),TR_RETRANS_INTERVAL);
    m_retransInterval = 1000 * (val < TR_RETRANS_INTERVAL_MIN ? TR_RETRANS_INTERVAL_MIN : val);
    val = params.getIntValue(YSTRING("retrans_count"),TR_RETRANS_COUNT);
    m_retransCount = (val < TR_RETRANS_COUNT_MIN ? TR_RETRANS_COUNT_MIN : val);
    val = params.getIntValue(YSTRING("extra_time_to_live"),TR_EXTRA_TIME);
    m_extraTime = 1000 * (val < TR_EXTRA_TIME_MIN ? TR_EXTRA_TIME_MIN : val);

    if (!m_initialized) {
	val = params.getIntValue(YSTRING("max_recv_packet"),RECV_BUF_LEN);
	m_maxRecvPacket = val < RECV_BUF_LEN ? RECV_BUF_LEN : val;
    }

    m_parseParamToLower = params.getBoolValue(YSTRING("lower_case_params"),true);
    m_provisional = params.getBoolValue(YSTRING("send_provisional"),true);
    m_ackRequest = params.getBoolValue(YSTRING("request_ack"),true);

    // Bind socket if not valid
    if (!m_socket.valid()) {
	m_address.host(params.getValue("localip"));
	int port = params.getIntValue("port",-1);
	m_address.port(port < 0 ? defaultPort(gateway()) : port);
	m_socket.create(AF_INET,SOCK_DGRAM);

	int reqlen = params.getIntValue("buffer");
	if (reqlen > 0) {
#ifdef SO_RCVBUF
	    int buflen = reqlen;
	    if ((unsigned int)buflen < maxRecvPacket())
		buflen = maxRecvPacket();
	    if (buflen < 4096)
		buflen = 4096;
	    if (m_socket.setOption(SOL_SOCKET,SO_RCVBUF,&buflen,sizeof(buflen))) {
		buflen = 0;
		socklen_t sz = sizeof(buflen);
		if (m_socket.getOption(SOL_SOCKET,SO_RCVBUF,&buflen,&sz))
		    Debug(this,DebugAll,"UDP buffer size is %d (requested %d)",buflen,reqlen);
		else
		    Debug(this,DebugWarn,"Could not get UDP buffer size (requested %d)",reqlen);
	    }
	    else
		Debug(this,DebugWarn,"Could not set UDP buffer size %d (%d: %s)",
		    buflen,m_socket.error(),::strerror(m_socket.error()));
#else
	    Debug(this,DebugMild,"Can't set socket receive buffer: unsupported feature");
#endif
	}

	if (!m_socket.bind(m_address)) {
	    Alarm(this,"socket",DebugWarn,"Failed to bind socket to %s:%d. Error: %d: %s",
		m_address.host().safe(),m_address.port(),
		m_socket.error(),::strerror(m_socket.error()));
	    m_socket.terminate();
	}
	else
	    m_socket.getSockName(m_address);
	m_socket.setBlocking(false);
    }

    // Create private threads
    if (!m_initialized) {
	Thread::Priority prio = Thread::priority(params.getValue("thread_priority"));
	int c = params.getIntValue("private_receive_threads",1);
	for (int i = 0; i < c; i++)
	    (new MGCPPrivateThread(this,false,prio))->startup();
	c = params.getIntValue("private_process_threads",1);
	for (int i = 0; i < c; i++)
	    (new MGCPPrivateThread(this,true,prio))->startup();
    }

    if (debugAt(DebugAll)) {
	String tmp;
	tmp << "\r\ntype:              " << (gateway() ? "Gateway" : "Call Agent");
	tmp << "\r\nbind address:      " << m_address.host() << ":" << m_address.port();
	tmp << "\r\nallow_unknown_cmd: " << String::boolText(m_allowUnkCmd);
	tmp << "\r\nretrans_interval:  " << m_retransInterval;
	tmp << "\r\nretrans_count:     " << m_retransCount;
	tmp << "\r\nlower_case_params: " << m_parseParamToLower;
	tmp << "\r\nmax_recv_packet:   " << maxRecvPacket();
	tmp << "\r\nsend_provisional:  " << provisional();
	Debug(this,DebugInfo,"%s:%s",m_initialized?"Reloaded":"Initialized",tmp.c_str());
    }

    m_initialized = true;
}

// Add a command to the list of known commands
void MGCPEngine::addCommand(const char* cmd)
{
    String* tmp = new String(cmd);
    Lock lock(this);
    tmp->toUpper();
    if (tmp->length() == 4 && !knownCommand(*tmp)) {
	Debug(this,DebugInfo,"Adding extra command %s",tmp->c_str());
	m_knownCommands.append(tmp);
    }
    else
	TelEngine::destruct(tmp);
}

// Append an endpoint to this engine if not already done
void MGCPEngine::attach(MGCPEndpoint* ep)
{
    if (!ep)
	return;
    Lock lock(this);
    if (!m_endpoints.find(ep)) {
	m_endpoints.append(ep);
	Debug(this,DebugInfo,"Attached endpoint '%s'",ep->id().c_str());
    }
}

// Remove an endpoint from this engine and, optionally, remove all its transactions
void MGCPEngine::detach(MGCPEndpoint* ep, bool del, bool delTrans)
{
    if (!ep)
	return;
    if (del)
	delTrans = true;
    Debug(this,DebugInfo,"Detaching endpoint '%s'",ep->id().c_str());

    Lock lock(this);
    // Remove transactions
    if (delTrans) {
	ListIterator iter(m_transactions);
	for (GenObject* o; 0 != (o = iter.get());) {
	    MGCPTransaction* tr = static_cast<MGCPTransaction*>(o);
	    if (ep->id() == tr->ep())
		m_transactions.remove(tr,true);
	}
    }
    m_endpoints.remove(ep,del);
}

// Find an endpoint by its pointer
MGCPEndpoint* MGCPEngine::findEp(MGCPEndpoint* ep)
{
    Lock lock(this);
    return m_endpoints.find(ep) ? ep : 0;
}

// Find an endpoint by its id
MGCPEndpoint* MGCPEngine::findEp(const String& epId)
{
    Lock lock(this);
    return static_cast<MGCPEndpoint*>(m_endpoints[epId]);
}

// find a transaction
MGCPTransaction* MGCPEngine::findTrans(unsigned int id, bool outgoing)
{
    Lock lock(this);
    for (ObjList* o = m_transactions.skipNull(); o; o = o->skipNext()) {
	MGCPTransaction* tr = static_cast<MGCPTransaction*>(o->get());
	if (outgoing == tr->outgoing() && id == tr->id())
	    return tr;
    }
    return 0;
}

// Generate a new id for an outgoing transaction
unsigned int MGCPEngine::getNextId()
{
    Lock lock(this);
    if (m_nextId < MAX_TRANS_ID)
	return m_nextId++;
    m_nextId = 1;
    return MAX_TRANS_ID;
}

// Send a command message. Create a transaction for it.
// Fail if the message is not a valid one or isn't a valid command
MGCPTransaction* MGCPEngine::sendCommand(MGCPMessage* cmd, const SocketAddr& addr,
    bool engineProcess)
{
    if (!cmd)
	return 0;
    if (!(cmd->valid() && cmd->isCommand())) {
	Debug(this,DebugNote,"Can't initiate outgoing transaction for (%p) cmd=%s",
	    cmd,cmd->name().c_str());
	TelEngine::destruct(cmd);
	return 0;
    }

    Lock lock(this);
    return new MGCPTransaction(this,cmd,true,addr,engineProcess);
}

// Read data from the socket. Parse and process the received message
bool MGCPEngine::receive(unsigned char* buffer, SocketAddr& addr)
{
    if (!m_socket.valid())
	return false;
    if (Socket::efficientSelect() && m_socket.canSelect()) {
	bool canRead = false;
	if (m_socket.select(&canRead,0,0,Thread::idleUsec()) && !canRead)
	    return false;
    }
    int len = maxRecvPacket();
    int rd = m_socket.recvFrom(buffer,len,addr);
    if (rd == Socket::socketError()) {
	if (!m_socket.canRetry())
	    Debug(this,DebugWarn,"Socket read error: %d: %s",
		m_socket.error(),::strerror(m_socket.error()));
	return false;
    }
    if (rd > 0)
	len = rd;
    else
	return false;

    ObjList msgs;
    if (!MGCPMessage::parse(this,msgs,buffer,len)) {
	ObjList* o = msgs.skipNull();
	MGCPMessage* msg = static_cast<MGCPMessage*>(o?o->get():0);
	if (msg && msg->valid() && !msg->isCommand()) {
	    String tmp;
	    msg->toString(tmp);
	    sendData(tmp,addr);
	}
	return false;
    }
    if (!msgs.skipNull())
	return false;

    Lock lock(this);
    if (debugAt(DebugInfo)) {
	String tmp((const char*)buffer,len);
	Debug(this,DebugInfo,
	    "Received %u message(s) from %s:%d\r\n-----\r\n%s\r\n-----",
	    msgs.count(),addr.host().c_str(),addr.port(),tmp.c_str());
    }

    // Process received message(s)
    while (true) {
	MGCPMessage* msg = static_cast<MGCPMessage*>(msgs.remove(false));
	if (!msg)
	    break;

	// Command messages may contain ACK'd incoming transaction's responses
	// See RFC 3435: 3.2.2.19 and 3.5.1
	if (msg->isCommand()) {
	    String s = msg->params.getValue(YSTRING("k"));
	    if (!(s || m_parseParamToLower))
		s = msg->params.getValue(YSTRING("K"));
	    if (s) {
		unsigned int len = 0;
		unsigned int* trList = decodeAck(s,len);
		// Build an ACK message for each of ACK'd transaction response
		if (trList) {
		    for (unsigned int i = 0; i < len; i++) {
			MGCPTransaction* tr = findTrans(trList[i],false);
			if (tr)
			    tr->processMessage(new MGCPMessage(tr,0));
			else
			    DDebug(this,DebugNote,
				"Message %s carry ACK for unknown transaction %u",
				msg->name().c_str(),trList[i]);
		    }
		    delete trList;
		}
		else {
		    DDebug(this,DebugNote,"Message %s has invalid k: '%s' parameter",
			msg->name().c_str(),s.c_str());
		    MGCPTransaction* tr = findTrans(msg->transactionId(),false);
		    if (!tr)
			tr = new MGCPTransaction(this,msg,false,addr);
		    tr->setResponse(400,"Bad Transaction Ack");
		    continue;
		}
	    }
	}

	// Outgoing transaction id namespace is different then the incoming one
	// Check message:
	//   Command or response ACK: Destination is an incoming transaction
	//   Response: Destination is an outgoing transaction
	bool outgoing = !(msg->isCommand() || msg->isAck());
	MGCPTransaction* tr = findTrans(msg->transactionId(),outgoing);
	if (tr) {
	    tr->processMessage(msg);
	    continue;
	}
	// No transaction
	if (msg->isCommand()) {
	    new MGCPTransaction(this,msg,false,addr);
	    continue;
	}
	Debug(this,DebugNote,"Received response %d for unknown transaction %u",
	    msg->code(),msg->transactionId());
	TelEngine::destruct(msg);
    }

    return true;
}

// Try to get an event from a transaction.
// If the event contains an unknown command and this engine is not allowed
//  to process such commands, calls the @ref returnEvent() method, otherwise,
//  calls the @ref processEvent() method
bool MGCPEngine::process(u_int64_t time)
{
    MGCPEvent* event = getEvent(time);
    if (!event)
	return false;
    if (!processEvent(event))
	returnEvent(event);
    return true;
}

// Try to get an event from a given transaction.
// If the event contains an unknown command and this engine is not allowed
//  to process such commands, calls the returnEvent() method, otherwise,
//  calls the processEvent() method
bool MGCPEngine::processTransaction(MGCPTransaction* tr, u_int64_t time)
{
    MGCPEvent* event = tr ? tr->getEvent(time) : 0;
    if (!event)
	return false;
    if (!processEvent(event))
	returnEvent(event);
    return true;
}

// Repeatedly calls receive() until the calling thread terminates
void MGCPEngine::runReceive(SocketAddr& addr)
{
    if (m_recvBuf)
	delete[] m_recvBuf;
    m_recvBuf = new unsigned char[maxRecvPacket()];

    while (true)
	if (!receive(m_recvBuf,addr))
	    Thread::idle(true);
	else
	    Thread::check(true);
}

// Repeatedly calls receive() until the calling thread terminates
void MGCPEngine::runReceive()
{
    SocketAddr addr(AF_INET);
    runReceive(addr);
}

// Repeatedly calls process() until the calling thread terminates
void MGCPEngine::runProcess()
{
    while (true)
	if (!process())
	    Thread::idle(true);
	else
	    Thread::check(true);
}

// Try to get an event from a transaction
MGCPEvent* MGCPEngine::getEvent(u_int64_t time)
{
    lock();
    while (true) {
	if (Thread::check(false))
	    break;
	MGCPTransaction* tr = static_cast<MGCPTransaction*>(m_iterator.get());
        // End of iteration? NO: get a reference to the transaction
	if (!tr) {
	    m_iterator.assign(m_transactions);
	    break;
	}
	if (!tr->m_engineProcess)
	    continue;
	RefPointer<MGCPTransaction> sref = tr;
	if (!sref)
	    continue;
	// Get an event from the transaction
	unlock();
	MGCPEvent* event = sref->getEvent(time);
	// Remove the transaction if destroying
	if (event)
	    return event;
	lock();
    }
    unlock();
    return 0;
}

// Process an event generated by a transaction. Descendants must override this
//  method if they want to process events without breaking them apart
bool MGCPEngine::processEvent(MGCPEvent* event)
{
    DDebug(this,DebugAll,"MGCPEngine::processEvent(%p)",event);
    if (!event)
	return false;
    MGCPTransaction* trans = event->transaction();
    if (processEvent(trans,event->message())) {
	// Get rid of the event, it was handled
	delete event;
	return true;
    }
    return false;
}

// Process an event generated by a transaction. Descendants must override this
//  method if they want to process events
bool MGCPEngine::processEvent(MGCPTransaction* trans, MGCPMessage* msg)
{
    Debug(this,DebugStub,"MGCPEngine::processEvent(%p,%p)",trans,msg);
    return false;
}

// Returns an unprocessed event to this engine to be deleted.
// Incoming transactions will be responded. Unknown commands will receive a
//  504 Unknown Command response, the others will receive a 507 Unsupported Functionality one
void MGCPEngine::returnEvent(MGCPEvent* event)
{
    if (!event)
	return;
    DDebug(this,DebugInfo,"Event (%p) returned to the engine",event);
    MGCPTransaction* tr = event->transaction();
    const MGCPMessage* msg = event->message();
    if (tr && !tr->outgoing() && msg && msg->isCommand())
	tr->setResponse(knownCommand(msg->name()) ? 507 : 504);
    delete event;
}

// Terminate all transactions. Cancel all private threads if any and
// wait for them to terminate
void MGCPEngine::cleanup(bool gracefully, const char* text)
{
    DDebug(this,DebugAll,"Cleanup (gracefully=%s text=%s)",
	String::boolText(gracefully),text);

    // Terminate transactions
    Lock mylock(this);
    if (gracefully)
	for (ObjList* o = m_transactions.skipNull(); o; o = o->skipNext()) {
	    MGCPTransaction* tr = static_cast<MGCPTransaction*>(o->get());
	    if (!tr->outgoing())
		tr->setResponse(400,text);
	}
    m_transactions.clear();

    // Check if we have any private threads to wait
    if (!m_threads.skipNull())
	return;

    // Terminate private threads
    Debug(this,DebugAll,"Terminating %u private threads",m_threads.count());
    ListIterator iter(m_threads);
    for (GenObject* o = 0; 0 != (o = iter.get());)
	static_cast<MGCPPrivateThread*>(o)->cancel(!gracefully);
    DDebug(this,DebugAll,"Waiting for private threads to terminate");
    u_int64_t maxWait = Time::now() + 2000000;
    while (m_threads.skipNull()) {
	mylock.drop();
	if (Time::now() > maxWait) {
	    Debug(this,DebugCrit,"Private threads did not terminate!");
	    return;
	}
	Thread::idle();
	mylock.acquire(this);
    }
    DDebug(this,DebugAll,"Private threads terminated");
}

// Write data to socket
bool MGCPEngine::sendData(const String& msg, const SocketAddr& address)
{
    if (debugAt(DebugInfo)) {
	SocketAddr local;
	m_socket.getSockName(local);
	Debug(this,DebugInfo,
	    "Sending message from %s:%d to %s:%d\r\n-----\r\n%s\r\n-----",
	    local.host().c_str(),local.port(),address.host().c_str(),address.port(),
	    msg.c_str());
    }

    int len = m_socket.sendTo(msg.c_str(),msg.length(),address);
    if (len != Socket::socketError())
	return true;
    if (!m_socket.canRetry())
	Alarm(this,"socket",DebugWarn,"Socket write error: %d: %s",
	    m_socket.error(),::strerror(m_socket.error()));
    else
	DDebug(this,DebugMild,"Socket temporary unavailable: %d: %s",
	    m_socket.error(),::strerror(m_socket.error()));
    return false;
}

// Append a transaction to the list
void MGCPEngine::appendTrans(MGCPTransaction* trans)
{
    if (!trans)
	return;
    Lock lock(this);
    DDebug(this,DebugAll,"Added transaction (%p)",trans);
    m_transactions.append(trans);
}

// Remove a transaction from the list
void MGCPEngine::removeTrans(MGCPTransaction* trans, bool del)
{
    if (!trans)
	return;
    Lock lock(this);
    DDebug(this,DebugAll,"Removed transaction (%p) del=%u",trans,del);
    m_transactions.remove(trans,del);
}

// Append a private thread to the list
void MGCPEngine::appendThread(MGCPPrivateThread* thread)
{
    if (!thread)
	return;
    Lock lock(this);
    m_threads.append(thread)->setDelete(false);
    XDebug(this,DebugAll,"Added private thread (%p)",thread);
}

// Remove private thread from the list without deleting it
void MGCPEngine::removeThread(MGCPPrivateThread* thread)
{
    if (!thread)
	return;
    Lock lock(this);
    m_threads.remove(thread,false);
    XDebug(this,DebugAll,"Removed private thread (%p)",thread);
}

// Process ACK received with a message or response
// Build an ACK message for each of responded incoming transaction
unsigned int* MGCPEngine::decodeAck(const String& param, unsigned int& count)
{
    ObjList* list = param.split(',',false);
    if (!list->count()) {
	TelEngine::destruct(list);
	return 0;
    }

    unsigned int maxArray = 0;
    unsigned int* array = 0;
    bool ok = true;
    int first, last;

    for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
	String* s = static_cast<String*>(o->get());
	s->trimBlanks();
	// Get the interval (may be a single value)
	int sep = s->find('-');
	if (sep == -1)
	    first = last = s->toInteger(-1);
	else {
	    first = s->substr(0,sep).toInteger(-1);
	    last = s->substr(sep + 1).toInteger(-2);
	}
	if (first < 0 || last < 0 || last < first) {
	    ok = false;
	    break;
	}
	// Resize and copy array if not enough room
	unsigned int len = (unsigned int)(last - first + 1);
	if (count + len > maxArray) {
	    maxArray = count + len;
	    unsigned int* tmp = new unsigned int[maxArray];
	    if (array) {
		::memcpy(tmp,array,sizeof(unsigned int) * count);
		delete[] array;
	    }
	    array = tmp;
	}
	// Add to array code list
	for (; first <= last; first++)
	    array[count++] = first;
    }
    TelEngine::destruct(list);

    if (ok && count)
	return array;
    count = 0;
    if (array)
	delete[] array;
    return 0;
}


/**
 * MGCPEvent
 */
// Constructs an event from a transaction
MGCPEvent::MGCPEvent(MGCPTransaction* trans, MGCPMessage* msg)
    : m_transaction(0),
    m_message(0)
{
    if (trans && trans->ref())
	m_transaction = trans;
    if (msg && msg->ref())
	m_message = msg;
}

// Delete the message. Notify and deref the transaction
MGCPEvent::~MGCPEvent()
{
    if (m_transaction) {
	m_transaction->eventTerminated(this);
	m_transaction->deref();
    }
    TelEngine::destruct(m_message);
}


/**
 * The list of known commands defined in RFC 3435
 */
TokenDict MGCPEngine::mgcp_commands[] = {
    {"EPCF", 1},                         // CA --> GW  EndpointConfiguration
    {"CRCX", 2},                         // CA --> GW  CreateConnection
    {"MDCX", 3},                         // CA --> GW  ModifyConnection
    {"DLCX", 4},                         // CA <--> GW DeleteConnection
    {"RQNT", 5},                         // CA --> GW  NotificationRequest
    {"AUEP", 6},                         // CA --> GW  AuditEndpoint
    {"AUCX", 7},                         // CA --> GW  AuditConnection
    {"RSIP", 8},                         // GW --> CA  RestartInProgress
    {"NTFY", 9},                         // GW --> CA  Notify
    {"MESG", 10},                        // GW --> CA  Message
    {0,0}
};

/**
 * The list of known responses defined in RFC 3435 2.4
 */
TokenDict MGCPEngine::mgcp_responses[] = {
    {"ACK", 0},                          // Response Acknowledgement
    {"Trying", 100},                     // The transaction is currently being executed
    {"Queued", 101},                     // The transaction has been queued for execution
    {"OK", 200},                         // The requested transaction was executed normally
    {"OK", 250},                         // Used only to respond to DeleteConnection
    {"Unspecified", 400},                // The transaction could not be executed, due to some unspecified transient error
    {"Already Off Hook", 401},           // The phone is already off hook
    {"Already On Hook", 402},            // The phone is already on hook
    {"No Resources Now", 403},           // The transaction could not be executed, because the endpoint does
                                         //  not have sufficient resources at this time
    {"Insufficient Bandwidth", 404},
    {"Endpoint Is Restarting", 405},     // The transaction could not be executed, because the endpoint is restarting
    {"Timeout", 406},                    // The transaction did not complete in a reasonable period of time
    {"Aborted", 407},                    // The transaction was aborted by some external action,
                                         //  e.g., a ModifyConnection command aborted by a DeleteConnection command
    {"Overload", 409},                   // The transaction could not be executed because of internal overload
    {"No Endpoint Available", 410},      // A valid "any of" wildcard was used, but there was no endpoint
                                         //  available to satisfy the request
    {"Unknown Endpoint", 500},
    {"Endpoint Not Ready", 501},         // The endpoint is not ready. Includes out-of-service
    {"No Resources", 502},               // The endpoint doesn't have sufficient resources (permanent condition)
    {"Wildcard Too Complicated", 503},   // "All of" wildcard too complicated
    {"Unknown Command", 504},            // Unknown or unsupported command.
    {"Unsupported RemoteConnectionDescriptor", 505}, // This SHOULD be used when one or more mandatory parameters
                                         //  or values in the RemoteConnectionDescriptor is not supported
    {"Unable To Satisfy LocalConnectionOptions And RemoteConnectionDescriptor", 506}, // LocalConnectionOptions and
                                         //  RemoteConnectionDescriptor contain one or more mandatory parameters
                                         //  or values that conflict with each other
    {"Unsupported Functionality", 507},
    {"Unknown Or Unsupported Quarantine Handling", 508},
    {"Bad RemoteConnectionDescriptor", 509}, // Syntax or semantic error in the RemoteConnectionDescriptor
    {"Protocol Error", 510},             // Unspecified protocol error was detected
    {"Unrecognized Extension", 511},     // Used for unsupported critical parameter extensions ("X+")
    {"Can't Detect Event", 512},         // The gateway is not equipped to detect one of the requested events
    {"Can't Generate Signal", 513},      // The gateway is not equipped to generate one of the requested signals
    {"Can't Send Announcement", 514},    // The gateway cannot send the specified announcement.
    {"No Connection", 515},              // The transaction refers to an incorrect connection-id
    {"Bad Call-id", 516},                // Unknown or incorrect call-id (connection-id not associated with this call-id)
    {"Unsupported Mode", 517},           // Unsupported or invalid mode
    {"Unsupported Package", 518},
    {"No Digit Map", 519},               // Endpoint does not have a digit map
    {"Endpoint Is Restarting", 520},
    {"Endpoint Redirected To Another Call Agent", 521},  // The associated redirection behavior is only well-defined
                                         //  when this response is issued for a RestartInProgress command
    {"Unknown Event Or Signal", 522},    // The request referred to an event or signal that is not defined in
                                         //  the relevant package (which could be the default package)
    {"Illegal Action", 523},             // Unknown action or illegal combination of actions
    {"Inconsistency In LocalConnectionOptions", 524},
    {"Unknown Extension In LocalConnectionOptions", 525},  // Used for unsupported mandatory vendor extensions ("x+")
    {"Insufficient Bandwidth", 526},
    {"Missing RemoteConnectionDescriptor", 527},
    {"Incompatible Protocol Version", 528},
    {"Internal Hardware Failure", 529},
    {"CAS Signaling Protocol Error", 530},
    {"Grouping Of Trunks Failure", 531}, // e.g., facility failure
    {"Unsupported LocalConnectionOptions", 532}, // Unsupported value(s) in LocalConnectionOptions.
    {"Response Too Large", 533},
    {"Codec Negotiation Failure", 534},
    {"Packetization Period Not Supported", 535},
    {"Unsupported RestartMethod", 536},
    {"Unsupported Digit Map Extension", 537},
    {"Event/Signal Parameter Error", 538}, // e.g., missing, erroneous, unsupported, unknown, etc.
    {"Unsupported Command Parameter", 539}, // Used when the parameter is neither a package or vendor extension parameter
    {"Per Endpoint Connection Limit Exceeded", 540},
    {"Unsupported LocalConnectionOptions", 541}, // Used when the LocalConnectionOptions is neither a package
                                         //  nor a vendor extension LocalConnectionOptions.
    {0,0},
};

/**
 * The list of known reason codes defined in RFC 3435 2.5
 * Reason codes are used by the gateway when deleting a connection to
   inform the Call Agent about the reason for deleting the connection.
   They may also be used in a RestartInProgress command to inform the
   Call Agent of the reason for the RestartInProgress
 */
TokenDict MGCPEngine::mgcp_reasons[] = {
    {"Normal", 0},                       // Endpoint state is normal (only in response to audit requests)
    {"Endpoint Malfunctioning", 900},
    {"Endpoint Taken Out-Of-Service", 901},
    {"Loss Of Lower Layer Connectivity", 902},
    {"QoS Resource Reservation Was Lost", 903},
    {"Manual Intervention", 904},
    {"Facility failure", 905},
    {0,0},
};

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