/**
 * ysipchan.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Yet Another Sip Channel
 *
 * 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 <yatephone.h>
#include <yatesip.h>
#include <yatesdp.h>

#include <string.h>


using namespace TelEngine;
namespace { // anonymous

class YateSIPListener;                   // Base class for listeners (need binding)
class YateSIPPartyHolder;                // A SIPParty holder
class YateSIPTransport;                  // SIP transport: keeps a socket, read/send data
class YateSIPUDPTransport;               // UDP transport
class YateSIPTCPTransport;               // TCP/TLS transport
class YateSIPTransportWorker;            // A transport worker
class YateSIPTCPListener;                // A TCP listener
class YateUDPParty;                      // A SIP UDP party
class YateTCPParty;                      // A SIP TCP/TLS party
class YateSIPEngine;                     // The SIP engine
class YateSIPLine;                       // A line
class YateSIPEndPoint;                   // Endpoint processor
class SIPDriver;

#define EXPIRES_MIN 60
#define EXPIRES_DEF 600
#define EXPIRES_MAX 3600

// TCP transport idle values in seconds
// Outgoing: interval to send keep alive
// Incoming: interval allowed to stay with refcounter=1 and no data received/sent
#define TCP_IDLE_MIN 32
#define TCP_IDLE_DEF 120
#define TCP_IDLE_MAX 600

// Maximum allowed value for bind retry interval in milliseconds
// 1 minute
#define BIND_RETRY_MAX 60000

static const TokenDict dict_errors[] = {
    { "incomplete", 484 },
    { "noroute", 404 },
    { "noroute", 604 },
    { "noconn", 503 },
    { "noconn", 408 },
    { "noauth", 401 },
    { "noautoauth", 401 },
    { "nomedia", 415 },
    { "nocall", 481 },
    { "busy", 486 },
    { "busy", 600 },
    { "noanswer", 480 },
    { "noanswer", 487 },
    { "rejected", 406 },
    { "rejected", 606 },
    { "forbidden", 403 },
    { "forbidden", 603 },
    { "offline", 404 },
    { "congestion", 480 },
    { "unallocated", 410 },
    { "moved", 410 },
    { "failure", 500 },
    { "pending", 491 },
    { "looping", 483 },
    { "timeout", 408 },
    { "timeout", 504 },
    { "postdialdelay", 504 },
    { "service-not-implemented", 501 },
    { "unimplemented", 501 },
    { "service-unavailable", 503 },
    { "unacceptable", 488 },
    { "noresource", 503 },
    { "interworking", 500 },
    { "interworking", 400 },
    { "invalid-message", 400 },
    { "protocol-error", 400 },
    {  0,   0 },
};

static const char s_dtmfs[] = "0123456789*#ABCDF";

static const TokenDict info_signals[] = {
    { "*", 10 },
    { "#", 11 },
    { "A", 12 },
    { "B", 13 },
    { "C", 14 },
    { "D", 15 },
    {  0,   0 },
};

class BodyTrace : public String
{
public:
    inline BodyTrace(const String& body)
	: m_hash(body.hash()), m_length(body.length())
	{}

    inline bool sameBody(const String& body) const
	{ return body.hash() == m_hash && body.length() == m_length; }

    unsigned int m_hash;
    unsigned int m_length;
};

// Protocol definition
class ProtocolHolder
{
public:
    enum Protocol {
	Unknown = 0,
	Udp,
	Tcp,
	Tls
    };
    inline ProtocolHolder(int p)
	: m_proto(p)
	{}
    // Retrieve protocol
    inline int protocol() const
	{ return m_proto; }
    inline const char* protoName(bool upperCase = true) const
	{ return lookupProtoName(protocol(),upperCase); }
    static inline const char* lookupProtoName(int proto, bool upperCase = true)
	{ return lookup(proto,upperCase ? s_protoUC : s_protoLC); }
    static inline int lookupProto(const char* name, bool upperCase = true,
	int def = Unknown)
	{ return lookup(name,upperCase ? s_protoUC : s_protoLC,def); }
    static inline int lookupProtoAny(const String& name, int def = Unknown) {
	    String tmp = name;
	    return lookupProto(tmp.toLower(),false,def);
	}
    static const TokenDict s_protoLC[];  // Lower case proto name
    static const TokenDict s_protoUC[];  // Upper case proto name
protected:
    int m_proto;
private:
    ProtocolHolder() {}                  // No default
};

class DtmfMethods
{
public:
    enum Method {
	Info,
	Rfc2833,
	Inband,
	MethodCount
    };
    inline DtmfMethods()
	{ setDefault();	}
    inline void set(int _0 = MethodCount, int _1 = MethodCount, int _2 = MethodCount) {
	    m_methods[0] = _0;
	    m_methods[1] = _1;
	    m_methods[2] = _2;
	}
    inline void setDefault()
	{ set(Rfc2833,Info,Inband); }
    // Replace all methods from comma separated list
    // If no method is set use other or setDefEmpty (reset to default)
    // Return false if methods contain unknown methods
    bool set(const String& methods, const DtmfMethods* other, bool setDefEmpty = true,
	bool intersectOther = false);
    // Intersect with other methods
    void intersect(const DtmfMethods& other);
    // Retrieve a method from deperecated parameters
    // Reset the method if the parameter is false
    // Display a message anyway if warn is not false
    // Return true if the parameter was found
    bool getDeprecatedDtmfMethod(const NamedList& list, const char* param, int method, bool* warn);
    // Reset a method
    void reset(int method);
    // Build a string list from methods
    void buildMethods(String& buf, const char* sep = ",");
    bool hasMethod(int method) const;
    inline void printMethods(DebugEnabler* enabler, int level, const String& str) {
	    String tmp;
	    buildMethods(tmp);
	    Debug(enabler,level,"Built DTMF methods '%s' from '%s'",tmp.safe(),str.safe());
	}
    inline int operator[](unsigned int index) {
	    if (index < MethodCount)
		return m_methods[index];
	    return MethodCount;
	}
    inline DtmfMethods& operator=(const DtmfMethods& other) {
	    for (int i = 0; i < MethodCount; i++)
		m_methods[i] = other.m_methods[i];
	    return *this;
	}
    static const TokenDict s_methodName[];
protected:
    int m_methods[MethodCount];
};

// A SIP party holder
class YateSIPPartyHolder : public ProtocolHolder
{
public:
    inline YateSIPPartyHolder(DebugEnabler* enabler, Mutex* mutex = 0, const String& traceId = String::empty())
	: ProtocolHolder(Udp),
	m_party(0), m_partyMutex(mutex), m_sips(false),	m_transLocalPort(0), m_transRemotePort(0),
	m_enabler(enabler), m_traceId(traceId)
	{}
    virtual ~YateSIPPartyHolder()
	{ setParty(); }
    // Retrieve a referrenced pointer to the held party
    inline SIPParty* party() {
	    Lock lock(m_partyMutex);
	    return (m_party && m_party->ref()) ? m_party : 0;
	}
    // Retrieve the transport from party
    YateSIPTransport* transport(bool ref = false);
    // Check if a transport is used by our party
    inline bool isTransport(YateSIPTransport* trans)
	{ return trans == transport(); }
    // Set the held party. Referrence it before
    void setParty(SIPParty* party = 0);
    // Set the held party if remote address changed
    // Return true if holder party was set to given party
    bool setPartyChanged(SIPParty* party);
    // Set the party of a non answer message. Return true on success
    bool setSipParty(SIPMessage* message, const YateSIPLine* line = 0,
	bool useEp = false, const char* host = 0, int port = 0) const;
    // (Re)Build party. Return true on success
    bool buildParty(bool force = true, bool isTemp = false);
    // Change party and its transport if the parameter list contains a transport
    // Set force to true to try building a party anyway
    // Return true on success
    bool setParty(const NamedList& params, bool force,
	const String& prefix = String::empty(),
	const String& defRemoteAddr = String::empty(), int defRemotePort = 0,
	bool isTemp = false);
    // SIPS URI usage
    inline bool sips() const
	{ return m_sips; }
    inline bool sips(bool on)
	{ return change(m_sips,on); }
    // Transport status changed notification
    virtual void transportChangedStatus(int stat, const String& reason);
    // Check if transport config params are present in parameters list
    static inline bool haveTransParams(const NamedList& params,
	const String& prefix = String::empty()) {
	    return params[prefix + "connection_id"] ||
		params.getParam(prefix + "ip_transport") ||
		params.getBoolValue(prefix + "ip_transport_tcp");
	}

protected:
    // Change a parameter, notify descendents
    bool change(String& dest, const String& src);
    bool change(int& dest, int src);
    bool change(bool& dest, bool src);
    // Changing notification for descendents
    virtual void changing();
    // Update transport type. Return true if changed
    bool updateProto(const NamedList& params, const String& prefix = String::empty());
    // Update transport remote addr/port. Return true if changed
    bool updateRemoteAddr(const NamedList& params, const String& prefix = String::empty(),
	const String& defRemoteAddr = String::empty(), int defRemotePort = 0);
    // Update transport local addr/port. Return true if changed
    bool updateLocalAddr(const NamedList& params, const String& prefix = String::empty());
    // Update RTP local address
    void setRtpLocalAddr(String& addr, Message* m = 0);

    SIPParty* m_party;                   // Held party
    Mutex* m_partyMutex;                 // Mutex protecting the party pointer
    bool m_sips;                         // SIPS URI is used
    // Data used to (re)build the transport
    String m_transId;
    String m_transLocalAddr;
    int m_transLocalPort;
    String m_transRemoteAddr;
    int m_transRemotePort;
    // Failure
    String m_partyInvalidRemote;

private:
    DebugEnabler* m_enabler;
    String m_traceId;
};

// Base class for listeners (need binding)
class YateSIPListener
{
public:
    YateSIPListener(const char* name, int proto, const String& addr = String::empty(),
	int port = 0);
    inline const String& listenerName() const
	{ return m_name; }
    inline const char* lName() const
	{ return m_name; }
    inline const String& address() const
	{ return m_address; }
    inline int port() const
	{ return m_port; }
    inline bool ipv6() const
	{ return m_ipv6; }
    inline bool ipv6Support() const
	{ return m_ipv6Support; }
    // Check if address would change
    inline bool addrWouldChange(Mutex* mutex, const String& addr, int port) {
	    Lock lck(mutex);
	    return m_port != port || m_cfgAddr != addr;
	}
protected:
    // Check bind now flag
    bool bindNow(Mutex* mutex);
    // Set addr/port/IPv6 support. Set bind flag if changed
    void setAddr(const String& addr, int port, bool ipv6);
    // Update IPv6 support from global. Set bind flag if changed and we must use IPv6
    void updateIPv6Support();
    // Update rtp address. Return true if set rtp addr flag changed
    bool updateRtpAddr(const NamedList& params, String& buf, Mutex* mutex = 0);
    // Initialize a socket
    Socket* initSocket(SocketAddr& addr, Mutex* mutex, int backLogBuffer, bool forceBind,
	String& reason);

    unsigned int m_bindInterval;         // Interval to try binding
    u_int64_t m_nextBind;                // Next time to bind
    bool m_bind;                         // Re-bind flag
    String m_cfgAddr;                    // Configured address
    String m_address;                    // Address to bind
    int m_port;                          // Port to bind
    bool m_ipv6;                         // Listen on IPv6 address(es)
    bool m_ipv6Support;                  // IPv6 supported
    bool m_setRtpAddr;                   // Set rtp address from bind address
    String m_bindRtpLocalAddr;           // Rtp local address set from bind address

private:
    String m_name;                       // Listener name
    int m_proto;                         // Listener protocol
};

// SIP transport: keeps a socket, read data from it, send data through it
class YateSIPTransport : public Mutex, public RefObject, public ProtocolHolder
{
    YCLASS(YateSIPTransport,RefObject);
    YNOCOPY(YateSIPTransport);
    friend class SIPDriver;
    friend class YateSIPEndPoint;
    friend class YateSIPTransportWorker;
public:
    enum Status {
	Idle = 0,
	Connected,
	Terminating,
	Terminated
    };
    // (Re)Initialize the transport
    bool init(const NamedList& params, const NamedList& defs, bool first,
	Thread::Priority prio = Thread::Normal);
    // Retrieve status
    inline int status() const
	{ return m_status; }
    // Check if valid (connected)
    inline bool valid() const
	{ return status() == Connected; }
    // Check if VIA header should be ignored
    inline bool ignoreVia() const
	{ return m_ignoreVia; }
    // Retrieve local address for this transport
    // This method is not thread safe for outgoing TCP
    inline const SocketAddr& local() const
	{ return m_local; }
    // Retrieve remote address for this transport
    // This method is not thread safe for outgoing TCP
    inline const SocketAddr& remote() const
	{ return m_remote; }
    // Safely retrieve RTP local address
    inline void rtpAddr(String& buf) {
	    Lock lock(this);
	    buf = m_rtpLocalAddr;
	}
    // Safely retrieve RTP external NAT address, only if set
    inline void rtpNatAddr(String& buf) {
	    Lock lock(this);
	    if (m_rtpNatAddr)
		buf = m_rtpNatAddr;
	}
    // Print sent messages to output
    void printSendMsg(const SIPMessage* msg, const SocketAddr* addr = 0);
    // Print received messages to output
    // For TCP transports the function will assume 'buf' is not null terminated
    void printRecvMsg(const char* buf, int len, const String& traceId = String::empty());
    // Add transport data yate message
    void fillMessage(Message& msg, bool addRoute = false);
    // Transport descendents
    virtual YateSIPUDPTransport* udpTransport()
	{ return 0; }
    virtual YateSIPTCPTransport* tcpTransport()
	{ return 0; }
    // Stop the worker. Change status
    void terminate(const char* reason = 0);
    // Process data (read/send).
    // Return 0 to continue processing, positive to sleep (usec),
    //  negative to terminate and destroy
    virtual int process() = 0;
    // Retrieve the transport id
    virtual const String& toString() const;
    // Reset and delete a socket
    static void resetSocket(Socket*& sock, int linger);
    // Status names
    static inline const char* statusName(int stat, const char* defVal = "Unknown")
	{ return lookup(stat,s_statusName,defVal); }
    static const TokenDict s_statusName[];
protected:
    YateSIPTransport(int proto, const String& id, Socket* sock, int stat = Connected);
    virtual void destroyed();
    // Status changed notification for descendents
    virtual void statusChanged()
	{}
    // Start the worker thread
    bool startWorker(Thread::Priority prio);
    // Change transport status. Notify it
    void changeStatus(int stat);
    // Handle received messages, set party, add to engine
    // Consume the message
    void receiveMsg(SIPMessage*& msg);
    // Print socket read error to output
    void printReadError();
    // Print socket write error to output
    void printWriteError(int res, unsigned int len, bool alarm = false);
    // Set m_protoAddr from local/remote ip/port or reset it
    void setProtoAddr(bool set);

    String m_id;                         // Transport id
    int m_status;                        // Transport status
    unsigned int m_statusChgTime;        // Last status changed time (seconds)
    String m_reason;                     // Termination reason
    Socket* m_sock;                      // The socket
    unsigned int m_maxpkt;               // Max receive packet length
    DataBlock m_buffer;                  // Read buffer
    SocketAddr m_local;                  // Local ip/port
    SocketAddr m_remote;                 // Remote ip/port
    String m_rtpLocalAddr;               // RTP local address
    String m_rtpNatAddr;                 // NAT IP to override RTP local address
    YateSIPTransportWorker* m_worker;    // Transport worker
    bool m_initialized;                  // Flag reset when initializing by the module and set in init()
    String m_protoAddr;                  // Proto + addr: used for debug (send/recv msg)
    String m_role;
    bool m_ignoreVia;                    // Ignore VIA header (override from global)
private:
    YateSIPTransport() : ProtocolHolder(Udp) {} // No default constructor
};

// UDP transport
class YateSIPUDPTransport : public YateSIPTransport, public YateSIPListener
{
    YCLASS(YateSIPUDPTransport,YateSIPTransport);
    friend class YateSIPTransport;
public:
    YateSIPUDPTransport(const String& id);
    inline bool isDefault() const
	{ return m_default; }
    virtual YateSIPUDPTransport* udpTransport()
	{ return this; }
    // (Re)Initialize the transport
    bool init(const NamedList& params, const NamedList& defs, bool first,
	Thread::Priority prio = Thread::Normal);
    // Send data
    bool send(const void* data, unsigned int len, const SocketAddr& addr);
    // Process data (read)
    virtual int process();
protected:
    bool m_default;
    bool m_forceBind;
    bool m_errored;
    int m_bufferReq;
};

// TCP/TLS transport
class YateSIPTCPTransport : public YateSIPTransport
{
    YCLASS(YateSIPTCPTransport,YateSIPTransport);
    friend class YateTCPParty;
public:
    // Build an outgoing transport
    YateSIPTCPTransport(bool tls, const String& laddr, const String& raddr, int rport);
    // Build an incoming transport
    YateSIPTCPTransport(Socket* sock, bool tls);
    inline bool outgoing() const
	{ return m_outgoing; }
    inline bool tls() const
	{ return protocol() == Tls; }
    inline const String& remoteAddr() const
	{ return m_remoteAddr; }
    inline int remotePort() const
	{ return m_remotePort; }
    inline const String& localAddr() const
	{ return m_localAddr; }
    // Safely return a reference to party
    YateTCPParty* getParty();
    virtual YateSIPTCPTransport* tcpTransport()
	{ return this; }
    // (Re)Initialize the transport
    bool init(const NamedList& params, bool first, Thread::Priority prio = Thread::Normal);
    // Set flow timer flag and idle interval (in seconds)
    // Reset idle timeout
    void setFlowTimer(bool on, unsigned int interval);
    // Send an event
    bool send(SIPEvent* event);
    // Process data (read/send)
    virtual int process();
protected:
    virtual void destroyed();
    // Status changed notification
    virtual void statusChanged();
    // Reset transport's party
    void resetParty(YateTCPParty* party, bool set);
    // Connect an outgoing transport. Terminate the socket before it
    // Return: 1: OK, 0: retry connect, -1: stop the transport
    int connect(u_int64_t connToutUs = 60000000);
    // Send pending messages or keepalive, return false on failure
    bool sendPending(const Time& time, bool& sent);
    // Read data
    bool readData(const Time& time, bool& read);
    // Reset socket and connection related data
    void resetConnection(Socket* sock = 0);
    // Set transport idle timeout
    void setIdleTimeout(u_int64_t time = Time::now());
    // Send keep alive (or response to keep alive)
    bool sendKeepAlive(bool request);
    inline bool sendPendingKeepAlive() {
	    if (!m_keepAlivePending)
		return true;
	    m_keepAlivePending = false;
	    return sendKeepAlive(false);
	}

    bool m_outgoing;                     // Direction
    YateTCPParty* m_party;               // Transport party
    ObjList m_queue;                     // Pending message queue
    int m_sent;                          // Sent bytes from first message in queue
                                         // -1 to dequeue a new message and print it
    unsigned int m_firstKeepalive;       // Outgoing: first keep alive interval
    bool m_firstKeepaliveSent;           // First keep alive used
    unsigned int m_idleInterval;         // Incoming: interval allowed to stay with a reference
                                         //  counter=1 without receiving any data
                                         // Outgoing: keep alive interval
    u_int64_t m_idleTimeout;             // Idle timeout: check state or send keep alive
    bool m_flowTimer;                    // Flow timer flag (RFC5626)
    bool m_keepAlivePending;             // Pending keep alive response
    SIPMessage* m_msg;                   // Partially received SIP message (expecting body)
    DataBlock m_sipBuffer;               // Accumulated read data
    unsigned int m_sipBufOffs;           // Offset in sip buffer for partial sip message
    unsigned int m_contentLen;           // Expected content length for partial sip message
    // Outgoing (re-connect info)
    String m_remoteAddr;                 // Remote party address
    int m_remotePort;                    // Remote port
    String m_localAddr;                  // Optional local address to bind to
    unsigned int m_connectRetry;         // Number of re-connect
    u_int64_t m_nextConnect;             // Interval to try ro re-connect
};

// Transport worker
class YateSIPTransportWorker : public Thread
{
    friend class YateSIPTransport;
public:
    YateSIPTransportWorker(YateSIPTransport* trans, Thread::Priority prio);
    ~YateSIPTransportWorker();
    virtual void run();
private:
    void cleanupTransport(bool final, bool terminate = false);
    YateSIPTransport* m_transport;
};

class YateSIPTCPListener : public Thread, public GenObject, public ProtocolHolder, public YateSIPListener
{
    friend class SIPDriver;
    friend class YateSIPEndPoint;
public:
    YateSIPTCPListener(int proto, const String& name, const NamedList& params);
    ~YateSIPTCPListener();
    void init(const NamedList& params, bool first);
    inline bool tls() const
	{ return protocol() == Tls; }
    inline bool listening() const
	{ return m_socket != 0; }
    // Retrieve local address for this transport
    // This method is not thread safe
    inline const SocketAddr& local() const
	{ return m_local; }
    inline void setReason(const char* reason) {
	    if (!reason)
		return;
	    Lock lck(m_mutex);
	    m_reason = reason;
	}
    virtual void run();
    virtual const String& toString() const
	{ return listenerName(); }
private:
    // Close the socket. Remove from endpoint list
    void cleanup(bool final);
    // Reset socket
    void stopListening(const char* reason = 0, int level = DebugNote);

    Mutex m_mutex;                       // Mutex protecting transport parameters and bind ip/port
    String m_reason;                     // Last error (state change) string
    bool m_sslContextChanged;            // SSL context changed flag
    bool m_sslContextCheck;              // Check SSL context availability
    bool m_transParamsChanged;           // Transport parameters changed flag
    Socket* m_socket;                    // The socket
    SocketAddr m_local;                  // Local ip/port
    unsigned int m_backlog;              // Pending connections queue length
    String m_sslContext;                 // SSL/TLS context
    NamedList m_transParams;             // Parameters for created transports
    bool m_initialized;                  // Flag reset when initializing by the module and set in init()
};

class YateUDPParty : public SIPParty
{
public:
    YateUDPParty(YateSIPUDPTransport* trans, const SocketAddr& addr, int* localPort = 0,
	const char* localAddr = 0);
    ~YateUDPParty();
    inline const SocketAddr& addr() const
	{ return m_addr; }
    virtual bool transmit(SIPEvent* event);
    virtual const char* getProtoName() const;
    virtual bool setParty(const URI& uri);
    virtual void* getTransport();
    // Get an object from this one
    virtual void* getObject(const String& name) const;
protected:
    YateSIPUDPTransport* m_transport;
    SocketAddr m_addr;
};

class YateTCPParty : public SIPParty
{
public:
    YateTCPParty(YateSIPTCPTransport* trans);
    ~YateTCPParty();
    virtual bool transmit(SIPEvent* event);
    virtual const char* getProtoName() const;
    virtual bool setParty(const URI& uri);
    virtual void* getTransport();
    // Get an object from this one
    virtual void* getObject(const String& name) const;
    // Update party local/remote addr/port from transport
    void updateAddrs();
protected:
    virtual void destroyed();
    YateSIPTCPTransport* m_transport;
};

class SipHandler;

class YateSIPEngine : public SIPEngine
{
public:
    YateSIPEngine(YateSIPEndPoint* ep);
    // Initialize the engine
    void initialize(NamedList* params);
    virtual bool buildParty(SIPMessage* message);
    virtual void allocTraceId(String& id);
    virtual void traceMsg(SIPMessage* message, bool incoming = true);
    virtual bool checkUser(String& username, const String& realm, const String& nonce,
	const String& method, const String& uri, const String& response,
	const SIPMessage* message, const MimeHeaderLine* authLine, GenObject* userData);
    virtual SIPTransaction* forkInvite(SIPMessage* answer, SIPTransaction* trans);
    // Transport status changed notification
    void transportChangedStatus(YateSIPTransport* trans, int stat, const String& reason);
    // Check if the engine has an active transaction using a given transport
    bool hasActiveTransaction(YateSIPTransport* trans);
    // Check if the engine has pending transactions
    bool hasInitialTransaction();
    // Clear transactions
    inline void clearTransactions() {
	    Lock lck(this);
	    m_transList.clear();
	}
    inline bool prack() const
	{ return m_prack; }
    inline bool info() const
	{ return m_info; }
    inline bool foreignAuth() const
	{ return m_foreignAuth; }
private:
    static bool copyAuthParams(NamedList* dest, const NamedList& src, bool ok = true);
    YateSIPEndPoint* m_ep;
    bool m_prack;
    bool m_info;
    bool m_fork;
    bool m_forkEarly;
    bool m_foreignAuth;
    uint64_t m_traceIds;
};

class YateSIPLine : public String, public Mutex, public CallAccount, public YateSIPPartyHolder
{
    YCLASS(YateSIPLine,String)
public:
    YateSIPLine(const String& name);
    virtual ~YateSIPLine();
    bool matchInbound(const String& addr, int port, const String& user) const;
    void setupAuth(SIPMessage* msg) const;
    SIPMessage* buildRegister(int expires);
    void login();
    void logout(bool sendLogout = true, const char* reason = 0);
    bool process(SIPEvent* ev);
    void timer(const Time& when);
    bool update(const Message& msg);
    // Transport status changed notification
    virtual void transportChangedStatus(int stat, const String& reason);
    inline const String& getLocalAddr() const
	{ return m_localAddr; }
    inline const String& getPartyAddr() const
	{ return m_partyAddr ? m_partyAddr : m_transRemoteAddr; }
    inline int getLocalPort() const
	{ return m_localPort; }
    inline int getPartyPort() const
	{ return m_partyPort ? m_partyPort : m_transRemotePort; }
    inline bool localDetect() const
	{ return m_localDetect; }
    inline const String& getFullName() const
	{ return m_display; }
    inline const String& getUserName() const
	{ return m_username; }
    inline const String& getAuthName() const
	{ return m_authname ? m_authname : m_username; }
    inline const String& regDomain() const
	{ return m_registrar ? m_registrar : m_transRemoteAddr; }
    inline const String& domain() const
	{ return m_domain ? m_domain : regDomain(); }
    inline const char* domain(const char* defDomain) const
	{ return m_domain ? m_domain.c_str() :
	    (TelEngine::null(defDomain) ? regDomain().c_str() : defDomain); }
    inline bool valid() const
	{ return m_valid; }
    inline bool marked() const
	{ return m_marked; }
    inline void marked(bool mark)
	{ m_marked = mark; }
private:
    void clearTransaction();
    void detectLocal(const SIPMessage* msg);
    void keepalive();
    void setValid(bool valid, const char* reason = 0, const char* error = 0);
    virtual void changing();

    String m_registrar;
    String m_username;
    String m_authname;
    String m_password;
    String m_domain;
    String m_display;
    u_int64_t m_resend;
    u_int64_t m_keepalive;
    int m_interval;
    int m_alive;
    int m_flags;
    int m_trans;
    SIPTransaction* m_tr;
    RefPointer<SIPSequence> m_seq;
    bool m_marked;
    bool m_valid;
    String m_callid;
    String m_localAddr;
    String m_partyAddr;
    int m_localPort;
    int m_partyPort;
    bool m_localDetect;
    bool m_keepTcpOffline;               // Don't reset party when offline
    bool m_matchPort;
    bool m_matchUser;
    bool m_forceNotify;
};

class YateSIPEndPoint : public Thread
{
    friend class SIPDriver;
    friend class YateSIPTCPListener;
public:
    YateSIPEndPoint(Thread::Priority prio = Thread::Normal,
	unsigned int partyMutexCount = 5);
    ~YateSIPEndPoint();
    bool Init(void);
    void run(void);
    bool incoming(SIPEvent* e, SIPTransaction* t);
    void invite(SIPEvent* e, SIPTransaction* t);
    void regReq(SIPEvent* e, SIPTransaction* t);
    void regRun(const SIPMessage* message, SIPTransaction* t);
    void options(SIPEvent* e, SIPTransaction* t);
    bool generic(SIPEvent* e, SIPTransaction* t, int defErr = 405, bool async = false);
    bool generic(const SIPMessage* message, SIPTransaction* t, const String& meth, bool autoAuth, bool isMsg);
    bool buildParty(SIPMessage* message, const char* host = 0, int port = 0, const YateSIPLine* line = 0);
    inline void addTcpTransport(YateSIPTCPTransport* trans) {
	    if (!trans)
		return;
	    Lock lock(m_mutex);
	    m_transports.append(trans)->setDelete(false);
	}
    // Retrieve the default transport. Return a referrenced object
    inline YateSIPUDPTransport* defTransport() {
	    Lock lock(m_mutex);
	    return (m_defTransport && m_defTransport->ref()) ? m_defTransport : 0;
	}
    // (re)set default UDP transport
    void updateDefUdpTransport();
    // Retrieve a transport by name (name can be a prefix).
    // Return a referrenced object
    YateSIPTransport* findTransport(const String& name);
    // Retrieve an UDP transport. Return a referrenced object
    YateSIPUDPTransport* findUdpTransport(const String& name);
    // Retrieve an UDP transport by addr/port. Return a referrenced object
    YateSIPUDPTransport* findUdpTransport(const String& addr, int port);
    // Build or delete an UDP transport (re-init existing). Start the thread
    bool setupUdpTransport(const String& name, bool enabled, const NamedList& params,
	const NamedList& defs = NamedList::empty(), const char* reason = 0);
    // Delete an UDP transport
    bool removeUdpTransport(const String& name, const char* reason);
    // Remove a transport from list without deleting it. Notify termination.
    // Return true if found
    bool removeTransport(YateSIPTransport* trans, bool updDef = true);
    // Clear all transports
    void clearUdpTransports(const char* reason);
    // Transport status changed notification
    void transportChangedStatus(YateSIPTransport* trans, int stat, const String& reason);
    // Build or delete a TCP listener. Start the thread
    bool setupListener(int proto, const String& name, bool enabled, const NamedList& params);
    // Remove a listener from list without deleting it. Return true if found
    bool removeListener(YateSIPTCPListener* listener);
    // Remove a listener from list. Remove all if name is empty. Wait for termination
    void cancelListener(const String& name = String::empty(), const char* reason = 0);
    // This method is called by the driver when start/end initializing
    void initializing(bool start);
    // Complete transport names
    void completeTransports(Message& msg, const String& partWord,
	bool udp = true, bool tcp = true, bool tls = true);
    inline YateSIPEngine* engine() const
	{ return m_engine; }
    inline void incFailedAuths()
	{ m_failedAuths++; }
    inline unsigned int failedAuths()
    {
	unsigned int tmp = m_failedAuths;
	m_failedAuths = 0;
	return tmp;
    }
    inline unsigned int timedOutTrs()
    {
	unsigned int tmp = m_timedOutTrs;
	m_timedOutTrs = 0;
	return tmp;
    }
    inline unsigned int timedOutByes()
    {
	unsigned int tmp = m_timedOutByes;
	m_timedOutByes = 0;
	return tmp;
    }
    MutexPool m_partyMutexPool;          // SIPParty mutex pool
    // Check if data is allowed to be read from socket(s) and processed
    static bool canRead();
    static int s_evCount;
private:
    YateSIPEngine *m_engine;
    Mutex m_mutex;                       // Protect transports and listeners
    ObjList m_transports;                // All transports (non UDP are not owned)
    YateSIPUDPTransport* m_defTransport; // Default transport (pointer to object in m_transports)
    ObjList m_listeners;                 // Listeners list

    unsigned int m_failedAuths;
    unsigned int m_timedOutTrs;
    unsigned int m_timedOutByes;
};

// Handle transfer requests
// Respond to the enclosed transaction
class YateSIPRefer : public Thread
{
public:
    YateSIPRefer(const String& transferorID, const String& transferredID,
	Driver* transferredDrv, Message* msg, SIPMessage* sipNotify,
	SIPTransaction* transaction);
    virtual void run(void);
    virtual void cleanup(void)
	{ release(true); }
private:
    // Respond the transaction and deref() it
    void setTrResponse(int code);
    // Set transaction response. Send the notification message. Notify the
    // connection and release other objects
    void release(bool fromCleanup = false);

    String m_transferorID;           // Transferor channel's id
    String m_transferredID;          // Transferred channel's id
    Driver* m_transferredDrv;        // Transferred driver's pointer
    Message* m_msg;                  // 'call.route' message
    SIPMessage* m_sipNotify;         // NOTIFY message to send the result
    int m_notifyCode;                // The result to send with NOTIFY
    SIPTransaction* m_transaction;   // The transaction to respond to
    int m_rspCode;                   // The transaction response
};

class YateSIPRegister : public Thread
{
public:
    inline YateSIPRegister(YateSIPEndPoint* ep, SIPMessage* message, SIPTransaction* t)
	: Thread("YSIP Register"),
	  m_ep(ep), m_msg(message), m_tr(t)
	{ }
    virtual void run()
	{ m_ep->regRun(m_msg,m_tr); }
private:
    YateSIPEndPoint* m_ep;
    RefPointer<SIPMessage> m_msg;
    RefPointer<SIPTransaction> m_tr;
};

class YateSIPGeneric : public Thread
{
public:
    inline YateSIPGeneric(YateSIPEndPoint* ep, SIPMessage* message, SIPTransaction* t,
	const char* method, int defErr, bool autoAuth, bool isMsg)
	: Thread("YSIP Generic"),
	  m_ep(ep), m_msg(message), m_tr(t),
	  m_method(method), m_error(defErr), m_auth(autoAuth), m_message(isMsg)
	{ }
    virtual void run()
	{ if (!m_ep->generic(m_msg,m_tr,m_method,m_auth,m_message)) m_tr->setResponse(m_error); }
private:
    YateSIPEndPoint* m_ep;
    RefPointer<SIPMessage> m_msg;
    RefPointer<SIPTransaction> m_tr;
    String m_method;
    int m_error;
    bool m_auth;
    bool m_message;
};

class YateSIPConnection : public Channel, public SDPSession, public YateSIPPartyHolder
{
    friend class SIPDriver;
    YCLASS(YateSIPConnection,Channel)
public:
    enum {
	Incoming = 0,
	Outgoing = 1,
	Ringing = 2,
	Established = 3,
	Cleared = 4,
    };
    enum {
	ReinviteNone,
	ReinvitePending,
	ReinviteRequest,
	ReinviteReceived,
    };
    YateSIPConnection(SIPEvent* ev, SIPTransaction* tr);
    YateSIPConnection(Message& msg, const String& uri, const char* target = 0);
    ~YateSIPConnection();
    virtual void destroyed();
    virtual void complete(Message& msg, bool minimal=false) const;
    virtual void disconnected(bool final, const char *reason);
    virtual bool msgProgress(Message& msg);
    virtual bool msgRinging(Message& msg);
    virtual bool msgAnswered(Message& msg);
    virtual bool msgTone(Message& msg, const char* tone);
    virtual bool msgText(Message& msg, const char* text);
    virtual bool msgDrop(Message& msg, const char* reason);
    virtual bool msgUpdate(Message& msg);
    virtual bool msgControl(Message& msg);
    virtual bool callPrerouted(Message& msg, bool handled);
    virtual bool callRouted(Message& msg);
    virtual void callAccept(Message& msg);
    virtual void callRejected(const char* error, const char* reason, const Message* msg);
    void startRouter();
    bool process(SIPEvent* ev);
    bool checkUser(SIPTransaction* t, bool refuse = true);
    void doBye(SIPTransaction* t);
    void doCancel(SIPTransaction* t);
    bool doInfo(SIPTransaction* t);
    void doRefer(SIPTransaction* t);
    void doMessage(SIPTransaction* t);
    void reInvite(SIPTransaction* t);
    void hangup();
    inline const SIPDialog& dialog() const
	{ return m_dialog; }
    inline void setStatus(const char *stat, int state = -1)
	{ status(stat); if (state >= 0) m_state = state; }
    inline void setReason(const char* str = "Request Terminated", int code = 487,
	Mutex* mtx = 0) {
	    Lock lck(mtx);
	    m_reason = str;
	    m_reasonCode = code;
	}
    inline SIPTransaction* getTransaction() const
	{ return m_tr; }
    inline const String& callid() const
	{ return m_dialog; }
    inline const String& user() const
	{ return m_user; }
    inline int getPort() const
	{ return m_port; }
    inline const String& getLine() const
	{ return m_line; }
    inline void referTerminated()
	{ m_referring = false; }
    inline bool isDialog(const String& callid, const String& fromTag,
	const String& toTag) const
	{ return callid == m_dialog &&
	    m_dialog.fromTag(isOutgoing()) == fromTag &&
	    m_dialog.toTag(isOutgoing()) == toTag; }
    inline bool stopOCall() const
	{ return m_stopOCall; }
    // Build and add a callid parameter to a list
    static inline void addCallId(NamedList& nl, const String& dialog,
	const String& fromTag, const String& toTag) {
	    String tmp;
	    tmp << "sip/" << dialog << "/" << fromTag << "/" << toTag;
	    nl.addParam("callid",tmp);
	}
    const String& traceId() const
        { return m_traceId; }

protected:
    virtual Message* buildChanRtp(RefObject* context);
    MimeSdpBody* createProvisionalSDP(Message& msg);
    virtual void mediaChanged(const SDPMedia& media);
    virtual void dispatchingRtp(Message*& msg, SDPMedia* media);
    virtual void endDisconnect(const Message& msg, bool handled);

private:
    virtual void statusParams(String& str);
    void clearTransaction();
    void detachTransaction2();
    void startPendingUpdate();
    bool processTransaction2(SIPEvent* ev, const SIPMessage* msg, int code);
    SIPMessage* createDlgMsg(const char* method, const char* uri = 0);
    void updateTarget(const SIPMessage* msg, bool force = false, bool chgParty = true);
    void emitUpdate();
    bool emitPRACK(const SIPMessage* msg);
    bool startClientReInvite(NamedList& msg, bool rtpForward);
    bool reInviteForward(SIPTransaction* t, MimeSdpBody* sdp, int invite);
    bool reInviteProxy(SIPTransaction* t, MimeSdpBody* sdp, int invite);
    // Build the 'call.route' and NOTIFY messages needed by the transfer thread
    bool initTransfer(Message*& msg, SIPMessage*& sipNotify, const SIPMessage* sipRefer,
	const MimeHeaderLine* refHdr, const URI& uri, const MimeHeaderLine* replaces);
    // Decode an application/isup body into 'msg' if configured to do so
    // The message's name and user data are restored before exiting, regardless the result
    // Return true if an ISUP message was succesfully decoded
    bool decodeIsupBody(Message& msg, MimeBody* body);
    // Build the body of a SIP message from an engine message
    // Encode an ISUP message from parameters received in msg if enabled to process them
    // Build a multipart/mixed body if more then one body is going to be sent
    MimeBody* buildSIPBody(Message& msg, MimeSdpBody* sdp = 0, const char* prefixName = 0);
    // Build the body of a hangup SIP message
    MimeBody* buildSIPBody();
    // Update NAT address from params or transport
    void updateRtpNatAddress(NamedList* params = 0);
    // Process allow list. Get INFO support
    bool infoAllowed(const SIPMessage* msg);
    // Send tone(s) using method
    bool sendTone(Message& msg, const char* tone, int meth, bool& retVal);
    inline void setSilent(const char* reason) {
	    if (m_silent || (YSTRING("silent") != reason))
		return;
	    m_silent = true;
	    clearTransaction();
	}

    SIPTransaction* m_tr;
    SIPTransaction* m_tr2;
    // are we already hung up?
    bool m_hungup;
    // should we send a BYE?
    bool m_byebye;
    // should we CANCEL?
    bool m_cancel;
    int m_state;
    String m_reason;
    int m_reasonCode;
    // SIP dialog of this call, used for re-INVITE or BYE
    SIPDialog m_dialog;
    // remote URI as we send in dialog messages
    URI m_uri;
    String m_domain;
    String m_user;
    String m_line;
    int m_port;
    Message* m_route;
    ObjList* m_routes;
    bool m_authBye;
    bool m_autoChangeParty;              // Auto change party from received message
    bool m_checkAllowInfo;               // Check Allow in INVITE and OK for INFO support
    bool m_missingAllowInfoDefVal;       // Default INFO support if Allow header is missing
    DtmfMethods m_dtmfMethods;
    bool m_honorDtmfDetect;
    // REFER already running
    bool m_referring;
    // reINVITE requested or in progress
    int m_reInviting;
    // sequence number of last transmitted PRACK
    int m_lastRseq;
    // media parameters before we sent a reINVITE
    NamedList m_revert;
    bool m_silent;                       // Silently discard SIP dialog
    bool m_stopOCall;
    String m_traceId;
};

class YateSIPGenerate : public GenObject
{
    YCLASS(YateSIPGenerate,GenObject)
public:
    YateSIPGenerate(SIPMessage* m, int tries);
    virtual ~YateSIPGenerate();
    bool process(SIPEvent* ev);
    inline bool busy() const
	{ return m_tr != 0; }
    inline int code() const
	{ return m_code; }
    inline const SIPMessage* answer() const
	{ return m_msg; }
private:
    void clearTransaction();
    SIPTransaction* m_tr;
    RefPointer<SIPMessage> m_msg;
    int m_code;
};

class SIPDriver : public Driver
{
public:
    enum Relay {
	Stop = Private,
	Start = Private << 1,
    };
    SIPDriver();
    ~SIPDriver();
    virtual void initialize();
    virtual bool hasLine(const String& line) const;
    virtual bool msgExecute(Message& msg, String& dest);
    virtual bool received(Message& msg, int id);
    inline YateSIPEndPoint* ep() const
	{ return m_endpoint; }
    inline SDPParser& parser()
        { return m_parser; }
    inline bool traceActive() const
    { 
	return s_trace || Engine::sharedVars().exists(YSTRING("trace")) ||
	    Engine::sharedVars().exists(YSTRING("trace_sip"));
    }
    void epTerminated(YateSIPEndPoint* ep);
    YateSIPConnection* findCall(const String& callid, bool incRef = false);
    YateSIPConnection* findDialog(const SIPDialog& dialog, bool incRef = false);
    YateSIPConnection* findDialog(const String& dialog, const String& fromTag,
	const String& toTag, bool incRef = false);
    YateSIPLine* findLine(const String& line) const;
    YateSIPLine* findLine(const String& addr, int port, const String& user = String::empty(),
	SIPParty* party = 0);
    // Drop channels belonging using a given transport
    // Return the number of disconnected channels
    unsigned int transportTerminated(YateSIPTransport* trans);
    bool validLine(const String& line);
    bool commandComplete(Message& msg, const String& partLine, const String& partWord);
    virtual bool commandExecute(String& retVal, const String& line);
    virtual void msgStatus(Message& msg);
    virtual void statusParams(String& str);
    // Build and dispatch a socket.ssl message
    bool socketSsl(Socket** sock, bool server, const String& context = String::empty());
    // Send a SIP method
    bool sendMethod(Message& msg, const char* method, bool msgExec = false,
	const char* target = 0);

    static bool s_trace;

protected:
    virtual void genUpdate(Message& msg);
    // Setup a listener from config
    void setupListener(const String& name, const NamedList& params, bool isGeneral,
	const NamedList& defs = NamedList::empty());
    bool canStopCall() const
	{ return true; }
private:
    bool onHelp(Message& msg);
    // Add status methods
    void msgStatusAccounts(Message& msg);
    void msgStatusTransports(Message& msg, bool showUdp, bool showTcp, bool showTls);
    void msgStatusListener(Message& msg);
    void msgStatusTransport(Message& msg, const String& id);

    SDPParser m_parser;
    YateSIPEndPoint *m_endpoint;
};

static SIPDriver plugin;

class UserHandler : public MessageHandler
{
public:
    UserHandler()
	: MessageHandler("user.login",150,plugin.name())
	{ }
    virtual bool received(Message &msg);
};

class SipHandler : public MessageHandler
{
public:
    enum {
	BodyRaw = 0,
	BodyBase64 = 1,
	BodyHex = 2,
	BodyHexS = 3,
    };
    SipHandler()
	: MessageHandler("xsip.generate",110,plugin.name())
	{ }
    virtual bool received(Message &msg);
    static const TokenDict s_bodyEnc[];
};

static ObjList s_lines;
static Configuration s_cfg;
static Mutex s_globalMutex(true,"SIPGlobal"); // Protect globals (don't use the plugin to avoid deadlocks)
static bool s_engineStart = false;       // engine.start received
static unsigned int s_engineStop = 0;    // engine.stop message counter
static bool s_engineHalt = false;        // engine.halt received
static bool s_sslClientAvailable = false;// SSL is available to be used when connecting to remote hosts
static unsigned int s_bindRetryMs = 500; // Listeners bind retry interval
static String s_realm = "Yate";
static int s_floodEvents = 100;
static bool s_floodProtection = true;
static int s_maxForwards = 20;
static int s_congRetry = 30;
static int s_nat_refresh = 25;
static bool s_privacy = false;
static bool s_auto_nat = true;
static bool s_progress = false;
static bool s_start_rtp = false;
static bool s_ack_required = true;
static bool s_1xx_formats = true;
static bool s_sdp_implicit = true;
static bool s_rtp_preserve = false;
static bool s_enable_transfer = false;
static bool s_enable_options = false;
static bool s_enable_message = false;
static bool s_enable_register = false;
static bool s_auth_message = true;
static bool s_auth_register = true;
static bool s_msg_async = true;
static bool s_reg_async = true;
static bool s_gen_async = true;
static bool s_multi_ringing = false;
static bool s_refresh_nosdp = true;
static bool s_update_target = false;
static bool s_update_verify = false;
static bool s_preventive_bye = true;
static bool s_ignoreVia = true;          // Ignore Via headers and send answer back to the source
static bool s_changeParty2xx = false;    // Change party when handling 2xx. Ignored if autochangeparty is disabled
static bool s_warnPacketUDP = true;      // Warn when receiving possible truncated packet
static bool s_initialHeaders = false;    // Put all headers as received in initial message sent to yate
static bool s_sipt_isup = false;         // Control the application/isup body processing
static bool s_printMsg = true;           // Print sent/received SIP messages to output
static ObjList* s_authCopyHeader = 0;    // Copy headers in user.auth

static bool s_ipv6 = false;              // IPv6 support enabled
static u_int64_t s_waitActiveUdpTrans = 1000000; // Time to wait for active UDP transactions
                                                 // to complete when a listener is disabled
static unsigned int s_tcpConnectRetry = 3; // Number of TCP connect attempts
static u_int64_t s_tcpConnectInterval = 1000000; // The interval to attempt tcp connect
static unsigned int s_tcpIdle = TCP_IDLE_DEF; // TCP transport idle interval
static unsigned int s_tcpKeepalive = TCP_IDLE_DEF; // TCP transport keepalive interval
static unsigned int s_tcpKeepaliveFirst = 0; // TCP transport first keepalive interval
static unsigned int s_tcpMaxpkt = 1500;  // Maximum packet to accept on TCP connections
static String s_tcpOutRtpip;             // RTP ip for outgoing tcp/tls transports (protected by plugin mutex)
static bool s_lineKeepTcpOffline = true; // Lines: keep TCP transports when offline
static String s_sslCertFile;             // File containing the SSL client certificate to present if requested by the server
static String s_sslKeyFile;              // File containing the key of the SSL client certificate

static int s_expires_min = EXPIRES_MIN;
static int s_expires_def = EXPIRES_DEF;
static int s_expires_max = EXPIRES_MAX;
static int s_defEncoding = SipHandler::BodyBase64;

static const String s_statusCmd = "status";
static const String s_noAutoAuth = "noautoauth";
static const String s_username = "username";

static u_int64_t s_printFloodTime = 0;

int YateSIPEndPoint::s_evCount = 0;
bool SIPDriver::s_trace = false;

// DTMF methods
static bool s_checkAllowInfo = true;         // Check Allow in INVITE and OK for INFO support
static bool s_missingAllowInfoDefVal = true; // Default INFO support if Allow header is missing
static DtmfMethods s_dtmfMethods;
static bool s_honorDtmfDetect = true;
// Deprecated dtmf params warn
static bool s_warnDtmfInfoCfg = true;
static bool s_warnDtmfInbandCfg = true;
static bool s_warnDtmfInfoCallExecute = true;
static bool s_warnDtmfInbandCallExecute = true;
static bool s_warnDtmfMethodChanDtmf = true;

const TokenDict DtmfMethods::s_methodName[] = {
    { "info",     Info},
    { "rfc2833",  Rfc2833},
    { "inband",   Inband},
    { "sip-info", Info},
    { 0, 0 },
};

// Lower case proto name
const TokenDict ProtocolHolder::s_protoLC[] = {
    { "udp",  Udp},
    { "tcp",  Tcp},
    { "tls",  Tls},
    { 0, 0 },
};

// Upper case proto name
const TokenDict ProtocolHolder::s_protoUC[] = {
    { "UDP",  Udp},
    { "TCP",  Tcp},
    { "TLS",  Tls},
    { 0, 0 },
};

// Transport status names
const TokenDict YateSIPTransport::s_statusName[] = {
    { "Idle",        Idle},
    { "Connected",   Connected},
    { "Terminating", Terminating},
    { "Terminated",  Terminated},
    { 0, 0 },
};

// Body encodings
const TokenDict SipHandler::s_bodyEnc[] = {
    { "raw",    BodyRaw},
    { "base64", BodyBase64},
    { "hex",    BodyHex},
    { "hexs",   BodyHexS},
    { 0, 0 },
};

static inline String& getGlobal(String& dest, String& src)
{
    Lock lck(s_globalMutex);
    return (dest = src);
}

// Get an address. Check if enclosed in []
static inline void getAddrCheckIPv6(String& dest, const String& src)
{
    if (!src)
	return;
    const char* s = src;
    if (!(s[0] == '[' && s[src.length() - 1] == ']'))
	dest = src;
    else
	dest.assign(s + 1,src.length() - 2);
}

// Generate a transport id index when needed
static inline unsigned int getTransIndex()
{
    static unsigned int s_index = 0;
    Lock lck(plugin);
    if (!++s_index)
	++s_index;
    return s_index;
}

// Add a socket error to a buffer
static inline void addSockError(String& buf, Socket& sock, const char* sep = " ")
{
    String tmp;
    Thread::errorString(tmp,sock.error());
    buf << sep << tmp << " (" << sock.error() << ")";
}

// Build an address from host and interface part of another address
static inline void addIfaceAddr(String& buf, const String& host, const String& addr)
{
    buf << host;
    int p = addr.find('%');
    if (p >= 0)
	buf.append(addr.c_str() + p,addr.length() - p);
}

// Return default udp/tcp/tls port
static inline int sipPort(bool noTls)
{
    return noTls ? 5060 : 5061;
}

// Return valid tcp idle interval
static unsigned int tcpIdleInterval(int val)
{
    int min = TCP_IDLE_MIN;
    // Adjust minimum value to engine INVITE timeout
    if (plugin.ep())
	min = (int)(plugin.ep()->engine()->getTimer('B',true) / 1000000) * 3 / 2;
    if (val >= min && val <= TCP_IDLE_MAX)
	return val;
    return (val < min) ? min : TCP_IDLE_MAX;
}

// Return a valid maxpkt
static unsigned int getMaxpkt(int val, int defVal)
{
    if (val >= 524 && val <= 65528)
	return val;
    if (val <= 0)
	return defVal;
    if (val > 65528)
	return 65528;
    return 524;
}

// Skip tabs, spaces, CR and LF from buffer start
// Return true if the buffer changed
static inline bool skipSpaces(const char*& buf, unsigned int& len, bool crlf = true)
{
    unsigned int i = 0;
    if (crlf) {
	for (; i < len; i++)
	    if (buf[i] != '\r' && buf[i] != '\n' && buf[i] != ' ' && buf[i] != '\t')
		break;
    }
    else
	for (; i < len; i++)
	    if (buf[i] != ' ' && buf[i] != '\t')
		break;
    if (!i)
	return false;
    buf += i;
    len -= i;
    return true;
}

// Skip spaces at buffer start
// Check for keep alive request
// Return true if keep alive request was found
static inline bool skipSpacesCheckKeepAlive(const char*& buf, unsigned int& len)
{
    static const char* s_keepAlive = "\r\n\r\n";
    bool found = false;
    while (len) {
	skipSpaces(buf,len,false);
	if (!len)
	    break;
	if (buf[0] != '\r') {
	    if (buf[0] != '\n')
		break;
	    buf++;
	    len--;
	    continue;
	}
	unsigned int i = 1;
	while (i < len && i < 4 && buf[i] == s_keepAlive[i])
	    i++;
	// End of buffer before full keep live: don't advance
	if (i < 4 && i == len)
	    break;
	buf += i;
	len -= i;
	if (i == 4) {
	    found = true;
	    break;
	}
    }
    if (found)
	skipSpaces(buf,len);
    return found;
}

// Find an empty line in a buffer
// Return the position past it or buffer length + 1 if not found
// NOTE: returned value may be buffer length
static inline unsigned int getEmptyLine(const char* buf, unsigned int len)
{
    int count = 0;
    unsigned int i = 0;
    for (; count < 2 && i < len; i++) {
	if (buf[i] == '\r') {
	    i++;
	    if (i < len && buf[i] == '\n')
		count++;
	    else
		count = 0;
	}
	else if (buf[i] == '\n')
	    count++;
	else
	    count = 0;
    }
    return (count == 2) ? i : len + 1;
}

// Fill a buffer with message method/code for debug purposes
static inline void getMsgLine(String& buf, const SIPMessage* msg)
{
    if (!msg)
	return;
    if (msg->isAnswer())
	buf << "code " << msg->code;
    else
	buf << "'" << msg->method << " " << msg->uri << "'";
}

enum {
    START,
    CHECK_TO,
    HAS_TO,
    CHECK_TAG,
    HAS_TAG,
};

static bool msgIsAllowed(const char* buf, int len)
{
    if (!(buf && len))
	return false;


    int pos = 0;
    // check REGISTER
    while (pos < len) {
	if (buf[pos] == ' ' || buf[pos] == '\n' || buf[pos] == '\r' || buf[pos] == '\t') {
	    pos++;
	    continue;
	}
	if ((pos <= len - 8) && (buf[pos] == 'O' || buf[pos] == 'o') &&
	    (buf[pos + 1] == 'P' || buf[pos + 1] == 'p') && (buf[pos + 2] == 'T' || buf[pos + 2] == 't') &&
	    (buf[pos + 3] == 'I' || buf[pos + 3] == 'i') && (buf[pos + 4] == 'O' || buf[pos + 4] == 'o') &&
	    (buf[pos + 5] == 'N' || buf[pos + 5] == 'n') && (buf[pos + 6] == 'S' || buf[pos + 6] == 's') &&
	    (buf[pos + 7] == ' ' || buf[pos + 7] == '\t'))
	    return false;
	if ((pos <= len - 9) && (buf[pos] == 'R' || buf[pos] == 'r') &&
	    (buf[pos + 1] == 'E' || buf[pos + 1] == 'e') && (buf[pos + 2] == 'G' || buf[pos + 2] == 'g') &&
	    (buf[pos + 3] == 'I' || buf[pos + 3] == 'i') && (buf[pos + 4] == 'S' || buf[pos + 4] == 's') &&
	    (buf[pos + 5] == 'T' || buf[pos + 5] == 't') && (buf[pos + 6] == 'E' || buf[pos + 6] == 'e') &&
	    (buf[pos + 7] == 'R' || buf[pos + 7] == 'r') && (buf[pos + 8] == ' ' || buf[pos + 8] == '\t'))
	    return false;
	else if ((pos <= len - 10) && (buf[pos] == 'S' || buf[pos] == 's') &&
	    (buf[pos + 1] == 'U' || buf[pos + 1] == 'u') && (buf[pos + 2] == 'B' || buf[pos + 2] == 'b') &&
	    (buf[pos + 3] == 'S' || buf[pos + 3] == 's') && (buf[pos + 4] == 'C' || buf[pos + 4] == 'c') &&
	    (buf[pos + 5] == 'R' || buf[pos + 5] == 'r') && (buf[pos + 6] == 'I' || buf[pos + 6] == 'i') &&
	    (buf[pos + 7] == 'B' || buf[pos + 7] == 'b') && (buf[pos + 8] == 'E' || buf[pos + 8] == 'e') &&
	    (buf[pos + 9] == ' ' || buf[pos + 9] == '\t'))
	    return false;
	else if ((pos <= len - 7) && ((buf[pos] == 'I' || buf[pos] == 'i') && ++pos) &&
	    ((buf[pos] == 'N' || buf[pos] == 'n') && pos++) && ((buf[pos] == 'V' || buf[pos] == 'v') && pos++) &&
	    ((buf[pos] == 'I' || buf[pos] == 'i') && pos++) && ((buf[pos] == 'T' || buf[pos] == 't') && pos++) &&
	    ((buf[pos] == 'E' || buf[pos] == 'e') && pos++) && ((buf[pos] == ' ' || buf[pos] == '\t') && pos++))
	    break;
	else
	    return true;
    }

    /**
     * START = begin of line
     * CHECK_TO = T/t[o/O] has been found
     * HAS_TO = T/t[o/O]: has been found
     * CHECK_TAG = T/t[o/O]:...; has been found
     * HAS_TAG = T/t[o/O]:...;tag has been found - expecting =
     */
    while (pos < len - 1) {
	// go to new line
	while (pos < len && buf[pos] != '\r' && buf[pos] != '\n') pos++;
	int status = START;
	bool breakLoop = false;
	while (pos < len) {

	    switch (buf[pos]) {
		case ' ':
		case '\t':
		    if (status == START)
			breakLoop = true;
		    break;
		case '\n':
		case '\r':
		    if (status != START) {
			// new line found before finding a tag, return false
			if (status == HAS_TO)
			    return false;
			// try the next line
			breakLoop = true;
		    }
		    break;
		case 'T':
		case 't':
		    if (status == START) {
			if (pos < len - 1)
			    if (buf[pos + 1] == 'o' || buf[pos + 1] == 'O') pos++;
			status = CHECK_TO;
		    }
		    else if (status == CHECK_TO)
			// something like Tot is at the start of the line, try next line
			breakLoop = true;
		    // 	 if status is HAS_TO, skip over
		    else if (status == CHECK_TAG) {
			if (pos < len - 2) {
			    if ((buf[pos + 1] == 'a' || buf[pos + 1] == 'A') && (buf[pos + 2] == 'g' || buf[pos + 2] == 'G')) {
				pos += 2;
				status = HAS_TAG;
			    }
			    else
				// might be another tag, return to HAS_TO
				status = HAS_TO;
			}
			else
			    // we're at the end of the string and we don't have enough chars to check for tag
			    breakLoop = true;
		    }
		    else if (status == HAS_TAG)
			// might be another tag, return to HAS_TO
			status = HAS_TO;
		    break;
		case ':':
		    if (status == CHECK_TO)
			status = HAS_TO;
		    else if (status == START)
			// : at start of line, unnacceptable, try next line
			breakLoop = true;
		    // if status is HAS_TO, skip over
		    else if (status == CHECK_TAG || status == HAS_TAG)
			// skip over : while looking for 'tag', return to HAS_TO
			status = HAS_TO;
		    break;
		case ';':
		    if (status == HAS_TO)
			status = CHECK_TAG;
		    else if (status == START || status == CHECK_TO)
			// ; at start of line or after 'to' unnacceptable, try next line
			breakLoop = true;
		    // if status is HAS_TO/CHECK_TAG, skip over
		    else if (status == HAS_TAG)
			// skip over : while looking for 'tag', return to HAS_TO
			status = HAS_TO;
		    break;
		case '=':
		    if (status == HAS_TAG)
			// found ';tag=', is reINVITE
			return true;
		    else if (status == START || status == CHECK_TO)
			// ; at start of line or after 'to' unnacceptable, try next line
			breakLoop = true;
		    // if status is HAS_TO, skip over
		    else if (status == CHECK_TAG)
			// ignore, return to HAS_TO
			status = HAS_TO;
		    break;
		default:
		    if (status == CHECK_TAG || status == HAS_TAG)
			status = HAS_TO;
		    else if (status == START || status == CHECK_TO)
			breakLoop = true;
		    // if status is HAS_TO, skip over
		    break;
	    }
	    if (breakLoop)
		break;
	    pos++;
	}
    }
    return false;
}

// Reset transport timeout from expires
static void resetTransportIdle(const SIPMessage* msg, int interval)
{
    if (!msg || interval <= 0)
	return;
    YateSIPTCPTransport* tcp = YOBJECT(YateSIPTCPTransport,msg->getParty());
    if (!tcp)
	return;
    const MimeHeaderLine* hl = msg->getHeader("Flow-Timer");
    int val = hl ? hl->toInteger() : 0;
    bool on = (val > 0 && val < interval);
    if (on) {
	// Wait extra time for incoming transports
	interval = tcp->outgoing() ? val : val + 20;
    }
    tcp->setFlowTimer(on,interval);
}

// Check if an IPv4 address belongs to one of the non-routable blocks
static bool isPrivateAddr(const String& host)
{
    if (host.startsWith("192.168.") || host.startsWith("169.254.") || host.startsWith("10."))
	return true;
    String s(host);
    if (!s.startSkip("172.",false))
	return false;
    int i = 0;
    s >> i;
    return (i >= 16) && (i <= 31) && s.startsWith(".");
}

// Check if there may be a NAT between an address embedded in the protocol
//  and an address obtained from the network layer
static bool isNatBetween(const String& embAddr, const String& netAddr)
{
    if (embAddr == netAddr)
	return false;
    int embFamily = SocketAddr::family(embAddr);
    if (embFamily != SocketAddr::IPv4)
	return false;
    int netFamily = SocketAddr::family(netAddr);
    if (embFamily != netFamily)
	return false;
    return isPrivateAddr(embAddr) && !isPrivateAddr(netAddr);
}

// List of headers we may not want to handle generically
static const char* s_filterHeaders[] = {
    "from",
    "to",
    0
};

// List of critical headers we surely don't want to handle generically
static const char* s_rejectHeaders[] = {
    "via",
    "route",
    "record-route",
    "call-id",
    "cseq",
    "max-forwards",
    "content-length",
    0
};

// List of authentication headers we normally don't handle generically
static const char* s_authHeaders[] = {
    "www-authenticate",
    "proxy-authenticate",
    "authorization",
    "proxy-authorization",
    0
};

// Check if a string matches one member of a static list
static bool matchAny(const String& name, const char** strs)
{
    for (; *strs; strs++)
	if (name == *strs)
	    return true;
    return false;
}

// Copy headers from SIP message to Yate message
static void copySipHeaders(NamedList& msg, const SIPMessage& sip, bool filter = true, bool auth = false,
    bool all = false)
{
    const ObjList* l = sip.header.skipNull();
    for (; l; l = l->skipNext()) {
	const MimeHeaderLine* t = static_cast<const MimeHeaderLine*>(l->get());
	String name(t->name());
	name.toLower();
	// Filtered headers (from/to) are added explicitly
	if (filter && matchAny(name,s_filterHeaders))
	    continue;
	const char* prefix = "sip_";
	if (matchAny(name,s_rejectHeaders) || (!auth && matchAny(name,s_authHeaders))) {
	    if (!all)
		continue;
	    prefix = "sipheader_";
	}
	NamedString* ns = new NamedString(prefix + name);
	t->buildLine(*ns,false);
	msg.addParam(ns);
    }
}

// Copy headers from Yate message to SIP message
static void copySipHeaders(SIPMessage& sip, const NamedList& msg, const char* prefix = "osip_")
{
    prefix = msg.getValue(YSTRING("osip-prefix"),prefix);
    if (!prefix)
	return;
    unsigned int n = msg.length();
    for (unsigned int i = 0; i < n; i++) {
	NamedString* str = msg.getParam(i);
	if (!str)
	    continue;
	String name(str->name());
	if (!name.startSkip(prefix,false))
	    continue;
	if (name.trimBlanks().null())
	    continue;
	sip.addHeader(name,*str);
    }
}

// Copy privacy related information from SIP message to Yate message
static void copyPrivacy(NamedList& msg, const SIPMessage& sip)
{
    bool anonip = (sip.getHeaderValue("Anonymity") &= "ipaddr");
    const MimeHeaderLine* hl = sip.getHeader("Remote-Party-ID");
    const MimeHeaderLine* ai = sip.getHeader("P-Asserted-Identity");
    const MimeHeaderLine* pr = sip.getHeader("Privacy");
    if (ai) {
	URI uri(*ai);
	const char* tmp = uri.getDescription();
	if (tmp)
	    msg.setParam("asserted_callername",tmp);
	tmp = uri.getUser();
	if (tmp)
	    msg.setParam("asserted_caller",tmp);
	tmp = uri.getHost();
	if (tmp)
	    msg.setParam("asserted_domain",tmp);
	tmp = uri.getExtra();
	if (tmp)
	    msg.setParam("asserted_params",tmp);
    }
    if (!(anonip || hl || ai || pr))
	return;
    const NamedString* p = hl ? hl->getParam("screen") : 0;
    if (p)
	msg.setParam("screened",*p);
    if (pr && (*pr &= "none")) {
	msg.setParam("privacy",String::boolText(false));
	return;
    }
    bool privname = false;
    bool privuri = false;
    String priv;
    if (anonip)
	priv.append("addr",",");
    p = hl ? hl->getParam("privacy") : 0;
    if (p) {
	if ((*p &= "full") || (*p &= "full-network"))
	    privname = privuri = true;
	else if ((*p &= "name") || (*p &= "name-network"))
	    privname = true;
	else if ((*p &= "uri") || (*p &= "uri-network"))
	    privuri = true;
    }
    if (pr) {
	if ((*pr &= "user") || pr->getParam("user"))
	    privname = true;
	if ((*pr &= "header") || pr->getParam("header"))
	    privuri = true;
    }
    if (privname)
	priv.append("name",",");
    if (privuri)
	priv.append("uri",",");
    if (pr) {
	if ((*pr &= "session") || pr->getParam("session"))
	    priv.append("session",",");
	if ((*pr &= "critical") || pr->getParam("critical"))
	    priv.append("critical",",");
	if ((*pr &= "id") || pr->getParam("id"))
	    priv.append("id",",");
    }
    if (priv)
	msg.setParam("privacy",priv);
    if (hl) {
	URI uri(*hl);
	const char* tmp = uri.getDescription();
	if (tmp)
	    msg.setParam("privacy_callername",tmp);
	tmp = uri.getUser();
	if (tmp)
	    msg.setParam("privacy_caller",tmp);
	tmp = uri.getHost();
	if (tmp)
	    msg.setParam("privacy_domain",tmp);
	const String* str = hl->getParam("party");
	if (!TelEngine::null(str))
	    msg.setParam("remote_party",*str);
	str = hl->getParam("id-type");
	if (!TelEngine::null(str))
	    msg.setParam("remote_id_type",*str);
    }
}

// Copy privacy related information from Yate message to SIP message
static void copyPrivacy(SIPMessage& sip, const NamedList& msg)
{
    const String& screened = msg[YSTRING("screened")];
    const String& privacy = msg[YSTRING("privacy")];
    if (screened.null() && privacy.null())
	return;
    bool screen = screened.toBoolean();
    bool anonip = (privacy.find("addr") >= 0);
    bool privname = (privacy.find("name") >= 0);
    bool privuri = (privacy.find("uri") >= 0);
    bool privid = (privacy.find("id") >= 0);
    bool nopriv = !privacy.toBoolean(true);
    String rfc3323;
    // allow for a simple "privacy=yes" or similar
    if (privacy.toBoolean(false) || (privacy == YSTRING("full")))
	privname = privuri = true;
    // "privacy=no" is translated to RFC 3323 "none"
    else if (nopriv)
	rfc3323 = "none";
    if (anonip)
	sip.setHeader("Anonymity","ipaddr");
    if (screen || privname || privuri) {
	const char* caller = msg.getValue(YSTRING("privacy_caller"),msg.getValue(YSTRING("caller")));
	if (!caller)
	    caller = "anonymous";
	const char* domain = msg.getValue(YSTRING("privacy_domain"),msg.getValue(YSTRING("domain")));
	if (!domain)
	    domain = "domain";
	String tmp = msg.getValue(YSTRING("privacy_callername"),msg.getValue(YSTRING("callername")));
	if (tmp) {
	    MimeHeaderLine::addQuotes(tmp);
	    tmp += " ";
	}
	tmp << "<sip:" << caller << "@" << domain << ">";
	MimeHeaderLine* hl = new MimeHeaderLine("Remote-Party-ID",tmp);
	if (screen)
	    hl->setParam("screen","yes");
	if (privname && privuri)
	    hl->setParam("privacy","full");
	else if (privname)
	    hl->setParam("privacy","name");
	else if (privuri)
	    hl->setParam("privacy","uri");
	else
	    hl->setParam("privacy","none");
	const char* str = msg.getValue(YSTRING("remote_party"));
	if (str)
	    hl->setParam("party",str);
	str = msg.getValue(YSTRING("remote_id_type"));
	if (str)
	    hl->setParam("id-type",str);
	sip.addHeader(hl);
    }
    if (privid || nopriv) {
	const char* caller = msg.getValue(YSTRING("asserted_caller"),msg.getValue(YSTRING("caller")));
	if (caller) {
	    const char* domain = msg.getValue(YSTRING("asserted_domain"),msg.getValue(YSTRING("domain")));
	    const char* extra = msg.getValue(YSTRING("asserted_params"));
	    String tmp;
	    if (domain)
		tmp << "<sip:" << caller << "@" << domain << extra << ">";
	    else if (isE164(caller))
		tmp << "<tel:" << caller << extra << ">";
	    if (tmp) {
		String desc = msg.getValue(YSTRING("asserted_callername"),msg.getValue(YSTRING("callername")));
		if (desc) {
		    MimeHeaderLine::addQuotes(desc);
		    tmp = desc + " " + tmp;
		}
		sip.addHeader(new MimeHeaderLine("P-Asserted-Identity",tmp));
		if (privid) {
		    MimeHeaderLine* hl = const_cast<MimeHeaderLine*>(sip.getHeader("From"));
		    if (hl) {
			URI uri(*hl);
			if (uri.getHost() &&
			    (uri.getProtocol() == YSTRING("sip") || uri.getProtocol() == YSTRING("sips"))) {
			    String tmp = "<" + uri.getProtocol() + ":Anonymous@";
			    if (uri.getPort())
				SocketAddr::appendTo(tmp,uri.getHost(),uri.getPort());
			    else
				SocketAddr::appendAddr(tmp,uri.getHost());
			    *hl = tmp + uri.getExtra() + ">";
			}
			else if (domain)
			    *hl = "<sip:Anonymous@" + String(domain) + ">";
			else
			    *hl = "<sip:Anonymous@anonymous.invalid>";
		    }
		}
	    }
	}
    }
    if (rfc3323.null()) {
	if (privname)
	    rfc3323.append("user",";");
	if (privuri)
	    rfc3323.append("header",";");
	if (privid)
	    rfc3323.append("id",";");
	if (privacy.find("session") >= 0)
	    rfc3323.append("session",";");
	if (rfc3323 && (privacy.find("critical") >= 0))
	    rfc3323.append("critical",";");
    }
    if (rfc3323)
	sip.addHeader("Privacy",rfc3323);
}

// Check if a SDP body is accepted by UAC
static bool sdpAccept(const SIPMessage* sip, bool def)
{
    static const Regexp r("\\(^\\|,\\) *application/sdp *\\($\\|[,;]\\)",false,true);
    const MimeHeaderLine* hl = sip ? sip->getHeader("Accept") : 0;
    return hl ? r.matches(*hl) : def;
}

// Copy message body to yate message
static bool copySipBody(NamedList& msg, const MimeBody* body, bool text = false, unsigned int idx = 0)
{
    if (!body)
	return false;
    if (body->isMultipart()) {
	idx = 0;
	for (const ObjList* l = static_cast<const MimeMultipartBody*>(body)->bodies().skipNull();
		l; l = l->skipNext()) {
	    if (copySipBody(msg,static_cast<const MimeBody*>(l->get()),text,idx))
		idx++;
	}
	return idx > 0;
    }

    if (body->isSDP())
	return false;
    String suffix;
    if (idx)
	suffix << "." << idx;
    // add the body if it's a string one
    MimeStringBody* strBody = YOBJECT(MimeStringBody,body);
    if (strBody) {
	msg.addParam("xsip_type" + suffix,strBody->getType());
	msg.addParam("xsip_body" + suffix,strBody->text());
	if (text)
	    msg.addParam("text" + suffix,strBody->text());
    }
    else {
	MimeLinesBody* txtBody = YOBJECT(MimeLinesBody,body);
	if (txtBody) {
	    String bodyText((const char*)txtBody->getBody().data(),txtBody->getBody().length());
	    msg.addParam("xsip_type" + suffix,txtBody->getType());
	    msg.addParam("xsip_body" + suffix,bodyText);
	}
	else if (s_sipt_isup && (body->getType() == YSTRING("application/isup")))
	    return false;
	else {
	    const DataBlock& binBody = body->getBody();
	    String bodyText;
	    int enc = s_defEncoding;
	    switch (enc) {
		case SipHandler::BodyRaw:
		    bodyText.assign((const char*)binBody.data(),binBody.length());
		    break;
		case SipHandler::BodyHex:
		    bodyText.hexify(binBody.data(),binBody.length());
		    break;
		case SipHandler::BodyHexS:
		    bodyText.hexify(binBody.data(),binBody.length(),' ');
		    break;
		default:
		    enc = SipHandler::BodyBase64;
		    {
			Base64 b64(binBody.data(),binBody.length(),false);
			b64.encode(bodyText);
			b64.clear(false);
		    }
	    }
	    msg.addParam("xsip_type" + suffix,body->getType());
	    msg.addParam("xsip_body_encoding" + suffix,lookup(enc,SipHandler::s_bodyEnc));
	    msg.addParam("xsip_body" + suffix,bodyText);
	}
    }
    const ObjList* o = body->headers().skipNull();
    if (o) {
	String hdrPrefix = "xsip_body" + suffix + ".header_";
	for (; o; o = o->skipNext()) {
	    const MimeHeaderLine* hdr = static_cast<const MimeHeaderLine*>(o->get());
	    String val;
	    hdr->buildLine(val,false);
	    msg.addParam(hdrPrefix + hdr->name(),val);
	}
    }
    return true;
}

// Copy body from SIP message to Yate message
static bool copySipBody(NamedList& msg, const SIPMessage& sip, bool text = false)
{
    return copySipBody(msg,sip.body,text);
}

// Create a custom body from Yate message
static MimeBody* createSipBody(const NamedList& msg, MimeBody* b1 = 0, MimeBody* b2 = 0,
    const ObjList* bodyTrace = 0)
{
    ObjList extra;
    for (int i = 0; ; i++) {
	String suffix;
	if (i)
	    suffix << "." << i;
	String pName = "xsip_body" + suffix;
	const String& body = msg[pName];
	if (!body)
	    break;
	if (bodyTrace) {
	    BodyTrace* bt = static_cast<BodyTrace*>(bodyTrace->get());
	    bodyTrace = bodyTrace->skipNext();
	    if (bt && bt->sameBody(body))
		break;
	}
	const String& type = msg["xsip_type" + suffix];
	if (!type)
	    continue;
	const String& bodyEnc = msg["xsip_body_encoding" + suffix];
	MimeBody* bExtra = 0;
	if (bodyEnc.null())
	    bExtra = new MimeStringBody(type,body.c_str(),body.length());
	else {
	    DataBlock binBody;
	    bool ok = false;
	    switch (bodyEnc.toInteger(SipHandler::s_bodyEnc)) {
		case SipHandler::BodyRaw:
		    binBody.append(body);
		    ok = true;
		    break;
		case SipHandler::BodyHex:
		case SipHandler::BodyHexS:
		    ok = binBody.unHexify(body);
		    break;
		case SipHandler::BodyBase64:
		    {
			Base64 b64;
			b64 << body;
			ok = b64.decode(binBody);
		    }
		    break;
	    }
	    if (!ok) {
		Debug(&plugin,DebugWarn,"Invalid xsip_body_encoding%s '%s'",suffix.safe(),bodyEnc.c_str());
		continue;
	    }
	    bExtra = new MimeBinaryBody(type,(const char*)binBody.data(),binBody.length());
	}
	String hdr;
	hdr << pName << ".header_";
	for (const ObjList* o = msg.paramList()->skipNull(); o; o = o->skipNext()) {
	    const NamedString* ns = static_cast<const NamedString*>(o->get());
	    if (ns->name().startsWith(hdr)) {
		String name = ns->name().substr(hdr.length());
		if (name)
		    bExtra->appendHdr(new MimeHeaderLine(name,*ns));
	    }
	}
	extra.append(bExtra);
    }

    unsigned int n = 0;
    if (b1)
	n++;
    if (b2)
	n++;
    MimeBody* bExtra = static_cast<MimeBody*>(extra.remove(false));
    if (bExtra) {
	n++;
	if (extra.skipNull())
	    n++;
    }
    if (n < 2)
	return b1 ? b1 : (b2 ? b2 : bExtra);

    MimeMultipartBody* multi = new MimeMultipartBody;
    if (b1)
	multi->appendBody(b1);
    if (b2)
	multi->appendBody(b2);
    while (bExtra) {
	multi->appendBody(bExtra);
	bExtra = static_cast<MimeBody*>(extra.remove(false));
    }
    return multi;
}

// Copy body from Yate message to SIP message
static bool copySipBody(SIPMessage& sip, const NamedList& msg, const ObjList* bodyTrace = 0)
{
    MimeBody* body = createSipBody(msg,0,0,bodyTrace);
    if (!body)
	return false;
    sip.setBody(body);
    return true;
}

// Check if the given body have the given type
// Find the given type inside multiparts
static inline MimeBody* getOneBody(MimeBody* body, const char* type)
{
    return body ? body->getFirst(type) : 0;
}

// Check if the given body is a SDP one or find an enclosed SDP body
//  if it is a multipart
static inline MimeSdpBody* getSdpBody(MimeBody* body)
{
    if (!body)
	return 0;
    return static_cast<MimeSdpBody*>(body->isSDP() ? body : body->getFirst("application/sdp"));
}

// Add a mime body parameter to a list of parameters
// Remove quotes, trim blanks and convert to lower case before adding
// Return false if the parameter wasn't added
inline bool addBodyParam(NamedList& nl, const char* param, MimeBody* body, const char* bodyParam)
{
    const NamedString* ns = body ? body->getParam(bodyParam) : 0;
    if (!ns)
	return false;
    String p = *ns;
    MimeHeaderLine::delQuotes(p);
    p.trimBlanks();
    if (p.null())
	return false;
    p.toLower();
    nl.addParam(param,p);
    return true;
}

// Decode an application/isup body into 'msg' if configured to do so
// The message's name and user data are restored before exiting, regardless the result
// Return true if an ISUP message was succesfully decoded
static bool doDecodeIsupBody(const DebugEnabler* debug, Message& msg, MimeBody* body)
{
    if (!s_sipt_isup)
	return false;
    // Get a valid application/isup body
    MimeBinaryBody* isup = static_cast<MimeBinaryBody*>(getOneBody(body,"application/isup"));
    if (!isup)
	return false;
    // Remember the message's name and user data and fill parameters
    String name = msg;
    RefObject* userdata = msg.userData();
    if (userdata)
	userdata->ref();
    msg = "isup.decode";
    msg.addParam("message-prefix","isup.");
    addBodyParam(msg,"isup.protocol-type",isup,"version");
    addBodyParam(msg,"isup.protocol-basetype",isup,"base");
    msg.addParam(new NamedPointer("rawdata",new DataBlock(isup->body())));
    bool ok = Engine::dispatch(msg);
    // Clear added params and restore message
    if (!ok) {
	Debug(debug,DebugMild,"%s failed error='%s'",
	    msg.c_str(),msg.getValue(YSTRING("error")));
	msg.clearParam(YSTRING("error"));
    }
    msg.clearParam(YSTRING("rawdata"));
    msg = name;
    msg.userData(userdata);
    TelEngine::destruct(userdata);
    return ok;
}

// Build the body of a SIP message from an engine message
// Encode an ISUP message from parameters received in msg if enabled to process them
// Build a multipart/mixed body if more then one body is going to be sent
static MimeBody* doBuildSIPBody(const DebugEnabler* debug, Message& msg,
    MimeSdpBody* sdp, const char* prefixName = 0)
{
    MimeBinaryBody* isup = 0;

    // Build isup
    while (s_sipt_isup) {
	static const String s_stdPrefix("message-prefix");
	String prefix = msg.getValue(prefixName ? prefixName : s_stdPrefix.c_str());
	if (!msg.getParam(prefix + "message-type"))
	    break;

	// Remember the message's name, prefix and user data
	String pre;
	String name = msg;
	RefObject* userdata = msg.userData();
	if (userdata)
	    userdata->ref();

	DataBlock* data = 0;
	msg = "isup.encode";
	if (prefixName) {
	    pre = msg.getValue(s_stdPrefix);
	    msg.setParam(s_stdPrefix,prefix);
	}
	if (Engine::dispatch(msg)) {
	    NamedString* ns = msg.getParam(YATOM("rawdata"));
	    if (ns) {
		NamedPointer* np = static_cast<NamedPointer*>(ns->getObject(YATOM("NamedPointer")));
		if (np)
		    data = static_cast<DataBlock*>(np->userObject(YATOM("DataBlock")));
	    }
	}
	if (data && data->length()) {
	    isup = new MimeBinaryBody("application/isup",(const char*)data->data(),data->length());
	    isup->setParam("version",msg.getValue(prefix + "protocol-type"));
	    const char* s = msg.getValue(prefix + "protocol-basetype");
	    if (s)
		isup->setParam("base",s);
	    MimeHeaderLine* line = new MimeHeaderLine("Content-Disposition","signal");
	    line->setParam("handling","optional");
	    isup->appendHdr(line);
	}
	else {
	    Debug(debug,DebugMild,"%s failed error='%s'",
		msg.c_str(),msg.getValue(YSTRING("error")));
	    msg.clearParam(YSTRING("error"));
	}

	// Restore message
	msg = name;
	if (prefixName) {
	    if (pre)
		msg.setParam(s_stdPrefix,pre);
	    else
		msg.clearParam(s_stdPrefix);
	}
	msg.userData(userdata);
	TelEngine::destruct(userdata);
	break;
    }

    return createSipBody(msg,sdp,isup);
}

// Find an URI parameter separator. Accept '?' or '&'
static inline int findURIParamSep(const String& str, int start)
{
    if (start < 0)
	return -1;
    for (int i = start; i < (int)str.length(); i++)
	if (str[i] == '?' || str[i] == '&')
	    return i;
    return -1;
}

// Set a transaction response from an authentication error
static void setAuthError(SIPTransaction* trans, const NamedList& params,
    bool stale, const String& domain = String::empty())
{
    const String& error = params[YSTRING("error")];
    while (error) {
	int code = error.toInteger(dict_errors,401);
	if (code < 400 || code > 699)
	    break;
	if ((code == 401) && (s_noAutoAuth != error))
	    break;
	SIPMessage* m = new SIPMessage(trans->initialMessage(),code,params.getValue(YSTRING("reason")));
	copySipHeaders(*m,params);
	trans->setResponse(m);
	m->deref();
	return;
    }
    String r;
    trans->requestAuth(getGlobal(r,s_realm),domain,stale);
}


// Replace all methods from comma separated list
// If no method is set use other or setDefEmpty (reset to default)
bool DtmfMethods::set(const String& methods, const DtmfMethods* other, bool setDefEmpty,
    bool intersectOther)
{
    set();
    bool found = false;
    bool ok = true;
    ObjList* m = methods.split(',');
    int i = 0;
    for (ObjList* o = m->skipNull(); o && i < MethodCount; o = o->skipNext()) {
	String* s = static_cast<String*>(o->get());
	int meth = lookup(s->trimBlanks(),s_methodName,MethodCount);
	if (meth != MethodCount) {
	    m_methods[i++] = meth;
	    found = true;
	}
	else if (*s)
	    ok = false;
    }
    TelEngine::destruct(m);
    if (!found) {
	if (other) {
	    *this = *other;
	    intersectOther = false;
	}
	else if (setDefEmpty)
	    setDefault();
    }
    if (intersectOther && other)
	intersect(*other);
    return ok;
}

// Intersect with other methods
void DtmfMethods::intersect(const DtmfMethods& other)
{
    for (int i = 0; i < MethodCount; i++)
	if (m_methods[i] != MethodCount && !other.hasMethod(m_methods[i]))
	    m_methods[i] = MethodCount;
}

// Retrieve a method from deperecated parameters
// Reset the method if the parameter is false
// Display a message anyway if warn is not false
bool DtmfMethods::getDeprecatedDtmfMethod(const NamedList& list, const char* param,
    int method, bool* warn)
{
    String* p = list.getParam(param);
    if (!p)
	return false;
    if (!p->toBoolean())
	reset(method);
    if (warn && *warn) {
	*warn = false;
	Debug(&plugin,DebugConf,"Deprecated '%s' in '%s'. Use 'dtmfmethods' instead!",param,list.c_str());
    }
    return true;
}

// Reset a method
void DtmfMethods::reset(int method)
{
    for (int i = 0; i < MethodCount; i++)
	if (m_methods[i] == method) {
	    m_methods[i] = MethodCount;
	    break;
	}
}

// Build a string list from methods
void DtmfMethods::buildMethods(String& buf, const char* sep)
{
    for (int i = 0; i < MethodCount; i++)
	buf.append(lookup(m_methods[i],s_methodName),sep);
}

bool DtmfMethods::hasMethod(int method) const
{
    for (int i = 0; i < MethodCount; i++)
	if (m_methods[i] == method)
	    return true;
    return false;
}


bool YateSIPPartyHolder::change(String& dest, const String& src)
{
    if (dest == src)
	return false;
    changing();
    dest = src;
    return true;
}

bool YateSIPPartyHolder::change(int& dest, int src)
{
    if (dest == src)
	return false;
    changing();
    dest = src;
    return true;
}

bool YateSIPPartyHolder::change(bool& dest, bool src)
{
    if (dest == src)
	return false;
    changing();
    dest = src;
    return true;
}

void YateSIPPartyHolder::changing()
{
}

// Check if a transport is used by our party
YateSIPTransport* YateSIPPartyHolder::transport(bool ref)
{
    Lock lock(m_partyMutex);
    YateSIPTransport* trans = 0;
    if (m_party)
	trans = static_cast<YateSIPTransport*>(m_party->getTransport());
    return (trans && (!ref || trans->ref())) ? trans : 0;
}

// Set the held party. Referrence it before
void YateSIPPartyHolder::setParty(SIPParty* party)
{
    Lock lck(m_partyMutex);
    if (party == m_party)
	return;
    if (party && !party->ref())
	party = 0;
#ifdef DEBUG
    if (party != m_party) {
	String extra;
	if (party) {
	    String local, remote;
	    extra += " local=";
	    party->appendAddr(extra,true);
	    extra += " remote=";
	    party->appendAddr(extra,false);
	}
	TraceDebug(m_traceId,m_enabler,DebugAll,"YateSIPPartyHolder set party (%p)%s trans=(%p) [%p]",
	    party,extra.safe(),party ? party->getTransport() : 0,this);
    }
#endif
    SIPParty* tmp = m_party;
    m_party = party;
    lck.drop();
    TelEngine::destruct(tmp);
}

// Set the held party if remote address changed
bool YateSIPPartyHolder::setPartyChanged(SIPParty* party)
{
    if (!(party && m_party))
	return false;
    if (party == m_party)
	return true;
    Lock lck(m_partyMutex);
    if (!m_party)
	return false;
    if (party == m_party)
	return true;
    RefPointer<SIPParty> crt = m_party;
    if (!crt)
	return false;
    lck.drop();
    String partyAddr, crtAddr;
    int partyPort, crtPort;
    party->getAddr(partyAddr,partyPort,false);
    crt->getAddr(crtAddr,crtPort,false);
    crt = 0;
    bool changed = partyPort != crtPort || partyAddr != crtAddr;
    if (changed) {
	String crt;
	String p;
	SocketAddr::appendTo(crt,crtAddr,crtPort);
	SocketAddr::appendTo(p,partyAddr,partyPort);
	TraceDebug(m_traceId,m_enabler,DebugInfo,"YateSIPPartyHolder party addr changed '%s' -> '%s' [%p]",
	    crt.c_str(),p.c_str(),this);
	setParty(party);
    }
    return changed;
}

// Set the party of a non answer message
bool YateSIPPartyHolder::setSipParty(SIPMessage* message, const YateSIPLine* line,
    bool useEp, const char* host, int port) const
{
    if (!message || message->isAnswer())
	return false;
    Lock lck(m_partyMutex);
    if (!m_party) {
	lck.drop();
	if (useEp && plugin.ep())
	    plugin.ep()->buildParty(message,host,port,line);
	return 0 != message->getParty();
    }
    message->setParty(m_party);
    lck.drop();
    if (line)
	line->setupAuth(message);
    return true;
}

// Change party and its transport. Return true on success
bool YateSIPPartyHolder::buildParty(bool force, bool isTemp)
{
    XDebug(m_enabler,DebugAll,"YateSIPPartyHolder::buildParty(%s,%s,%d,%s,%d) force=%u [%p]",
	protoName(),m_transLocalAddr.c_str(),m_transLocalPort,
	m_transRemoteAddr.c_str(),m_transRemotePort,force,this);
    m_partyInvalidRemote.clear();
    if (!force) {
	Lock lock(m_partyMutex);
	if (m_party)
	    return true;
    }
    YateSIPTCPTransport* tcpTrans = 0;
    YateSIPUDPTransport* udpTrans = 0;
    bool initTcp = false;
    bool addrValid = true;
    if (m_transId) {
	YateSIPTransport* trans = 0;
	if (plugin.ep())
	    trans = plugin.ep()->findTransport(m_transId);
	if (trans) {
	    tcpTrans = trans->tcpTransport();
	    if (!tcpTrans) {
		udpTrans = trans->udpTransport();
		if (!udpTrans)
		    TelEngine::destruct(trans);
	    }
	}
    }
    if (!(tcpTrans || udpTrans)) {
	if (protocol() == Udp) {
	    if (plugin.ep()) {
		if (!m_transLocalAddr)
		    udpTrans = plugin.ep()->defTransport();
		else {
		    SocketAddr addr(s_ipv6 ? SocketAddr::Unknown : SocketAddr::IPv4);
		    addr.host(m_transLocalAddr);
		    udpTrans = plugin.ep()->findUdpTransport(addr.host(),m_transLocalPort);
		}
	    }
	}
	else {
	    initTcp = true;
	    bool tls = (protocol() == Tls);
	    if (tls || protocol() == Tcp) {
		addrValid = m_transRemoteAddr && m_transRemotePort > 0;
		if (addrValid)
		    tcpTrans = new YateSIPTCPTransport(tls,m_transLocalAddr,
			m_transRemoteAddr,m_transRemotePort);
	    }
	    else
		TraceDebug(m_traceId,DebugStub,"YateSIPPartyHolder::buildParty() transport %s not implemented",
		    protoName());
	}
    }
    SIPParty* p = 0;
    if (udpTrans) {
	SocketAddr addr(s_ipv6 ? SocketAddr::Unknown : SocketAddr::IPv4);
	addr.host(m_transRemoteAddr);
	addr.port(m_transRemotePort);
	addrValid = addr.host() && addr.port() > 0;
	if (addrValid)
	    p = new YateUDPParty(udpTrans,addr);
    }
    else if (tcpTrans) {
	p = tcpTrans->getParty();
	if (!p)
	    p = new YateTCPParty(tcpTrans);
    }
    setParty(p);
    TelEngine::destruct(p);
    if (!addrValid) {
	m_partyInvalidRemote = "";
	SocketAddr::appendTo(m_partyInvalidRemote,m_transRemoteAddr,m_transRemotePort);
	DDebug(m_enabler,DebugNote,"Failed to build %s transport with invalid remote addr=%s",
	    protoName(),m_partyInvalidRemote.c_str());
    }
    if (tcpTrans && initTcp) {
	// TODO: handle other params: maxpkt, thread prio
	tcpTrans->init(NamedList::empty(),true);
    }
    if (!isTemp || m_party)
	TraceDebug(m_traceId,m_enabler,m_party ? DebugAll : DebugNote,
	    "%s party trans_name=%s proto=%s local=%s remote=%s [%p]",
	    m_party ? "Set" : "Failed to set",m_transId.c_str(),protoName(),
	    SocketAddr::appendTo(m_transLocalAddr,m_transLocalPort).c_str(),
	    SocketAddr::appendTo(m_transRemoteAddr,m_transRemotePort).c_str(),m_enabler);
    TelEngine::destruct(udpTrans);
    TelEngine::destruct(tcpTrans);
    return m_party != 0;
}

// Transport status changed notification
void YateSIPPartyHolder::transportChangedStatus(int stat, const String& reason)
{
    if (stat != YateSIPTransport::Connected)
	return;
    Lock lck(m_partyMutex);
    RefPointer<SIPParty> p = m_party;
    if (!p)
	return;
    YateSIPTransport* trans = static_cast<YateSIPTransport*>(p->getTransport());
    if (!(trans && trans->udpTransport()))
	return;
    // No need to lock transport to get its addr: this method is called from
    //  transport itself just after connected
    String tLocalAddr = trans->local().host();
    int tLocalPort = trans->local().port();
    lck.drop();
    String pLocalAddr;
    int pLocalPort = 0;
    p->getAddr(pLocalAddr,pLocalPort,true);
    if (pLocalPort != tLocalPort || tLocalAddr != pLocalAddr) {
	p->setAddr(tLocalAddr,tLocalPort,true);
	DDebug(m_enabler,DebugInfo,"YateSIPPartyHolder party local addr changed '%s' -> '%s' [%p]",
	    SocketAddr::appendTo(pLocalAddr,pLocalPort).c_str(),
	    SocketAddr::appendTo(tLocalAddr,tLocalPort).c_str(),m_enabler);
    }
}

// Change party and its transport. Return true on success
bool YateSIPPartyHolder::setParty(const NamedList& params, bool force, const String& prefix,
    const String& defRemoteAddr, int defRemotePort, bool isTemp)
{
    if (!(force || haveTransParams(params,prefix))) {
	setParty();
	return false;
    }
    const String& transId = params[prefix + "connection_id"];
    if (change(m_transId,transId))
	TraceDebug(m_traceId,m_enabler,DebugAll,
	    "YateSIPPartyHolder transport id changed to '%s' [%p]",
	    m_transId.c_str(),this);
    updateProto(params,prefix);
    updateRemoteAddr(params,prefix,defRemoteAddr,defRemotePort);
    updateLocalAddr(params,prefix);
    return buildParty(true,isTemp);
}

// Update transport type. Return true if changed
bool YateSIPPartyHolder::updateProto(const NamedList& params, const String& prefix)
{
    int proto = lookupProtoAny(params[prefix + "ip_transport"]);
    if (proto == Unknown) {
	if (!params.getBoolValue(prefix + "ip_transport_tcp")) {
	    if (m_transId) {
		// Check transport id prefix
		if (m_transId.startsWith("tcp:",false))
		    proto = Tcp;
		else if (m_transId.startsWith("tls:",false))
		    proto = Tls;
		else
		    proto = Udp;
	    }
	    else
		proto = sips() ? Tls : Udp;
	}
	else if (!params.getBoolValue(prefix + "ip_transport_tls"))
	    proto = Tcp;
	else
	    proto = Tls;
    }
    bool chg = change(m_proto,proto);
    if (chg)
	TraceDebug(m_traceId,m_enabler,DebugAll,"YateSIPPartyHolder transport proto changed to '%s' [%p]",
	    protoName(),this);
    return chg;
}

// Update transport remote addr/port. Return true if changed
bool YateSIPPartyHolder::updateRemoteAddr(const NamedList& params, const String& prefix,
    const String& defRemoteAddr, int defRemotePort)
{
    const char* addr = params.getValue(prefix + "ip_transport_remoteip",defRemoteAddr);
    int port = params.getIntValue(prefix + "ip_transport_remoteport",defRemotePort);
    if (port <= 0)
	port = sipPort(protocol() != Tls);
    bool chg = change(m_transRemoteAddr,addr);
    chg = change(m_transRemotePort,port) || chg;
    if (chg && plugin.debugAt(DebugAll)) {
	String s;
	SocketAddr::appendTo(s,m_transRemoteAddr,m_transRemotePort);
	TraceDebug(m_traceId,m_enabler,DebugAll,"YateSIPPartyHolder remote addr changed to '%s' [%p]",
	    s.c_str(),this);
    }
    return chg;
}

// Update transport local addr/port. Return true if changed
bool YateSIPPartyHolder::updateLocalAddr(const NamedList& params, const String& prefix)
{
    bool chg = change(m_transLocalAddr,params[prefix + "ip_transport_localip"]);
    int port = params.getIntValue(prefix + "ip_transport_localport");
    chg = change(m_transLocalPort,port) || chg;
    if (chg && plugin.debugAt(DebugAll)) {
	String s;
	SocketAddr::appendTo(s,m_transLocalAddr,m_transLocalPort);
	TraceDebug(m_traceId,m_enabler,DebugAll,"YateSIPPartyHolder local addr changed to '%s' [%p]",
	    s.c_str(),this);
    }
    return chg;
}

// Update RTP local address
void YateSIPPartyHolder::setRtpLocalAddr(String& addr, Message* m)
{
    addr.clear();
    if (m)
	addr = m->getValue(YSTRING("rtp_localip"));
    if (!addr && m_party) {
	Lock lock(m_partyMutex);
	YateSIPTransport* t = YOBJECT(YateSIPTransport,m_party);
	if (t && !t->ref())
	    t = 0;
	lock.drop();
	if (t)
	    t->rtpAddr(addr);
	TelEngine::destruct(t);
    }
    DDebug(m_enabler,DebugAll,"YateSIPPartyHolder rtp local addr is '%s' [%p]",
	addr.c_str(),this);
}


YateSIPListener::YateSIPListener(const char* name, int proto, const String& addr, int port)
    : m_bindInterval(0), m_nextBind(0),
    m_bind(true), m_address(addr), m_port(port),
    m_ipv6(false), m_ipv6Support(false), m_setRtpAddr(false),
    m_name(name), m_proto(proto)
{
}

// Check bind now flag
bool YateSIPListener::bindNow(Mutex* mutex)
{
    if (!m_bind)
	return false;
    Lock lck(mutex);
    bool old = m_bind;
    m_bind = false;
    return old;
}

// Set addr/port and bind flag. Return the bind flag
void YateSIPListener::setAddr(const String& addr, int port, bool ipv6)
{
    if (m_cfgAddr != addr) {
	m_bind = true;
	SocketAddr::splitIface(addr,m_address);
	m_cfgAddr = addr;
    }
    if (m_port != port) {
	m_bind = true;
	m_port = port;
    }
    if (m_ipv6 != ipv6) {
	m_bind = true;
	m_ipv6 = ipv6;
    }
}

// Update IPv6 support from global
void YateSIPListener::updateIPv6Support()
{
    if (m_ipv6Support == s_ipv6)
	return;
    m_ipv6Support = s_ipv6;
    // Force re-bind if we are using IPv6
    if (m_ipv6 && !m_bind) {
	Debug(&plugin,DebugAll,"Listener(%s,'%s') IPv6 support changed. Forcing re-bind",
	    ProtocolHolder::lookupProtoName(m_proto),lName());
	m_bind = true;
    }
}

// Update rtp address
bool YateSIPListener::updateRtpAddr(const NamedList& params, String& buf, Mutex* mutex)
{
    Lock lck(mutex);
    NamedString* ns = params.getParam(YSTRING("rtp_localip"));
    if (ns)
	buf = *ns;
    else
	buf.clear();
    bool val = false;
    if (m_proto == ProtocolHolder::Udp && params == YSTRING("general"))
	val = false;
    else if (ns && ns->null())
	val = false;
    else
	val = buf.null();
    // We should set rtp addr from bind address and we already have one
    if (val && m_bindRtpLocalAddr) {
	buf = m_bindRtpLocalAddr;
	m_setRtpAddr = false;
	return false;
    }
    if (val == m_setRtpAddr)
	return false;
    m_setRtpAddr = val;
    return true;
}

// Initialize a socket
Socket* YateSIPListener::initSocket(SocketAddr& lAddr, Mutex* mutex,
    int backLogBuffer, bool forceBind, String& reason)
{
    reason = "";
    Lock lck(mutex);
    String addr = m_cfgAddr;
    int port = m_port;
    bool ipv6 = m_ipv6;
    bool ipv6Support = m_ipv6Support;
    lck.drop();
    bool udp = (m_proto == ProtocolHolder::Udp);
    const char* type = ProtocolHolder::lookupProtoName(m_proto);
    Debug(&plugin,DebugAll,
	"Listener(%s,'%s') initializing socket addr='%s' port=%d",
	type,lName(),addr.c_str(),port);
    Socket* sock = 0;
    // Use a while() to break to the end
    while (true) {
	if (!ipv6)
	    lAddr.assign(SocketAddr::IPv4);
	else {
	    if (!ipv6Support) {
		reason << "IPv6 support not enabled";
		break;
	    }
	    if (!lAddr.assign(SocketAddr::IPv6)) {
		reason << "IPv6 not available";
		break;
	    }
	}
	if (addr && !lAddr.host(addr)) {
	    reason << "Invalid address";
	    break;
	}
	lAddr.port(port);
	if (udp)
	    sock = new Socket(lAddr.family(),SOCK_DGRAM,IPPROTO_UDP);
	else
	    sock = new Socket(lAddr.family(),SOCK_STREAM);
	if (!sock->valid()) {
	    reason = "Create socket failed";
	    break;
	}
	if (ipv6 && !sock->setIpv6OnlyOption(true)) {
	    reason << "Failed to set option IPv6 only";
	    break;
	}
	if (!udp)
	    sock->setReuse();
#ifdef SO_RCVBUF
	// Set UDP buffer size
	if (udp && backLogBuffer > 0) {
	    int buflen = backLogBuffer;
	    if (buflen < 4096)
		buflen = 4096;
	    if (sock->setOption(SOL_SOCKET,SO_RCVBUF,&buflen,sizeof(buflen))) {
		buflen = 0;
		socklen_t sz = sizeof(buflen);
		if (sock->getOption(SOL_SOCKET,SO_RCVBUF,&buflen,&sz))
		    Debug(&plugin,DebugNote,"Listener(%s,'%s') buffer size is %d (requested %d)",
			type,lName(),buflen,backLogBuffer);
		else
		    Debug(&plugin,DebugWarn,
			"Listener(%s,'%s') could not get UDP buffer size (requested %d)",
			type,lName(),backLogBuffer);
	    }
	    else
		Debug(&plugin,DebugWarn,"Listener(%s,'%s') could not set buffer size %d",
		    type,lName(),buflen);
	}
#endif
	// Bind the socket
	bool ok = sock->bind(lAddr);
	if (!ok && forceBind) {
	    String error;
	    Thread::errorString(error,sock->error());
	    Debug(&plugin,DebugWarn,
		"Listener(%s,'%s') unable to bind on %s - trying a random port. %d '%s'",
		type,lName(),lAddr.addr().c_str(),sock->error(),error.c_str());
	    lAddr.port(0);
	    ok = sock->bind(lAddr);
	    if (ok && !sock->getSockName(lAddr)) {
		reason = "Failed to retrieve bind address";
		break;
	    }
	}
	if (!ok) {
	    reason = "Bind failed";
	    break;
	}
	if (!sock->setBlocking(false)) {
	    reason = "Set non blocking mode failed";
	    break;
	}
	if (!udp && !sock->listen(backLogBuffer)) {
	    reason = "Listen failed";
	    break;
	}
	break;
    }
    if (!reason) {
	Debug(&plugin,DebugInfo,"Listener(%s,'%s') started on '%s' (%s)",
	    type,lName(),lAddr.addr().c_str(),lAddr.familyName());
	m_nextBind = 0;
	m_bindInterval = 0;
	return sock;
    }
    String s;
    if (sock) {
	String tmp;
	Thread::errorString(tmp,sock->error());
	s << " (" << sock->error() << " '" << tmp << "')";
    }
    Alarm(&plugin,"socket",DebugWarn,
	"Listener(%s,'%s') failed to start on addr='%s' port=%d ipv6=%s: %s%s",
	type,lName(),addr.safe(),port,String::boolText(ipv6),
	reason.c_str(),s.safe());
    if (!m_bindInterval)
	m_bindInterval = s_bindRetryMs;
    else if (m_bindInterval < BIND_RETRY_MAX)
	m_bindInterval *= 2;
    m_nextBind = Time::now() + m_bindInterval * 1000;
    YateSIPTransport::resetSocket(sock,0);
    return 0;
}


YateSIPTransport::YateSIPTransport(int proto, const String& id, Socket* sock, int stat)
    : Mutex(true,"YateSIPTransport"),
    ProtocolHolder(proto),
    m_id(id), m_status(stat), m_statusChgTime(Time::secNow()),
    m_sock(sock), m_maxpkt(1500),
    m_worker(0), m_initialized(false),
    m_ignoreVia(s_ignoreVia)
{
}

// Initialize transport
bool YateSIPTransport::init(const NamedList& params, const NamedList& defs,
    bool first, Thread::Priority prio)
{
    static String s_maxPkt = "maxpkt";
    lock();
    m_initialized = true;
    m_ignoreVia = s_ignoreVia;
    if (udpTransport()) {
	int v = params.getIntValue(s_maxPkt,defs.getIntValue(s_maxPkt,m_maxpkt));
	m_maxpkt = getMaxpkt(v,1500);
	m_ignoreVia = params.getBoolValue(YSTRING("ignorevia"),m_ignoreVia);
    }
    else {
	m_rtpLocalAddr = params.getValue(YSTRING("rtp_localip"));
	m_maxpkt = getMaxpkt(params.getIntValue(YSTRING("tcp_maxpkt"),m_maxpkt),m_maxpkt);
	// Set rtp ip for tcp outgoing
	if (!m_rtpLocalAddr && tcpTransport() && tcpTransport()->outgoing()) {
	    Lock lck(s_globalMutex);
	    m_rtpLocalAddr = s_tcpOutRtpip;
	}
    }
    m_rtpNatAddr = params.getValue(YSTRING("nat_address"));
    m_role = params[YSTRING("role")];
    unlock();
    // Done if not first
    if (!first)
	return true;
    if (m_sock) {
	m_sock->getSockName(m_local);
	m_sock->getPeerName(m_remote);
    }
    return true;
}

void YateSIPTransport::printSendMsg(const SIPMessage* msg, const SocketAddr* addr)
{
    if (!msg)
	return;
    if (!plugin.debugAt(DebugInfo))
	return;
    if (!plugin.filterDebug(addr ? addr->addr() : m_remote.addr()))
	return;
    String tmp;
    getMsgLine(tmp,msg);
    String raddr;
    if (addr)
	raddr = " to " + addr->addr();
    String buf((char*)msg->getBuffer().data(),msg->getBuffer().length());
    TraceDebug(msg->traceId(),&plugin,DebugInfo,"'%s' sending %s %p%s [%p]\r\n------\r\n%s------",
	m_protoAddr.c_str(),tmp.c_str(),msg,raddr.safe(),this,buf.c_str());
}

// Print received messages to output
void YateSIPTransport::printRecvMsg(const char* buf, int len,const String& traceId)
{
    if (!buf)
	return;
    if (!plugin.debugAt(DebugInfo))
	return;
    if (!plugin.filterDebug(m_remote.addr()))
	return;
    String tmp;
    String raddr;
    if (udpTransport())
	raddr = " from " + m_remote.addr();
    else {
	tmp.assign(buf,len);
	buf = tmp;
    }
    TraceDebug(traceId,&plugin,DebugInfo,
	"'%s' received %d bytes SIP message%s [%p]\r\n------\r\n%s------",
	m_protoAddr.c_str(),len,raddr.safe(),this,buf);
}

// Add transport data yate message
void YateSIPTransport::fillMessage(Message& msg, bool addRoute)
{
    msg.setParam("connection_id",toString());
    msg.setParam("connection_reliable",String::boolText(0 != tcpTransport()));
    if (m_role) {
	Lock lck(this);
	msg.setParam("role",m_role);
    }
    if (addRoute) {
	msg.setParam("route_params","oconnection_id");
	msg.setParam("oconnection_id",toString());
    }
}

// Stop the worker. Remove from global list
void YateSIPTransport::terminate(const char* reason)
{
    XDebug(&plugin,DebugInfo,"YateSIPTransport::terminate(%s) [%p]",reason,this);
    changeStatus(Terminating);
    if (m_worker) {
	bool wait = false;
	lock();
	if (m_worker) {
	    if (Thread::current() != m_worker)
		wait = true;
	    else
		m_worker->m_transport = 0;
	    m_worker->cancel();
	}
	unlock();
	if (wait) {
	    unsigned int n = 500;
	    while (m_worker && n--)
		Thread::idle();
	    if (m_worker)
		Debug(&plugin,DebugFail,"Transport(%s) terminating with worker running [%p]",
		    m_id.c_str(),this);
	}
    }
    if (!TelEngine::null(reason)) {
	Lock lock(this);
	if (!m_reason)
	    m_reason = reason;
    }
    changeStatus(Terminated);
}

const String& YateSIPTransport::toString() const
{
    return m_id;
}

// Reset and delete a socket
void YateSIPTransport::resetSocket(Socket*& sock, int linger)
{
    if (!sock)
	return;
    sock->setLinger(linger);
    delete sock;
    sock = 0;
}

void YateSIPTransport::destroyed()
{
    terminate("Destroyed");
    resetSocket(m_sock,-1);
    Debug(&plugin,DebugAll,"Transport(%s) destroyed [%p]",m_id.c_str(),this);
    RefObject::destroyed();
}

// Start the worker thread
bool YateSIPTransport::startWorker(Thread::Priority prio)
{
    Lock lck(this);
    if (m_worker)
	return true;
    m_worker = new YateSIPTransportWorker(this,prio);
    if (m_worker->startup())
	return true;
    Debug(&plugin,DebugWarn,"Transport(%s) failed to start worker thread [%p]",m_id.c_str(),this);
    m_reason = "Failed to start worker";
    m_worker = 0;
    return false;
}

// Change transport status. Notify it
void YateSIPTransport::changeStatus(int stat)
{
    Lock lock(this);
    if (stat == m_status || m_status == Terminated)
	return;
    unsigned int t = Time::secNow();
    DDebug(&plugin,DebugAll,"Transport(%s) changed status old=%s new=%s statustime=%u [%p]",
	m_id.c_str(),statusName(m_status),statusName(stat),t - m_statusChgTime,this);
    m_statusChgTime = t;
    m_status = stat;
    String reason;
    if (m_status == Terminated) {
	reason = m_reason;
	m_reason.clear();
    }
    lock.drop();
    statusChanged();
    if (plugin.ep())
	plugin.ep()->transportChangedStatus(this,m_status,reason);
}

// Handle received messages, set party, add to engine
void YateSIPTransport::receiveMsg(SIPMessage*& msg)
{
    if (!msg)
	return;
    YateSIPEngine* engine = plugin.ep() ? plugin.ep()->engine() : 0;
    if (!engine) {
	TelEngine::destruct(msg);
	return;
    }
    if (!msg->isAnswer() || engine->autoChangeParty()) {
	SIPParty* party = 0;
	YateSIPUDPTransport* udp = udpTransport();
	YateSIPTCPTransport* tcp = tcpTransport();
	if (udp) {
	    URI uri(msg->uri);
	    YateSIPLine* line = plugin.findLine(m_remote.host(),m_remote.port(),uri.getUser());
	    const char* host = 0;
	    int port = -1;
	    if (line && line->getLocalPort()) {
		host = line->getLocalAddr();
		port = line->getLocalPort();
	    }
	    if (!host)
		host = m_local.host();
	    if (port <= 0)
		port = m_local.port();
	    party = new YateUDPParty(udp,m_remote,&port,host);
	}
	else if (tcp) {
	    party = tcp->getParty();
	    if (!party) {
		party = new YateTCPParty(tcp);
		DDebug(&plugin,DebugAll,
		    "Transport(%s) built tcp party (%p) for received message (%p) [%p]",
		    m_id.c_str(),party,msg,this);
	    }
	}
	if (party) {
	    msg->setParty(party);
	    TelEngine::destruct(party);
	}
    }
    engine->addMessage(msg);
    TelEngine::destruct(msg);
}

// Print socket read error to output
void YateSIPTransport::printReadError()
{
    if (m_sock->canRetry())
	return;
    m_reason = "Socket read error:";
    addSockError(m_reason,*m_sock);
    Debug(&plugin,DebugWarn,"Transport(%s) %s [%p]",m_id.c_str(),m_reason.c_str(),this);
}

// Print socket write error to output
void YateSIPTransport::printWriteError(int res, unsigned int len, bool alarm)
{
    if (res == (int)len) {
	XDebug(&plugin,DebugAll,"Transport(%s) sent %u bytes [%p]",
	    m_id.c_str(),len,this);
	return;
    }
    if (res >= 0) {
	Debug(&plugin,DebugAll,"Transport(%s) sent %d/%u [%p]",m_id.c_str(),res,len,this);
	return;
    }
    if (m_sock->canRetry())
        return;
    m_reason = "Socket send error:";
    addSockError(m_reason,*m_sock);
    if (alarm)
	Alarm(&plugin,"socket",DebugWarn,"Transport(%s) %s [%p]",m_id.c_str(),m_reason.c_str(),this);
    else
	Debug(&plugin,DebugWarn,"Transport(%s) %s [%p]",m_id.c_str(),m_reason.c_str(),this);
}

// Set m_protoAddr from local/remote ip/port or reset it
void YateSIPTransport::setProtoAddr(bool set)
{
    Lock lck(this);
    if (!set) {
	m_protoAddr = "";
	return;
    }
    m_protoAddr << protoName(false) << ":" << m_local.addr();
    if (!udpTransport())
	m_protoAddr << "-" << m_remote.addr();
}


YateSIPUDPTransport::YateSIPUDPTransport(const String& id)
    : YateSIPTransport(Udp,id,0,Idle), YateSIPListener(id,Udp),
    m_default(false), m_forceBind(true), m_errored(false), m_bufferReq(0)
{
    Debug(&plugin,DebugAll,"Transport(%s) created [%p]",m_id.c_str(),this);
}

// (Re)Initialize the transport
bool YateSIPUDPTransport::init(const NamedList& params, const NamedList& defs, bool first,
    Thread::Priority prio)
{
    updateRtpAddr(params,m_rtpLocalAddr,this);
    m_default = params.getBoolValue("default",toString() == YSTRING("general"));
    m_forceBind = params.getBoolValue("udp_force_bind",true);
    m_bufferReq = params.getIntValue("buffer",defs.getIntValue("buffer"));
    if (first) {
	const String& addr = params["addr"];
	setAddr(addr,params.getIntValue("port",5060),
	    params.getBoolValue("ipv6",(addr.find(':') >= 0)));
	m_ipv6Support = s_ipv6;
    }
    bool ok = YateSIPTransport::init(params,defs,first,prio);
    if (plugin.debugAt(DebugAll)) {
	Lock lck(this);
	String s;
	SocketAddr::appendTo(s,m_address,m_port);
	Debug(&plugin,DebugAll,
	    "Listener(%s,'%s') initialized addr='%s' default=%s maxpkt=%u rtp_localip=%s nat_address=%s [%p]",
	    protoName(),lName(),s.c_str(),String::boolText(m_default),m_maxpkt,
	    m_rtpLocalAddr.c_str(),m_rtpNatAddr.c_str(),this);
    }
    if (ok && first)
	ok = startWorker(prio);
    return ok;
}

// Send data
bool YateSIPUDPTransport::send(const void* data, unsigned int len, const SocketAddr& addr)
{
    if (!m_sock)
	return false;
    Lock lck(this);
    if (!m_sock)
	return false;
    int sent = m_sock->sendTo(data,len,addr);
    bool err = (sent < 0);
    printWriteError(sent,len,err && !m_errored);
    if (m_errored && !err)
	Alarm(&plugin,"socket",DebugNote,"Transport(%s) error cleared [%p]",m_id.c_str(),this);
    m_errored = err;
    return !err || m_sock->canRetry();
}

// Process data (read/send).
// Return 0 to continue processing, positive to sleep (usec),
//  negative to terminate and destroy
int YateSIPUDPTransport::process()
{
    bool force = bindNow(this);
    if (force || !m_sock) {
	if (m_sock) {
	    changeStatus(Idle);
	    Lock lck(this);
	    YateSIPTransport::resetSocket(m_sock,-1);
	    m_local.clear();
	    m_bindRtpLocalAddr.clear();
	    setProtoAddr(false);
	}
	if (!force && m_nextBind > Time::now())
	    return Thread::idleUsec();
	String reason;
	SocketAddr addr;
	Socket* sock = initSocket(addr,this,m_bufferReq,m_forceBind,reason);
	if (!sock) {
	    changeStatus(Idle);
	    Lock lck(this);
	    m_reason = reason;
	    return Thread::idleUsec();
	}
	lock();
	m_sock = sock;
	m_local = addr;
	m_reason.clear();
	unlock();
	setProtoAddr(true);
	changeStatus(Connected);
    }
    else if (m_ipv6 && !m_ipv6Support) {
	Lock lck(this);
	if (m_ipv6 && !m_ipv6Support) {
	    // Force re-bind
	    Debug(&plugin,DebugInfo,
		"Listener(%s,'%s') IPv6 support changed. Forcing re-bind [%p]",
		protoName(),lName(),this);
	    m_bind = true;
	    return Thread::idleUsec();
	}
    }
    // Set RTP addr from bind address
    if (m_setRtpAddr) {
	Lock lck(this);
	if (m_setRtpAddr) {
	    m_rtpLocalAddr.clear();
	    if (!m_local.isNullAddr()) {
		addIfaceAddr(m_rtpLocalAddr,m_local.host(),m_cfgAddr);
		if (m_rtpLocalAddr)
		    Debug(&plugin,DebugAll,"Listener(%s,'%s') set rtp_localip='%s' [%p]",
			protoName(),lName(),m_rtpLocalAddr.c_str(),this);
	    }
	    m_bindRtpLocalAddr = m_rtpLocalAddr;
	    m_setRtpAddr = false;
	}
    }
    int& evc = YateSIPEndPoint::s_evCount;
    // Do nothing if the endpoint is flooded with events or terminating
    if (!(YateSIPEndPoint::canRead() || ((evc & 3) == 0)))
	return Thread::idleUsec();
    int retVal = 0;
    // Check if we can read (select is available)
    // Wait up to the platform idle time if we had no events in last run
    if (m_sock->canSelect()) {
	bool ok = false;
	if (m_sock->select(&ok,0,0,Thread::idleUsec())) {
	    if (!ok)
		return 0;
	}
	else {
	    // Select failed
	    if (m_sock->canRetry())
		return Thread::idleUsec();
	    String tmp;
	    Thread::errorString(tmp,m_sock->error());
	    Debug(&plugin,DebugWarn,"Transport(%s) select failed: %d '%s' [%p]",
		m_id.c_str(),m_sock->error(),tmp.c_str(),this);
	    return Thread::idleUsec();
	}
    }
    else
	retVal = Thread::idleUsec();
    // We can read the data
    m_buffer.resize(m_maxpkt + 1);
    int res = m_sock->recvFrom((void*)m_buffer.data(),m_buffer.length() - 1,m_remote);
    if (res <= 0) {
	printReadError();
	return retVal;
    }
    if (res < 72) {
	DDebug(&plugin,DebugInfo,
	    "Transport(%s) received short SIP message of %d bytes from %s [%p]",
	    m_id.c_str(),res,m_remote.addr().c_str(),this);
	return 0;
    }
    if (res == (int)m_maxpkt && s_warnPacketUDP) {
	s_warnPacketUDP = false;
	Alarm(&plugin,"config",DebugConf,
	    "Transport(%s) received likely truncated packet with length %d, try to increase maxpkt [%p]",
	    m_id.c_str(),res,this);
    }
    char* b = (char*)m_buffer.data();
    b[res] = 0;
    bool print = true;
    if (s_printMsg && !plugin.traceActive()) {
	print = false;
	printRecvMsg(b,res);
    }

    if (s_floodProtection && s_floodEvents && evc >= s_floodEvents) {
	if (!s_printFloodTime)
	    Alarm(&plugin,"performance",DebugWarn,
		"Flood detected, dropping INVITE/REGISTER/SUBSCRIBE/OPTIONS, allowing reINVITES");
	s_printFloodTime = Time::now() + 10000000;
	if (!msgIsAllowed(b,res)) {
	    if (s_printMsg && print)
		printRecvMsg(b,res);
	    return 0;
	}
    }
    else if (s_printFloodTime && s_printFloodTime < Time::now()) {
	s_printFloodTime = 0;
	Alarm(&plugin,"performance",DebugNote,"Flood drop cleared, resumed normal message processing");
    }

    SIPMessage* msg = SIPMessage::fromParsing(0,b,res);
    if (msg) {
	msg->msgPrint = print;
	receiveMsg(msg);
    }
    return 0;
}


// Outgoing
YateSIPTCPTransport::YateSIPTCPTransport(bool tls, const String& laddr, const String& raddr,
    int rport)
    : YateSIPTransport(tls ? Tls : Tcp,String::empty(),0,Idle),
    m_outgoing(true), m_party(0), m_sent(-1),
    m_firstKeepalive(0), m_firstKeepaliveSent(false),
    m_idleInterval(TCP_IDLE_DEF), m_idleTimeout(0),
    m_flowTimer(false), m_keepAlivePending(false),
    m_msg(0), m_sipBufOffs(0), m_contentLen(0),
    m_remoteAddr(raddr), m_remotePort(rport), m_localAddr(laddr),
    m_connectRetry(s_tcpConnectRetry), m_nextConnect(0)
{
    m_maxpkt = s_tcpMaxpkt;
    if (m_remotePort <= 0)
	m_remotePort = sipPort(protocol() != Tls);
    m_id << (tls ? "tls:" : "tcp:") << getTransIndex() << "-";
    SocketAddr::appendTo(m_id,raddr,m_remotePort);
    Debug(&plugin,DebugAll,"Transport(%s) created [%p]",m_id.c_str(),this);
    if (plugin.ep())
	plugin.ep()->addTcpTransport(this);
}

// Incoming
YateSIPTCPTransport::YateSIPTCPTransport(Socket* sock, bool tls)
    : YateSIPTransport(tls ? Tls : Tcp,String::empty(),sock,sock ? Connected : Idle),
    m_outgoing(false), m_party(0), m_sent(-1),
    m_firstKeepalive(0), m_firstKeepaliveSent(false),
    m_idleInterval(TCP_IDLE_DEF), m_idleTimeout(0),
    m_flowTimer(false), m_keepAlivePending(false),
    m_msg(0), m_sipBufOffs(0), m_contentLen(0),
    m_remotePort(0), m_connectRetry(0), m_nextConnect(0)
{
    m_maxpkt = s_tcpMaxpkt;
    m_id << (tls ? "tls:" : "tcp:");
    if (m_sock) {
    	m_sock->getSockName(m_local);
	m_sock->getPeerName(m_remote);
	m_id << m_local.addr() << "-" << m_remote.addr();
	setProtoAddr(true);
    }
    else
	m_id << getTransIndex();
    Debug(&plugin,DebugAll,"Transport(%s) created [%p]",m_id.c_str(),this);
    if (plugin.ep())
	plugin.ep()->addTcpTransport(this);
}

YateTCPParty* YateSIPTCPTransport::getParty()
{
    Lock lock(this);
    return (m_party && m_party->ref()) ? m_party : 0;
}

// (Re)Initialize the transport
bool YateSIPTCPTransport::init(const NamedList& params, bool first, Thread::Priority prio)
{
    bool ok = YateSIPTransport::init(params,NamedList::empty(),first,prio);
    String extra;
    if (outgoing()) {
	m_idleInterval = params.getIntValue(YSTRING("tcp_keepalive"),s_tcpKeepalive,0);
	m_firstKeepalive = params.getIntValue(YSTRING("tcp_keepalive_first"),
	    s_tcpKeepaliveFirst,0);
	if (m_firstKeepalive && m_idleInterval && m_firstKeepalive >= m_idleInterval)
	    m_firstKeepalive = 0;
	extra << " (first=" << m_firstKeepalive << ")";
    }
    else
	m_idleInterval = tcpIdleInterval(params.getIntValue(YSTRING("tcp_idle"),s_tcpIdle));
    setIdleTimeout();
    Debug(&plugin,DebugAll,
	"Transport(%s) initialized maxpkt=%u rtp_localip=%s nat_address=%s tcp_%s=%usec%s [%p]",
	m_id.c_str(),m_maxpkt,m_rtpLocalAddr.c_str(),m_rtpNatAddr.c_str(),
	(outgoing() ? "keepalive" : "idle"),m_idleInterval,extra.safe(),this);
    if (ok && first)
	ok = startWorker(prio);
    return ok;
}

// Set flow timer flag and idle interval (in seconds)
// Reset idle timeout
void YateSIPTCPTransport::setFlowTimer(bool on, unsigned int interval)
{
    Lock lock(this);
    m_flowTimer = on;
    if (m_flowTimer || m_outgoing || (!m_outgoing && m_idleInterval < interval))
	m_idleInterval = interval;
    Debug(&plugin,DebugInfo,"Transport(%s) flow timer is '%s' idle interval is %u seconds [%p]",
	m_id.c_str(),String::boolText(m_flowTimer),m_idleInterval,this);
    setIdleTimeout();
}

// Send data
bool YateSIPTCPTransport::send(SIPEvent* event)
{
    SIPMessage* msg = event->getMessage();
    if (!msg)
	return true;
    if (s_engineHalt)
	return false;
    Lock lock(this);
    if (m_status == Terminated)
	return false;
    if (m_queue.find(msg))
	return true;
    if (!msg->ref())
	return false;
    m_queue.append(msg);
#ifdef XDEBUG
    String tmp;
    getMsgLine(tmp,msg);
    Debug(&plugin,DebugAll,"Transport(%s) enqueued (%p,%s) [%p]",
	m_id.c_str(),msg,tmp.c_str(),this);
#endif
    return true;
}

// Process data (read/send)
int YateSIPTCPTransport::process()
{
    if (s_engineHalt) {
	// Stop processing
	Lock lck(this);
	// Last chance to send pending data
	ObjList* first = m_queue.skipNull();
	if (first && m_sock && m_sock->valid()) {
	    DataBlock buf;
	    for (ObjList* o = first; o && buf.length() < 4096; o = o->skipNext()) {
		SIPMessage* msg = static_cast<SIPMessage*>(o->get());
		if (s_printMsg && (o != first || m_sent < 0))
		    printSendMsg(msg);
		if (!msg->dontSend()) {
		    if (o != first || m_sent <= 0)
			buf += msg->getBuffer();
		    else {
			int remaining = msg->getBuffer().length() - m_sent;
			if (remaining > 0)
			    buf.assign(((char*)msg->getBuffer().data()) + m_sent,remaining);
		    }
		}
	    }
	    if (buf.length()) {
		DDebug(&plugin,DebugAll,"Transport(%s) sending last %u bytes [%p]",
		    m_id.c_str(),buf.length(),this);
		m_sock->writeData(buf.data(),buf.length());
	    }
	}
	m_queue.clear();
	// Terminate now incoming with no reference
	// Remember: the worker is referencing us
	if (!m_outgoing && refcount() == 2)
	    return -1;
	return 2000;
    }
    if (!(m_sock && m_sock->valid())) {
	if (!m_outgoing)
	    return -1;
	if (tls() && !s_sslClientAvailable) {
	    Debug(&plugin,DebugNote,"Transport(%s) SSL not available locally [%p]",
		toString().c_str(),this);
	    return -1;
	}
	if (!m_connectRetry || s_engineStop)
	    return -1;
	if (m_nextConnect > Time::now())
	    return Thread::idleUsec();
	m_connectRetry--;
	int conn = connect();
	m_firstKeepaliveSent = false;
	if (conn > 0)
	    setIdleTimeout();
	else if (conn < 0)
	    m_connectRetry = 0;
	if (conn > 0 || m_connectRetry) {
	    m_nextConnect = Time::now() + s_tcpConnectInterval;
	    return Thread::idleUsec();
	}
	return -1;
    }
    Time time;
    bool sent = false;
    // Send pending data/keepalive
    if (!sendPending(time,sent)) {
	resetConnection();
	return m_outgoing ? 0 : -1;
    }
    // Read data
    bool read = false;
    if (!readData(time,read)) {
	resetConnection();
	return m_outgoing ? 0 : -1;
    }
    // Outgoing received data: reset reconnect
    if (m_outgoing && read && m_nextConnect) {
	DDebug(&plugin,DebugAll,"Transport(%s) resetting re-connect [%p]",
	    m_id.c_str(),this);
	m_connectRetry = s_tcpConnectRetry;
	m_nextConnect = 0;
    }
    // Idle incoming with refcount=2 (the worker is referencing us): terminate
    if (!m_outgoing && m_idleTimeout < time) {
	if (refcount() == 2) {
	    m_reason = "Connection idle timeout";
	    Debug(&plugin,DebugInfo,"Transport(%s) idle [%p]",m_id.c_str(),this);
	    return -1;
	}
	setIdleTimeout(time);
    }
    return read ? 0 : Thread::idleUsec();
}

void YateSIPTCPTransport::destroyed()
{
    TelEngine::destruct(m_msg);
    YateSIPTransport::destroyed();
}

// Status changed notification for descendents
void YateSIPTCPTransport::statusChanged()
{
    Lock lock(this);
    if (m_status == Terminated) {
	// Remove messages now: they keep a party who is keeping a reference to us
	m_queue.clear();
	m_sent = -1;
    }
}

// Reset transport's party
void YateSIPTCPTransport::resetParty(YateTCPParty* party, bool set)
{
    if (!party)
	return;
    Lock lock(this);
    if (!m_party) {
	if (!set) {
	    Debug(&plugin,DebugNote,
		"Transport(%s) party (%p) trying to reset empty [%p]",
		m_id.c_str(),party,this);
	    return;
	}
    }
    else if (set || m_party != party) {
	int level = DebugNote;
#ifdef DEBUG
	if (set && m_party != party)
	    level = DebugFail;
#endif
	Debug(&plugin,level,"Transport(%s) party (%p) trying to %sset (%p) [%p]",
	    m_id.c_str(),party,set ? "" : "re",m_party,this);
	return;
    }
    m_party = set ? party : 0;
    DDebug(&plugin,DebugAll,"Transport(%s) party changed to (%p) [%p]",
	m_id.c_str(),m_party,this);
}

// Connect an outgoing transport. Terminate the socket before it
int YateSIPTCPTransport::connect(u_int64_t connToutUs)
{
    resetConnection();
    Socket* sock = 0;
    int retVal = -1;
    m_reason.clear();
    // Use a while() to break to the end
    while (true) {
	if (!m_remoteAddr) {
	    m_reason = "Empty remote address";
	    break;
	}
	// Allow connect retry
	retVal = 0;
	SocketAddr a(s_ipv6 ? SocketAddr::Unknown : SocketAddr::IPv4);
	if (!a.host(m_remoteAddr)) {
	    m_reason << "Failed to resolve '" << m_remoteAddr << "'";
	    break;
	}
	a.port(m_remotePort);
	sock = new Socket(a.family(),SOCK_STREAM);
	if (!sock->valid()) {
	    m_reason << "Failed to create socket";
	    break;
	}
	// Bind to local ip
	SocketAddr lip(s_ipv6 ? SocketAddr::Unknown : SocketAddr::IPv4);
	if (m_localAddr) {
	    if (!lip.host(m_localAddr)) {
		m_reason << "Invalid local address '" << m_localAddr << "'";
		// Don't allow connect retry
		retVal = -1;
		break;
	    }
	    if (!sock->bind(lip)) {
		m_reason << "Failed to bind on '" << lip.host() << "' (" << m_localAddr << "). ";
		addSockError(m_reason,*sock);
		// Don't allow connect retry
		retVal = -1;
		break;
	    }
	}
	// Use async connect
	if (connToutUs && !(sock->canSelect() && sock->setBlocking(false))) {
	    connToutUs = 0;
	    if (sock->canSelect()) {
		String tmp;
		addSockError(tmp,*sock);
		Debug(&plugin,DebugInfo,
		    "Transport(%s) using sync connect (async set failed).%s [%p]",
		    m_id.c_str(),tmp.c_str(),this);
	    }
	    else
		Debug(&plugin,DebugInfo,
		    "Transport(%s) using sync connect (select() not available) [%p]",
		    m_id.c_str(),this);
	}
	u_int64_t start = connToutUs ? Time::now() : 0;
	unsigned int intervals = 0;
	if (start) {
	    intervals = (unsigned int)(connToutUs / Thread::idleUsec());
	    // Make sure we wait for at least 1 timeout interval
	    if (!intervals)
		intervals = 1;
	}
	if (plugin.debugAt(DebugAll)) {
	    String s;
	    s << "'" << a.addr() << "'";
	    if (a.host() != m_remoteAddr)
		s << " (" << m_remoteAddr << ")";
	    if (m_localAddr)
		s << " localip=" << lip.addr();
	    Debug(&plugin,DebugAll,"Transport(%s) attempt to connect to %s [%p]",
		m_id.c_str(),s.safe(),this);
	}
	bool ok = sock->connect(a);
	bool timeout = false;
	bool stop = false;
	// Async connect in progress
	if (!ok && sock->inProgress()) {
	    bool done = false;
	    bool event = false;
	    while (intervals && !(done || event || stop)) {
		if (!sock->select(0,&done,&event,Thread::idleUsec()))
		    break;
	        intervals--;
		stop = Thread::check(false) || Engine::exiting();
	    }
	    timeout = !intervals && !(done || event);
	    if (!stop && sock && !sock->error() && (done || event) && sock->updateError())
		ok = !sock->error();
	}
	if (ok) {
	    // TLS?
	    if (tls() && !plugin.socketSsl(&sock,false)) {
		m_reason = "SSL not available locally";
		retVal = -1;
		ok = false;
	    }
	    if (ok) {
		if (!Thread::check(false))
		    retVal = 1;
		else {
		    m_reason = "Cancelled";
		    retVal = -1;
		}
	    }
	}
	else if (!stop) {
	    m_reason << "Failed to connect to '" << a.addr() << "'";
	    if (a.host() != m_remoteAddr)
		m_reason << " (" << m_remoteAddr << ")";
	    if (timeout)
		m_reason << " . Connect timeout";
	    else
		addSockError(m_reason,*sock);
	}
	break;
    }
    if (retVal > 0)
	resetConnection(sock);
    else {
	int level = DebugWarn;
	if (!m_reason) {
	    if (Thread::check(false) || Engine::exiting()) {
		level = DebugInfo;
		m_reason = "Connect cancelled";
	    }
	    else
		m_reason = "Connect failed";
	}
	Debug(&plugin,level,"Transport(%s) %s (remaining %u connect attempts) [%p]",
	    m_id.c_str(),m_reason.c_str(),!retVal ? m_connectRetry : 0,this);
	resetSocket(sock,0);
    }
    return retVal;
}

// Send pending messages, return false on failure
bool YateSIPTCPTransport::sendPending(const Time& time, bool& sent)
{
    sent = false;
    if (!m_sock)
	return false;
    int attempts = 3;
    while (attempts--) {
	Lock lock(this);
	ObjList* o = m_queue.skipNull();
	SIPMessage* msg = o ? static_cast<SIPMessage*>(o->get()) : 0;
	if (msg && m_sent < 0) {
	    m_sent = 0;
	    if (!sendPendingKeepAlive())
		return false;
	    XDebug(&plugin,DebugAll,"Transport(%s) dequeued (%p) [%p]",
		m_id.c_str(),msg,this);
	    if (s_printMsg)
		printSendMsg(msg);
	}
	else if (!msg) {
	    m_sent = -1;
	    if (!sendPendingKeepAlive())
		return false;
	    break;
	}
	if (msg->dontSend())
	    break;
	const DataBlock& buf = msg->getBuffer();
	sent = !msg->dontSend();
	int len = buf.length();
	if (sent && len > m_sent) {
	    char* b = (char*)(buf.data());
	    len -= m_sent;
	    int wr = m_sock->writeData(b + m_sent,len);
	    printWriteError(wr,len);
	    if (wr > 0) {
		m_sent += wr;
		// Outgoing: reset keep alive timer
		if (m_outgoing)
		    setIdleTimeout(time);
	    }
	    else if (wr && !m_sock->canRetry())
		return false;
	}
	if (m_sent >= (int)buf.length()) {
#ifdef DEBUG
	    String tmp;
	    getMsgLine(tmp,msg);
	    Debug(&plugin,DebugAll,"Transport(%s) sent (%p,%s) [%p]",
		m_id.c_str(),msg,tmp.c_str(),this);
#endif
	    o->remove();
	    m_sent = -1;
	    continue;
	}
	break;
    }
    // Keep alive?
    if (m_outgoing && !sent && m_idleTimeout <= time) {
	if (sendKeepAlive(true)) {
	    sent = true;
	    setIdleTimeout(time);
	}
	else
	    return false;
    }
    return true;
}

// Read data
bool YateSIPTCPTransport::readData(const Time& time, bool& read)
{
    read = false;
    m_buffer.resize(m_maxpkt + 1);
    int res = m_sock->readData((void*)m_buffer.data(),m_buffer.length() - 1);
    if (res < 0) {
	printReadError();
	return m_sock->canRetry();
    }
    if (!res) {
	m_reason = "Network down";
	Debug(&plugin,DebugNote,"Transport(%s) %s [%p]",m_id.c_str(),m_reason.c_str(),this);
	return false;
    }
    read = true;
#ifdef XDEBUG
#if 0
    String nb((const char*)m_buffer.data(),res);
    String ob((const char*)m_sipBuffer.data(),m_sipBuffer.length());
#else
    String nb, ob;
    nb.hexify(m_buffer.data(),res,' ');
    ob.hexify(m_sipBuffer.data(),m_sipBuffer.length(),' ');
#endif
    Debug(&plugin,DebugAll,"%s current buffer %u '%s' read %d '%s' [%p]",
	m_id.c_str(),m_sipBuffer.length(),ob.safe(),res,nb.safe(),this);
#endif
    const char* data = (const char*)m_buffer.data();
    unsigned int len = res;
    if (m_sipBuffer.length()) {
	m_sipBuffer.append(m_buffer.data(),len);
	len = m_sipBuffer.length();
	data = (const char*)m_sipBuffer.data();
    }
    bool ok = true;
    unsigned int over = 0;
    bool respond = false;
    while (len > 3) {
	ok = false;
	if (!m_msg) {
	    m_sipBufOffs = 0;
	    m_contentLen = 0;
	    // Skip spaces, check for keep alive
	    if (m_outgoing || respond)
		skipSpaces(data,len);
	    else
		respond = skipSpacesCheckKeepAlive(data,len);
	    if (len < 72) {
		ok = true;
		break;
	    }
	    // Find an empty line
	    m_sipBufOffs = getEmptyLine(data,len);
	    if (m_sipBufOffs > len) {
		m_sipBufOffs = 0;
		if (len <= m_maxpkt)
		    ok = true;
		else
		    over = len;
		break;
	    }
	    if (m_sipBufOffs > m_maxpkt) {
		over = m_sipBufOffs;
		break;
	    }
	    // Parse the message headers
	    m_msg = SIPMessage::fromParsing(0,data,m_sipBufOffs,&m_contentLen);
	    if (!m_msg) {
		m_reason = "Received invalid message";
		String tmp(data,m_sipBufOffs);
		Debug(&plugin,DebugNote,
		    "'%s' got invalid message [%p]\r\n------\r\n%s\r\n------",
		    m_id.c_str(),this,tmp.c_str());
		break;
	    }
	    // Check now if expected message length exceeds maxpkt
	    unsigned int expected = m_sipBufOffs + m_contentLen;
	    if (expected > m_maxpkt) {
		over = expected;
		break;
	    }
	}
	ok = true;
	// Expecting message body ?
	if (m_contentLen) {
	    if (m_sipBufOffs + m_contentLen > len)
		break;
	    m_msg->buildBody(data + m_sipBufOffs,m_contentLen);
	    m_sipBufOffs += m_contentLen;
	    m_contentLen = 0;
	}
	bool print = true;
	if (s_printMsg && !plugin.traceActive()) {
	    print = false;
	    printRecvMsg(data,m_sipBufOffs);
	}
	SIPMessage* msg = m_msg;
	msg->msgPrint = print;
	m_msg = 0;
	receiveMsg(msg);
	data += m_sipBufOffs;
	len -= m_sipBufOffs;
	m_sipBufOffs = 0;
    }
    if (!len)
	m_sipBuffer.clear();
    else
	m_sipBuffer.assign((void*)data,len);
    if (!ok) {
	if (over) {
	    m_reason = "Buffer overflow (message too long)";
	    Debug(&plugin,DebugNote,"'%s' %s len=%u maxpkt=%u [%p]",
		m_id.c_str(),m_reason.c_str(),over,m_maxpkt,this);
	}
	return false;
    }
    if (respond) {
	// RFC5626: send CR/LF in response
	Lock lck(this);
	m_keepAlivePending = true;
    }
    // Got data: reset timeout for incoming and connection check for all
    if (!m_outgoing)
	setIdleTimeout(time);
    return true;
}

// Reset socket and status
void YateSIPTCPTransport::resetConnection(Socket* sock)
{
    Lock lck(this);
    DDebug(&plugin,DebugAll,"Transport(%s) resetting connection sock=%p [%p]",
	m_id.c_str(),sock,this);
    // Reset send/recv data
    TelEngine::destruct(m_msg);
    m_sent = -1;
    m_sipBuffer.clear();
    m_sipBufOffs = 0;
    m_contentLen = 0;
    m_keepAlivePending = false;
    m_flowTimer = false;
    setProtoAddr(false);
    // Reset socket and addresses
    if (m_sock) {
	resetSocket(m_sock,-1);
	m_local.clear();
	m_remote.clear();
    }
    m_sock = sock;
    if (m_sock) {
	m_sock->getSockName(m_local);
	m_sock->getPeerName(m_remote);
	setProtoAddr(true);
	Debug(&plugin,DebugAll,"Transport(%s) connected local=%s remote=%s [%p]",
	    m_id.c_str(),m_local.addr().c_str(),m_remote.addr().c_str(),this);
    }
    // Update party local/remote ip/port
    if (m_party)
	m_party->updateAddrs();
    lck.drop();
    changeStatus(m_sock ? Connected : Idle);
}

void YateSIPTCPTransport::setIdleTimeout(u_int64_t time)
{
    unsigned int i = m_idleInterval;
    if (outgoing() && !m_firstKeepaliveSent) {
	m_firstKeepaliveSent = true;
	if (m_firstKeepalive)
	    i = m_firstKeepalive;
    }
    m_idleTimeout = time + (u_int64_t)i * 1000000;
    XDebug(&plugin,DebugAll,"Transport(%s) set idle timeout to %u (interval=%u) [%p]",
	m_id.c_str(),(unsigned int)(m_idleTimeout / 1000000),i,this);
}

bool YateSIPTCPTransport::sendKeepAlive(bool request)
{
    XDebug(&plugin,DebugAll,"Transport(%s) sending keep alive%s [%p]",
	m_id.c_str(),request ? "" : " response",this);
    unsigned int len = request ? 4 : 2;
    int wr = m_sock->writeData("\r\n\r\n",len);
    printWriteError(wr,len);
    return wr >= 0 || m_sock->canRetry();
}


YateSIPTransportWorker::YateSIPTransportWorker(YateSIPTransport* trans,
    Thread::Priority prio)
    : Thread("YSIP Worker",prio), m_transport(trans)
{
    XDebug(&plugin,DebugAll,"YateSIPTransportWorker(%p,%s) [%p]",
	trans,trans ? trans->toString().c_str() : "",this);
}

YateSIPTransportWorker::~YateSIPTransportWorker()
{
    cleanupTransport(true);
}

void YateSIPTransportWorker::run()
{
    if (!m_transport)
	return;
    DDebug(&plugin,DebugAll,"YateSIPTransportWorker (%p) '%s' started [%p]",
	m_transport,m_transport->toString().c_str(),this);
    while (true) {
	if (Thread::check(false))
	    break;
	// Keep the transport alive while calling its method
	RefPointer<YateSIPTransport> trans = m_transport;
	int n = trans ? trans->process() : -1;
	trans = 0;
	if (n > 0)
	    Thread::usleep(n);
	else if (n < 0)
	    break;
    }
    DDebug(&plugin,DebugAll,"YateSIPTransportWorker terminated [%p]",this);
    cleanupTransport(false,!Thread::check(false));
}

void YateSIPTransportWorker::cleanupTransport(bool final, bool terminate)
{
    // Reset now transport data: the thread might be cancelled from
    // YateSIPTransport::destroyed()
    if (m_transport) {
	Lock lock(m_transport);
	m_transport->m_worker = 0;
    }
    RefPointer<YateSIPTransport> trans = m_transport;
    m_transport = 0;
#ifdef XDEBUG
    Debugger debug(DebugAll,"YateSIPTransportWorker::cleanupTransport()",
	" final=%u terminate=%u transport=%p [%p]",
	final,terminate,(YateSIPTransport*)trans,this);
#endif
    if (!trans)
	return;
    if (final)
	Debug(DebugWarn,"YateSIPTransportWorker abnormally terminated! [%p]",this);
    YateSIPTCPTransport* tcp = trans->tcpTransport();
    if (tcp && tcp->outgoing())
	tcp = 0;
    if (terminate)
	trans->terminate();
    // Deref incoming TCP
    if (tcp)
	tcp->deref();
    trans = 0;
}


YateSIPTCPListener::YateSIPTCPListener(int proto, const String& name, const NamedList& params)
    : Thread("YSIP Listener",Thread::priority(params.getValue("thread"))),
    ProtocolHolder(proto),
    YateSIPListener(name,proto),
    m_mutex(true,"YSIPListener"),
    m_sslContextChanged(true), m_sslContextCheck(true), m_transParamsChanged(true),
    m_socket(0), m_backlog(5), m_transParams(params), m_initialized(false)
{
    init(params,true);
}

YateSIPTCPListener::~YateSIPTCPListener()
{
    cleanup(true);
}

// Init data
void YateSIPTCPListener::init(const NamedList& params, bool first)
{
    m_initialized = true;
    const String& addr = params["addr"];
    int port = params.getIntValue("port");
    if (port <= 0)
	port = sipPort(!tls());
    String sslContext;
    m_mutex.lock();
    if (tls()) {
	sslContext = params.getValue("sslcontext");
	m_sslContextChanged = first || m_sslContextChanged || (sslContext != m_sslContext);
	m_sslContext = sslContext;
	if (!m_sslContext)
	    Alarm(&plugin,"config",DebugConf,"Listener(%s,'%s') ssl context is empty [%p]",
		protoName(),lName(),this);
	// Always set check flag on re-init
	m_sslContextCheck = true;
    }
    m_backlog = params.getIntValue("backlog",5,0);
    setAddr(addr,port,params.getBoolValue("ipv6",(addr.find(':') >= 0)));
    if (first)
	m_bind = true;
    updateIPv6Support();
    m_transParamsChanged = m_transParamsChanged || first;
    const String& role = params[YSTRING("role")];
    if (role != m_transParams[YSTRING("role")]) {
	m_transParamsChanged = true;
	m_transParams.setParam("role",role);
    }
    String rtp;
    bool setRtpAddrChg = updateRtpAddr(params,rtp);
    if (rtp != m_transParams["rtp_localip"]) {
	m_transParamsChanged = true;
	m_transParams.setParam("rtp_localip",rtp);
    }
    else if (setRtpAddrChg)
	m_transParamsChanged = true;
    Debug(&plugin,DebugAll,
	"Listener(%s,'%s') initialized addr='%s' port=%d sslcontext='%s' rtp_localip='%s' [%p]",
	protoName(),lName(),addr.c_str(),port,sslContext.safe(),rtp.c_str(),this);
    m_mutex.unlock();
}

void YateSIPTCPListener::run()
{
    DDebug(&plugin,DebugAll,"Listener(%s,'%s') start running [%p]",
	protoName(),lName(),this);
    SocketAddr lAddr;
    NamedList transParams("");
    String sslContext;
    bool showWaitStart = true;
    bool sslAvailable = false;
    while (true) {
	if (Thread::check(false))
	    break;
	bool setRtpAddr = false;
	if (m_sslContextChanged || m_transParamsChanged) {
	    Lock lock(m_mutex);
	    if (m_sslContextChanged) {
		if (sslContext != m_sslContext) {
		    sslAvailable = false;
		    sslContext = m_sslContext;
		}
		if (tls() && !sslContext)
		    m_reason = "Empty SSL context";
	    }
	    if (m_transParamsChanged) {
		transParams = m_transParams;
		setRtpAddr = m_setRtpAddr;
	    }
	    m_sslContextChanged = false;
	    m_transParamsChanged = false;
	}
	if (tls()) {
	    if (!sslContext) {
		stopListening();
		Thread::msleep(3 * Thread::idleMsec());
		continue;
	    }
	    if (m_sslContextCheck) {
		if (!s_engineStart) {
		    if (showWaitStart) {
			Debug(&plugin,DebugAll,
			    "Listener(%s,'%s') waiting for engine start to check SSL context [%p]",
			    protoName(),lName(),this);
			showWaitStart = false;
		    }
		    Thread::idle();
		    continue;
		}
		sslAvailable = plugin.socketSsl(0,true,sslContext);
		Lock lck(m_mutex);
		m_sslContextCheck = false;
		if (!sslAvailable)
		    m_reason = "SSL context not available";
		lck.drop();
		if (!sslAvailable) {
		    Alarm(&plugin,"config",DebugConf,
			"Listener(%s,'%s') SSL context '%s' not available [%p]",
			protoName(),lName(),sslContext.c_str(),this);
		    stopListening();
		}
	    }
	    if (!sslAvailable) {
		Thread::msleep(3 * Thread::idleMsec());
		continue;
	    }
	}
	bool force = bindNow(&m_mutex);
	if (force || !m_socket) {
	    if (m_socket) {
		stopListening("Address changed",DebugInfo);
		Lock lck(m_mutex);
		m_bindRtpLocalAddr.clear();
	    }
	    if (!force && m_nextBind > Time::now()) {
		Thread::idle();
		continue;
	    }
	    String reason;
	    Socket* sock = initSocket(lAddr,&m_mutex,m_backlog,false,reason);
	    Lock lck(m_mutex);
	    m_socket = sock;
	    m_reason = reason;
	    if (!m_socket)
		continue;
	    m_local = lAddr;
	    setRtpAddr = m_setRtpAddr;
	}
	else if (m_ipv6 && !m_ipv6Support) {
	    // Check if we should drop and re-bind
	    Lock lck(m_mutex);
	    bool disable = m_ipv6 && !m_ipv6Support;
	    lck.drop();
	    if (disable) {
		stopListening("IPv6 support changed",DebugInfo);
		Thread::idle();
		continue;
	    }
	}
	if (setRtpAddr) {
	    // Set RTP addr from bind address
	    String rtp;
	    String old = transParams["rtp_localip"];
	    if (!lAddr.isNullAddr())
		addIfaceAddr(rtp,lAddr.host(),m_cfgAddr);
	    if (rtp)
		transParams.setParam("rtp_localip",rtp);
	    else
		transParams.clearParam("rtp_localip");
	    if (rtp != old)
		Debug(&plugin,DebugAll,"Listener(%s,'%s') set rtp_localip='%s' [%p]",
		    protoName(),lName(),rtp.c_str(),this);
	    m_bindRtpLocalAddr = rtp;
	}
	SocketAddr addr;
	Socket* sock = m_socket->accept(addr);
	if (!sock) {
	    Thread::idle();
	    continue;
	}
	Debug(&plugin,DebugAll,"Listener(%s,'%s') '%s' got conn from '%s' [%p]",
	    protoName(),lName(),lAddr.addr().c_str(),addr.addr().c_str(),this);
	if (!sock->setBlocking(false)) {
	    String tmp;
	    Thread::errorString(tmp,sock->error());
	    Debug(&plugin,DebugAll,
		"Listener(%s,'%s') '%s' failed to set non-blocking mode for '%s'. %d '%s' [%p]",
		protoName(),lName(),lAddr.addr().c_str(),addr.addr().c_str(),
		sock->error(),tmp.c_str(),this);
	    delete sock;
	    Thread::idle();
	    continue;
	}
	if (!tls() || plugin.socketSsl(&sock,true,sslContext)) {
	    YateSIPTCPTransport* trans = new YateSIPTCPTransport(sock,tls());
	    if (!trans->init(transParams,true))
		TelEngine::destruct(trans);
	}
	else {
	    Debug(&plugin,DebugWarn,"Listener(%s,'%s') failed to start SSL [%p]",
		protoName(),lName(),this);
	    delete sock;
	}
    }
    cleanup(false);
}

// Close the socket. Remove from endpoint list
void YateSIPTCPListener::cleanup(bool final)
{
    if (plugin.ep())
	plugin.ep()->removeListener(this);
    if (final) {
	if (!m_socket)
	    DDebug(&plugin,DebugInfo,"Listener(%s,'%s') terminated [%p]",
		protoName(),lName(),this);
	else
	    Alarm(&plugin,"system",DebugWarn,"Listener(%s,'%s') abnormally terminated [%p]",
		protoName(),lName(),this);
    }
    m_mutex.lock();
    const char* reason = 0;
    if (!m_reason)
	reason = "Terminated";
    m_mutex.unlock();
    stopListening(reason,DebugInfo);
}

// Reset socket
void YateSIPTCPListener::stopListening(const char* reason, int level)
{
    if (!m_socket)
	return;
    Lock lck(m_mutex);
    m_local.clear();
    if (!m_socket)
	return;
    if (!reason)
	reason = m_reason;
    Debug(&plugin,level,"Listener(%s,'%s') stop listening reason='%s' [%p]",
	protoName(),lName(),reason,this);
    YateSIPTransport::resetSocket(m_socket,0);
}


YateUDPParty::YateUDPParty(YateSIPUDPTransport* trans, const SocketAddr& addr,
    int* localPort, const char* localAddr)
    : m_transport(0), m_addr(addr)
{
    if (plugin.ep())
	m_mutex = plugin.ep()->m_partyMutexPool.mutex(this);
    if (trans && trans->ref())
	m_transport = trans;
    if (!localPort) {
	if (m_transport) {
	    m_localPort = m_transport->local().port();
	    m_local = m_transport->local().host();
	}
    }
    else {
	m_localPort = *localPort;
	m_local = localAddr;
    }
    m_party = m_addr.host();
    m_partyPort = m_addr.port();
    if (SocketAddr::isNullAddr(m_local)) {
	SocketAddr laddr(s_ipv6 ? SocketAddr::Unknown : SocketAddr::IPv4);
	if (laddr.local(addr))
	    m_local = laddr.host();
	else
	    m_local = "localhost";
	if (!m_localPort)
	    m_localPort = 5060;
    }
    DDebug(&plugin,DebugAll,"YateUDPParty local '%s' party '%s' transport=%p [%p]",
	SocketAddr::appendTo(m_local,m_localPort).c_str(),m_addr.addr().c_str(),
	m_transport,this);
}

YateUDPParty::~YateUDPParty()
{
    DDebug(&plugin,DebugAll,"YateUDPParty::~YateUDPParty() transport=%p [%p]",
	m_transport,this);
    TelEngine::destruct(m_transport);
}

bool YateUDPParty::transmit(SIPEvent* event)
{
    const SIPMessage* msg = event->getMessage();
    if (!msg)
	return false;
    if (m_transport) {
	Lock lck(m_transport);
	if (s_printMsg)
	    m_transport->printSendMsg(msg,&m_addr);
	if (msg->dontSend()) {
	    if (event->getTransaction())
		event->getTransaction()->setSilent();
	    return true;
	}
	return m_transport->send(msg->getBuffer().data(),msg->getBuffer().length(),m_addr);
    }
    String tmp;
    getMsgLine(tmp,msg);
    Debug(&plugin,DebugWarn,"No transport to send %s to %s:%d",
	tmp.c_str(),m_addr.host().c_str(),m_addr.port());
    return false;
}

const char* YateUDPParty::getProtoName() const
{
    return "UDP";
}

bool YateUDPParty::setParty(const URI& uri)
{
    Lock lock(m_mutex);
    if (m_partyPort && m_party) {
	if (m_transport) {
	    if (m_transport->ignoreVia())
		return true;
	}
	else if (s_ignoreVia)
	    return true;
    }
    if (uri.getHost().null())
	return false;
    int port = uri.getPort();
    if (port <= 0)
	port = 5060;
    if (!m_addr.host(uri.getHost())) {
	Debug(&plugin,DebugWarn,"Could not resolve UDP party name '%s' [%p]",
	    uri.getHost().safe(),this);
	return false;
    }
    m_addr.port(port);
    m_party = uri.getHost();
    m_partyPort = port;
    DDebug(&plugin,DebugInfo,"New UDP party is %s (%s) [%p]",
	SocketAddr::appendTo(m_party,m_partyPort).c_str(),m_addr.addr().c_str(),this);
    return true;
}

void* YateUDPParty::getTransport()
{
    return m_transport;
}

// Get an object from this one
void* YateUDPParty::getObject(const String& name) const
{
    if (name == YATOM("YateUDPParty"))
	return (void*)this;
    if (name == YATOM("YateSIPUDPTransport") || name == YATOM("YateSIPTransport"))
	return m_transport;
    return SIPParty::getObject(name);
}


YateTCPParty::YateTCPParty(YateSIPTCPTransport* trans)
    : SIPParty(true),
    m_transport(0)
{
    if (plugin.ep())
	m_mutex = plugin.ep()->m_partyMutexPool.mutex(this);
    if (trans && trans->ref()) {
	m_transport = trans;
	trans->resetParty(this,true);
    }
    updateAddrs();
    DDebug(&plugin,DebugAll,"YateTCPParty local %s party %s transport=%p [%p]",
	SocketAddr::appendTo(m_local,m_localPort).c_str(),
	SocketAddr::appendTo(m_party,m_partyPort).c_str(),m_transport,this);
}

YateTCPParty::~YateTCPParty()
{
    XDebug(&plugin,DebugAll,"YateTCPParty::~YateTCPParty() transport=%p [%p]",
	m_transport,this);
    TelEngine::destruct(m_transport);
}

bool YateTCPParty::transmit(SIPEvent* event)
{
    const SIPMessage* msg = event->getMessage();
    if (!msg)
	return false;
    if (m_transport) {
	bool ok = m_transport->send(event);
	if (msg->dontSend() && event->getTransaction())
	    event->getTransaction()->setSilent();
	return ok;
    }
    String tmp;
    getMsgLine(tmp,msg);
    Debug(&plugin,DebugWarn,"YateTCPParty no transport to send %s [%p]",
	tmp.c_str(),this);
    return false;
}

const char* YateTCPParty::getProtoName() const
{
    if (m_transport)
	return m_transport->protoName();
    return "TCP";
}

bool YateTCPParty::setParty(const URI& uri)
{
    Lock lock(m_mutex);
    if (m_partyPort && m_party && s_ignoreVia)
	return true;
    Debug(&plugin,DebugWarn,"YateTCPParty::setParty(%s) not implemented [%p]",
	uri.safe(),this);
    return false;
}

void* YateTCPParty::getTransport()
{
    return m_transport;
}

// Get an object from this one
void* YateTCPParty::getObject(const String& name) const
{
    if (name == YATOM("YateTCPParty"))
	return (void*)this;
    if (name == YATOM("YateSIPTCPTransport") || name == YATOM("YateSIPTransport"))
	return m_transport;
    return SIPParty::getObject(name);
}

void YateTCPParty::updateAddrs()
{
    if (!m_transport)
	return;
    m_transport->lock();
    String laddr = m_transport->local().host();
    int lport = m_transport->local().port();
    String raddr = m_transport->remote().host();
    int rport = m_transport->remote().port();
    String transLocalAddr = m_transport->localAddr();
    SocketAddr remote;
    if (raddr)
	remote = m_transport->remote();
    else {
	if (!s_ipv6)
	    remote.assign(AF_INET);
	remote.host(m_transport->remoteAddr());
	remote.port(m_transport->remotePort());
	raddr = remote.host();
	rport = remote.port();
    }
    m_transport->unlock();
    if (!laddr) {
	SocketAddr addr;
	if (transLocalAddr && addr.host(transLocalAddr))
	    laddr = addr.host();
	if (!laddr) {
	    addr.clear();
	    if (addr.local(remote))
		laddr = addr.host();
	    else
		laddr = "localhost";
	}
    }
    if (lport <= 0)
	lport = sipPort(!m_transport->tls());
    setAddr(laddr,lport,true);
    setAddr(raddr,rport,false);
}

void YateTCPParty::destroyed()
{
    DDebug(&plugin,DebugAll,"YateTCPParty::destroyed() transport=%p [%p]",
	m_transport,this);
    if (m_transport) {
	m_transport->resetParty(this,false);
	TelEngine::destruct(m_transport);
    }
    SIPParty::destroyed();
}


YateSIPEngine::YateSIPEngine(YateSIPEndPoint* ep)
    : SIPEngine(s_cfg.getValue("general","useragent")),
      m_ep(ep), m_prack(false), m_info(false), m_foreignAuth(false),
      m_traceIds(0)
{
    addAllowed("INVITE");
    addAllowed("BYE");
    addAllowed("CANCEL");
    if (s_enable_message)
	addAllowed("MESSAGE");
    if (s_enable_register)
	addAllowed("REGISTER");
    if (s_enable_transfer)
	addAllowed("REFER");
    if (s_enable_options)
	addAllowed("OPTIONS");
    m_prack = s_cfg.getBoolValue("general","prack");
    if (m_prack)
	addAllowed("PRACK");
    m_info = s_cfg.getBoolValue("general","info",true);
    if (m_info)
	addAllowed("INFO");
    NamedList *l = s_cfg.getSection("methods");
    if (l) {
	unsigned int len = l->length();
	for (unsigned int i=0; i<len; i++) {
	    NamedString *n = l->getParam(i);
	    if (!n)
		continue;
	    String meth(n->name());
	    meth.toUpper();
	    addAllowed(meth);
	}
    }
    initialize(s_cfg.getSection("general"));
}

// Initialize the engine
void YateSIPEngine::initialize(NamedList* params)
{
    NamedList dummy("");
    if (!params)
	params = &dummy;
    lazyTrying(params->getBoolValue("lazy100",false));
    m_fork = params->getBoolValue("fork",true);
    m_forkEarly = params->getBoolValue("fork_early",false);
    m_flags = params->getIntValue("flags",m_flags);
    m_foreignAuth = params->getBoolValue("auth_foreign",false);
    m_reqTransCount = params->getIntValue("sip_req_trans_count",4,2,10,false);
    m_rspTransCount = params->getIntValue("sip_rsp_trans_count",5,2,10,false);
    m_autoChangeParty = params->getBoolValue("autochangeparty");
    int64_t t1 = params->getIntValue("t1",500,100,5000,false);
    int64_t t2 = params->getIntValue("t4",5000,1000,25000,false);
    if (t2 < 3 * t1)
	t2 = 3 * t1;
    m_t1 = 1000 * t1;
    m_t4 = 1000 * t2;
    DDebug(this,DebugAll,"Initialized sip_req_trans_count=%d sip_rsp_trans_count=%d",
	m_reqTransCount,m_rspTransCount);
}

SIPTransaction* YateSIPEngine::forkInvite(SIPMessage* answer, SIPTransaction* trans)
{
    if (m_fork && (answer->code > 100) && trans->isActive()) {
	switch (answer->code / 100) {
	    case 1:
		if (!m_forkEarly)
		    break;
		// fall through
	    case 2:
		Debug(this,DebugNote,"Changing early dialog tag because of forked %d",answer->code);
		trans->setDialogTag(answer->getParamValue("To","tag"));
		trans->processMessage(answer);
		return trans;
	}
    }
    return SIPEngine::forkInvite(answer,trans);
}

// Transport status changed notification
void YateSIPEngine::transportChangedStatus(YateSIPTransport* trans, int stat, const String& reason)
{
    if (!(trans && stat == YateSIPTransport::Terminated))
	return;
    // Clear transactions
    Lock lock(this);
    for (ObjList* l = m_transList.skipNull(); l; l = l->skipNext()) {
	SIPTransaction* t = static_cast<SIPTransaction*>(l->get());
	if (t->initialMessage() && t->initialMessage()->getParty() &&
	    trans == t->initialMessage()->getParty()->getTransport()) {
	    bool active = t->isActive();
	    Debug(this,active ? DebugInfo : DebugAll,
		"Clearing %stransaction (%p) transport terminated reason=%s",
		active ? "active " : "",t,reason.c_str());
	    t->setCleared();
	}
    }
}

// Check if the engine has an active transaction using a given transport
bool YateSIPEngine::hasActiveTransaction(YateSIPTransport* trans)
{
    if (!trans)
	return false;
    Lock lock(this);
    for (ObjList* l = m_transList.skipNull(); l; l = l->skipNext()) {
	SIPTransaction* t = static_cast<SIPTransaction*>(l->get());
	if (t->isActive() && t->initialMessage() && t->initialMessage()->getParty() &&
	    trans == t->initialMessage()->getParty()->getTransport())
	    return true;
    }
    return false;
}

// Check if the engine has pending transactions
bool YateSIPEngine::hasInitialTransaction()
{
    Lock lock(this);
    for (ObjList* l = m_transList.skipNull(); l; l = l->skipNext()) {
	SIPTransaction* t = static_cast<SIPTransaction*>(l->get());
	if (t->getState() == SIPTransaction::Initial)
	    return true;
    }
    return false;
}

bool YateSIPEngine::buildParty(SIPMessage* message)
{
    return m_ep->buildParty(message);
}

void YateSIPEngine::allocTraceId(String& id)
{
    if (!plugin.traceActive())
      return;
    id << "sip-";
    Lock l(this);
    id << m_traceIds++;
}

void YateSIPEngine::traceMsg(SIPMessage* message, bool incoming)
{
    if (!(message && message->msgPrint && s_printMsg && message->getParty()))
	return;
    YateSIPTransport* trans = static_cast<YateSIPTransport*>(message->getParty()->getTransport());
    if (!trans)
	return;
    if (incoming) {
	DataBlock d(message->getBuffer().data(),message->getBuffer().length(),false,1);
	*((uint8_t*)d.data() + (d.length() - 1)) = 0;
	trans->printRecvMsg ((const char*)d.data(),
		    d.length(),message->traceId());
	d.clear(false);
      }
    else
	trans->printSendMsg(message);
}

bool YateSIPEngine::copyAuthParams(NamedList* dest, const NamedList& src, bool ok)
{
    // we added those and we want to exclude them from copy
    static TokenDict exclude[] = {
	{ "protocol", 1 },
	// purposely copy the username and realm
	{ "nonce", 1 },
	{ "method", 1 },
	{ "uri", 1 },
	{ "response", 1 },
	{ "ip_host", 1 },
	{ "ip_port", 1 },
	{ "address", 1 },
	{ "id", 1 },
	{ "billid", 1 },
	{ "handlers", 1 },
	{  0,   0 },
    };
    if (!dest)
	return ok;
    unsigned int n = src.length();
    for (unsigned int i = 0; i < n; i++) {
	NamedString* s = src.getParam(i);
	if (!s)
	    continue;
	// Don't copy added SIP headers: on success they will be added again
	if (s->name().startsWith("sip_"))
	    continue;
	String name = s->name();
	if (name.startSkip("authfail_",false) == ok)
	    continue;
	if (name.toInteger(exclude,0))
	    continue;
	dest->setParam(name,*s);
    }
    return ok;
}

bool YateSIPEngine::checkUser(String& username, const String& realm, const String& nonce,
    const String& method, const String& uri, const String& response,
    const SIPMessage* message, const MimeHeaderLine* authLine, GenObject* userData)
{
    NamedList* params = YOBJECT(NamedList,userData);

    Message m("user.auth");
    m.addParam("protocol","sip");
    if (username) {
	m.addParam(s_username,username);
	m.addParam("realm",realm);
	m.addParam("nonce",nonce);
	m.addParam("response",response);
    }
    m.addParam("method",method);
    m.addParam("uri",uri);
    if (message) {
	String raddr;
	int rport = 0;
	message->getParty()->getAddr(raddr,rport,false);
	String port(rport);
	m.addParam("ip_host",raddr);
	m.addParam("ip_port",port);
	m.addParam("ip_transport",message->getParty()->getProtoName());
	YateSIPTransport* trans = YOBJECT(YateSIPTransport,message->getParty());
	if (trans)
	    trans->fillMessage(m);
	if (raddr)
	    m.addParam("address",SocketAddr::appendTo(raddr,rport));
	// a dialogless INVITE could create a new call
	m.addParam("newcall",String::boolText((message->method == YSTRING("INVITE")) && !message->getParam("To","tag")));
	URI domain;
	const MimeHeaderLine* hl = message->getHeader("From");
	if (hl)
	    domain = *hl;
	if (!domain.getHost())
	    domain = uri;
	m.addParam("domain",domain.getHost(),false);
	hl = message->getHeader("User-Agent");
	if (hl)
	    m.addParam("device",*hl);
	m.addParam("trace_id",message->traceId());
	s_globalMutex.lock();
	for (const ObjList* l = message->header.skipNull(); l; l = l->skipNext()) {
	    hl = static_cast<const MimeHeaderLine*>(l->get());
	    String name(hl->name());
	    name.toLower();
	    if (!(name.startsWith("security-") ||
		(s_authCopyHeader && s_authCopyHeader->find(name))))
		continue;
	    String tmp;
	    hl->buildLine(tmp,false);
	    m.addParam("sip_" + name,tmp);
	}
	s_globalMutex.unlock();
    }

    if (params) {
	m.copyParam(*params,"id");
	m.copyParam(*params,"number");
	m.copyParam(*params,"caller");
	m.copyParam(*params,"called");
	m.copyParam(*params,"billid");
	m.copyParam(*params,"expires");
	m.copyParam(*params,"trace_id");
    }
    if (authLine && m_foreignAuth) {
	m.addParam("auth",*authLine);
	for (const ObjList* l = authLine->params().skipNull(); l; l = l->skipNext()) {
	    const NamedString* p = static_cast<const NamedString*>(l->get());
	    m.setParam("auth_" + p->name(),*p);
	}
    }
    else
	authLine = 0;

    if (!Engine::dispatch(m))
	return copyAuthParams(params,m,false);

    // empty password returned means authentication succeeded
    if (m.retValue().null()) {
	if (username.null()) {
	    if (authLine) {
		username = authLine->getParam(s_username);
		MimeHeaderLine::delQuotes(username);
	    }
	    if (username.null())
		username = m.getValue(s_username);
	}
	return copyAuthParams(params,m);
    }
    // check for refusals
    if (m.retValue() == "-") {
	if (params) {
	    const char* err = m.getValue(YSTRING("error"));
	    if (err)
		params->setParam("error",err);
	    err = m.getValue(YSTRING("reason"));
	    if (err)
		params->setParam("reason",err);
	}
	return copyAuthParams(params,m,false);
    }
    // password works only with username
    if (!username)
	return copyAuthParams(params,m,false);

    String res;
    buildAuth(username,realm,m.retValue(),nonce,method,uri,res);
    if (res == response)
	return copyAuthParams(params,m);
    // if the URI included some parameters retry after stripping them off
    int sc = uri.find(';');
    bool ok = false;
    if (sc >= 0) {
	buildAuth(username,realm,m.retValue(),nonce,method,uri.substr(0,sc),res);
	ok = (res == response) && copyAuthParams(params,m);
    }

    if (!ok && !response.null()) {
	DDebug(&plugin,DebugNote,"Failed authentication for username='%s'",username.c_str());
	m_ep->incFailedAuths();
	plugin.changed();
	Message* fail = new Message(m);
	*fail = "user.authfail";
	fail->retValue().clear();
	Engine::enqueue(fail);
    }
    return ok || copyAuthParams(params,m,false);
}


YateSIPEndPoint::YateSIPEndPoint(Thread::Priority prio, unsigned int partyMutexCount)
    : Thread("YSIP EndPoint",prio),
      m_partyMutexPool(partyMutexCount,true,"SIPParty"),
      m_engine(0), m_mutex(true,"YateSIPEndPoint"), m_defTransport(0),
      m_failedAuths(0),m_timedOutTrs(0), m_timedOutByes(0)
{
    Debug(&plugin,DebugAll,"YateSIPEndPoint::YateSIPEndPoint(%s) [%p]",
	Thread::priority(prio),this);
}

YateSIPEndPoint::~YateSIPEndPoint()
{
    Debug(&plugin,DebugAll,"YateSIPEndPoint::~YateSIPEndPoint() [%p]",this);
    plugin.channels().clear();
    s_lines.clear();
    if (m_engine) {
	// send any pending events
	while (m_engine->process())
	    ;
	delete m_engine;
	m_engine = 0;
    }
    m_defTransport = 0;
    plugin.epTerminated(this);
}

bool YateSIPEndPoint::buildParty(SIPMessage* message, const char* host, int port, const YateSIPLine* line)
{
    if (message->isAnswer())
	return false;
    Debug(&plugin,DebugAll,"YateSIPEndPoint::buildParty(%p,'%s',%d,%p)",
	message,host,port,line);
    if (line && line->setSipParty(message,line))
	return true;
    // Find transport
    YateSIPUDPTransport* trans = defTransport();
    if (!trans && line && line->getLocalAddr())
	trans = findUdpTransport(line->getLocalAddr(),line->getLocalPort());
    if (!trans)
	return false;
    // Build an udp party
    URI uri(message->uri);
    if (line) {
	if (!host)
	    host = line->getPartyAddr();
	if (port <= 0)
	    port = line->getPartyPort();
	line->setupAuth(message);
    }
    if (!host) {
	host = uri.getHost().safe();
	if (port <= 0)
	    port = uri.getPort();
    }
    if (port <= 0)
	port = 5060;
    trans->lock();
    int f = trans->local().family();
    trans->unlock();
    SocketAddr addr(f);
    if (!addr.host(host)) {
	TelEngine::destruct(trans);
	Debug(&plugin,DebugWarn,"Error resolving name '%s' (address family '%s')",
	    host,SocketAddr::lookupFamily(f));
	return false;
    }
    addr.port(port);
    DDebug(&plugin,DebugAll,"built addr: %s",addr.addr().c_str());
    YateUDPParty* party = new YateUDPParty(trans,addr);
    TelEngine::destruct(trans);
    message->setParty(party);
    TelEngine::destruct(party);
    return true;
}

// (re)set default UDP transport
void YateSIPEndPoint::updateDefUdpTransport()
{
    static const String s_general("general");
    Lock lock(m_mutex);
    m_defTransport = 0;
    // Find a non-general default transport
    for (ObjList* o = m_transports.skipNull(); o; o = o->skipNext()) {
	YateSIPTransport* t = static_cast<YateSIPTransport*>(o->get());
	m_defTransport = t->udpTransport();
	if (m_defTransport && m_defTransport->toString() != s_general &&
	    m_defTransport->isDefault())
	    break;
	m_defTransport = 0;
    }
    if (!m_defTransport) {
	m_defTransport = findUdpTransport(s_general);
	if (m_defTransport)
	    m_defTransport->deref();
    }
    if (m_defTransport)
	Debug(&plugin,DebugInfo,"Default UDP transport is '%s'",
	    m_defTransport->toString().c_str());
    else if (!Engine::exiting())
	Debug(&plugin,DebugNote,"Default UDP transport not set");
}

// Retrieve a transport by name. Return referrenced object
YateSIPTransport* YateSIPEndPoint::findTransport(const String& name)
{
    if (!name)
	return 0;
    Lock lock(m_mutex);
    ObjList* o = m_transports.find(name);
    YateSIPTransport* t = o ? static_cast<YateSIPTransport*>(o->get()) : 0;
    return (t && t->ref()) ? t : 0;
}

// Retrieve an UDP transport by name. Return referrenced object
YateSIPUDPTransport* YateSIPEndPoint::findUdpTransport(const String& name)
{
    YateSIPTransport* t = findTransport(name);
    YateSIPUDPTransport* trans = t ? t->udpTransport() : 0;
    if (!trans)
	TelEngine::destruct(t);
    return trans;
}

// Retrieve an UDP transport by addr/port. Return referrenced object
YateSIPUDPTransport* YateSIPEndPoint::findUdpTransport(const String& addr, int port)
{
    Lock lock(m_mutex);
    for (ObjList* o = m_transports.skipNull(); o; o = o->skipNext()) {
	YateSIPTransport* t = static_cast<YateSIPTransport*>(o->get());
	if (!t->udpTransport())
	    continue;
	Lock lck(t);
	if (t->local().port() == port && t->local().host() == addr)
	    return t->ref() ? static_cast<YateSIPUDPTransport*>(t) : 0;
    }
    return 0;
}

// Build an UDP transport (re-init existing). Start the thread
bool YateSIPEndPoint::setupUdpTransport(const String& name, bool enabled,
    const NamedList& params, const NamedList& defs, const char* reason)
{
    if (!name)
	return false;
    YateSIPUDPTransport* rd = findUdpTransport(name);
    if (rd) {
	if (enabled) {
	    reason = 0;
	    const String& addr = params["addr"];
	    bool ipv6 = params.getBoolValue("ipv6",(addr.find(':') >= 0));
	    if (rd->ipv6() == ipv6 && (!rd->ipv6() || rd->ipv6Support() == s_ipv6)) {
		int port = params.getIntValue("port",5060);
		if (rd->addrWouldChange(rd,addr,port))
		    reason = "Address changed";
	    }
	    else if (rd->ipv6() == ipv6)
		reason = "IPv6 support changed";
	    else
		reason = "Address family changed";
	}
	else if (!reason)
	    reason = "Disabled";
	if (!reason)
	    rd->init(params,defs,false);
	else
	    removeUdpTransport(name,reason);
	TelEngine::destruct(rd);
	if (!(enabled && reason))
	    return true;
    }
    if (!enabled)
	return true;
    Lock lock(m_mutex);
    const char* s = params.getValue("thread",defs.getValue("thread"));
    rd = new YateSIPUDPTransport(name);
    rd->init(params,defs,true,Thread::priority(s));
    m_transports.append(rd);
    return true;
}

// Delete an UDP transport
bool YateSIPEndPoint::removeUdpTransport(const String& name, const char* reason)
{
    XDebug(&plugin,DebugAll,"YateSIPEndPoint::removeUdpTransport(%s,%s)",name.c_str(),reason);
    if (!name)
	return false;
    YateSIPUDPTransport* rd = findUdpTransport(name);
    if (!rd)
	return false;
    Debug(&plugin,DebugInfo,"Listener(%s,'%s') stop listening reason='%s' [%p]",
	rd->protoName(),rd->lName(),reason,rd);
    removeTransport(rd,false);
    // Terminate channels, disconnect lines
    plugin.transportTerminated(rd);
    // Notify lines
    for (ObjList* ol = s_lines.skipNull(); ol; ol = ol->skipNext()) {
	YateSIPLine* line = static_cast<YateSIPLine*>(ol->get());
	if (line->isTransport(rd))
	    line->transportChangedStatus(YateSIPTransport::Terminated,reason);
    }
    // Wait for active transactions to terminate
    if (!Engine::exiting()) {
	unsigned int intervals = (unsigned int)(s_waitActiveUdpTrans / Thread::idleUsec());
	if (!intervals)
	    intervals = 1;
	while (intervals > 0 && !Engine::exiting() &&
	    m_engine->hasActiveTransaction(rd)) {
	    Thread::idle();
	    intervals--;
	}
	if (!intervals)
	    Debug(&plugin,DebugNote,
		"Removing udp transport '%s' with active transactions using it",
		name.c_str());
    }
    rd->terminate(reason);
    rd->deref();
    TelEngine::destruct(rd);
    return true;
}

// Remove a transport from list
bool YateSIPEndPoint::removeTransport(YateSIPTransport* trans, bool updDef)
{
    if (!trans)
	return false;
    Lock lock(m_mutex);
    if (!m_transports.remove(trans,false))
	return false;
    Debug(&plugin,DebugAll,"Removed transport (%p,'%s')",trans,trans->toString().c_str());
    if (trans == m_defTransport) {
	Debug(&plugin,DebugInfo,"Reset default UDP transport");
	m_defTransport = 0;
	if (updDef)
	    updateDefUdpTransport();
    }
    return true;
}

// Remove all transports
void YateSIPEndPoint::clearUdpTransports(const char* reason)
{
#ifdef DEBUG
    Debugger debug(DebugAll,"YateSIPEndPoint::clearUdpTransports()");
#else
    Debug(&plugin,DebugAll,"Clearing udp transports reason=%s",reason);
#endif
    while (true) {
	Lock lock(m_mutex);
	RefPointer<YateSIPUDPTransport> trans;
	for (ObjList* o = m_transports.skipNull(); o; o = o->skipNext()) {
	    trans = YOBJECT(YateSIPUDPTransport,o->get());
	    if (trans)
		break;
	}
	if (!trans)
	    break;
	lock.drop();
	Debug(&plugin,DebugInfo,"Listener(%s,'%s') stop listening reason='%s' [%p]",
	    trans->protoName(),trans->lName(),reason,(YateSIPUDPTransport*)trans);
	removeTransport(trans);
	trans->terminate(reason);
	trans->deref();
	trans = 0;
    }
}

// TCP transport status changed notification
void YateSIPEndPoint::transportChangedStatus(YateSIPTransport* trans, int stat, const String& reason)
{
    if (!trans)
	return;
    DDebug(&plugin,DebugAll,"YateSIPEndPoint::transportChangedStatus(%p,%s,%s)",
	trans,YateSIPTransport::statusName(stat),reason.c_str());
    // Remove from list
    if (stat == YateSIPTCPTransport::Terminated)
	removeTransport(trans);
    // Notify lines
    for (ObjList* ol = s_lines.skipNull(); ol; ol = ol->skipNext()) {
	YateSIPLine* line = static_cast<YateSIPLine*>(ol->get());
	if (line->isTransport(trans))
	    line->transportChangedStatus(stat,reason);
	else if (line->valid() && stat == YateSIPTransport::Connected
	    && trans->protocol() == ProtocolHolder::Udp && line->protocol() == ProtocolHolder::Udp
	    && !line->transport()) {
	    // Line is configured to be online without registering to server
	    //  has no transport but has configured UDP transport
	    // Try to re-build the party
	    line->buildParty();
	}
    }
    // Notify transactions in engine
    if (m_engine)
	m_engine->transportChangedStatus(trans,stat,reason);
    if (stat != YateSIPTCPTransport::Terminated)
	return;
    // Notify unregister
    if (!Engine::exiting()) {
	Message* m = new Message("user.unregister");
	m->addParam("connection_id",trans->toString());
	Engine::enqueue(m);
    }
    // Notify channels
    plugin.transportTerminated(trans);
}

// Build or delete a TCP listener. Start the thread
bool YateSIPEndPoint::setupListener(int proto, const String& name, bool enabled, const NamedList& params)
{
    if (!name)
	return false;
    Lock lock(m_mutex);
    ObjList* o = m_listeners.find(name);
    if (o) {
	YateSIPTCPListener* l = static_cast<YateSIPTCPListener*>(o->get());
	if (enabled) {
	    if (l->protocol() == proto)
		l->init(params,false);
	    else {
		lock.drop();
		cancelListener(name,"Type changed");
		return setupListener(proto,name,enabled,params);
	    }
	}
	else {
	    lock.drop();
	    cancelListener(name,"Disabled");
	}
	return true;
    }
    if (!enabled)
	return true;
    // Build it
    YateSIPTCPListener* listener = new YateSIPTCPListener(proto,name,params);
    if (listener->startup()) {
	m_listeners.append(listener);
	DDebug(&plugin,DebugAll,"Added listener %p '%s'",listener,listener->toString().c_str());
	return true;
    }
    Alarm(&plugin,"config",DebugWarn,"Failed to start listener thread type=%s name='%s'",
	ProtocolHolder::lookupProtoName(proto),name.c_str());
    return false;
}

// Remove a listener from list without deleting it. Return true if found
bool YateSIPEndPoint::removeListener(YateSIPTCPListener* listener)
{
    if (!listener)
	return false;
    Lock lock(m_mutex);
    if (!m_listeners.remove(listener,false))
	return false;
    DDebug(&plugin,DebugAll,"Removed listener (%p,'%s')",listener,listener->toString().c_str());
    return true;
}

// Remove a listener from list. Remove all if name is empty. Wait for termination
void YateSIPEndPoint::cancelListener(const String& name, const char* reason)
{
    m_mutex.lock();
    bool wait = false;
    for (ObjList* o = m_listeners.skipNull(); o; o = o->skipNext()) {
	YateSIPTCPListener* l = static_cast<YateSIPTCPListener*>(o->get());
	if (name && name != l->toString())
	    continue;
	wait = true;
	Debug(&plugin,DebugAll,"Stopping listener (%p,'%s') reason=%s",
	    l,l->toString().c_str(),reason);
	l->setReason(reason);
	l->cancel();
	if (name)
	    break;
    }
    m_mutex.unlock();
    if (!wait)
	return;
    while (true) {
	Thread::idle();
	Lock lck(m_mutex);
	ObjList* o = !name ? m_listeners.skipNull() : m_listeners.find(name);
	if (!o)
	    break;
    }
    if (!name)
    	Debug(&plugin,DebugAll,"Stopped all listeners");
    else
    	Debug(&plugin,DebugAll,"Stopped listener '%s'",name.c_str());
}

// This method is called by the driver when start/end initializing
// start==true: Reset initialized flag for listeners and UDP transports
// start==false: Terminate not initialized listeners and UDP transports
void YateSIPEndPoint::initializing(bool start)
{
    ObjList rmListener;
    ObjList rmUdpTrans;
    m_mutex.lock();
    for (ObjList* o = m_listeners.skipNull(); o; o = o->skipNext()) {
	YateSIPTCPListener* l = static_cast<YateSIPTCPListener*>(o->get());
	if (start)
	    l->m_initialized = false;
	else if (!l->m_initialized)
	    rmListener.append(new String(l->toString()));
    }
    for (ObjList* o = m_transports.skipNull(); o; o = o->skipNext()) {
	YateSIPTCPTransport* t = static_cast<YateSIPTCPTransport*>(o->get());
	if (start)
	    t->m_initialized = false;
	else if (!t->m_initialized && t->udpTransport())
	    rmUdpTrans.append(new String(t->toString()));
    }
    m_mutex.unlock();
    if (start)
	return;
    for (ObjList* o = rmListener.skipNull(); o; o = o->skipNext()) {
	String* name = static_cast<String*>(o->get());
	Debug(&plugin,DebugNote,"Stopping deleted listener '%s'",name->c_str());
	cancelListener(*name,"Deleted");
    }
    for (ObjList* o = rmUdpTrans.skipNull(); o; o = o->skipNext())
	removeUdpTransport(o->get()->toString(),"Deleted");
}

// Complete transport names
void YateSIPEndPoint::completeTransports(Message& msg, const String& partWord,
    bool udp, bool tcp, bool tls)
{
    Lock lock(m_mutex);
    for (ObjList* o = m_transports.skipNull(); o; o = o->skipNext()) {
	YateSIPTransport* t = static_cast<YateSIPTransport*>(o->get());
	switch (t->protocol()) {
	    case ProtocolHolder::Udp:
		if (!udp)
		    continue;
		break;
	    case ProtocolHolder::Tcp:
		if (!tcp)
		    continue;
		break;
	    case ProtocolHolder::Tls:
		if (!tls)
		    continue;
		break;
	}
	Module::itemComplete(msg.retValue(),t->toString(),partWord);
    }
}

bool YateSIPEndPoint::Init()
{
    m_engine = new YateSIPEngine(this);
    m_engine->debugChain(&plugin);
    return true;
}

// Check if data is allowed to be read from socket(s) and processed
bool YateSIPEndPoint::canRead()
{
    return s_floodEvents <= 1 || (s_evCount < s_floodEvents) || Engine::exiting();
}

void YateSIPEndPoint::run()
{
    for (;;)
    {
	if (!canRead()) {
	    if (s_evCount == s_floodEvents)
	        Debug(&plugin,DebugMild,"Flood detected: %d handled events",s_evCount);
	    else if ((s_evCount % s_floodEvents) == 0)
	        Debug(&plugin,DebugWarn,"Severe flood detected: %d events",s_evCount);
	}
	SIPEvent* e = m_engine->getEvent();
	if (e)
	    s_evCount++;
	else
	    s_evCount = 0;
	// hack: use a loop so we can use break and continue
	for (; e; m_engine->processEvent(e),e = 0) {
	    SIPTransaction* t = e->getTransaction();
	    if (!t)
		continue;
	    plugin.lock();

	    if (t->isOutgoing() && t->getResponseCode() == 408) {
	    	if (t->getMethod() == YSTRING("BYE")) {
		    DDebug(&plugin,DebugInfo,"BYE for transaction %p has timed out",t);
		    m_timedOutByes++;
		    plugin.changed();
		}
		if (e->getState() == SIPTransaction::Cleared && e->getUserData()) {
		    DDebug(&plugin,DebugInfo,"Transaction %p has timed out",t);
		    m_timedOutTrs++;
		    plugin.changed();
		}
	    }

	    GenObject* obj = static_cast<GenObject*>(t->getUserData());
	    RefPointer<YateSIPConnection> conn = YOBJECT(YateSIPConnection,obj);
	    YateSIPLine* line = YOBJECT(YateSIPLine,obj);
	    YateSIPGenerate* gen = YOBJECT(YateSIPGenerate,obj);
	    plugin.unlock();
	    if (conn) {
		if (conn->process(e)) {
		    delete e;
		    break;
		}
		else
		    continue;
	    }
	    if (line) {
		if (line->process(e)) {
		    delete e;
		    break;
		}
		else
		    continue;
	    }
	    if (gen) {
		if (gen->process(e)) {
		    delete e;
		    break;
		}
		else
		    continue;
	    }
	    if ((e->getState() == SIPTransaction::Trying) &&
		!e->isOutgoing() && incoming(e,e->getTransaction())) {
		delete e;
		break;
	    }
	}
	if (s_evCount || s_engineHalt) {
	    if (Thread::check(false))
		break;
	}
	else
	    Thread::usleep(Thread::idleUsec());
    }
    plugin.epTerminated(this);
}

bool YateSIPEndPoint::incoming(SIPEvent* e, SIPTransaction* t)
{
    if (t->isInvite())
	invite(e,t);
    else if (t->getMethod() == YSTRING("BYE")) {
	YateSIPConnection* conn = plugin.findCall(t->getCallID(),true);
	if (conn) {
	    conn->doBye(t);
	    conn->deref();
	}
	else
	    t->setResponse(481);
    }
    else if (t->getMethod() == YSTRING("CANCEL")) {
	YateSIPConnection* conn = plugin.findCall(t->getCallID(),true);
	if (conn) {
	    conn->doCancel(t);
	    conn->deref();
	}
	else
	    t->setResponse(481);
    }
    else if (t->getMethod() == YSTRING("INFO")) {
	YateSIPConnection* conn = plugin.findCall(t->getCallID(),true);
	bool done = false;
	if (conn) {
	    done = conn->doInfo(t);
	    conn->deref();
	    if (!done)
		done = generic(e,t,415);
	}
	else if (t->getDialogTag()) {
	    done = true;
	    t->setResponse(481);
	}
	else
	    done = generic(e,t,415);
	if (!done)
	    t->setResponse(415);
    }
    else if (s_enable_register && t->getMethod() == YSTRING("REGISTER"))
	regReq(e,t);
    else if (s_enable_options && t->getMethod() == YSTRING("OPTIONS"))
	options(e,t);
    else if (s_enable_transfer && t->getMethod() == YSTRING("REFER")) {
	YateSIPConnection* conn = plugin.findCall(t->getCallID(),true);
	if (conn) {
	    conn->doRefer(t);
	    conn->deref();
	}
	else
	    t->setResponse(481);
    }
    else if (s_enable_message && t->getMethod() == YSTRING("MESSAGE")) {
	YateSIPConnection* conn = plugin.findCall(t->getCallID(),true);
	if (conn) {
	    conn->doMessage(t);
	    conn->deref();
	}
	else
	    return generic(e,t);
    }
    else
	return generic(e,t);
    return true;
}

void YateSIPEndPoint::invite(SIPEvent* e, SIPTransaction* t)
{
    if (e->getMessage()->getParam("To","tag")) {
	SIPDialog dlg(*e->getMessage());
	YateSIPConnection* conn = plugin.findDialog(dlg,true);
	if (conn) {
	    conn->reInvite(t);
	    conn->deref();
	}
	else {
	    Debug(&plugin,DebugWarn,"Got re-INVITE for missing dialog");
	    t->setResponse(481);
	}
	return;
    }

    if (!plugin.canAccept()) {
	Debug(&plugin,DebugWarn,"Refusing new SIP call, full or exiting");
	t->setResponse(480);
	return;
    }

    YateSIPConnection* conn = new YateSIPConnection(e,t);
    conn->initChan();
    conn->startRouter();

}

void YateSIPEndPoint::regReq(SIPEvent* e, SIPTransaction* t)
{
    if (Engine::exiting()) {
	Debug(&plugin,DebugWarn,"Dropping request, engine is exiting");
	t->setResponse(500, "Server Shutting Down");
	return;
    }
    if (s_reg_async) {
	YateSIPRegister* reg = new YateSIPRegister(this,e->getMessage(),t);
	if (reg->startup())
	    return;
	Debug(&plugin,DebugWarn,"Failed to start register thread");
	delete reg;
    }
    regRun(e->getMessage(),t);
}

void YateSIPEndPoint::regRun(const SIPMessage* message, SIPTransaction* t)
{
    const MimeHeaderLine* hl = message->getHeader("Contact");
    if (!hl) {
	t->setResponse(400);
	return;
    }

    String tmp(message->getHeader("Expires"));
    if (tmp.null())
	tmp = hl->getParam("expires");
    int expires = tmp.toInteger(-1);
    if (expires < 0)
	expires = s_expires_def;
    if (expires > s_expires_max)
	expires = s_expires_max;
    if (expires && (expires < s_expires_min) && (*hl != "*")) {
	tmp = s_expires_min;
	SIPMessage* r = new SIPMessage(t->initialMessage(),423);
	r->addHeader("Min-Expires",tmp);
	t->setResponse(r);
	r->deref();
	return;
    }

    URI addr(*hl);
    String num = addr.getUser();
    if (!num) {
	const MimeHeaderLine* to = message->getHeader("To");
	if (to) {
	    URI toUri(*to);
	    num = toUri.getUser();
	}
    }
    Message msg("user.register");
    msg.addParam("number",num,false);
    msg.addParam("sip_uri",t->getURI());
    msg.addParam("sip_callid",t->getCallID());
    tmp = expires;
    msg.setParam("expires",tmp);
    String user;
    int age = t->authUser(user,false,&msg);
    DDebug(&plugin,DebugAll,"User '%s' age %d",user.c_str(),age);
    if (((age < 0) || (age > 10)) && s_auth_register) {
	setAuthError(t,msg,age >= 0);
	return;
    }

    // TODO: track registrations, allow deregistering all
    if (*hl == "*") {
	t->setResponse(200);
	return;
    }

    if (user.null())
	user = num;
    msg.setParam(s_username,user);
    msg.setParam("driver","sip");
    String data(addr);
    String raddr;
    int rport = 0;
    message->getParty()->getAddr(raddr,rport,false);
    bool nat = false;
    if (addr.getProtocol().startsWith("sip")) {
	nat = isNatBetween(addr.getHost(),raddr);
	if (!nat && YOBJECT(YateUDPParty,message->getParty())) {
	    int port = addr.getPort();
	    if (!port)
		port = 5060;
	    nat = (rport != port) && msg.getBoolValue(YSTRING("nat_port_support"),true);
	}
    }
    bool natChanged = false;
    if (msg.getBoolValue(YSTRING("nat_support"),s_auto_nat && nat)) {
	String tmp;
	if (addr.getPort())
	    SocketAddr::appendTo(tmp,addr.getHost(),addr.getPort());
	else
	    tmp = addr.getHost();
	String r;
	SocketAddr::appendTo(r,raddr,rport);
	Debug(&plugin,DebugInfo,"Registration NAT detected: private '%s' public '%s'",
	    tmp.c_str(),r.c_str());
	msg.addParam("reg_nat_addr",tmp);
	int pos = data.find(tmp);
	if (pos >= 0) {
	    int len = tmp.length();
	    tmp.clear();
	    tmp << data.substr(0,pos) << r << data.substr(pos + len);
	    data = tmp;
	    natChanged = true;
	}
    }
    msg.setParam("data","sip/" + data);
    msg.setParam("ip_host",raddr);
    msg.setParam("ip_port",String(rport));
    msg.setParam("ip_transport",message->getParty()->getProtoName());

    bool dereg = false;
    if (!expires) {
	msg = "user.unregister";
	dereg = true;
    }
    else
	msg.setParam("sip_to",addr);
    hl = message->getHeader("User-Agent");
    if (hl)
	msg.setParam("device",*hl);
    // Add transport and also route if registering
    if (message->getParty()) {
	YateSIPTransport* trans = static_cast<YateSIPTransport*>(message->getParty()->getTransport());
	if (trans)
	    trans->fillMessage(msg,(0 != expires));
    }
    copySipHeaders(msg,*message,true,
	static_cast<const YateSIPEngine*>(t->getEngine())->foreignAuth(),s_initialHeaders);
    SIPMessage* r = 0;
    bool ok = Engine::dispatch(msg);
    t->setTransCount(msg.getIntValue(YSTRING("xsip_trans_count"),-1));
    // Always OK deregistration attempts
    if (ok || dereg) {
	if (dereg) {
	    r = new SIPMessage(t->initialMessage(),200);
	    Debug(&plugin,DebugNote,"Unregistered user '%s'",user.c_str());
	}
	else {
	    tmp = msg.getValue(YSTRING("expires"),tmp);
	    if (tmp.null())
		tmp = expires;
	    r = new SIPMessage(t->initialMessage(),200);
	    r->addHeader("Expires",tmp);
	    MimeHeaderLine* contact = new MimeHeaderLine("Contact","<" + addr + ">");
	    contact->setParam("expires",tmp);
	    r->addHeader(contact);
	    if (natChanged) {
		if (s_nat_refresh > 0)
		    r->addHeader("P-NAT-Refresh",String(s_nat_refresh));
		r->addHeader("X-Real-Contact",data);
	    }
	    if (t->initialMessage() && t->initialMessage()->getParty() &&
		t->initialMessage()->getParty()->isReliable()) {
		const String& ftValue = msg[YSTRING("xsip_flow-timer")];
		int flowTimer = ftValue.toInteger();
		if (flowTimer > 10 && flowTimer <= 120)
		    r->addHeader(new MimeHeaderLine("Flow-Timer",ftValue));
	    }
	    // Reset transport timeout
	    resetTransportIdle(r,tmp.toInteger());
	    TraceDebugObj(r,&plugin,DebugNote,"Registered user '%s' expires in %s s%s",
		user.c_str(),tmp.c_str(),natChanged ? " (NAT)" : "");
	}
    }
    else {
	int code = msg.getIntValue(YSTRING("code"),msg.getIntValue(YSTRING("reason"),dict_errors,404));
	if (code < 300 || code > 699)
	    code = 404;
	r = new SIPMessage(t->initialMessage(),code);
    }
    copySipHeaders(*r,msg);
    t->setResponse(r);
    TelEngine::destruct(r);
}

void YateSIPEndPoint::options(SIPEvent* e, SIPTransaction* t)
{
    if (!sdpAccept(e->getMessage(),true)) {
	t->setResponse(415);
	return;
    }
    switch (Engine::accept()) {
	case Engine::Congestion:
	    if (t->setResponse()) {
		SIPMessage* m = new SIPMessage(e->getMessage(),503);
		m->addHeader("Retry-After",String(s_congRetry));
		t->setResponse(m);
		TelEngine::destruct(m);
	    }
	    break;
	case Engine::Reject:
	    t->setResponse(503);
	    break;
	default:
	    t->setResponse(Engine::exiting() ? 503 : 200);
    }
}

bool YateSIPEndPoint::generic(SIPEvent* e, SIPTransaction* t, int defErr, bool async)
{
    String meth(t->getMethod());
    meth.toLower();
    bool autoAuth = false;
    bool isMsg = false;
    if (s_enable_message && meth == YSTRING("message")) {
	isMsg = true;
	autoAuth = s_auth_message;
	if (s_msg_async)
	    async = true;
    }
    if (!isMsg) {
	Lock mylock(s_globalMutex);
	const String* auth = s_cfg.getKey("methods",meth);
	if (!auth)
	    return false;
	autoAuth = auth->toBoolean(true);
	if (s_gen_async)
	    async = true;
    }
    if (async) {
	YateSIPGeneric* gen = new YateSIPGeneric(this,e->getMessage(),t,meth,defErr,autoAuth,isMsg);
	if (gen->startup())
	    return true;
	Debug(&plugin,DebugWarn,"Failed to start generic thread");
	delete gen;
    }
    return generic(e->getMessage(),t,meth,autoAuth,isMsg);
}

bool YateSIPEndPoint::generic(const SIPMessage* message, SIPTransaction* t, const String& meth, bool autoAuth, bool isMsg)
{
    if (!(message && t))
	return false;
    Message m(isMsg ? "call.route" : ("sip." + meth).c_str());
    if (isMsg) {
	m.addParam("module",plugin.name());
	m.addParam("route_type","msg");
    }
    YateSIPConnection* conn = 0;
    if (message->getParam("To","tag")) {
	SIPDialog dlg(*message);
	conn = plugin.findDialog(dlg,true);
	if (conn)
	    m.addParam("id",conn->id());
    }
    String host;
    int portNum = 0;
    message->getParty()->getAddr(host,portNum,false);
    URI uri(message->uri);
    String user;
    YateSIPLine* line = plugin.findLine(host,portNum,uri.getUser(),message->getParty());
    m.addParam("called",uri.getUser(),false);
    uri = message->getHeader("From");
    uri.parse();
    m.addParam("caller",uri.getUser(),false);
    m.addParam("callername",uri.getDescription(),false);
    if (line) {
	// message comes from line we have registered to
	if (user.null())
	    user = line->getUserName();
	m.addParam("domain",line->domain());
	m.addParam("in_line",*line);
    }
    else if (autoAuth) {
	int age = t->authUser(user,false,&m);
	DDebug(&plugin,DebugAll,"User '%s' age %d",user.c_str(),age);
	if ((age < 0) || (age > 10)) {
	    setAuthError(t,m,age >= 0);
	    TelEngine::destruct(conn);
	    return true;
	}
    }
    // Add transport info
    YateSIPTransport* trans = YOBJECT(YateSIPTransport,message->getParty());
    if (trans)
	trans->fillMessage(m);
    if (conn) {
	m.userData(conn);
	conn->complete(m);
	TelEngine::destruct(conn);
    }
    m.addParam(s_username,user,false);

    String tmp(message->getHeaderValue("Max-Forwards"));
    int maxf = tmp.toInteger(s_maxForwards);
    if (maxf > s_maxForwards)
	maxf = s_maxForwards;
    tmp = maxf-1;
    m.addParam("antiloop",tmp);

    m.addParam("address",SocketAddr::appendTo(host,portNum));
    m.addParam("ip_host",host);
    m.addParam("ip_port",String(portNum));
    m.addParam("ip_transport",message->getParty()->getProtoName());
    m.addParam("sip_uri",t->getURI());
    m.addParam("sip_callid",t->getCallID());
    // establish the dialog here so user code will have the dialog tag handy
    t->setDialogTag();
    m.addParam("xsip_dlgtag",t->getDialogTag());
    copySipHeaders(m,*message,false,
	static_cast<const YateSIPEngine*>(t->getEngine())->foreignAuth(),s_initialHeaders);

    doDecodeIsupBody(&plugin,m,message->body);
    copySipBody(m,*message);
    // attempt to find out if the handler modified the body
    ObjList bodies;
    const String* body = m.getParam(YSTRING("xsip_body"));
    if (body) {
	bodies.append(new BodyTrace(*body));
	for (int i = 1; ; i++) {
	    body = m.getParam("xsip_body." + String(i));
	    if (!body)
		break;
	    bodies.append(new BodyTrace(*body));
	}
    }

    int code = 0;
    bool ok = Engine::dispatch(m);
    t->setTransCount(m.getIntValue(YSTRING("xsip_trans_count"),-1));
    while (isMsg && ok) {
	ok = m.retValue() && m.retValue() != YSTRING("-") &&
	    m.retValue() != YSTRING("error");
	if (!ok)
	    break;
	ok = (m.getIntValue(YSTRING("antiloop"),1) > 0);
	if (!ok) {
	    code = 483;
	    break;
	}
	m = "msg.execute";
	m.setParam("callto",m.retValue());
	m.clearParam(YSTRING("error"));
	m.clearParam(YSTRING("reason"));
	m.retValue().clear();
	ok = Engine::dispatch(m);
	break;
    }
    if (ok) {
	const String* ret = m.getParam(YSTRING("code"));
	if (!ret)
	    ret = &m.retValue();
	code = ret->toInteger(m.getIntValue(YSTRING("reason"),dict_errors,200));
    }
    else {
	int reason = m.getIntValue(YSTRING("reason"),dict_errors,code);
	code = m.getIntValue(YSTRING("code"),reason);
	// msg.execute: other modules migth not set code
	// Check error if code is 0 and reason was 0
	// (reason!=0 and code==0 means code was explicitly set to 0 in the message)
	if (isMsg && !code && !reason)
	    code = m.getIntValue(YSTRING("error"));
	if (code < 300)
	    code = 0;
    }
    if ((code >= 200) && (code < 700)) {
	SIPMessage* resp = new SIPMessage(message,code);
	copySipHeaders(*resp,m);
	copySipBody(*resp,m,bodies.skipNull());
	t->setResponse(resp);
	resp->deref();
	return true;
    }
    return false;
}


// Build the transfer thread
// transferorID: Channel id of the sip connection that received the REFER request
// transferredID: Channel id of the transferor's peer
// transferredDrv: Channel driver of the transferor's peer
// msg: already populated 'call.route'
// sipNotify: already populated SIPMessage("NOTIFY")
YateSIPRefer::YateSIPRefer(const String& transferorID, const String& transferredID,
    Driver* transferredDrv, Message* msg, SIPMessage* sipNotify,
    SIPTransaction* transaction)
    : Thread("YSIP Transfer"),
    m_transferorID(transferorID), m_transferredID(transferredID),
    m_transferredDrv(transferredDrv), m_msg(msg), m_sipNotify(sipNotify),
    m_notifyCode(200), m_transaction(0), m_rspCode(500)
{
    if (transaction && transaction->ref())
	m_transaction = transaction;
}

void YateSIPRefer::run()
{
    String* attended = m_msg->getParam(YSTRING("transfer_callid"));
#ifdef DEBUG
    if (attended)
	Debug(&plugin,DebugAll,"%s(%s) running callid=%s fromtag=%s totag=%s [%p]",
	    name(),m_transferorID.c_str(),attended->c_str(),
	    m_msg->getValue(YSTRING("transfer_fromtag")),
	    m_msg->getValue(YSTRING("transfer_totag")),this);
    else
	Debug(&plugin,DebugAll,"%s(%s) running [%p]",name(),m_transferorID.c_str(),this);
#endif

    // Use a while() to break to the end
    while (m_transferredDrv && m_msg) {
	// Attended transfer: check if the requested channel is owned by our plugin
	// NOTE: Remove the whole 'if' when a routing module will be able to route
	//  attended transfer requests
	if (attended) {
	    String* from = m_msg->getParam(YSTRING("transfer_fromtag"));
	    String* to = m_msg->getParam(YSTRING("transfer_totag"));
	    if (null(from) || null(to)) {
		m_rspCode = m_notifyCode = 487;     // Request Terminated
		break;
	    }
	    YateSIPConnection* conn = plugin.findDialog(*attended,*from,*to,true);
	    if (conn) {
		m_transferredDrv->lock();
		RefPointer<Channel> chan = m_transferredDrv->find(m_transferredID);
		m_transferredDrv->unlock();
		if (chan && conn->getPeer() &&
		    chan->connect(conn->getPeer(),m_msg->getValue(YSTRING("reason")))) {
		    m_rspCode = 202;
		    m_notifyCode = 200;
		}
		else
		    m_rspCode = m_notifyCode = 487;     // Request Terminated
		TelEngine::destruct(conn);
		break;
	    }
	    // Not ours
	    m_msg->clearParam("called");
	    YateSIPConnection::addCallId(*m_msg,*attended,*from,*to);
	}

	// Route the call
	bool ok = Engine::dispatch(m_msg);
	m_transferredDrv->lock();
	RefPointer<Channel> chan = m_transferredDrv->find(m_transferredID);
	m_transferredDrv->unlock();
	if (!(ok && chan)) {
#ifdef DEBUG
	    if (ok)
		Debug(&plugin,DebugAll,"%s(%s). Connection vanished while routing! [%p]",
		    name(),m_transferorID.c_str(),this);
	    else
		Debug(&plugin,DebugAll,"%s(%s). 'call.route' failed [%p]",
		    name(),m_transferorID.c_str(),this);
#endif
	    m_rspCode = m_notifyCode = (ok ? 487 : 481);
	    break;
	}
	m_msg->userData(chan);
	if ((m_msg->retValue() == "-") || (m_msg->retValue() == YSTRING("error")))
	    m_rspCode = m_notifyCode = 603; // Decline
	else if (m_msg->getIntValue(YSTRING("antiloop"),1) <= 0)
	    m_rspCode = m_notifyCode = 482; // Loop Detected
	else {
	    DDebug(&plugin,DebugAll,"%s(%s). Call succesfully routed [%p]",
		name(),m_transferorID.c_str(),this);
	    *m_msg = "call.execute";
	    m_msg->setParam("callto",m_msg->retValue());
	    m_msg->clearParam(YSTRING("error"));
	    m_msg->retValue().clear();
	    if (Engine::dispatch(m_msg)) {
		DDebug(&plugin,DebugAll,"%s(%s). 'call.execute' succeeded [%p]",
		    name(),m_transferorID.c_str(),this);
		m_rspCode = 202;
		m_notifyCode = 200;
	    }
	    else {
		DDebug(&plugin,DebugAll,"%s(%s). 'call.execute' failed [%p]",
		    name(),m_transferorID.c_str(),this);
		m_rspCode = m_notifyCode = 603; // Decline
	    }
	}
	break;
    }
    release();
}

// Respond the transaction and deref() it
void YateSIPRefer::setTrResponse(int code)
{
    if (!m_transaction)
	return;
    SIPTransaction* t = m_transaction;
    m_transaction = 0;
    m_rspCode = code;
    t->setResponse(m_rspCode);
    TelEngine::destruct(t);
}

// Set transaction response. Send the notification message. Notify the
// connection and release other objects
void YateSIPRefer::release(bool fromCleanup)
{
    setTrResponse(m_rspCode);
    TelEngine::destruct(m_msg);
    // Set NOTIFY response and send it (only if the transaction was accepted)
    if (m_sipNotify) {
	if (m_rspCode < 300 && plugin.ep() && plugin.ep()->engine()) {
	    String s;
	    s << "SIP/2.0 " << m_notifyCode << " " << lookup(m_notifyCode,SIPResponses) << "\r\n";
	    m_sipNotify->setBody(new MimeStringBody("message/sipfrag;version=2.0",s));
	    plugin.ep()->engine()->addMessage(m_sipNotify);
	}
	TelEngine::destruct(m_sipNotify);
	// If we still have a NOTIFY message in cleanup() the thread
	//  was cancelled in the hard way
	if (fromCleanup)
	    Debug(&plugin,DebugWarn,"YateSIPRefer(%s) thread terminated abnormally [%p]",
		m_transferorID.c_str(),this);
    }
    // Notify transferor on termination
    if (m_transferorID) {
	plugin.lock();
	YateSIPConnection* conn = static_cast<YateSIPConnection*>(plugin.find(m_transferorID));
	if (conn)
	    conn->referTerminated();
	plugin.unlock();
	m_transferorID = "";
    }
}


// Incoming call constructor - just before starting the routing thread
YateSIPConnection::YateSIPConnection(SIPEvent* ev, SIPTransaction* tr)
    : Channel(plugin,0,false),
      SDPSession(&plugin.parser()),
      YateSIPPartyHolder(this,driver(),tr->traceId()),
      m_tr(tr), m_tr2(0), m_hungup(false), m_byebye(true), m_cancel(false),
      m_state(Incoming), m_port(0), m_route(0), m_routes(0),
      m_authBye(true), m_autoChangeParty(tr->getEngine()->autoChangeParty()),
      m_checkAllowInfo(s_checkAllowInfo), m_missingAllowInfoDefVal(s_missingAllowInfoDefVal),
      m_honorDtmfDetect(s_honorDtmfDetect),
      m_referring(false), m_reInviting(ReinviteNone), m_lastRseq(0),
      m_revert(""), m_silent(false), m_stopOCall(false), m_traceId(tr->traceId())
{
    m_ipv6 = s_ipv6;
    setSdpDebug(this,this,m_traceId);
    TraceDebug(m_traceId,this,DebugAll,"YateSIPConnection::YateSIPConnection(%p,%p) [%p]",ev,tr,this);
    setReason();
    m_tr->ref();
    m_routes = m_tr->initialMessage()->getRoutes();
    m_dialog = *m_tr->initialMessage();
    m_tr->initialMessage()->getParty()->getAddr(m_host,m_port,false);
    SocketAddr::appendTo(m_address,m_host,m_port);
    filterDebug(m_address);
    m_uri = m_tr->initialMessage()->getHeader("From");
    m_uri.parse();
    m_tr->setUserData(this);
    // Set channel SIP party
    setParty(m_tr->initialMessage()->getParty());
    updateRtpNatAddress();
    // Get dtmf methods
    s_globalMutex.lock();
    m_dtmfMethods = s_dtmfMethods;
    s_globalMutex.unlock();

    URI uri(m_tr->getURI());
    YateSIPLine* line = plugin.findLine(m_host,m_port,uri.getUser(),m_party);
    Message *m = message("call.preroute");
    decodeIsupBody(*m,m_tr->initialMessage()->body);
    copySipBody(*m,m_tr->initialMessage()->body);
    if (m_traceId)
	m->setParam("trace_id",m_traceId);
    m->addParam("caller",m_uri.getUser());
    m->addParam("called",uri.getUser());
    if (m_uri.getDescription())
	m->addParam("callername",m_uri.getDescription());
    const MimeHeaderLine* hl = m_tr->initialMessage()->getHeader("Call-Info");
    if (hl) {
	const NamedString* type = hl->getParam("purpose");
	if (!type || *type == YSTRING("info"))
	    m->addParam("caller_info_uri",*hl);
	else if (*type == YSTRING("icon"))
	    m->addParam("caller_icon_uri",*hl);
	else if (*type == YSTRING("card"))
	    m->addParam("caller_card_uri",*hl);
    }

    if (s_privacy)
	copyPrivacy(*m,*ev->getMessage());

    String tmp(ev->getMessage()->getHeaderValue("Max-Forwards"));
    int maxf = tmp.toInteger(s_maxForwards);
    if (maxf > s_maxForwards)
	maxf = s_maxForwards;
    tmp = maxf-1;
    m->addParam("antiloop",tmp);
    m->addParam("ip_host",m_host);
    m->addParam("ip_port",String(m_port));
    m->addParam("ip_transport",m_tr->initialMessage()->getParty()->getProtoName());
    YateSIPTransport* trans = YOBJECT(YateSIPTransport,m_tr->initialMessage()->getParty());
    if (trans)
	trans->fillMessage(*m);
    m->addParam("sip_uri",uri);
    m->addParam("sip_from",m_uri);
    m->addParam("sip_to",ev->getMessage()->getHeaderValue("To"));
    m->addParam("sip_callid",callid(),false);
    m->addParam("device",ev->getMessage()->getHeaderValue("User-Agent"));
    copySipHeaders(*m,*ev->getMessage(),true,
	static_cast<const YateSIPEngine*>(tr->getEngine())->foreignAuth(),s_initialHeaders);

    const char* reason = 0;
    hl = m_tr->initialMessage()->getHeader("Referred-By");
    if (hl)
	reason = "transfer";
    else {
	hl = m_tr->initialMessage()->getHeader("Diversion");
	if (hl) {
	    reason = "divert";
	    const String* par = hl->getParam("reason");
	    if (par) {
		tmp = par->c_str();
		MimeHeaderLine::delQuotes(tmp);
		if (tmp.trimBlanks())
		    m->addParam("divert_reason",tmp);
	    }
	    par = hl->getParam("privacy");
	    if (par) {
		tmp = par->c_str();
		MimeHeaderLine::delQuotes(tmp);
		if (tmp.trimBlanks())
		    m->addParam("divert_privacy",tmp);
	    }
	    par = hl->getParam("screen");
	    if (par) {
		tmp = par->c_str();
		MimeHeaderLine::delQuotes(tmp);
		if (tmp.trimBlanks())
		    m->addParam("divert_screen",tmp);
	    }
	    par = hl->getParam("counter");
	    if (par) {
		tmp = par->c_str();
		MimeHeaderLine::delQuotes(tmp);
		if (tmp.trimBlanks())
		    m->addParam("divert_counter",tmp);
	    }
	}
    }

    if (hl) {
	URI div(*hl);
	m->addParam("diverter",div.getUser());
	if (div.getDescription())
	    m->addParam("divertername",div.getDescription());
	m->addParam("diverteruri",div);
    }
    if (line) {
	// call comes from line we have registered to - trust it...
	m_user = line->getUserName();
	m_externalAddr = line->getLocalAddr();
	m_line = *line;
	m_domain = line->domain();
	m->addParam(s_username,m_user);
	m->addParam("domain",m_domain);
	m->addParam("in_line",m_line);
    }
    else {
	String user;
	int age = tr->authUser(user,false,m);
	DDebug(this,DebugAll,"User '%s' age %d",user.c_str(),age);
	if (age >= 0) {
	    if (age < 10) {
		m_user = user;
		m->setParam(s_username,m_user);
	    }
	    else
		m->setParam("expired_user",user);
	    m->setParam("xsip_nonce_age",String(age));
	}
	m_domain = m->getValue(YSTRING("domain"));
	setChanParams(*m,true);
    }

    setRtpLocalAddr(m_rtpLocalAddr);
    MimeSdpBody* sdp = getSdpBody(ev->getMessage()->body);
    if (sdp) {
	setMedia(plugin.parser().parse(sdp,m_rtpAddr,m_rtpMedia));
	if (m_rtpMedia) {
	    m_rtpForward = true;
	    // guess if the call comes from behind a NAT
	    bool nat = isNatBetween(m_rtpAddr,m_host);
	    if (m->getBoolValue(YSTRING("nat_support"),s_auto_nat && nat)) {
		TraceDebug(m_traceId,this,DebugInfo,"RTP NAT detected: private '%s' public '%s'",
		    m_rtpAddr.c_str(),m_host.c_str());
		m->addParam("rtp_nat_addr",m_rtpAddr);
		m_rtpAddr = m_host;
	    }
	    m->addParam("rtp_addr",m_rtpAddr);
	    putMedia(*m);
	}
	if (m_sdpForward) {
	    const DataBlock& raw = sdp->getBody();
	    String tmp((const char*)raw.data(),raw.length());
	    m->addParam("sdp_raw",tmp);
	    m_rtpForward = true;
	}
	if (m_rtpForward)
	    m->addParam("rtp_forward","possible");
    }
    else if (ev->getMessage()->body)
	m->addParam("media",String::boolText(false));
    else if (sdpAccept(ev->getMessage(),s_sdp_implicit)) {
	tr->autoAck(false);
	m_rtpForward = true;
	m->addParam("rtp_forward","missing");
    }
    DDebug(this,DebugAll,"RTP addr '%s' [%p]",m_rtpAddr.c_str(),this);
    if (reason)
	m->addParam("reason",reason);
    if (line)
	line->setInboundParams(*m);
    m_route = m;
    Message* s = message("chan.startup");
    s->addParam("caller",m_uri.getUser());
    s->addParam("called",uri.getUser());
    if (m_user)
	s->addParam(s_username,m_user);
    s->copyParam(*m,YSTRING("connection_id"));
    s->copyParam(*m,YSTRING("trace_id"));
    Engine::enqueue(s);
}

// Outgoing call constructor - in call.execute handler
YateSIPConnection::YateSIPConnection(Message& msg, const String& uri, const char* target)
    : Channel(plugin,0,true),
      SDPSession(&plugin.parser()),
      YateSIPPartyHolder(this,driver(),msg.getValue(YSTRING("trace_id"))),
      m_tr(0), m_tr2(0), m_hungup(false), m_byebye(true), m_cancel(true),
      m_state(Outgoing), m_port(0), m_route(0), m_routes(0),
      m_authBye(false), m_autoChangeParty(true),
      m_checkAllowInfo(s_checkAllowInfo), m_missingAllowInfoDefVal(s_missingAllowInfoDefVal),
      m_honorDtmfDetect(s_honorDtmfDetect),
      m_referring(false), m_reInviting(ReinviteNone), m_lastRseq(0),
      m_revert(""), m_silent(false), m_stopOCall(msg.getBoolValue(YSTRING("stop_call"))),
      m_traceId(msg.getValue(YSTRING("trace_id")))
{
    TraceDebug(m_traceId,this,DebugAll,"YateSIPConnection::YateSIPConnection(%p,'%s') [%p]",
	&msg,uri.c_str(),this);
    setChanParams(msg);
    m_autoChangeParty = msg.getBoolValue(YSTRING("oautochangeparty"),
	plugin.ep()->engine()->autoChangeParty());
    m_line = msg.getValue(YSTRING("line"));
    YateSIPLine* line = 0;
    if (m_line) {
	line = plugin.findLine(m_line);
	if (line)
	    line->setOutboundParams(msg);
    }
    m_ipv6 = msg.getBoolValue(YSTRING("ipv6_support"),s_ipv6);
    setSdpDebug(this,this,m_traceId);
    setFormatsExtra(msg,true);
    m_targetid = target;
    setReason();
    m_checkAllowInfo = msg.getBoolValue(YSTRING("ocheck_allow_info"),m_checkAllowInfo);
    m_missingAllowInfoDefVal = msg.getBoolValue(YSTRING("omissing_allow_info"),m_missingAllowInfoDefVal);
    String* meths = msg.getParam("odtmfmethods");
    s_globalMutex.lock();
    if (meths)
	m_dtmfMethods.set(*meths,&s_dtmfMethods);
    else {
	m_dtmfMethods = s_dtmfMethods;
	m_dtmfMethods.getDeprecatedDtmfMethod(msg,"dtmfinfo",DtmfMethods::Info,&s_warnDtmfInfoCallExecute);
	m_dtmfMethods.getDeprecatedDtmfMethod(msg,"dtmfinband",DtmfMethods::Inband,&s_warnDtmfInbandCallExecute);
    }
    s_globalMutex.unlock();
    m_honorDtmfDetect = msg.getBoolValue(YSTRING("ohonor_dtmf_detect"),m_honorDtmfDetect);
    m_secure = msg.getBoolValue(YSTRING("secure"),plugin.parser().secure());
    setRfc2833(msg.getParam(YSTRING("rfc2833")));
    m_rtpForward = msg.getBoolValue(YSTRING("rtp_forward"));
    m_sdpForward = msg.getBoolValue(YSTRING("forward_sdp"),m_sdpForward);
    m_user = msg.getValue(YSTRING("user"));
    String tmp;
    bool genSips = sips(msg.getBoolValue(YSTRING("sips"),line && line->sips()));
    if (line) {
	if (uri.find('@') < 0 && !uri.startsWith("tel:")) {
	    if (!(uri.startsWith("sip:") || uri.startsWith("sips:")))
		tmp = genSips ? "sips:" : "sip:";
	    tmp << uri << "@";
	    SocketAddr::appendAddr(tmp,line->domain());
	}
	m_externalAddr = line->getLocalAddr();
    }
    if (tmp.null()) {
	if (!(uri.startsWith("tel:") || uri.startsWith("sip:") || uri.startsWith("sips:"))) {
	    int sep = uri.find(':');
	    if ((sep < 0) || ((sep > 0) && (uri.substr(sep+1).toInteger(-1) > 0)))
		tmp = genSips ? "sips:" : "sip:";
	}
	tmp << uri;
    }
    m_uri = tmp;
    m_uri.parse();
    sips(m_uri.getProtocol() == YSTRING("sips"));
    if (!setParty(msg,false,"o",m_uri.getHost(),m_uri.getPort(),true) && line) {
	SIPParty* party = line->party();
	setParty(party);
	TelEngine::destruct(party);
    }
    // No party for SIPS and we don't have transport related params
    // Force party creation: this will force used protocol to TLS
    if (!m_party && sips() && !haveTransParams(msg,"o"))
	setParty(msg,true,"o",m_uri.getHost(),m_uri.getPort(),true);
    SIPMessage* m = new SIPMessage("INVITE",m_uri);
    m->dontSend(m_stopOCall);
    m->msgTraceId = m_traceId;
    setSipParty(m,line,true,msg.getValue("host"),msg.getIntValue("port"));
    if (!m->getParty()) {
	String tmp;
	if (m_partyInvalidRemote)
	    tmp << ": invalid remote addr '" << m_partyInvalidRemote << "'";
	TraceDebug(m_traceId,this,DebugWarn,"Could not create party for '%s'%s [%p]",m_uri.c_str(),tmp.safe(),this);
	TelEngine::destruct(m);
	setReason("Failed to create party",500);
	msg.setParam("reason",m_reason);
	msg.setParam("error","notransport");
	return;
    }
    setParty(m->getParty());
    String connId;
    YateSIPTransport* trans = YOBJECT(YateSIPTransport,m->getParty());
    if (trans)
	connId = trans->toString();
    updateRtpNatAddress(&msg);
    int maxf = msg.getIntValue(YSTRING("antiloop"),s_maxForwards);
    m->addHeader("Max-Forwards",String(maxf));
    copySipHeaders(*m,msg);
    m_domain = msg.getValue(YSTRING("domain"));
    const String* callerId = msg.getParam(YSTRING("caller"));
    String caller;
    if (callerId)
	caller = *callerId;
    else if (line) {
	caller = line->getUserName();
	callerId = &caller;
	m_domain = line->domain(m_domain);
    }
    String display = msg.getValue(YSTRING("callername"),(line ? line->getFullName().c_str() : (const char*)0));
    m_dialog.setCSeq(plugin.ep()->engine()->getSequence()->getNextCSeq() - 1);
    m->setSequence(m_dialog.getSequence());
    m->complete(plugin.ep()->engine(),
	callerId ? (callerId->null() ? "anonymous" : callerId->c_str()) : (const char*)0,
	m_domain,
	0,
	msg.getIntValue(YSTRING("xsip_flags"),-1));
    if (display) {
	MimeHeaderLine* hl = const_cast<MimeHeaderLine*>(m->getHeader("From"));
	if (hl) {
	    MimeHeaderLine::addQuotes(display);
	    *hl = display + " " + *hl;
	}
    }
    if (msg.getParam(YSTRING("calledname"))) {
	display = msg.getValue(YSTRING("calledname"));
	MimeHeaderLine* hl = const_cast<MimeHeaderLine*>(m->getHeader("To"));
	if (hl) {
	    MimeHeaderLine::addQuotes(display);
	    *hl = display + " " + *hl;
	}
    }
    if (plugin.ep()->engine()->prack())
	m->addHeader("Supported","100rel");
    m->getParty()->getAddr(m_host,m_port,false);
    SocketAddr::appendTo(m_address,m_host,m_port);
    filterDebug(m_address);
    m_dialog = *m;
    m_dialog.remoteCSeq = msg.getIntValue("remote_cseq",-1);
    if (s_privacy)
	copyPrivacy(*m,msg);

    // Check if this is a transferred call
    String* diverter = msg.getParam(YSTRING("diverter"));
    if (!null(diverter)) {
	const MimeHeaderLine* from = m->getHeader("From");
	if (from) {
	    URI fr(*from);
	    URI d(fr.getProtocol(),*diverter,fr.getHost(),fr.getPort(),
		msg.getValue(YSTRING("divertername"),""));
	    String* reason = msg.getParam(YSTRING("divert_reason"));
	    String* privacy = msg.getParam(YSTRING("divert_privacy"));
	    String* screen = msg.getParam(YSTRING("divert_screen"));
	    int counter = msg.getIntValue(YSTRING("divert_counter"));
	    bool divert = !(TelEngine::null(reason) && TelEngine::null(privacy) && TelEngine::null(screen));
	    divert = msg.getBoolValue(YSTRING("diversion"),divert);
	    MimeHeaderLine* hl = new MimeHeaderLine(divert ? "Diversion" : "Referred-By",d);
	    if (divert) {
		if (!TelEngine::null(reason))
		    hl->setParam("reason",MimeHeaderLine::quote(*reason));
		if (!TelEngine::null(privacy))
		    hl->setParam("privacy",MimeHeaderLine::quote(*privacy));
		if (!TelEngine::null(screen))
		    hl->setParam("screen",MimeHeaderLine::quote(*screen));
		if ((counter > 0) && (counter < 100))
		    hl->setParam("counter",String(counter));
	    }
	    m->addHeader(hl);
	}
    }

    // add some Call-Info headers
    const char* info = msg.getValue(YSTRING("caller_info_uri"));
    if (info) {
	MimeHeaderLine* hl = new MimeHeaderLine("Call-Info",info);
	hl->setParam("purpose","info");
	m->addHeader(hl);
    }
    info = msg.getValue(YSTRING("caller_icon_uri"));
    if (info) {
	MimeHeaderLine* hl = new MimeHeaderLine("Call-Info",info);
	hl->setParam("purpose","icon");
	m->addHeader(hl);
    }
    info = msg.getValue(YSTRING("caller_card_uri"));
    if (info) {
	MimeHeaderLine* hl = new MimeHeaderLine("Call-Info",info);
	hl->setParam("purpose","card");
	m->addHeader(hl);
    }

    setRtpLocalAddr(m_rtpLocalAddr,&msg);
    bool fwd = m_rtpForward;
    MimeSdpBody* sdp = createPasstroughSDP(msg);
    if (msg.getBoolValue(YSTRING("sdp_ack"))) {
	if (sdp) {
	    TelEngine::destruct(sdp);
	    m_rtpLocalAddr = msg.getValue("rtp_addr");
	}
	m->addHeader(new MimeHeaderLine("Accept","application/sdp"));
    }
    else if (!sdp)
	sdp = createRtpSDP(m_host,msg);
    m->setBody(buildSIPBody(msg,sdp));
    int tries = msg.getIntValue(YSTRING("xsip_trans_count"),-1);
    m_tr = plugin.ep()->engine()->addMessage(m,&m_autoChangeParty);
    if (m_tr) {
	m_tr->ref();
	m_tr->setUserData(this);
	m_tr->setTransCount(tries);
	if (!sdp && msg.getBoolValue(YSTRING("sdp_ack"),fwd)) {
	    m_tr->autoAck(false);
	    msg.setParam("sdp_ack",String::boolText(true));
	    if (fwd)
		msg.setParam("rtp_forward","accepted");
	}
    }
    m->deref();
    setMaxcall(msg);
    setMaxPDD(msg);
    Message* s = message("chan.startup",msg);
    s->setParam("caller",caller);
    s->copyParams(msg,"callername,called,billid,callto,username");
    s->setParam("calledfull",m_uri.getUser());
    if (callid())
	s->setParam("sip_callid",callid());
    s->addParam("connection_id",connId);
    Engine::enqueue(s);
}

YateSIPConnection::~YateSIPConnection()
{
    TraceDebug(m_traceId,this,DebugAll,"YateSIPConnection::~YateSIPConnection() [%p]",this);
}

void YateSIPConnection::destroyed()
{
    DDebug(this,DebugAll,"YateSIPConnection::destroyed() [%p]",this);
    hangup();
    clearTransaction();
    TelEngine::destruct(m_route);
    TelEngine::destruct(m_routes);
    Channel::destroyed();
}

void YateSIPConnection::startRouter()
{
    Message* m = m_route;
    m_route = 0;
    Channel::startRouter(m);
}

void YateSIPConnection::clearTransaction()
{
    if (!(m_tr || m_tr2))
	return;
    Lock lock(driver());
    if (m_tr) {
	m_tr->setUserData(0);
	if (m_silent)
	    m_tr->setSilent();
	else if (m_tr->setResponse()) {
	    SIPMessage* m = new SIPMessage(m_tr->initialMessage(),m_reasonCode,
		m_reason.safe("Request Terminated"));
	    paramMutex().lock();
	    copySipHeaders(*m,parameters(),0);
	    paramMutex().unlock();
	    m->setBody(buildSIPBody());
	    m_tr->setResponse(m);
	    TelEngine::destruct(m);
	    m_byebye = false;
	}
	else if (m_hungup && m_tr->isIncoming() && m_dialog.localTag.null())
	    m_dialog.localTag = m_tr->getDialogTag();
	m_tr->deref();
	m_tr = 0;
    }
    // cancel any pending reINVITE
    if (m_tr2) {
	m_tr2->setUserData(0);
	if (m_silent)
	    m_tr2->setSilent();
	else {
	    m_tr2->autoAck(true);
	    if (m_tr2->isIncoming())
		m_tr2->setResponse(487);
	}
	m_tr2->deref();
	m_tr2 = 0;
    }
}

void YateSIPConnection::detachTransaction2()
{
    Lock lock(driver());
    if (m_tr2) {
	DDebug(this,DebugAll,"Detaching secondary transaction %p [%p]",m_tr2,this);
	m_tr2->setUserData(0);
	m_tr2->deref();
	m_tr2 = 0;
	if (m_reInviting != ReinvitePending)
	    m_reInviting = ReinviteNone;
    }
    startPendingUpdate();
}

void YateSIPConnection::hangup()
{
    if (m_hungup)
	return;
    Lock mylock(driver());
    if (m_hungup)
	return;
    m_hungup = true;
    const char* error = lookup(m_reasonCode,dict_errors);
    TraceDebug(m_traceId,this,DebugAll,"YateSIPConnection::hangup() state=%d trans=%p error='%s' code=%d reason='%s' [%p]",
	m_state,m_tr,error,m_reasonCode,m_reason.c_str(),this);
    setMedia(0);
    String res = m_reason;
    mylock.drop();
    Message* m = message("chan.hangup");
    if (res)
	m->setParam("reason",res);
    Engine::enqueue(m);
    if (!error)
	error = res.c_str();
    bool sendBye = !m_silent;
    switch (m_state) {
	case Cleared:
	    clearTransaction();
	    disconnect(error,parameters());
	    return;
	case Incoming:
	    if (m_tr) {
		clearTransaction();
		disconnect(error,parameters());
		return;
	    }
	    break;
	case Outgoing:
	case Ringing:
	    if (!m_silent && !m_stopOCall && m_cancel && m_tr) {
		SIPMessage* m = new SIPMessage("CANCEL",m_uri);
		setSipParty(m,plugin.findLine(m_line),true,m_host,m_port);
		if (!m->getParty())
		    TraceDebug(m_traceId,this,DebugWarn,"Could not create party for '%s' [%p]",
			SocketAddr::appendTo(m_host,m_port).c_str(),this);
		else {
		    const SIPMessage* i = m_tr->initialMessage();
		    m->copyHeader(i,"Via");
		    m->copyHeader(i,"From");
		    m->copyHeader(i,"To");
		    m->copyHeader(i,"Call-ID");
		    m->setCSeq(i->getCSeq());
		    if (res == YSTRING("pickup")) {
			MimeHeaderLine* hl = new MimeHeaderLine("Reason","SIP");
			hl->setParam("cause","200");
			hl->setParam("text","\"Call completed elsewhere\"");
			m->addHeader(hl);
		    }
		    m->setBody(buildSIPBody());
		    if (plugin.ep()->engine()->addMessage(m,&m_autoChangeParty) && !s_preventive_bye)
			sendBye = false;
		}
		m->deref();
	    }
	    break;
    }
    clearTransaction();
    m_state = Cleared;

    if (sendBye && m_byebye && m_dialog.localTag && m_dialog.remoteTag) {
	SIPMessage* m = createDlgMsg("BYE");
	if (m) {
	    if (res) {
		// FIXME: add SIP and Q.850 cause codes, set the proper reason
		MimeHeaderLine* hl = new MimeHeaderLine("Reason","SIP");
		if ((m_reasonCode >= 300) && (m_reasonCode <= 699) && (m_reasonCode != 487))
		    hl->setParam("cause",String(m_reasonCode));
		hl->setParam("text",MimeHeaderLine::quote(res));
		m->addHeader(hl);
	    }
	    paramMutex().lock();
	    const char* stats = parameters().getValue(YSTRING("rtp_stats"));
	    if (stats)
		m->addHeader("P-RTP-Stat",stats);
	    copySipHeaders(*m,parameters(),0);
	    copySipBody(*m,parameters());
	    paramMutex().unlock();
	    m->setBody(buildSIPBody());
	    plugin.ep()->engine()->addMessage(m,&m_autoChangeParty);
	    m->deref();
	}
    }
    m_byebye = false;
    disconnect(error,parameters());
}

// Creates a new message in an existing dialog
SIPMessage* YateSIPConnection::createDlgMsg(const char* method, const char* uri)
{
    if (!uri)
	uri = m_uri;
    SIPMessage* m = new SIPMessage(method,uri);
    m->addRoutes(m_routes);
    setSipParty(m,plugin.findLine(m_line),true,m_host,m_port);
    if (!m->getParty()) {
	TraceDebug(m_traceId,this,DebugWarn,"Could not create party for '%s' [%p]",
	    SocketAddr::appendTo(m_host,m_port).c_str(),this);
	m->destruct();
	return 0;
    }
    if (m_dialog.getLastCSeq() < 0)
	m_dialog.setCSeq(plugin.ep()->engine()->getNextCSeq() - 1);
    m->setSequence(m_dialog.getSequence());
    m->addHeader("Call-ID",callid());
    String tmp;
    tmp << "<" << m_dialog.localURI << ">";
    MimeHeaderLine* hl = new MimeHeaderLine("From",tmp);
    tmp = m_dialog.localTag;
    if (tmp.null() && m_tr)
	tmp = m_tr->getDialogTag();
    if (tmp)
	hl->setParam("tag",tmp);
    m->addHeader(hl);
    tmp.clear();
    tmp << "<" << m_dialog.remoteURI << ">";
    hl = new MimeHeaderLine("To",tmp);
    tmp = m_dialog.remoteTag;
    if (tmp.null() && m_tr)
	tmp = m_tr->getDialogTag();
    if (tmp)
	hl->setParam("tag",tmp);
    m->addHeader(hl);
    return m;
}

// Update the dialog from received target refresh message
void YateSIPConnection::updateTarget(const SIPMessage* msg, bool force, bool chgParty)
{
    if (!(s_update_target || force))
	return;
    if (!msg)
	return;
    const MimeHeaderLine* co = msg->getHeader("Contact");
    if (co) {
	String old = m_uri;
	m_uri = *co;
	m_uri.parse();
	m_dialog.remoteURI = m_uri;
	if (old != m_uri)
	    TraceDebug(m_traceId,this,DebugInfo,"Dialog URI changed '%s' -> '%s' [%p]",old.c_str(),m_uri.c_str(),this);
    }
    if (!(m_autoChangeParty && chgParty))
	return;
    SIPParty* party = msg->getParty();
    if (party) {
	party->getAddr(m_host,m_port,false);
	setParty(party);
	m_address.clear();
	SocketAddr::appendTo(m_address,m_host,m_port);
    }
}

// Emit a call.update to notify cdrbuild of callid dialog tags change
void YateSIPConnection::emitUpdate()
{
    Message* m = message("call.update");
    m->addParam("operation","cdrbuild");
    Engine::enqueue(m);
}

// Emit a PRovisional ACK if enabled in the engine, return true to handle them
bool YateSIPConnection::emitPRACK(const SIPMessage* msg)
{
    if (!(msg && msg->isAnswer() && (msg->code > 100) && (msg->code < 200)))
	return false;
    if (!plugin.ep()->engine()->prack())
	return true;
    const MimeHeaderLine* rs = msg->getHeader("RSeq");
    const MimeHeaderLine* cs = msg->getHeader("CSeq");
    if (!(rs && cs))
	return true;
    int seq = rs->toInteger(0,10);
    // return false only if we already seen this provisional response
    if (seq == m_lastRseq)
	return false;
    if (seq < m_lastRseq) {
	TraceDebug(m_traceId,this,DebugMild,"Not sending PRACK for RSeq %d < %d [%p]",
	    seq,m_lastRseq,this);
	return false;
    }
    String tmp;
    const MimeHeaderLine* co = msg->getHeader("Contact");
    if (co) {
	tmp = *co;
	static const Regexp r("^[^<]*<\\([^>]*\\)>.*$");
	if (tmp.matches(r))
	    tmp = tmp.matchString(1);
    }
    SIPMessage* m = createDlgMsg("PRACK",tmp);
    if (!m)
	return true;
    m_lastRseq = seq;
    tmp = *rs;
    tmp << " " << *cs;
    m->addHeader("RAck",tmp);
    plugin.ep()->engine()->addMessage(m,&m_autoChangeParty);
    m->deref();
    return true;
}

// Creates a SDP for provisional (1xx) messages
MimeSdpBody* YateSIPConnection::createProvisionalSDP(Message& msg)
{
    // check if we are forwarding RTP or our peer can source at least audio data
    if (!msg.getBoolValue(YSTRING("earlymedia"),m_rtpForward
	    || (getPeer() && getPeer()->getSource())))
	return 0;
    if (m_rtpForward)
	return createPasstroughSDP(msg);
    if (m_rtpAddr.null())
	return 0;
    if (s_1xx_formats)
	updateFormats(msg);
    return createRtpSDP(true);
}

// Build and populate a chan.rtp message
Message* YateSIPConnection::buildChanRtp(RefObject* context)
{
    Message* m = new Message("chan.rtp");
    if (context)
	m->userData(context);
    else {
	complete(*m,true);
	m->addParam("call_direction",direction());
	m->addParam("call_address",address());
	putStatus(*m,"call_status");
	m->addParam("call_billid",billid());
	m->userData(static_cast<CallEndpoint*>(this));
    }
    return m;
}

// Media changed notification, reimplemented from SDPSession
void YateSIPConnection::mediaChanged(const SDPMedia& media)
{
    SDPSession::mediaChanged(media);
    if (media.id() && media.transport()) {
	Message m("chan.rtp");
	m.addParam("rtpid",media.id());
	m.addParam("media",media);
	m.addParam("transport",media.transport());
	m.addParam("terminate",String::boolText(true));
	m.addParam("call_direction",direction());
	m.addParam("call_address",address());
	putStatus(m,"call_status");
	m.addParam("call_billid",billid());
	if (m_traceId)
	  m.addParam("trace_id",m_traceId);
	Engine::dispatch(m);
	const char* stats = m.getValue(YSTRING("stats"));
	if (stats) {
	    paramMutex().lock();
	    parameters().setParam("rtp_stats"+media.suffix(),stats);
	    paramMutex().unlock();
	}
    }
    // Clear the data endpoint, will be rebuilt later if required
    clearEndpoint(media);
}

void YateSIPConnection::dispatchingRtp(Message*& msg, SDPMedia* media)
{
    if (!(msg && media))
	return;
    if (media->formats() || !(media->isAudio() || media->isVideo()))
	return;
    TraceDebug(m_traceId,this,DebugInfo,"Not sending %s for empty media %s [%p]",
	msg->c_str(),media->c_str(),this);
    TelEngine::destruct(msg);
}

// Process SIP events belonging to this connection
bool YateSIPConnection::process(SIPEvent* ev)
{
    const SIPMessage* msg = ev->getMessage();
    SIPTransaction* tr = ev->getTransaction();
    int code = tr->getResponseCode();
    DDebug(this,DebugInfo,"YateSIPConnection::process(%p) %s %s code=%d [%p]",
	ev,ev->isActive() ? "active" : "inactive",
	SIPTransaction::stateName(ev->getState()),code,this);
#ifdef XDEBUG
    if (msg)
	TraceDebug(m_traceId,this,DebugInfo,"Message %p '%s' %s %s code=%d body=%p",
	    msg,msg->method.c_str(),
	    msg->isOutgoing() ? "outgoing" : "incoming",
	    msg->isAnswer() ? "answer" : "request",
	    msg->code,msg->body);
#endif

    // Change party
    if (m_autoChangeParty && ev->isActive() && msg && !msg->isOutgoing())
	setPartyChanged(msg->getParty());

    Lock mylock(driver());
    if (tr == m_tr2) {
	mylock.drop();
	return processTransaction2(ev,msg,code);
    }

    bool updateTags = true;
    SIPDialog oldDlg(m_dialog);
    m_dialog = *tr->recentMessage();
    mylock.drop();

    if (msg && !msg->isOutgoing() && msg->isAnswer() && (code >= 300) && (code <= 699)) {
	updateTags = false;
	m_cancel = false;
	m_byebye = false;
	setReason(msg->reason,code,driver());
	paramMutex().lock();
	parameters().clearParams();
	parameters().addParam("cause_sip",String(code));
	parameters().addParam("reason_sip",msg->reason);
	if (msg->body) {
	    paramMutex().unlock();
	    Message tmp("isup.decode");
	    bool ok = decodeIsupBody(tmp,msg->body);
	    ok = copySipBody(tmp,msg->body) || ok;
	    paramMutex().lock();
	    if (ok)
		parameters().copyParams(tmp);
	}
	copySipHeaders(parameters(),*msg,true,static_cast<const YateSIPEngine*>(tr->getEngine())->foreignAuth());
	if (code < 400) {
	    // this is a redirect, it should provide a Contact and possibly a Diversion
	    const MimeHeaderLine* hl = msg->getHeader("Contact");
	    if (hl) {
		parameters().addParam("redirect",String::boolText(true));
		URI uri(*hl);
		parameters().addParam("called",uri.getUser());
		if (uri.getDescription())
		    parameters().addParam("calledname",uri.getDescription());
		parameters().addParam("calleduri",uri);
		hl = msg->getHeader("Diversion");
		if (hl) {
		    uri = *hl;
		    parameters().addParam("diverter",uri.getUser());
		    if (uri.getDescription())
			parameters().addParam("divertername",uri.getDescription());
		    parameters().addParam("diverteruri",uri);
		    String tmp = hl->getParam("reason");
		    MimeHeaderLine::delQuotes(tmp);
		    if (tmp.trimBlanks())
			parameters().addParam("divert_reason",tmp);
		    tmp = hl->getParam("privacy");
		    MimeHeaderLine::delQuotes(tmp);
		    if (tmp.trimBlanks())
			parameters().addParam("divert_privacy",tmp);
		    tmp = hl->getParam("screen");
		    MimeHeaderLine::delQuotes(tmp);
		    if (tmp.trimBlanks())
			parameters().addParam("divert_screen",tmp);
		    tmp = hl->getParam("counter");
		    MimeHeaderLine::delQuotes(tmp);
		    if (tmp.trimBlanks())
			parameters().addParam("divert_counter",tmp);
		}
	    }
	    else if (code != 387)
		TraceDebug(m_traceId,this,DebugMild,"Received %d redirect without Contact [%p]",code,this);
	}
	paramMutex().unlock();
	hangup();
    }
    else if (code == 408) {
	// Proxy timeout does not provide an answer message
	updateTags = false;
	if (m_dialog.remoteTag.null())
	    m_byebye = false;
	paramMutex().lock();
	parameters().setParam("cause_sip","408");
	parameters().clearParam("reason_sip");
	paramMutex().unlock();
	setReason("Request Timeout",code,driver());
	hangup();
    }
    else if (!m_hungup && code >= 100) {
	Lock lck(paramMutex());
	parameters().setParam("cause_sip",String(code));
	if (msg && msg->reason)
	    parameters().setParam("reason_sip",msg->reason);
	else
	    parameters().clearParam("reason_sip");
    }

    // Only update channels' callid if dialog tags change
    if (updateTags) {
	Lock lock(driver());
	updateTags = (oldDlg |= m_dialog);
    }

    if (!ev->isActive()) {
	Lock lock(driver());
	if (m_tr) {
	    DDebug(this,DebugInfo,"YateSIPConnection clearing transaction %p [%p]",
		m_tr,this);
	    m_tr->setUserData(0);
	    m_tr->deref();
	    m_tr = 0;
	}
	if (m_state != Established) {
	    lock.drop();
	    hangup();
	}
	else if (s_ack_required && (code == 408)) {
	    // call was established but we didn't got the ACK
	    setReason("Not received ACK",code);
	    lock.drop();
	    hangup();
	}
	else {
	    if (updateTags)
		emitUpdate();
	    startPendingUpdate();
	}
	return false;
    }
    if (!msg || msg->isOutgoing()) {
	if (updateTags)
	    emitUpdate();
	return false;
    }
    String natAddr;
    MimeSdpBody* sdp = getSdpBody(msg->body);
    if (sdp) {
	DDebug(this,DebugInfo,"YateSIPConnection got SDP [%p]",this);
	setMedia(plugin.parser().parse(sdp,m_rtpAddr,m_rtpMedia));
	// guess if the call comes from behind a NAT
	if (s_auto_nat && isNatBetween(m_rtpAddr,m_host)) {
	    TraceDebug(m_traceId,this,DebugInfo,"RTP NAT detected: private '%s' public '%s'",
		m_rtpAddr.c_str(),m_host.c_str());
	    natAddr = m_rtpAddr;
	    m_rtpAddr = m_host;
	}
	DDebug(this,DebugAll,"RTP addr '%s' [%p]",m_rtpAddr.c_str(),this);
	if (m_rtpForward && plugin.parser().sdpForward() && !(msg->isACK() || tr->autoAck()))
	    m_sdpForward = true;
    }
    if ((!m_routes) && msg->isAnswer() && (msg->code > 100) && (msg->code < 300))
	m_routes = msg->getRoutes();

    if (msg->isAnswer() && m_externalAddr.null() && m_line) {
	// see if we should detect our external address
	const YateSIPLine* line = plugin.findLine(m_line);
	if (line && line->localDetect()) {
	    const MimeHeaderLine* hl = msg->getHeader("Via");
	    if (hl) {
		const NamedString* par = hl->getParam("received");
		if (par && *par) {
		    getAddrCheckIPv6(m_externalAddr,*par);
		    TraceDebug(m_traceId,this,DebugInfo,"Detected local address '%s' [%p]",
			m_externalAddr.c_str(),this);
		}
	    }
	}
    }

    if (msg->isAnswer() && ((msg->code / 100) == 2)) {
	if (m_checkAllowInfo) {
	    if (m_dtmfMethods.hasMethod(DtmfMethods::Info) && !infoAllowed(msg))
		m_dtmfMethods.reset(DtmfMethods::Info);
	}
	updateTags = false;
	m_cancel = false;
	Lock lock(driver());
	updateTarget(msg,true,s_changeParty2xx);
	const SIPMessage* ack = m_tr ? m_tr->latestMessage() : 0;
	if (ack && ack->isACK()) {
	    DDebug(this,DebugInfo,"YateSIPConnection clearing answered transaction %p [%p]",
		m_tr,this);
	    m_tr->setUserData(0);
	    m_tr->deref();
	    m_tr = 0;
	}
	setReason("",0);
	lock.drop();
	setStatus("answered",Established);
	Message *m = message("call.answered");
	copySipHeaders(*m,*msg);
	decodeIsupBody(*m,msg->body);
	copySipBody(*m,msg->body);
	addRtpParams(*m,natAddr,msg->body,false,m_sdpForward);
	Engine::enqueue(m);
	if (tr->autoAck())
	    startPendingUpdate();
	else if (!m_rtpForward) {
	    MimeSdpBody* sdp = m_rtpMedia ? createRtpSDP(true) : 0;
	    TraceDebug(m_traceId,this,DebugNote,"Sending ACK %s SDP now since RTP is not forwarded [%p]",
		(sdp ? "with" : "without"),this);
	    tr->setAcknowledge(sdp);
	    startPendingUpdate();
	}
    }
    if (emitPRACK(msg)) {
	if (s_multi_ringing || (m_state < Ringing)) {
	    const char* name = "call.progress";
	    const char* reason = 0;
	    switch (msg->code) {
		case 180:
		    updateTags = false;
		    name = "call.ringing";
		    setStatus("ringing",Ringing);
		    break;
		case 181:
		    reason = "forwarded";
		    setStatus("progressing");
		    break;
		case 182:
		    reason = "queued";
		    setStatus("progressing");
		    break;
		case 183:
		    setStatus("progressing");
		    break;
		// for all others emit a call.progress but don't change status
	    }
	    if (name) {
		Message* m = message(name);
		copySipHeaders(*m,*msg);
		decodeIsupBody(*m,msg->body);
		copySipBody(*m,msg->body);
		if (reason)
		    m->addParam("reason",reason);
		addRtpParams(*m,natAddr,msg->body,false,m_sdpForward);
		if (m_rtpAddr.null())
		    m->addParam("earlymedia","false");
		Engine::enqueue(m);
	    }
	}
    }
    if (updateTags)
	emitUpdate();
    if (msg->isACK()) {
	DDebug(this,DebugInfo,"YateSIPConnection got ACK [%p]",this);
	if (!tr->autoAck()) {
	    Message* m = message("call.update");
	    m->addParam("operation","notify");
	    if (sdp)
		addRtpParams(*m,natAddr,sdp,false,m_sdpForward);
	    else
		m->addParam("rtp_forward",String::boolText(m_rtpForward));
	    Engine::enqueue(m);
	    startPendingUpdate();
	}
	startRtp();
    }
    return false;
}

// Process secondary transaction (reINVITE)  belonging to this connection
bool YateSIPConnection::processTransaction2(SIPEvent* ev, const SIPMessage* msg, int code)
{
    Lock mylock(driver());
    if (ev->getState() == SIPTransaction::Cleared) {
	bool fatal = (m_reInviting == ReinviteRequest);
	detachTransaction2();
	if (fatal) {
	    setReason("Request Timeout",408);
	    mylock.drop();
	    hangup();
	    mylock.acquire(driver());
	}
	else {
	    Message* m = message("call.update");
	    m->addParam("operation","reject");
	    m->addParam("error","timeout");
	    Engine::enqueue(m);
	}
	m_revert.clearParams();
	return false;
    }
    if (!msg || msg->isOutgoing())
	return false;

    if ((m_reInviting == ReinviteReceived) && msg->isACK() && m_tr2 && !m_tr2->autoAck()) {
	String natAddr;
	MimeSdpBody* sdp = getSdpBody(msg->body);
	DDebug(this,DebugInfo,"YateSIPConnection got reINVITE ACK %s SDP [%p]",
	    (sdp ? "with" : "without"), this);
	if (sdp) {
	    setMedia(plugin.parser().parse(sdp,m_rtpAddr,m_rtpMedia));
	    // guess if the call comes from behind a NAT
	    if (s_auto_nat && isNatBetween(m_rtpAddr,m_host)) {
		TraceDebug(m_traceId,this,DebugInfo,"RTP NAT detected: private '%s' public '%s'",
		    m_rtpAddr.c_str(),m_host.c_str());
		natAddr = m_rtpAddr;
		m_rtpAddr = m_host;
	    }
	    DDebug(this,DebugAll,"RTP addr '%s' [%p]",m_rtpAddr.c_str(),this);
	}
	detachTransaction2();

	Message* m = message("call.update");
	m->addParam("operation","notify");
	if (sdp)
	    addRtpParams(*m,natAddr,sdp,false,m_sdpForward);
	else
	    m->addParam("rtp_forward",String::boolText(m_rtpForward));
	Engine::enqueue(m);
    }

    if ((code < 200) || !msg->isAnswer())
	return false;

    if (m_reInviting == ReinviteRequest) {
	detachTransaction2();
	// we emitted a client reINVITE, now we are forced to deal with it
	if (code < 300) {
	    MimeSdpBody* sdp = getSdpBody(msg->body);
	    while (sdp) {
		String addr;
		ObjList* lst = plugin.parser().parse(sdp,addr,0,String::empty(),m_rtpForward);
		if (!lst)
		    break;
		if ((addr == m_rtpAddr) || isNatBetween(addr,m_host)) {
		    ObjList* l = m_rtpMedia;
		    for (; l; l = l->next()) {
			SDPMedia* m = static_cast<SDPMedia*>(l->get());
			if (!m)
			    continue;
			SDPMedia* m2 = static_cast<SDPMedia*>((*lst)[*m]);
			if (!m2)
			    continue;
			// both old and new media exist, compare ports
			if (m->remotePort() != m2->remotePort()) {
			    DDebug(this,DebugWarn,"Port for '%s' changed: '%s' -> '%s' [%p]",
				m->c_str(),m->remotePort().c_str(),
				m2->remotePort().c_str(),this);
			    TelEngine::destruct(lst);
			    break;
			}
		    }
		    if (lst) {
			setMedia(lst);
			return false;
		    }
		}
		TelEngine::destruct(lst);
		setReason("Media information changed during reINVITE",415);
		mylock.drop();
		hangup();
		return false;
	    }
	    setReason("Missing media information",415);
	}
	else
	    setReason(msg->reason,code);
	mylock.drop();
	hangup();
	return false;
    }

    Message* m = message("call.update");
    decodeIsupBody(*m,msg->body);
    copySipBody(*m,msg->body);
    copySipHeaders(*m,*msg);
    if (code < 300) {
	m->addParam("operation","notify");
	String natAddr;
	MimeSdpBody* sdp = getSdpBody(msg->body);
	if (sdp) {
	    DDebug(this,DebugInfo,"YateSIPConnection got reINVITE SDP [%p]",this);
	    setMedia(plugin.parser().parse(sdp,m_rtpAddr,m_rtpMedia,String::empty(),
		m_rtpForward));
	    // guess if the call comes from behind a NAT
	    if (s_auto_nat && isNatBetween(m_rtpAddr,m_host)) {
		TraceDebug(m_traceId,this,DebugInfo,"RTP NAT detected: private '%s' public '%s'",
		    m_rtpAddr.c_str(),m_host.c_str());
		natAddr = m_rtpAddr;
		m_rtpAddr = m_host;
	    }
	    DDebug(this,DebugAll,"RTP addr '%s' [%p]",m_rtpAddr.c_str(),this);
	    if (m_rtpForward) {
		// drop any local RTP we might have before
		m_mediaStatus = m_rtpAddr.null() ? MediaMuted : MediaMissing;
		m_rtpLocalAddr.clear();
		clearEndpoint();
	    }
	}
	bool added = false;
	if (!m_rtpForward || m_rtpAddr)
	    added = addRtpParams(*m,natAddr,sdp);
	else
	    added = addRtpParams(*m,natAddr,sdp,false,true);
	if (!added)
	    addSdpParams(*m,sdp);
    }
    else {
	m->addParam("operation","reject");
	m->addParam("error",lookup(code,dict_errors,"failure"));
	m->addParam("reason",msg->reason);
	if (ReinviteNone == m_reInviting && !m_rtpForward && m_revert.count()) {
	    // we already changed our media so try to revert
	    detachTransaction2();
	    if (startClientReInvite(m_revert,false)) {
		m_revert.clearParams();
		Engine::enqueue(m);
		return false;
	    }
	}
    }
    if (!m_tr2 || m_tr2->autoAck())
	detachTransaction2();
    m_revert.clearParams();
    mylock.drop();
    Engine::enqueue(m);
    return false;
}

void YateSIPConnection::reInvite(SIPTransaction* t)
{
    if (!checkUser(t))
	return;
    DDebug(this,DebugAll,"YateSIPConnection::reInvite(%p) [%p]",t,this);
    Lock mylock(driver());
    int invite = m_reInviting;
    if (m_tr || m_tr2 || (invite == ReinviteRequest) || (invite == ReinviteReceived)) {
	// another request pending - refuse this one
	t->setResponse(491);
	return;
    }
    if (m_hungup) {
	t->setResponse(481);
	return;
    }
    m_reInviting = ReinviteReceived;
    mylock.drop();
    m_dialog.adjustCSeq(t->initialMessage());
    // Change party
    if (m_autoChangeParty && t->initialMessage() && !t->initialMessage()->isOutgoing())
	setPartyChanged(t->initialMessage()->getParty());

    MimeSdpBody* sdp = getSdpBody(t->initialMessage()->body);
    if (sdp || (m_rtpForward && sdpAccept(t->initialMessage(),true))) {
	if (m_rtpForward ? reInviteForward(t,sdp,invite) : reInviteProxy(t,sdp,invite))
	    return;
    }
    m_reInviting = invite;
    if (s_refresh_nosdp && !sdp) {
	// be permissive, accept session refresh with no SDP
	updateTarget(t->initialMessage());
	SIPMessage* m = new SIPMessage(t->initialMessage(),200);
	// if required provide our own media offer
	if (!m_rtpForward)
	    m->setBody(createSDP());
	t->setResponse(m);
	m->deref();
	return;
    }
    t->setResponse(488);
}

bool YateSIPConnection::reInviteForward(SIPTransaction* t, MimeSdpBody* sdp, int invite)
{
    String addr;
    String natAddr;
    ObjList* lst = 0;
    if (sdp) {
	lst = plugin.parser().parse(sdp,addr,0,String::empty(),true);
	if (!lst)
	    return false;
	// guess if the call comes from behind a NAT
	if (s_auto_nat && isNatBetween(addr,m_host)) {
	    TraceDebug(m_traceId,this,DebugInfo,"RTP NAT detected: private '%s' public '%s'",
		addr.c_str(),m_host.c_str());
	    natAddr = addr;
	    addr = m_host;
	}
	TraceDebug(m_traceId,this,DebugAll,"reINVITE RTP addr '%s'",addr.c_str());
    }

    // for pass-trough RTP we need support from our peer
    Message msg("call.update");
    complete(msg);
    if (s_update_target) {
	bool addrChg = false;
	SIPParty* party = t->initialMessage()->getParty();
	if (party) {
	    String addr;
	    party->appendAddr(addr,false);
	    if (addr != m_address) {
		msg.setParam("address",addr);
		msg.addParam("address_old",m_address);
		addrChg = true;
	    }
	}
	msg.addParam("address_changed",String::boolText(addrChg));
	bool contactChg = false;
	const MimeHeaderLine* co = t->initialMessage()->getHeader("Contact");
	if (co) {
	    URI uri(*co);
	    uri.parse();
	    if (uri != m_uri) {
		msg.addParam("contact",uri);
		msg.addParam("contact_old",m_uri);
		contactChg = true;
	    }
	}
	msg.addParam("contact_changed",String::boolText(contactChg));
    }
    msg.addParam("operation","request");
    copySipHeaders(msg,*t->initialMessage());
    msg.addParam("rtp_forward","yes");
    msg.addParam("rtp_addr",addr);
    if (natAddr)
	msg.addParam("rtp_nat_addr",natAddr);
    putMedia(msg,lst);
    TelEngine::destruct(lst);
    if (sdp)
	addSdpParams(msg,sdp);
    else {
	t->autoAck(false);
	msg.addParam("sdp_ack",String::boolText(true));
    }
    bool ok = Engine::dispatch(msg) && (msg.retValue() != YSTRING("error")) && (msg.retValue() != "-");
    Lock mylock(driver());
    // if peer doesn't support updates fail the reINVITE
    if (!ok) {
	t->setResponse(msg.getIntValue(YSTRING("error"),dict_errors,488),msg.getValue(YSTRING("reason")));
	m_reInviting = invite;
    }
    else if (m_tr2) {
	// ouch! this shouldn't have happened!
	t->setResponse(491);
	// media is uncertain now so drop the call
	setReason("Internal Server Error",500);
	mylock.drop();
	hangup();
    }
    else {
	// we remember the request and leave it pending
	t->ref();
	t->setUserData(this);
	m_tr2 = t;
    }
    return true;
}

bool YateSIPConnection::reInviteProxy(SIPTransaction* t, MimeSdpBody* sdp, int invite)
{
    // refuse request if we had no media at all before
    if (m_mediaStatus == MediaMissing)
	return false;
    String addr;
    String natAddr;
    ObjList* lst = plugin.parser().parse(sdp,addr);
    if (!lst)
	return false;
    // guess if the call comes from behind a NAT
    if (s_auto_nat && isNatBetween(addr,m_host)) {
	TraceDebug(m_traceId,this,DebugInfo,"RTP NAT detected: private '%s' public '%s'",
	    addr.c_str(),m_host.c_str());
	natAddr = addr;
	addr = m_host;
    }
    bool audioChg = (getMedia(YSTRING("audio")) != 0);
    audioChg ^= ((*lst)[YSTRING("audio")] != 0);

    bool keepExisting = true;
    Message ver("call.update");
    if (s_update_verify) {
	complete(ver);
	ver.addParam("operation","verify");
	ver.addParam("method",t->initialMessage()->method);
	copySipHeaders(ver,*t->initialMessage());
	ver.addParam("rtp_addr",addr);
	if (natAddr)
	    ver.addParam("rtp_nat_addr",natAddr);
	ver.addParam("audio_changed",String::boolText(audioChg));
	putMedia(ver,lst);
	addSdpParams(ver,sdp);
	if (!Engine::dispatch(ver) || (ver.retValue() == YSTRING("error")) || (ver.retValue() == "-")) {
	    TelEngine::destruct(lst);

	    SIPMessage* m = new SIPMessage(t->initialMessage(),
		ver.getIntValue(YSTRING("error"),dict_errors,488),ver.getValue(YSTRING("reason")));
	    copySipHeaders(*m,ver);
	    t->setResponse(m);
	    m->deref();
	    m_reInviting = invite;
	    return true;
	}
	keepExisting = ver.getBoolValue(YSTRING("keep_media"),keepExisting);
    }

    if (m_rtpAddr != addr) {
	m_rtpAddr = addr;
	TraceDebug(m_traceId,this,DebugAll,"New RTP addr '%s'",m_rtpAddr.c_str());
	// clear all data endpoints - createRtpSDP will build new ones
	if (!s_rtp_preserve)
	    setMedia(0);
    }
    setMedia(lst,keepExisting);

    m_mediaStatus = MediaMissing;
    // let RTP guess again the local interface or use the enforced address
    setRtpLocalAddr(m_rtpLocalAddr);
    addr = m_address;
    String uri = m_uri;
    updateTarget(t->initialMessage());
    bool addrChg = (addr != m_address);
    bool contactChg = (uri != m_uri);

    SIPMessage* m = new SIPMessage(t->initialMessage(), 200);
    copySipHeaders(*m,ver);
    MimeSdpBody* sdpNew = createRtpSDP(true);
    m->setBody(sdpNew);
    t->setResponse(m);
    m->deref();

    // notify peer about the changes
    Message* msg = message("call.update");
    msg->addParam("operation","notify");
    msg->addParam("mandatory","false");
    if (addrChg)
	msg->addParam("address_old",addr);
    msg->addParam("address_changed",String::boolText(addrChg));
    if (contactChg) {
	msg->addParam("contact",m_uri);
	msg->addParam("contact_old",uri);
    }
    msg->addParam("contact_changed",String::boolText(contactChg));
    msg->addParam("audio_changed",String::boolText(audioChg));
    msg->addParam("mute",String::boolText(MediaStarted != m_mediaStatus));
    putMedia(*msg);
    Engine::enqueue(msg);
    m_reInviting = invite;
    return true;
}

bool YateSIPConnection::checkUser(SIPTransaction* t, bool refuse)
{
    // don't try to authenticate requests from server
    if (m_user.null() || m_line)
	return true;
    NamedList params("");
    params.addParam("id",id());
    params.addParam("billid",billid(),false);
    int age = t->authUser(m_user,false,&params);
    if ((age >= 0) && (age <= 10))
	return true;
    DDebug(this,DebugAll,"YateSIPConnection::checkUser(%p) failed, age %d [%p]",t,age,this);
    if (refuse)
	setAuthError(t,params,age >= 0,m_domain);
    return false;
}

void YateSIPConnection::doBye(SIPTransaction* t)
{
    if (m_authBye && !checkUser(t))
	return;
    DDebug(this,DebugAll,"YateSIPConnection::doBye(%p) [%p]",t,this);
    const SIPMessage* msg = t->initialMessage();
    m_dialog.adjustCSeq(msg);
    // Change party
    if (m_autoChangeParty && t->initialMessage() && !t->initialMessage()->isOutgoing())
	setPartyChanged(t->initialMessage()->getParty());
    if (msg->body) {
	Message tmp("isup.decode");
	bool ok = decodeIsupBody(tmp,msg->body);
	ok = copySipBody(tmp,msg->body) || ok;
	if (ok) {
	    paramMutex().lock();
	    parameters().copyParams(tmp);
	    paramMutex().unlock();
	}
    }
    Lock mylock(driver());
    setMedia(0);
    mylock.drop();
    SIPMessage* m = new SIPMessage(t->initialMessage(),200);
    paramMutex().lock();
    copySipHeaders(parameters(),*msg);
    const char* stats = parameters().getValue(YSTRING("rtp_stats"));
    if (stats)
	m->addHeader("P-RTP-Stat",stats);
    paramMutex().unlock();
    const MimeHeaderLine* hl = msg->getHeader("Reason");
    if (hl) {
	const NamedString* text = hl->getParam("text");
	if (text)
	    setReason(MimeHeaderLine::unquote(*text),m_reasonCode,driver());
	// FIXME: add SIP and Q.850 cause codes
    }
    t->setResponse(m);
    m->deref();
    m_byebye = false;
    hangup();
}

void YateSIPConnection::doCancel(SIPTransaction* t)
{
#ifdef DEBUG
    // CANCEL cannot be challenged but it may (should?) be authenticated with
    //  an old nonce from the transaction that is being cancelled
    if (m_user && (t->authUser(m_user) < 0))
	TraceDebug(m_traceId,&plugin,DebugMild,"User authentication failed for user '%s' but CANCELing anyway [%p]",
	    m_user.c_str(),this);
#endif
    DDebug(this,DebugAll,"YateSIPConnection::doCancel(%p) [%p]",t,this);
    // Change party
    if (m_autoChangeParty && t->initialMessage() && !t->initialMessage()->isOutgoing())
	setPartyChanged(t->initialMessage()->getParty());
    if (m_tr) {
	t->setResponse(200);
	m_byebye = false;
	clearTransaction();
	disconnect("Cancelled");
	hangup();
    }
    else
	t->setResponse(481);
}

bool YateSIPConnection::doInfo(SIPTransaction* t)
{
    if (m_authBye && !checkUser(t))
	return true;
    DDebug(this,DebugAll,"YateSIPConnection::doInfo(%p) [%p]",t,this);
    m_dialog.adjustCSeq(t->initialMessage());
    // Change party
    if (m_autoChangeParty && t->initialMessage() && !t->initialMessage()->isOutgoing())
	setPartyChanged(t->initialMessage()->getParty());
    if (m_hungup) {
	t->setResponse(481);
	return true;
    }
    int sig = -1;
    const MimeLinesBody* lb = YOBJECT(MimeLinesBody,getOneBody(t->initialMessage()->body,"application/dtmf-relay"));
    const MimeStringBody* sb = YOBJECT(MimeStringBody,getOneBody(t->initialMessage()->body,"application/dtmf"));
    if (lb) {
	const ObjList* l = lb->lines().skipNull();
	for (; l; l = l->skipNext()) {
	    String tmp = static_cast<String*>(l->get());
	    tmp.toUpper();
	    if (tmp.startSkip("SIGNAL=",false)) {
		sig = tmp.trimBlanks().toInteger(info_signals,-1);
		break;
	    }
	}
    }
    else if (sb) {
	String tmp = sb->text();
	tmp.trimSpaces();
	sig = tmp.toInteger(info_signals,-1);
    }
    else
	return false;
    t->setResponse(200);
    if ((sig >= 0) && (sig <= 16)) {
	char tmp[2];
	tmp[0] = s_dtmfs[sig];
	tmp[1] = '\0';
	Message* msg = message("chan.dtmf");
	copySipHeaders(*msg,*t->initialMessage());
	msg->addParam("text",tmp);
	msg->addParam("detected","sip-info");
	dtmfEnqueue(msg);
    }
    return true;
}

void YateSIPConnection::doRefer(SIPTransaction* t)
{
    if (m_authBye && !checkUser(t))
	return;
    DDebug(this,DebugAll,"doRefer(%p) [%p]",t,this);
    m_dialog.adjustCSeq(t->initialMessage());
    // Change party
    if (m_autoChangeParty && t->initialMessage() && !t->initialMessage()->isOutgoing())
	setPartyChanged(t->initialMessage()->getParty());
    if (m_hungup) {
	t->setResponse(481);
	return;
    }
    if (m_referring) {
	DDebug(this,DebugAll,"doRefer(%p). Already referring [%p]",t,this);
	t->setResponse(491);           // Request Pending
	return;
    }
    m_referring = true;
    const MimeHeaderLine* refHdr = t->initialMessage()->getHeader("Refer-To");
    if (!(refHdr && refHdr->length())) {
	DDebug(this,DebugAll,"doRefer(%p). Empty or missing 'Refer-To' header [%p]",t,this);
	t->setResponse(400);           // Bad request
	m_referring = false;
	return;
    }

    // Get 'Refer-To' URI and its parameters
    URI uri(*refHdr);
    ObjList params;
    // Find the first parameter separator. Ignore everything before it
    int start = findURIParamSep(uri.getExtra(),0);
    if (start >= 0)
	start++;
    else
	start = uri.getExtra().length();
    while (start < (int)uri.getExtra().length()) {
	int end = findURIParamSep(uri.getExtra(),start);
	// Check if this is the last parameter or an empty one
	if (end < 0)
	    end = uri.getExtra().length();
	else if (end == start) {
	    start++;
	    continue;
	}
	String param;
	param = uri.getExtra().substr(start,end - start);
	start = end + 1;
	if (!param)
	    continue;
	param = param.uriUnescape();
	int eq = param.find("=");
	if (eq < 0) {
	    DDebug(this,DebugInfo,"doRefer(%p). Skipping 'Refer-To' URI param '%s' [%p]",
		t,param.c_str(),this);
	    continue;
	}
	String name = param.substr(0,eq).trimBlanks();
	String value = param.substr(eq + 1);
	DDebug(this,DebugAll,"doRefer(%p). Found 'Refer-To' URI param %s=%s [%p]",
	    t,name.c_str(),value.c_str(),this);
	if (name)
	    params.append(new MimeHeaderLine(name,value));
    }
    // Check attended transfer request parameters
    ObjList* repl = params.find("Replaces");
    const MimeHeaderLine* replaces = repl ? static_cast<MimeHeaderLine*>(repl->get()) : 0;
    if (replaces) {
	const String* fromTag = replaces->getParam("from-tag");
	const String* toTag = replaces->getParam("to-tag");
	if (null(replaces) || null(fromTag) || null(toTag)) {
	    DDebug(this,DebugAll,
		"doRefer(%p). Invalid 'Replaces' '%s' from-tag=%s to-tag=%s [%p]",
		t,replaces->safe(),c_safe(fromTag),c_safe(toTag),this);
	    t->setResponse(501);           // Not implemented
	    m_referring = false;
	    return;
	}
	// Avoid replacing the same connection
	if (isDialog(*replaces,*fromTag,*toTag)) {
	    DDebug(this,DebugAll,
		"doRefer(%p). Attended transfer request for the same dialog [%p]",
		t,this);
	    t->setResponse(400,"Can't replace the same dialog");           // Bad request
	    m_referring = false;
	    return;
	}
    }

    Message* msg = 0;
    SIPMessage* sipNotify = 0;
    Channel* ch = YOBJECT(Channel,getPeer());
    if (ch && ch->driver() &&
	initTransfer(msg,sipNotify,t->initialMessage(),refHdr,uri,replaces)) {
	(new YateSIPRefer(id(),ch->id(),ch->driver(),msg,sipNotify,t))->startup();
	return;
    }
    DDebug(this,DebugAll,"doRefer(%p). No peer or peer has no driver [%p]",t,this);
    t->setResponse(503);       // Service Unavailable
    m_referring = false;
}

void YateSIPConnection::doMessage(SIPTransaction* t)
{
    DDebug(this,DebugAll,"doMessage(%p) [%p]",t,this);
    const SIPMessage* sip = t ? t->initialMessage() : 0;
    if (!sip)
	return;
    if (m_authBye && !checkUser(t))
	return;
    m_dialog.adjustCSeq(sip);
    // Change party
    if (m_autoChangeParty && t->initialMessage() && !t->initialMessage()->isOutgoing())
	setPartyChanged(t->initialMessage()->getParty());
    if (m_hungup) {
	t->setResponse(481);
	return;
    }
    t->setResponse(200);
    Message* m = message("chan.text");
    copySipHeaders(*m,*sip);
    copySipBody(*m,*sip,true);
    Engine::enqueue(m);
}

void YateSIPConnection::complete(Message& msg, bool minimal) const
{
    Channel::complete(msg,minimal);
    if (minimal)
	return;
    Lock mylock(driver());
    if (m_domain)
	msg.setParam("domain",m_domain);
    addCallId(msg,m_dialog,m_dialog.fromTag(isOutgoing()),m_dialog.toTag(isOutgoing()));
}

void YateSIPConnection::disconnected(bool final, const char *reason)
{
    TraceDebug(m_traceId,this,DebugAll,"YateSIPConnection::disconnected() '%s' [%p]",reason,this);
    setSilent(reason);
    if (reason) {
	int code = lookup(reason,dict_errors);
	if (code >= 300 && code <= 699)
	    setReason(lookup(code,SIPResponses,reason),code,driver());
	else
	    setReason(reason,487,driver());
    }
    Channel::disconnected(final,reason);
}

bool YateSIPConnection::msgProgress(Message& msg)
{
    Channel::msgProgress(msg);
    int code = 183;
    const NamedString* reason = msg.getParam(YSTRING("reason"));
    if (reason) {
	// handle the special progress types that have provisional codes
	if (*reason == YSTRING("forwarded"))
	    code = 181;
	else if (*reason == YSTRING("queued"))
	    code = 182;
    }
    Lock lock(driver());
    if (m_hungup)
	return false;
    if (m_tr && (m_tr->getState() == SIPTransaction::Process)) {
	SIPMessage* m = new SIPMessage(m_tr->initialMessage(), code);
	copySipHeaders(*m,msg);
	m->setBody(buildSIPBody(msg,createProvisionalSDP(msg)));
	m_tr->setResponse(m);
	m->deref();
    }
    setStatus("progressing");
    return true;
}

bool YateSIPConnection::msgRinging(Message& msg)
{
    Channel::msgRinging(msg);
    Lock lock(driver());
    if (m_hungup)
	return false;
    if (m_tr && (m_tr->getState() == SIPTransaction::Process)) {
	SIPMessage* m = new SIPMessage(m_tr->initialMessage(), 180);
	copySipHeaders(*m,msg);
	m->setBody(buildSIPBody(msg,createProvisionalSDP(msg)));
	m_tr->setResponse(m);
	m->deref();
    }
    setStatus("ringing");
    return true;
}

bool YateSIPConnection::msgAnswered(Message& msg)
{
    Channel::msgAnswered(msg);
    Lock lock(driver());
    if (m_hungup)
	return false;
    if (m_tr && (m_tr->getState() == SIPTransaction::Process)) {
	updateFormats(msg,true);
	SIPMessage* m = new SIPMessage(m_tr->initialMessage(), 200);
	copySipHeaders(*m,msg);
	MimeSdpBody* sdp = createPasstroughSDP(msg);
	if (!sdp) {
	    m_rtpForward = false;
	    bool startNow = false;
	    if (m_rtpMedia)
		startNow = msg.getBoolValue(YSTRING("rtp_start"),s_start_rtp);
	    else {
		// early RTP start but media list yet unknown - build best guess
		String fmts;
		plugin.parser().getAudioFormats(fmts);
		ObjList* lst = new ObjList;
		lst->append(new SDPMedia("audio","RTP/AVP",msg.getValue(YSTRING("formats"),fmts)));
		setMedia(lst);
		m_rtpAddr = m_host;
	    }
	    // normally don't start RTP yet, only when we get the ACK
	    sdp = createRtpSDP(startNow);
	}
	m->setBody(buildSIPBody(msg,sdp));

	const MimeHeaderLine* co = m_tr->initialMessage()->getHeader("Contact");
	if (co) {
	    // INVITE had a Contact: header - time to change remote URI
	    m_uri = *co;
	    m_uri.parse();
	}

	// and finally send the answer, transaction will finish soon afterwards
	m_tr->setResponse(m);
	m->deref();
    }
    setReason("",0);
    setStatus("answered",Established);
    return true;
}

bool YateSIPConnection::msgTone(Message& msg, const char* tone)
{
    if (m_hungup)
	return false;
    if (TelEngine::null(tone))
	return true;
    DtmfMethods methods = m_dtmfMethods;
    const String* param = msg.getParam(YSTRING("methods"));
    if (param) {
	bool intersect = !msg.getBoolValue(YSTRING("methods_override"));
	methods.set(*param,&m_dtmfMethods,true,intersect);
    }
    else {
	const String* method = msg.getParam(YSTRING("method"));
	if (method) {
	    if (s_warnDtmfMethodChanDtmf) {
		s_warnDtmfMethodChanDtmf = false;
		TraceDebug(m_traceId,this,DebugConf,"Deprecated 'method' parameter in '%s'. Use 'methods' instead!",msg.c_str());
	    }
	    int meth = lookup(*method,DtmfMethods::s_methodName,DtmfMethods::MethodCount);
	    if (meth != DtmfMethods::MethodCount)
		methods.set(meth);
	}
    }
    bool retVal = false;
    bool ok = false;
    if (msg.getBoolValue(YSTRING("honor_dtmf_detect"),m_honorDtmfDetect)) {
	const String& detected = msg[YSTRING("detected")];
	int meth = lookup(detected,DtmfMethods::s_methodName,DtmfMethods::MethodCount);
	if (meth != DtmfMethods::MethodCount && methods.hasMethod(meth)) {
	    ok = sendTone(msg,tone,meth,retVal);
	    methods.reset(meth);
	}
    }
    for (int i = 0; !ok && i < DtmfMethods::MethodCount; i++) {
	int meth = methods[i];
	if (meth != DtmfMethods::MethodCount)
	    ok = sendTone(msg,tone,meth,retVal);
    }
    if (!ok && debugAt(DebugNote)) {
	String tmp;
	methods.buildMethods(tmp);
	TraceDebug(m_traceId,this,DebugNote,"Failed to send tones '%s' methods=%s [%p]",tone,tmp.c_str(),this);
    }
    return retVal;
}

bool YateSIPConnection::msgText(Message& msg, const char* text)
{
    if (m_hungup)
	return false;
    DDebug(this,DebugAll,"msgText(%s) [%p]",text,this);
    SIPMessage* m = createDlgMsg("MESSAGE");
    if (m) {
	if (!copySipBody(*m,msg)) {
	    if (TelEngine::null(text)) {
		TelEngine::destruct(m);
		return false;
	    }
	    m->setBody(new MimeStringBody("text/plain",text));
	}
	copySipHeaders(*m,msg);
	plugin.ep()->engine()->addMessage(m,&m_autoChangeParty);
	m->deref();
	return true;
    }
    return false;
}

bool YateSIPConnection::msgDrop(Message& msg, const char* reason)
{
    if (!Channel::msgDrop(msg,reason))
	return false;
    setSilent(reason);
    int code = lookup(reason,dict_errors);
    if (code >= 300 && code <= 699)
	setReason(lookup(code,SIPResponses,reason),code,driver());
    return true;
}

bool YateSIPConnection::msgUpdate(Message& msg)
{
    String* oper = msg.getParam(YSTRING("operation"));
    if (!oper || oper->null())
	return false;
    Lock lock(driver());
    if (m_hungup)
	return false;
    if (*oper == YSTRING("request")) {
	if (m_tr || m_tr2) {
	    DDebug(this,DebugWarn,"Update request rejected, pending:%s%s [%p]",
		m_tr ? " invite" : "",m_tr2 ? " reinvite" : "",this);
	    msg.setParam("error","pending");
	    msg.setParam("reason","Another INVITE Pending");
	    return false;
	}
	return startClientReInvite(msg,true);
    }
    if (*oper == YSTRING("initiate")) {
	if (m_reInviting != ReinviteNone) {
	    msg.setParam("error","pending");
	    msg.setParam("reason","Another INVITE Pending");
	    return false;
	}
	m_reInviting = ReinvitePending;
	startPendingUpdate();
	return true;
    }
    if (!m_tr2) {
	if (*oper == YSTRING("notify")) {
	    if (m_tr && m_tr->isOutgoing() && !m_tr->autoAck()) {
		// generate ACK explicitly and put the SDP in it
		MimeSdpBody* sdp = createPasstroughSDP(msg);
		if (m_rtpMedia && !sdp) {
		    if (m_rtpForward && m_rtpLocalAddr)
			sdp = createSDP(m_rtpLocalAddr,m_rtpMedia);
		    if (!sdp)
			sdp = createRtpSDP(true);
		}
		m_tr->setAcknowledge(sdp);
		return true;
	    }
	    switch (m_reInviting) {
		case ReinviteNone:
		    if (!msg.getBoolValue(YSTRING("audio_changed")))
			break;
		    // if any side is forwarding RTP we shouldn't reach here
		    if (m_rtpForward || msg.getBoolValue(YSTRING("rtp_forward")))
			break;
		    // reINVITE for mute not supported yet
		    if (msg.getBoolValue(YSTRING("mute"),false))
			break;
		    // save current RTP parameters so we can try to revert later
		    m_revert.clearParams();
		    addRtpParams(m_revert);
		    // fall through
		case ReinviteRequest:
		    if (startClientReInvite(msg,(ReinviteRequest == m_reInviting)))
			return true;
		    TraceDebug(m_traceId,this,DebugMild,"Failed to start reINVITE, %s: %s [%p]",
			msg.getValue(YSTRING("error"),"unknown"),
			msg.getValue(YSTRING("reason"),"No reason"),this);
		    return false;
	    }
	}
	msg.setParam("error","nocall");
	return false;
    }

    if (m_rtpForward && m_tr2->isOutgoing() && !m_tr2->autoAck() && (m_tr2->getState() == SIPTransaction::Retrans)) {
	m_tr2->setAcknowledge(createPasstroughSDP(msg,true,m_rtpForward));
	detachTransaction2();
	return true;
    }

    if (!(m_tr2->isIncoming() && (m_tr2->getState() == SIPTransaction::Process))) {
	msg.setParam("error","failure");
	msg.setParam("reason","Incompatible Transaction State");
	return false;
    }
    if (*oper == YSTRING("notify")) {
	bool rtpSave = m_rtpForward;
	m_rtpForward = msg.getBoolValue(YSTRING("rtp_forward"),m_rtpForward);
	MimeSdpBody* sdp = createPasstroughSDP(msg,true,m_rtpForward);
	if (!sdp) {
	    m_rtpForward = rtpSave;
	    m_tr2->setResponse(500,"Server failed to build the SDP");
	    detachTransaction2();
	    return false;
	}
	if (m_rtpForward != rtpSave)
	    TraceDebug(m_traceId,this,DebugInfo,"RTP forwarding changed: %s -> %s",
		String::boolText(rtpSave),String::boolText(m_rtpForward));
	const SIPMessage* m1 = m_tr2->initialMessage();
	updateTarget(m1);
	SIPMessage* m = new SIPMessage(m1,200);
	m->setBody(sdp);
	copySipHeaders(*m,msg);
	m_tr2->setResponse(m);
	if (m_tr2->autoAck())
	    detachTransaction2();
	m->deref();
	return true;
    }
    else if (*oper == YSTRING("reject")) {
	m_tr2->setResponse(msg.getIntValue(YSTRING("error"),dict_errors,488),msg.getValue(YSTRING("reason")));
	detachTransaction2();
	return true;
    }
    return false;
}

bool YateSIPConnection::msgControl(Message& msg)
{
    bool ok = false;
    if (msg[YSTRING("operation")] == YSTRING("query")) {
	msg.setParam("sip_uri",m_uri);
	msg.setParam("sip_callid",callid());
	String tmp;
	tmp << "<" << m_dialog.localURI << ">";
	if (m_dialog.localTag)
	    tmp << ";tag=" << m_dialog.localTag;
	msg.setParam("sip_from",tmp);
	tmp.clear();
	tmp << "<" << m_dialog.remoteURI << ">";
	if (m_dialog.remoteTag)
	    tmp << ";tag=" << m_dialog.remoteTag;
	msg.setParam("sip_to",tmp);
	int32_t cseq = m_dialog.getLastCSeq();
	if (cseq >= 0)
	    msg.setParam("local_cseq",String(cseq));
	if (m_dialog.remoteCSeq >= 0)
	    msg.setParam("remote_cseq",String(m_dialog.remoteCSeq));
	ok = true;
    }
    return Channel::msgControl(msg) || ok;
}

void YateSIPConnection::endDisconnect(const Message& msg, bool handled)
{
    const String* reason = msg.getParam(YSTRING("reason"));
    if (!TelEngine::null(reason)) {
	int code = reason->toInteger(dict_errors);
	if (code >= 300 && code <= 699)
	    setReason(lookup(code,SIPResponses,*reason),code,driver());
	else
	    setReason(*reason,m_reasonCode,driver());
    }
    const char* sPrefix = msg.getValue(YSTRING("osip-prefix"));
    const char* mPrefix = msg.getValue(YSTRING("message-prefix"));
    if (!(sPrefix || mPrefix))
        return;
    paramMutex().lock();
    parameters().clearParams();
    if (sPrefix) {
	parameters().setParam("osip-prefix",sPrefix);
	parameters().copySubParams(msg,sPrefix,false);
    }
    if (mPrefix) {
	parameters().setParam("message-prefix",mPrefix);
	parameters().copySubParams(msg,mPrefix,false);
    }
    paramMutex().unlock();
}

void YateSIPConnection::statusParams(String& str)
{
    Channel::statusParams(str);
    if (m_line)
	str << ",line=" << m_line;
    if (m_user)
	str << ",user=" << m_user;
    if (m_rtpForward)
	str << ",forward=" << (m_sdpForward ? "sdp" : "rtp");
    str << ",inviting=" << (m_tr != 0);
}

bool YateSIPConnection::callPrerouted(Message& msg, bool handled)
{
    bool ok = Channel::callPrerouted(msg,handled);
    m_autoChangeParty = msg.getBoolValue(YSTRING("iautochangeparty"),m_autoChangeParty);
    if (msg.getBoolValue(YSTRING("ioutbound_party")))
	ok = setParty(msg,true,"i",String::empty(),0,true) && ok;
    return ok;
}

bool YateSIPConnection::callRouted(Message& msg)
{
    // try to disable RTP forwarding earliest possible
    if (m_rtpForward) {
	m_rtpForward = msg.getBoolValue(YSTRING("rtp_forward"));
	m_sdpForward = msg.getBoolValue(YSTRING("forward_sdp"),m_sdpForward);
    }
    setRfc2833(msg.getParam(YSTRING("rfc2833")));
    updateRtpNatAddress(&msg);
    Channel::callRouted(msg);
    Lock lock(driver());
    if (m_hungup || !m_tr)
	return false;
    if (m_tr->getState() == SIPTransaction::Process) {
	m_tr->setTransCount(msg.getIntValue(YSTRING("isip_trans_count"),-1));
	String s(msg.retValue());
	if (s.startSkip("sip/",false) && s && msg.getBoolValue(YSTRING("redirect"))) {
	    TraceDebug(m_traceId,this,DebugAll,"YateSIPConnection redirecting to '%s' [%p]",s.c_str(),this);
	    String tmp(msg.getValue(YSTRING("calledname")));
	    if (tmp) {
		MimeHeaderLine::addQuotes(tmp);
		tmp += " ";
	    }
	    s = tmp + "<" + s + ">";
	    int code = msg.getIntValue(YSTRING("reason"),dict_errors,302);
	    if ((code < 300) || (code > 399))
		code = 302;
	    SIPMessage* m = new SIPMessage(m_tr->initialMessage(),code);
	    m->addHeader("Contact",s);
	    tmp = msg.getValue(YSTRING("diversion"));
	    if (tmp.trimBlanks() && tmp.toBoolean(true)) {
		// if diversion is a boolean true use the dialog local URI
		if (tmp.toBoolean(false))
		    tmp = m_dialog.localURI;
		if (!(tmp.startsWith("<") && tmp.endsWith(">")))
		    tmp = "<" + tmp + ">";
		MimeHeaderLine* hl = new MimeHeaderLine("Diversion",tmp);
		tmp = msg.getValue(YSTRING("divert_reason"));
		if (tmp) {
		    MimeHeaderLine::addQuotes(tmp);
		    hl->setParam("reason",tmp);
		}
		tmp = msg.getValue(YSTRING("divert_privacy"));
		if (tmp) {
		    MimeHeaderLine::addQuotes(tmp);
		    hl->setParam("privacy",tmp);
		}
		tmp = msg.getValue(YSTRING("divert_screen"));
		if (tmp) {
		    MimeHeaderLine::addQuotes(tmp);
		    hl->setParam("screen",tmp);
		}
		int cnt = msg.getIntValue(YSTRING("divert_counter"));
		if ((cnt > 0) && (cnt < 99))
		    hl->setParam("counter",String(cnt));
		m->addHeader(hl);
	    }
	    copySipHeaders(*m,msg);
	    m_tr->setResponse(m);
	    m->deref();
	    m_byebye = false;
	    setReason("Redirected",code);
	    setStatus("redirected");
	    return false;
	}

	setFormatsExtra(msg,false);
	updateFormats(msg);
	if (msg.getBoolValue(YSTRING("progress"),s_progress))
	    m_tr->setResponse(183);
    }
    return true;
}

void YateSIPConnection::callAccept(Message& msg)
{
    m_user = msg.getValue(s_username);
    if (m_authBye)
	m_authBye = msg.getBoolValue(YSTRING("xsip_auth_bye"),true);
    if (m_rtpForward) {
	m_rtpForward = (msg[YSTRING("rtp_forward")] == YSTRING("accepted"));
	m_sdpForward = msg.getBoolValue(YSTRING("forward_sdp"),m_sdpForward);
    }
    if (m_tr && msg.getBoolValue("sdp_ack"))
	m_tr->autoAck(false);
    m_secure = m_secure && msg.getBoolValue(YSTRING("secure"),true);
    // Update dtmf methods from message
    m_checkAllowInfo = msg.getBoolValue(YSTRING("icheck_allow_info"),m_checkAllowInfo);
    m_missingAllowInfoDefVal = msg.getBoolValue(YSTRING("imissing_allow_info"),m_missingAllowInfoDefVal);
    m_honorDtmfDetect = msg.getBoolValue(YSTRING("ihonor_dtmf_detect"),m_honorDtmfDetect);
    String* meths = msg.getParam(YSTRING("idtmfmethods"));
    if (meths) {
	DtmfMethods old = m_dtmfMethods;
	m_dtmfMethods.set(*meths,&old);
    }
    // Reset INFO?
    if (m_checkAllowInfo && m_tr && m_dtmfMethods.hasMethod(DtmfMethods::Info)) {
	Lock lock(driver());
	if (m_tr && !infoAllowed(m_tr->initialMessage()))
	    m_dtmfMethods.reset(DtmfMethods::Info);
    }
    Channel::callAccept(msg);

    if ((m_reInviting == ReinviteNone) && !m_rtpForward && !isAnswered() &&
	msg.getBoolValue(YSTRING("autoreinvite"),false)) {
	// remember we want to switch to RTP forwarding when party answers
	m_reInviting = ReinvitePending;
	startPendingUpdate();
    }
}

void YateSIPConnection::callRejected(const char* error, const char* reason, const Message* msg)
{
    Channel::callRejected(error,reason,msg);
    setSilent(error);
    int code = lookup(error,dict_errors,500);
    if (code < 300 || code > 699)
	code = 500;
    Lock lock(driver());
    if (m_tr && (m_tr->getState() == SIPTransaction::Process)) {
	if ((code == 401) && (s_noAutoAuth != error)) {
	    String r;
	    m_tr->requestAuth(getGlobal(r,s_realm),m_domain,false);
	}
	else if (msg) {
	    SIPMessage* m = new SIPMessage(m_tr->initialMessage(),code,reason);
	    copySipHeaders(*m,*msg);
	    m->setBody(buildSIPBody(const_cast<Message&>(*msg),0,"message-iprefix"));
	    m_tr->setResponse(m);
	    m->deref();
	}
	else
	    m_tr->setResponse(code,reason);
    }
    setReason(reason,code);
}

// Start a client reINVITE transaction
bool YateSIPConnection::startClientReInvite(NamedList& msg, bool rtpForward)
{
    bool hadRtp = !m_rtpForward;
    bool forced = msg.getBoolValue(YSTRING("rtp_forced"));
    if (msg.getBoolValue(YSTRING("rtp_forward"),m_rtpForward) != rtpForward) {
	if (forced)
	    rtpForward = !rtpForward;
	else {
	    msg.setParam("error","failure");
	    msg.setParam("reason","Mismatched RTP forwarding");
	    return false;
	}
    }
    m_rtpForward = rtpForward;
    // this is the point of no return
    if (hadRtp && !forced)
	clearEndpoint();
    MimeSdpBody* sdp = 0;
    if (rtpForward)
	sdp = createPasstroughSDP(msg,false,true);
    else {
	updateSDP(msg);
	sdp = createRtpSDP(true);
    }
    if (!(sdp || msg.getBoolValue(YSTRING("sdp_ack")))) {
	msg.setParam("error","failure");
	msg.setParam("reason","Could not build the SDP");
	if (hadRtp) {
	    TraceDebug(m_traceId,this,DebugWarn,"Could not build SDP for reINVITE, hanging up [%p]",this);
	    disconnect("nomedia");
	}
	return false;
    }
    TraceDebug(m_traceId,this,DebugNote,"Initiating reINVITE (%s RTP before) [%p]",
	hadRtp ? "had" : "no",this);
    SIPMessage* m = createDlgMsg("INVITE");
    copySipHeaders(*m,msg);
    if (s_privacy)
	copyPrivacy(*m,msg);
    if (sdp)
	m->setBody(sdp);
    else
	m->addHeader(new MimeHeaderLine("Accept","application/sdp"));
    m_tr2 = plugin.ep()->engine()->addMessage(m,&m_autoChangeParty);
    if (m_tr2) {
	m_tr2->ref();
	m_tr2->setUserData(this);
	if (!sdp)
	    m_tr2->autoAck(false);
    }
    m->deref();
    return true;
}

// Emit pending update if possible, method is called with driver mutex hold
void YateSIPConnection::startPendingUpdate()
{
    Lock mylock(driver());
    if (m_hungup || m_tr || m_tr2 || (m_reInviting != ReinvitePending))
	return;
    if (m_rtpAddr.null()) {
	TraceDebug(m_traceId,this,DebugWarn,"Cannot start update, remote RTP address unknown [%p]",this);
	m_reInviting = ReinviteNone;
	return;
    }
    if (!m_rtpMedia) {
	TraceDebug(m_traceId,this,DebugWarn,"Cannot start update, remote media unknown [%p]",this);
	m_reInviting = ReinviteNone;
	return;
    }
    m_reInviting = ReinviteRequest;
    mylock.drop();

    Message msg("call.update");
    complete(msg);
    msg.addParam("operation","request");
    msg.addParam("rtp_forward","yes");
    msg.addParam("rtp_addr",m_rtpAddr);
    putMedia(msg);
    // if peer doesn't support updates fail the reINVITE
    if (!Engine::dispatch(msg)) {
	TraceDebug(m_traceId,this,DebugWarn,"Cannot start update by '%s', %s: %s [%p]",
	    getPeerId().c_str(),
	    msg.getValue(YSTRING("error"),"not supported"),
	    msg.getValue(YSTRING("reason"),"No reason provided"),this);
	m_reInviting = ReinviteNone;
    }
}

// Build the 'call.route' and NOTIFY messages needed by the transfer thread
// msg: 'call.route' message to create & fill
// sipNotify: NOTIFY message to create & fill
// sipRefer: received REFER message, refHdr: 'Refer-To' header
// refHdr: The 'Refer-To' header
// uri: The already parsed 'Refer-To' URI
// replaces: An already checked Replaces parameter from 'Refer-To' or
//  0 for unattended transfer
// If return false, msg and sipNotify are 0
bool YateSIPConnection::initTransfer(Message*& msg, SIPMessage*& sipNotify,
    const SIPMessage* sipRefer, const MimeHeaderLine* refHdr,
    const URI& uri, const MimeHeaderLine* replaces)
{
    // call.route
    msg = new Message("call.route");
    msg->addParam("id",getPeer()->id());
    if (m_billid)
	msg->addParam("billid",m_billid);
    if (m_user)
	msg->addParam(s_username,m_user);

    const MimeHeaderLine* sh = sipRefer->getHeader("To");                   // caller
    if (sh) {
	URI uriCaller(*sh);
	uriCaller.parse();
	msg->addParam("caller",uriCaller.getUser());
	msg->addParam("callername",uriCaller.getDescription());
    }

    if (replaces) {                                                        // called or replace
	const String* fromTag = replaces->getParam("from-tag");
	const String* toTag = replaces->getParam("to-tag");
	msg->addParam("transfer_callid",*replaces);
	msg->addParam("transfer_fromtag",c_safe(fromTag));
	msg->addParam("transfer_totag",c_safe(toTag));
    }
    else {
	msg->addParam("called",uri.getUser());
	msg->addParam("calledname",uri.getDescription());
    }

    sh = sipRefer->getHeader("Referred-By");                               // diverter
    URI referBy;
    if (sh)
	referBy = *sh;
    else
	referBy = m_dialog.remoteURI;
    msg->addParam("diverter",referBy.getUser());
    msg->addParam("divertername",referBy.getDescription());

    msg->addParam("reason","transfer");                                    // reason
    // NOTIFY
    String tmp;
    const MimeHeaderLine* co = sipRefer->getHeader("Contact");
    // TODO: Handle contact: it might require a different transport
    // If we need another transport and is a connected one, try to delay party creation:
    //  we won't need it if the transfer fails
    // Set notify party from received REFER?
    // Remember: createDlgMsg() sets the party from channel's party
    TraceDebug(m_traceId,this,DebugStub,"initTransfer. Possible incomplete NOTIFY party creation [%p]",this);
    if (co) {
	tmp = *co;
	static const Regexp r("^[^<]*<\\([^>]*\\)>.*$");
	if (tmp.matches(r))
	    tmp = tmp.matchString(1);
    }
    sipNotify = createDlgMsg("NOTIFY",tmp);
    if (!sipNotify->getParty() && plugin.ep())
	plugin.ep()->buildParty(sipNotify);
    if (!sipNotify->getParty()) {
	DDebug(this,DebugAll,"initTransfer. Could not create party to send NOTIFY [%p]",this);
	TelEngine::destruct(sipNotify);
	TelEngine::destruct(msg);
	return false;
    }
    copySipHeaders(*msg,*sipRefer);
    sipNotify->complete(plugin.ep()->engine());
    sipNotify->addHeader("Event","refer");
    sipNotify->addHeader("Subscription-State","terminated;reason=noresource");
    sipNotify->addHeader("Contact",sipRefer->uri);
    return true;
}

// Decode an application/isup body into 'msg' if configured to do so
bool YateSIPConnection::decodeIsupBody(Message& msg, MimeBody* body)
{
    return doDecodeIsupBody(this,msg,body);
}

// Build the body of a SIP message from an engine message
MimeBody* YateSIPConnection::buildSIPBody(Message& msg, MimeSdpBody* sdp, const char* prefixName)
{
    return doBuildSIPBody(this,msg,sdp,prefixName);
}

// Build the body of a hangup SIP message from disconnect parameters
MimeBody* YateSIPConnection::buildSIPBody()
{
    Message msg("");
    paramMutex().lock();
    msg.copyParams(parameters());
    paramMutex().unlock();
    return doBuildSIPBody(this,msg,0,"message-prefix");
}

// Update NAT address from params or transport
void YateSIPConnection::updateRtpNatAddress(NamedList* params)
{
    if (params)
	m_rtpNatAddr = params->getValue(YSTRING("nat_address"),m_rtpNatAddr);
    if (!m_rtpNatAddr) {
	YateSIPTransport* trans = transport(true);
	if (trans) {
	    trans->rtpNatAddr(m_rtpNatAddr);
	    TelEngine::destruct(trans);
	}
    }
    TraceDebug(m_traceId,this,DebugAll,"NAT address is '%s' [%p]",m_rtpNatAddr.c_str(),this);
}

// Process allow list. Get INFO support
bool YateSIPConnection::infoAllowed(const SIPMessage* msg)
{
    static const String s_infoName = "INFO";
    if (!msg)
	return m_missingAllowInfoDefVal;
    bool ok = false;
    const MimeHeaderLine* hdr = msg->getHeader("Allow");
    if (hdr) {
	ObjList* allows = hdr->split(',');
	for (ObjList* o = allows->skipNull(); o; o = o->skipNext()) {
	    String* s = static_cast<String*>(o->get());
	    s->trimBlanks().toUpper();
	    if (*s == s_infoName) {
		ok = true;
		break;
	    }
	}
	TelEngine::destruct(allows);
    }
    else
	ok = m_missingAllowInfoDefVal;
    XDebug(this,DebugAll,"infoAllowed: info=%u [%p]",ok,this);
    return ok;
}

// Send tone(s) using method
bool YateSIPConnection::sendTone(Message& msg, const char* tone, int meth, bool& retVal)
{
    bool ok = false;
    if (meth == DtmfMethods::Info) {
	// Send INFO only if initial transaction finished
	if (!m_tr) {
	    const char* t = tone;
	    for (; t && *t; t++) {
		char c = *t;
		for (int j = 0; j <= 16; j++) {
		    if (s_dtmfs[j] != c)
			continue;
		    SIPMessage* m = createDlgMsg("INFO");
		    if (!m)
			break;
		    copySipHeaders(*m,msg);
		    String tmp;
		    tmp << "Signal=" << j << "\r\n";
		    m->setBody(new MimeStringBody("application/dtmf-relay",tmp));
		    plugin.ep()->engine()->addMessage(m,&m_autoChangeParty);
		    m->deref();
		    break;
		}
	    }
	    retVal = true;
	    ok = true;
	}
    }
    else if (meth == DtmfMethods::Rfc2833 || meth == DtmfMethods::Inband) {
	// RFC2833 and inband require media to be started
	if (m_rtpMedia && (m_mediaStatus == MediaStarted)) {
	    ObjList* l = m_rtpMedia->find("audio");
	    const SDPMedia* m = static_cast<const SDPMedia*>(l ? l->get() : 0);
	    if (meth == DtmfMethods::Rfc2833) {
		ok = m && m->rfc2833().toBoolean(true);
		if (ok)
		    msg.setParam("targetid",m->id());
	    }
	    else if (m) {
		ok = dtmfInband(tone);
		retVal = ok;
	    }
	}
    }
    XDebug(this,ok ? DebugAll : DebugNote,"sendTone(%s) meth=%s (%d) ok=%u [%p]",
	tone,lookup(meth,DtmfMethods::s_methodName),meth,ok,this);
    return ok;
}


YateSIPLine::YateSIPLine(const String& name)
    : String(name), Mutex(true,"YateSIPLine"), CallAccount(this),
      YateSIPPartyHolder(&plugin),
      m_resend(0), m_keepalive(0), m_interval(0), m_alive(0),
      m_flags(-1), m_trans(-1), m_tr(0), m_marked(false), m_valid(false),
      m_localPort(0), m_partyPort(0), m_localDetect(false),
      m_keepTcpOffline(s_lineKeepTcpOffline),
      m_matchPort(true), m_matchUser(true), m_forceNotify(false)
{
    m_partyMutex = this;
    DDebug(&plugin,DebugInfo,"YateSIPLine::YateSIPLine('%s') [%p]",c_str(),this);
    s_lines.append(this);
}

YateSIPLine::~YateSIPLine()
{
    DDebug(&plugin,DebugInfo,"YateSIPLine::~YateSIPLine() '%s' [%p]",c_str(),this);
    s_lines.remove(this,false);
    logout();
}

bool YateSIPLine::matchInbound(const String& addr, int port, const String& user) const
{
    if (m_matchPort && port && (getPartyPort() != port))
	return false;
    if (getPartyAddr() != addr)
	return false;
    if (m_matchUser && user && (getUserName() != user))
	return false;
    return true;
}

void YateSIPLine::setupAuth(SIPMessage* msg) const
{
    if (msg)
	msg->setAutoAuth(getAuthName(),m_password);
}

void YateSIPLine::setValid(bool valid, const char* reason, const char* error)
{
    DDebug(&plugin,DebugInfo,"YateSIPLine(%s) setValid(%u,%s) current=%u [%p]",
	c_str(),valid,reason,m_valid,this);
    if ((m_valid == valid) && !(m_forceNotify || reason))
	return;
    m_valid = valid;
    if (m_forceNotify || (m_registrar && m_username)) {
	Message* m = new Message("user.notify");
	m->addParam("account",*this);
	m->addParam("protocol","sip");
	m->addParam(s_username,m_username);
	if (m_domain)
	    m->addParam("domain",m_domain);
	m->addParam("registered",String::boolText(valid));
	m->addParam("reason",reason,false);
	m->addParam("error",error,false);
	Engine::enqueue(m);
    }
}

void YateSIPLine::changing()
{
    // we need to log out before any parameter changes
    logout();
}

SIPMessage* YateSIPLine::buildRegister(int expires)
{
    String exp(expires);
    const char* scheme = sips() ? "sips:" : "sip:";
    String tmp = scheme;
    SocketAddr::appendAddr(tmp,m_registrar);
    SIPMessage* m = new SIPMessage("REGISTER",tmp);
    setSipParty(m,this);
    if (!m->getParty()) {
	Debug(&plugin,DebugWarn,"Could not create party for '%s' [%p]",
	    m_registrar.c_str(),this);
	m->destruct();
	return 0;
    }
    tmp.clear();
    if (m_display)
	tmp = MimeHeaderLine::quote(m_display) + " ";
    tmp << "<" << scheme;
    tmp << m_username << "@";
    m->getParty()->appendAddr(tmp,true);
    tmp << ">";
    m->addHeader("Contact",tmp);
    m->addHeader("Expires",exp);
    tmp = "<";
    tmp << scheme << m_username << "@";
    SocketAddr::appendAddr(tmp,domain()) << ">";
    m->addHeader("To",tmp);
    if (m_callid)
	m->addHeader("Call-ID",m_callid);
    if (!m_seq) {
	m_seq = new SIPSequence(plugin.ep()->engine()->getNextCSeq() - 1);
	m_seq->deref();
    }
    m->setSequence(m_seq);
    m->complete(plugin.ep()->engine(),m_username,domain(),0,m_flags);
    if (m_display) {
	MimeHeaderLine* hl = const_cast<MimeHeaderLine*>(m->getHeader("From"));
	if (hl) {
	    String display = m_display;
	    MimeHeaderLine::addQuotes(display);
	    *hl = display + " " + *hl;
	}
    }
    copySipHeaders(*m,registerParams());
    return m;
}

void YateSIPLine::login()
{
    m_keepalive = 0;
    if (m_registrar.null() || m_username.null()) {
	logout();
	setValid(true);
	if (m_alive)
	    keepalive();
	return;
    }
    DDebug(&plugin,DebugInfo,"YateSIPLine '%s' logging in [%p]",c_str(),this);
    clearTransaction();
    // prepare a sane resend interval, just in case something goes wrong
    int interval = m_interval / 2;
    if (interval) {
	if (interval < 30)
	    interval = 30;
	else if (interval > 600)
	    interval = 600;
	m_resend = interval*(int64_t)1000000 + Time::now();
    }

    buildParty(false);
    if (m_localAddr && !m_localDetect) {
	if (!m_localPort)
	    m_localPort = sipPort(protocol() != Tls);
	SIPParty* p = party();
	if (p) {
	    p->setAddr(m_localAddr,m_localPort,true);
	    TelEngine::destruct(p);
	}
    }
    // Wait for the transport to become valid
    Lock lckParty(m_partyMutex);
    YateSIPTransport* trans = transport();
    if (!(trans && trans->valid())) {
	DDebug(&plugin,DebugInfo,
	    "YateSIPLine '%s' delaying login (transport not ready) [%p]",c_str(),this);
	return;
    }
    lckParty.drop();
    SIPMessage* m = buildRegister(m_interval);
    if (!m) {
	setValid(false);
	if (!m_keepTcpOffline)
	    setParty();
	return;
    }

    if (m_localDetect) {
	Lock lck(m->getParty()->mutex());
	if (m_localAddr.null())
	    m_localAddr = m->getParty()->getLocalAddr();
	if (!m_localPort)
	    m_localPort = m->getParty()->getLocalPort();
    }

    DDebug(&plugin,DebugInfo,"YateSIPLine '%s' emiting %p [%p]",
	c_str(),m,this);
    m_tr = plugin.ep()->engine()->addMessage(m);
    if (m_tr) {
	m_tr->ref();
	m_tr->setUserData(this);
	m_tr->setTransCount(m_trans);
	if (m_callid.null())
	    m_callid = m_tr->getCallID();
    }
    m->deref();
}

void YateSIPLine::logout(bool sendLogout, const char* reason)
{
    m_resend = 0;
    m_keepalive = 0;
    if (sendLogout)
	sendLogout = m_valid && m_registrar && m_username;
    clearTransaction();
    setValid(false,reason);
    if (m_localDetect) {
	m_localAddr.clear();
	m_localPort = 0;
    }
    if (sendLogout) {
	DDebug(&plugin,DebugInfo,"YateSIPLine '%s' logging out [%p]",c_str(),this);
	buildParty(false);
	SIPMessage* m = buildRegister(0);
	m_partyAddr.clear();
	m_partyPort = 0;
	if (!m)
	    return;
	plugin.ep()->engine()->addMessage(m);
	m->deref();
    }
    m_callid.clear();
    m_seq = 0;
}

bool YateSIPLine::process(SIPEvent* ev)
{
    DDebug(&plugin,DebugInfo,"YateSIPLine::process(%p) %s [%p]",
	ev,SIPTransaction::stateName(ev->getState()),this);
    if (ev->getTransaction() != m_tr)
	return false;
    if (ev->getState() == SIPTransaction::Cleared) {
	clearTransaction();
	setValid(false,"timeout");
	if (!m_keepTcpOffline)
	    setParty();
	m_keepalive = 0;
	Debug(&plugin,DebugWarn,"SIP line '%s' logon timeout",c_str());
	return false;
    }
    const SIPMessage* msg = ev->getMessage();
    if (!(msg && msg->isAnswer()))
	return false;
    if (ev->getState() != SIPTransaction::Process)
	return false;
    clearTransaction();
    DDebug(&plugin,DebugAll,"YateSIPLine '%s' got answer %d [%p]",
	c_str(),msg->code,this);
    int exp = m_interval;
    switch (msg->code) {
	case 200:
	    {
		const MimeHeaderLine* hl = msg->getHeader("Contact");
		if (hl) {
		    const NamedString* e = hl->getParam("expires");
		    if (e)
			exp = e->toInteger(exp);
		    else
			hl = 0;
		}
		if (!hl) {
		    hl = msg->getHeader("Expires");
		    if (hl)
			exp = hl->toInteger(exp);
		}
		if (exp <= 60)
		    exp = 60;
		else if ((exp > m_interval + 10) || (exp < m_interval - 10)) {
		    Debug(&plugin,DebugNote,"SIP line '%s' changed expire interval from %d to %d",
			c_str(),m_interval,exp);
		    m_interval = exp;
		}
		// Reset transport timeout from expires or flow timer
		resetTransportIdle(msg,m_alive ? m_alive : m_interval);
	    }
	    // re-register at 3/4 of the expire interval
	    m_resend = exp * (int64_t)750000 + Time::now();
	    m_keepalive = m_alive ? m_alive*(int64_t)1000000 + Time::now() : 0;
	    detectLocal(msg);
	    if (msg->getParty())
		msg->getParty()->getAddr(m_partyAddr,m_partyPort,false);
	    setValid(true);
	    Debug(&plugin,DebugCall,"SIP line '%s' logon success to %s",
		c_str(),SocketAddr::appendTo(m_partyAddr,m_partyPort).c_str());
	    break;
	default:
	    // detect local address even from failed attempts - helps next time
	    detectLocal(msg);
	    setValid(false,msg->reason,lookup(msg->code,dict_errors,String(msg->code)));
	    if (!m_keepTcpOffline)
		setParty();
	    Debug(&plugin,DebugWarn,"SIP line '%s' logon failure %d: %s",
		c_str(),msg->code,msg->reason.safe());
    }
    return false;
}

void YateSIPLine::detectLocal(const SIPMessage* msg)
{
    if (!(m_localDetect && msg->getParty()))
	return;
    String laddr = m_localAddr;
    int lport = m_localPort;
    MimeHeaderLine* hl = const_cast<MimeHeaderLine*>(msg->getHeader("Via"));
    if (hl) {
	const NamedString* par = hl->getParam("received");
	if (par)
	    getAddrCheckIPv6(laddr,*par);
	par = hl->getParam("rport");
	if (par) {
	    int port = par->toInteger(0,10);
	    if (port > 0)
		lport = port;
	}
    }
    Lock lckParty(msg->getParty()->mutex());
    if (laddr.null())
	laddr = msg->getParty()->getLocalAddr();
    if (!lport)
	lport = msg->getParty()->getLocalPort();
    lckParty.drop();
    if ((laddr != m_localAddr) || (lport != m_localPort)) {
	Debug(&plugin,DebugInfo,"Detected local address %s for SIP line '%s'",
	    SocketAddr::appendTo(laddr,lport).c_str(),c_str());
	m_localAddr = laddr;
	m_localPort = lport;
	// since local address changed register again in 2 seconds
	m_resend = 2000000 + Time::now();
	// Update now party local ip/port
	SIPParty* p = party();
	if (p) {
	    p->setAddr(m_localAddr,m_localPort,true);
	    TelEngine::destruct(p);
	}
    }
}

void YateSIPLine::keepalive()
{
    if (!m_party)
        return;
    Lock lock(m_partyMutex);
    if (!m_party || m_party->isReliable())
        return;
    YateUDPParty* udp = static_cast<YateUDPParty*>(m_party);
    YateSIPUDPTransport* t = static_cast<YateSIPUDPTransport*>(m_party->getTransport());
    if (t) {
	Debug(&plugin,DebugAll,"Sending UDP keepalive to %s for '%s'",
	    udp->addr().addr().c_str(),c_str());
	t->send("\r\n",2,udp->addr());
    }
    m_keepalive = m_alive ? m_alive*(int64_t)1000000 + Time::now() : 0;
}

void YateSIPLine::timer(const Time& when)
{
    if (!m_resend || (m_resend > when)) {
	if (m_keepalive && (m_keepalive <= when))
	    keepalive();
	return;
    }
    m_resend = 0;
    login();
}

void YateSIPLine::clearTransaction()
{
    if (m_tr) {
	DDebug(&plugin,DebugInfo,"YateSIPLine clearing transaction %p [%p]",
	    m_tr,this);
	m_tr->setUserData(0);
	m_tr->deref();
	m_tr = 0;
    }
}

bool YateSIPLine::update(const Message& msg)
{
    DDebug(&plugin,DebugInfo,"YateSIPLine::update() '%s' [%p]",c_str(),this);
    const String& oper = msg[YSTRING("operation")];
    if (oper == YSTRING("logout")) {
	logout();
	setParty();
	return true;
    }
    pickAccountParams(msg);
    bool chg = sips(msg.getBoolValue(YSTRING("sips")));
    bool transChg = updateProto(msg);
    transChg = updateLocalAddr(msg) || transChg;
    chg = chg || transChg;
    chg = change(m_registrar,msg.getValue(YSTRING("registrar"),msg.getValue(YSTRING("server")))) || chg;
    chg = change(m_username,msg.getValue(s_username)) || chg;
    chg = change(m_authname,msg.getValue(YSTRING("authname"))) || chg;
    chg = change(m_password,msg.getValue(YSTRING("password"))) || chg;
    chg = change(m_domain,msg.getValue(YSTRING("domain"))) || chg;
    chg = change(m_flags,msg.getIntValue(YSTRING("xsip_flags"),-1)) || chg;
    m_trans = msg.getIntValue(YSTRING("xsip_trans_count"),-1);
    m_display = msg.getValue(YSTRING("description"));
    m_interval = msg.getIntValue(YSTRING("interval"),600);
    m_matchPort = msg.getBoolValue(YSTRING("match_port"),true);
    m_matchUser = msg.getBoolValue(YSTRING("match_user"),true);
    String tmp(msg.getValue(YSTRING("localaddress"),s_auto_nat ? "auto" : ""));
    // "auto", "yes", "enable" or "true" to autodetect local address
    m_localDetect = (tmp == YSTRING("auto")) || tmp.toBoolean(false);
    if (!m_localDetect) {
	// "no", "disable" or "false" to just disable detection
	if (!tmp.toBoolean(true))
	    tmp.clear();
	int port = 0;
	if (tmp) {
	    SocketAddr::split(tmp,tmp,port);
	    if (!port)
		port = sipPort(protocol() != Tls);
	}
	chg = change(m_localAddr,tmp) || chg;
	chg = change(m_localPort,port) || chg;
    }
    String raddr;
    int rport = 0;
    const String& out = msg[YSTRING("outbound")];
    if (out)
	SocketAddr::split(out,raddr,rport);
    if (!raddr && m_registrar)
	SocketAddr::split(m_registrar,raddr,rport);
    if (!raddr)
	raddr = m_transRemoteAddr;
    if (rport <= 0)
	rport = sipPort(protocol() != Tls);
    bool rAddrChg = change(m_transRemoteAddr,raddr);
    rAddrChg = change(m_transRemotePort,rport) || rAddrChg;
    if (rAddrChg) {
	transChg = true;
	chg = true;
    }
    m_alive = msg.getIntValue(YSTRING("keepalive"),((m_localDetect && m_registrar) ? 25 : 0));
    // (Re)Set party
    if (transChg || !m_party) {
	// Logout if not already done
	if (!chg) {
	    chg = true;
	    logout();
	}
	buildParty();
	if (!m_party)
	    Debug(&plugin,DebugNote,"Line '%s' failed to set party [%p]",c_str(),this);
    }
    m_forceNotify = (protocol() == Tcp) || (protocol() == Tls);
    // if something changed we logged out so try to climb back
    if (chg || (oper == YSTRING("login")))
	login();
    return chg;
}

// Transport status changed notification
void YateSIPLine::transportChangedStatus(int stat, const String& reason)
{
    Debug(&plugin,DebugAll,"Line '%s' transport status is %s",
	c_str(),YateSIPTransport::statusName(stat));
    YateSIPPartyHolder::transportChangedStatus(stat,reason);
    YateSIPTransport* trans = transport();
    if (stat == YateSIPTransport::Terminated) {
	u_int64_t old = m_resend;
	logout(trans && trans->udpTransport(),reason);
	setParty();
	// Try to re-login if set to do that
	m_resend = old;
    }
    else if (stat == YateSIPTransport::Connected) {
	if (trans) {
	    Lock lock(trans);
	    m_localAddr = trans->local().host();
	    m_localPort = trans->local().port();
	}
	// Pending login
	if (trans && m_resend)
	    login();
	else if (trans && trans->tcpTransport())
	    setValid(true);
    }
}

YateSIPGenerate::YateSIPGenerate(SIPMessage* m, int tries)
    : m_tr(0), m_code(0)
{
    m_tr = plugin.ep()->engine()->addMessage(m);
    if (m_tr) {
	m_tr->ref();
	m_tr->setUserData(this);
	m_tr->setTransCount(tries);
    }
    m->deref();
}

YateSIPGenerate::~YateSIPGenerate()
{
    clearTransaction();
}

bool YateSIPGenerate::process(SIPEvent* ev)
{
    DDebug(&plugin,DebugInfo,"YateSIPGenerate::process(%p) %s [%p]",
	ev,SIPTransaction::stateName(ev->getState()),this);
    if (ev->getTransaction() != m_tr)
	return false;
    if (ev->getState() == SIPTransaction::Cleared) {
	clearTransaction();
	return false;
    }
    SIPMessage* msg = ev->getMessage();
    if (!(msg && msg->isAnswer()))
	return false;
    if (ev->getState() != SIPTransaction::Process)
	return false;
    m_msg = msg;
    clearTransaction();
    Debug(&plugin,DebugAll,"YateSIPGenerate got answer %d [%p]",
	m_code,this);
    return false;
}

void YateSIPGenerate::clearTransaction()
{
    if (m_tr) {
	DDebug(&plugin,DebugInfo,"YateSIPGenerate clearing transaction %p [%p]",
	    m_tr,this);
	m_code = m_tr->getResponseCode();
	m_tr->setUserData(0);
	m_tr->deref();
	m_tr = 0;
    }
}


bool UserHandler::received(Message &msg)
{
    String tmp(msg.getValue(YSTRING("protocol")));
    if (tmp != YSTRING("sip"))
	return false;
    tmp = msg.getValue(YSTRING("account"));
    if (tmp.null())
	return false;
    YateSIPLine* line = plugin.findLine(tmp);
    if (!line)
	line = new YateSIPLine(tmp);
    line->update(msg);
    return true;
}


bool SipHandler::received(Message &msg)
{
    const char* method = msg.getValue(YSTRING("method"));
    return method && plugin.sendMethod(msg,method);
}


void SIPDriver::epTerminated(YateSIPEndPoint* ep)
{
    if (!(ep && ep == m_endpoint))
	return;
    Debug(this,s_engineHalt ? DebugAll : DebugWarn,"Endpoint stopped");
    m_endpoint = 0;
}

YateSIPConnection* SIPDriver::findCall(const String& callid, bool incRef)
{
    XDebug(this,DebugAll,"SIPDriver finding call '%s'",callid.c_str());
    Lock mylock(this);
    ObjList* l = channels().skipNull();
    for (; l; l = l->skipNext()) {
	YateSIPConnection* c = static_cast<YateSIPConnection*>(l->get());
	if (c->callid() == callid)
	    return (incRef ? c->ref() : c->alive()) ? c : 0;
    }
    return 0;
}

YateSIPConnection* SIPDriver::findDialog(const SIPDialog& dialog, bool incRef)
{
    XDebug(this,DebugAll,"SIPDriver finding dialog '%s'",dialog.c_str());
    Lock mylock(this);
    ObjList* l = channels().skipNull();
    for (; l; l = l->skipNext()) {
	YateSIPConnection* c = static_cast<YateSIPConnection*>(l->get());
	if (c->dialog() &= dialog)
	    return (incRef ? c->ref() : c->alive()) ? c : 0;
    }
    return 0;
}

YateSIPConnection* SIPDriver::findDialog(const String& dialog, const String& fromTag,
    const String& toTag, bool incRef)
{
    XDebug(this,DebugAll,"SIPDriver finding dialog '%s' fromTag='%s' toTag='%s'",
	dialog.c_str(),fromTag.c_str(),toTag.c_str());
    Lock mylock(this);
    for (ObjList* o = channels().skipNull(); o; o = o->skipNext()) {
	YateSIPConnection* c = static_cast<YateSIPConnection*>(o->get());
	if (c->isDialog(dialog,fromTag,toTag))
	    return (incRef ? c->ref() : c->alive()) ? c : 0;
    }
    return 0;
}

// find line by name
YateSIPLine* SIPDriver::findLine(const String& line) const
{
    if (line.null())
	return 0;
    ObjList* l = s_lines.find(line);
    return l ? static_cast<YateSIPLine*>(l->get()) : 0;
}

// find line by party address and port
YateSIPLine* SIPDriver::findLine(const String& addr, int port, const String& user, SIPParty* party)
{
    YateSIPTCPTransport* tr = YOBJECT(YateSIPTCPTransport,party);
    if (tr && !tr->outgoing())
	tr = 0;
    bool matchAddr = port && addr;
    if (!(tr || matchAddr))
	return 0;
    Lock mylock(this);
    ObjList* l = s_lines.skipNull();
    for (; l; l = l->skipNext()) {
	YateSIPLine* sl = static_cast<YateSIPLine*>(l->get());
	if (tr && sl->isTransport(tr))
	    return sl;
	if (!matchAddr)
	    continue;
	if (sl->matchInbound(addr,port,user))
	    return sl;
	if (sl->getPartyPort() && (sl->getPartyPort() == port) && (sl->getPartyAddr() == addr)) {
	    if (user && (sl->getUserName() != user))
		continue;
	    return sl;
	}
    }
    return 0;
}

// Drop channels belonging using a given transport
// Return the number of disconnected channels
unsigned int SIPDriver::transportTerminated(YateSIPTransport* trans)
{
    unsigned int n = 0;
    lock();
    ListIterator iter(channels());
    while (true) {
	RefPointer<YateSIPConnection> conn = static_cast<YateSIPConnection*>(iter.get());
	unlock();
	if (!conn)
	    break;
	if (conn->isTransport(trans)) {
	    Debug(this,DebugNote,"Disconnecting '%s': transport terminated",
		conn->id().c_str());
	    n++;
	    conn->disconnect("notransport");
	}
	conn = 0;
	lock();
    }
    return n;
}

// check if a line is either empty or valid (logged in or no registrar)
bool SIPDriver::validLine(const String& line)
{
    if (line.null())
	return true;
    YateSIPLine* l = findLine(line);
    return l && l->valid();
}

bool SIPDriver::received(Message& msg, int id)
{
    if (id == Timer) {
	ObjList* l = s_lines.skipNull();
	for (; l; l = l->skipNext())
	    static_cast<YateSIPLine*>(l->get())->timer(msg.msgTime());
    }
    else if (id == Stop) {
	s_engineStop++;
	dropAll(msg);
	m_endpoint->cancelListener();
	// Logout lines on first handle
	// Delay engine.halt until all lines logged out, we have no more channels
	//  and there are no more transactions in initial state
	// This will give some time to TCP transports to send pending data
	bool noHalt = false;
	for (ObjList* o = s_lines.skipNull(); o; o = o->skipNext()) {
	    YateSIPLine* line = static_cast<YateSIPLine*>(o->get());
	    noHalt = noHalt || line->valid();
	    if (s_engineStop == 1)
		line->logout();
	}
	if (!noHalt) {
	    Lock lock(this);
	    noHalt = (0 != channels().skipNull());
	}
	if (!noHalt)
	    noHalt = m_endpoint->engine()->hasInitialTransaction();
	Debug(this,DebugAll,"Returning %s from %s handler",String::boolText(noHalt),msg.c_str());
	return noHalt;
    }
    else if (id == Halt) {
	s_engineHalt = true;
	dropAll(msg);
	channels().clear();
	s_lines.clear();
	// Clear transactions: they keep references to parties and transports
	m_endpoint->engine()->clearTransactions();
	m_endpoint->clearUdpTransports("Exiting");
	// Wait for transports to terminate
	unsigned int n = 100;
	while (--n) {
	    Lock lck(m_endpoint->m_mutex);
	    if (!m_endpoint->m_transports.skipNull())
		break;
	    lck.drop();
	    Thread::idle();
	}
	m_endpoint->m_mutex.lock();
	n = m_endpoint->m_transports.count();
	if (n)
	    Debug(this,DebugCrit,"Exiting with %u transports in queue",n);
	m_endpoint->m_mutex.unlock();
	m_endpoint->cancel();
    }
    else if (id == Status) {
	String target = msg.getValue(YSTRING("module"));
	if (target && target.startsWith(name(),true) && !target.startsWith(prefix())) {
	    msgStatus(msg);
	    return false;
	}
    }
    else if (id == Start) {
	s_engineStart = true;
	s_sslClientAvailable = socketSsl(0,false);
    }
    else if (id == MsgExecute) {
	const String& dest = msg[YSTRING("callto")];
	if (dest.startsWith(prefix()))
	    return sendMethod(msg,"MESSAGE",true,dest.substr(prefix().length()));
	return false;
    }
    else if (id == Help)
	return onHelp(msg);
    return Driver::received(msg,id);
}

bool SIPDriver::hasLine(const String& line) const
{
    return line && findLine(line);
}

bool SIPDriver::msgExecute(Message& msg, String& dest)
{
    if (!msg.userData()) {
	Debug(this,DebugWarn,"SIP call found but no data channel!");
	return false;
    }
    const String& line = msg["line"];
    if (!validLine(line)) {
	// asked to use a line but it's not registered
	msg.setParam("error","offline");
	return false;
    }
    YateSIPConnection* conn = new YateSIPConnection(msg,dest,msg.getValue(YSTRING("id")));
    if (msg.getBoolValue(YSTRING("stop_call"),false)) {
	conn->destruct();
	return true;
    }
    conn->initChan();
    if (conn->getTransaction()) {
	CallEndpoint* ch = YOBJECT(CallEndpoint,msg.userData());
	if (ch && conn->connect(ch,msg.getValue(YSTRING("reason")))) {
	    conn->callConnect(msg);
	    msg.setParam("peerid",conn->id());
	    msg.setParam("targetid",conn->id());
	    conn->deref();
	    return true;
	}
    }
    conn->destruct();
    return false;
}

SIPDriver::SIPDriver()
    : Driver("sip","varchans"),
      m_parser("sip","SIP Call"),
      m_endpoint(0)
{
    Output("Loaded module SIP Channel");
    m_parser.debugChain(this);
}

SIPDriver::~SIPDriver()
{
    Output("Unloading module SIP Channel");
    TelEngine::destruct(s_authCopyHeader);
}

void SIPDriver::initialize()
{
    Output("Initializing module SIP Channel");
    if (s_engineStart)
	s_sslClientAvailable = socketSsl(0,false);
    s_cfg = Engine::configFile("ysipchan");
    s_globalMutex.lock();
    s_cfg.load();
    NamedList* general = s_cfg.getSection("general");
    TelEngine::destruct(s_authCopyHeader);
    if (general) {
	String* dtmfMethods = general->getParam("dtmfmethods");
	if (dtmfMethods) {
	    if (!s_dtmfMethods.set(*dtmfMethods,0))
		s_dtmfMethods.printMethods(this,DebugConf,*dtmfMethods);
	}
	else {
	    s_dtmfMethods.setDefault();
	    s_dtmfMethods.getDeprecatedDtmfMethod(*general,"dtmfinfo",DtmfMethods::Info,&s_warnDtmfInfoCfg);
	    s_dtmfMethods.getDeprecatedDtmfMethod(*general,"dtmfinband",DtmfMethods::Inband,&s_warnDtmfInbandCfg);
	}
	const String& tmp = (*general)[YSTRING("auth_copy_headers")];
	if (tmp) {
	    s_authCopyHeader = tmp.split(',',false);
	    ObjList* o = s_authCopyHeader->skipNull();
	    if (o)
		for (; o; o = o->skipNext())
		    (static_cast<String*>(o->get()))->toLower();
	    else
		TelEngine::destruct(s_authCopyHeader);
	}
    }
    else
	s_dtmfMethods.setDefault();
    s_globalMutex.unlock();
    s_warnPacketUDP = true;
    s_checkAllowInfo = s_cfg.getBoolValue("general","check_allow_info",true);
    s_missingAllowInfoDefVal = s_cfg.getBoolValue("general","missing_allow_info",true);
    s_honorDtmfDetect = s_cfg.getBoolValue("general","honor_dtmf_detect",true);
    s_maxForwards = s_cfg.getIntValue("general","maxforwards",20);
    s_congRetry = s_cfg.getIntValue("general","congestion_retry",30,10,600);
    s_floodEvents = s_cfg.getIntValue("general","floodevents",100);
    s_floodProtection = s_cfg.getBoolValue("general","floodprotection",true);
    s_privacy = s_cfg.getBoolValue("general","privacy");
    s_auto_nat = s_cfg.getBoolValue("general","nat",true);
    s_progress = s_cfg.getBoolValue("general","progress",false);
    s_start_rtp = s_cfg.getBoolValue("general","rtp_start",false);
    s_multi_ringing = s_cfg.getBoolValue("general","multi_ringing",false);
    s_refresh_nosdp = s_cfg.getBoolValue("general","refresh_nosdp",true);
    s_update_target = s_cfg.getBoolValue("general","update_target",false);
    s_update_verify = s_cfg.getBoolValue("general","update_verify",false);
    s_preventive_bye = s_cfg.getBoolValue("general","preventive_bye",true);
    s_ignoreVia = s_cfg.getBoolValue("general","ignorevia",true);
    s_ipv6 = s_cfg.getBoolValue("general","ipv6_support",false);
    if (s_ipv6 && !SocketAddr::supports(SocketAddr::IPv6)) {
	Debug(this,DebugConf,"Ignoring IPv6 support enable: not supported");
	s_ipv6 = false;
    }
    s_printMsg = s_cfg.getBoolValue("general","printmsg",true);
    s_tcpMaxpkt = getMaxpkt(s_cfg.getIntValue("general","tcp_maxpkt",4096),4096);
    s_lineKeepTcpOffline = s_cfg.getBoolValue("general","line_keeptcpoffline",!Engine::clientMode());
    s_defEncoding = s_cfg.getIntValue("general","body_encoding",SipHandler::s_bodyEnc,SipHandler::BodyBase64);
    s_gen_async = s_cfg.getBoolValue("general","async_generic",true);
    s_sipt_isup = s_cfg.getBoolValue("sip-t","isup",false);
    s_enable_transfer = s_cfg.getBoolValue("general","transfer",!Engine::clientMode());
    s_enable_options = s_cfg.getBoolValue("options","enable",
	s_cfg.getBoolValue("general","options",true));
    s_enable_message = s_cfg.getBoolValue("message","enable",false);
    s_auth_message = s_cfg.getBoolValue("message","auth_required",true);
    s_msg_async = s_cfg.getBoolValue("message","async_process",true);
    s_enable_register = s_cfg.getBoolValue("registrar","enable",
	s_cfg.getBoolValue("general","registrar",!Engine::clientMode()));
    s_auth_register = s_cfg.getBoolValue("registrar","auth_required",true);
    s_expires_min = s_cfg.getIntValue("registrar","expires_min",EXPIRES_MIN);
    s_expires_def = s_cfg.getIntValue("registrar","expires_def",EXPIRES_DEF);
    s_expires_max = s_cfg.getIntValue("registrar","expires_max",EXPIRES_MAX);
    s_nat_refresh = s_cfg.getIntValue("registrar","nat_refresh",25);
    s_reg_async = s_cfg.getBoolValue("registrar","async_process",true);
    s_ack_required = !s_cfg.getBoolValue("hacks","ignore_missing_ack",false);
    s_1xx_formats = s_cfg.getBoolValue("hacks","1xx_change_formats",true);
    s_sdp_implicit = s_cfg.getBoolValue("hacks","sdp_implicit",true);
    s_rtp_preserve = s_cfg.getBoolValue("hacks","ignore_sdp_addr",false);
    s_changeParty2xx = s_cfg.getBoolValue("general","change_party_2xx",false);
    s_initialHeaders = s_cfg.getBoolValue("general","initial_headers");
    m_parser.initialize(s_cfg.getSection("codecs"),s_cfg.getSection("hacks"),s_cfg.getSection("general"));
    s_trace = s_cfg.getBoolValue("general","trace");
    if (!m_endpoint) {
	Thread::Priority prio = Thread::priority(s_cfg.getValue("general","thread"));
	unsigned int partyMutexCount = s_cfg.getIntValue("general","party_mutexcount",47,13,101);
	m_endpoint = new YateSIPEndPoint(prio,partyMutexCount);
	if (!(m_endpoint->Init())) {
	    delete m_endpoint;
	    m_endpoint = 0;
	    return;
	}
	m_endpoint->startup();
	setup();
	installRelay(Halt);
	installRelay(Progress);
	installRelay(Update);
	installRelay(Route);
	installRelay(Status);
	installRelay(Stop,"engine.stop");
	installRelay(Start,"engine.start");
	installRelay(MsgExecute);
	installRelay(Help);
	Engine::install(new UserHandler);
	if (s_cfg.getBoolValue("general","generate"))
	    Engine::install(new SipHandler);
    }
    else {
	m_endpoint->engine()->initialize(s_cfg.getSection("general"));
	loadLimits();
    }
    // Unsafe globals
    s_globalMutex.lock();
    s_realm = s_cfg.getValue("general","realm","Yate");
    s_tcpOutRtpip = s_cfg.getValue("general","tcp_out_rtp_localip");
    s_sslCertFile = s_cfg.getValue("general","ssl_certificate_file");
    s_sslKeyFile = s_cfg.getValue("general","ssl_key_file");
    s_globalMutex.unlock();
    // set max chans
    maxChans(s_cfg.getIntValue("general","maxchans",maxChans()));
    // Adjust here the TCP idle interval: it uses the SIP engine
    s_tcpIdle = tcpIdleInterval(s_cfg.getIntValue("general","tcp_idle",TCP_IDLE_DEF));
    s_tcpKeepalive = s_cfg.getIntValue("general","tcp_keepalive",s_tcpIdle);
    s_tcpKeepaliveFirst = s_cfg.getIntValue("general","tcp_keepalive_first",0,0);
    // Mark listeners
    m_endpoint->initializing(true);
    // Setup general listener
    NamedList dummy("general");
    NamedList* def = general;
    if (!def)
	def = &dummy;
    NamedList* generalListener = s_cfg.getSection("listener general");
    if (generalListener)
	setupListener("general",*generalListener,true,*def);
    else
	setupListener("general",*def,true);
    // Setup listeners
    unsigned int n = s_cfg.sections();
    for (unsigned int i = 0; i < n; i++) {
	NamedList* nl = s_cfg.getSection(i);
	String name = nl ? nl->c_str() : "";
	if (!name.startSkip("listener ",false))
	    continue;
	name.trimBlanks();
	if (name && name != YSTRING("general"))
	    setupListener(name,*nl,false,*def);
    }
    // Remove deleted listeners
    m_endpoint->initializing(false);
    // Everything set: update default udp transport
    m_endpoint->updateDefUdpTransport();
}

void SIPDriver::genUpdate(Message& msg)
{
    DDebug(this,DebugInfo,"fill module.update message");
    Lock l(this);
    if (m_endpoint) {
	msg.setParam("failed_auths",String(m_endpoint->failedAuths()));
	msg.setParam("transaction_timeouts",String(m_endpoint->timedOutTrs()));
	msg.setParam("bye_timeouts",String(m_endpoint->timedOutByes()));
    }
}

// Setup a listener from config
void SIPDriver::setupListener(const String& name, const NamedList& params,
    bool isGeneral, const NamedList& defs)
{
    const String& type = params[YSTRING("type")];
    int proto = ProtocolHolder::lookupProtoAny(type);
    if (proto == ProtocolHolder::Unknown) {
	proto = ProtocolHolder::Udp;
	if (!isGeneral || type)
	    Debug(this,DebugConf,"Invalid listener type '%s' in section '%s': defaults to %s",
		type.c_str(),params.c_str(),ProtocolHolder::lookupProtoName(proto,false));
    }
    bool enabled = (params == YSTRING("general")) || params.getBoolValue(YSTRING("enable"),true);
    switch (proto) {
	case ProtocolHolder::Udp:
	    m_endpoint->cancelListener(name,"Type changed");
	    m_endpoint->setupUdpTransport(name,enabled,params,defs);
	    break;
	case ProtocolHolder::Tcp:
	case ProtocolHolder::Tls:
	    m_endpoint->setupUdpTransport(name,false,NamedList::empty(),
		NamedList::empty(),"Type changed");
	    m_endpoint->setupListener(proto,name,enabled,params);
	    break;
	default:
	    if (enabled)
		Debug(this,DebugNote,"Unknown listener type '%s' in section '%s'",
		    type.c_str(),params.c_str());
    }
}

bool SIPDriver::commandComplete(Message& msg, const String& partLine, const String& partWord)
{
    if (!partLine || partLine == YSTRING("help")) {
	itemComplete(msg.retValue(),name(),partWord);
	return false;
    }
    if (partLine.startsWith(name())) {
	String tmp = partLine;
	if (!tmp.startSkip(name()))
	    return Driver::commandComplete(msg,partLine,partWord);
	if (!tmp)
	    itemComplete(msg.retValue(),YSTRING("drop"),partWord);
	else if (tmp == YSTRING("drop"))
	    itemComplete(msg.retValue(),YSTRING("transport"),partWord);
	else if (tmp == YSTRING("drop transport")) {
	    if (m_endpoint)
		m_endpoint->completeTransports(msg,partWord,false);
	}
	return true;
    }
    String cmd = s_statusCmd + " " + name();
    String overviewCmd = s_statusCmd + " overview " + name();
    if (partLine == cmd || partLine == overviewCmd) {
	itemComplete(msg.retValue(),YSTRING("accounts"),partWord);
	itemComplete(msg.retValue(),YSTRING("listeners"),partWord);
	itemComplete(msg.retValue(),YSTRING("transports"),partWord);
    }
    String cmdTrans = cmd + " transports";
    String cmdOverViewTrans = overviewCmd + " transports";
    if (partLine == cmdTrans || partLine == cmdOverViewTrans) {
	if (m_endpoint) {
	    itemComplete(msg.retValue(),YSTRING("all"),partWord);
	    itemComplete(msg.retValue(),YSTRING("udp"),partWord);
	    itemComplete(msg.retValue(),YSTRING("tcp"),partWord);
	    itemComplete(msg.retValue(),YSTRING("tls"),partWord);
	    if (partLine == cmdTrans)
		m_endpoint->completeTransports(msg,partWord);
	}
    }
    else
    	return Driver::commandComplete(msg,partLine,partWord);
    return false;
}

bool SIPDriver::commandExecute(String& retVal, const String& line)
{
    if (!line.startsWith(name()))
	return Driver::commandExecute(retVal,line);
    String tmp = line;
    if (!tmp.startSkip(name()))
	return Driver::commandExecute(retVal,line);
    if (tmp.startSkip("drop transport")) {
	if (!tmp)
	    return false;
	YateSIPTransport* t = m_endpoint ? m_endpoint->findTransport(tmp) : 0;
	YateSIPTCPTransport* trans = t ? t->tcpTransport() : 0;
	if (trans)
	    trans->terminate("dropped");
	TelEngine::destruct(t);
	return trans != 0;
    }
    return false;
}

void SIPDriver::msgStatus(Message& msg)
{
    String str = msg.getValue(YSTRING("module"));
    if (str.null() || str.startSkip(name())) {
	str.trimBlanks();
	if (str.null())
	    Module::msgStatus(msg);
	else if (str.startSkip("accounts"))
	    msgStatusAccounts(msg);
	else if (str.startSkip("transports")) {
	    String tmp = str;
	    tmp.trimBlanks().toLower();
	    if (tmp == YSTRING("udp"))
		msgStatusTransports(msg,true,false,false);
	    else if (tmp == YSTRING("tcp"))
		msgStatusTransports(msg,false,true,false);
	    else if (tmp == YSTRING("tls"))
		msgStatusTransports(msg,false,false,true);
	    else if (!tmp || tmp == YSTRING("all"))
		msgStatusTransports(msg,true,true,true);
	    else if (msg.getBoolValue("details",true))
		msgStatusTransport(msg,str);
	}
	else if (str.startSkip("listeners"))
	    msgStatusListener(msg);
    }
}

void SIPDriver::statusParams(String& str)
{
    Driver::statusParams(str);
    if (m_endpoint && m_endpoint->engine())
	str.append("transactions=",",") << m_endpoint->engine()->transactionCount();
}

// Build and dispatch a socket.ssl message
bool SIPDriver::socketSsl(Socket** sock, bool server, const String& context)
{
    Message m("socket.ssl");
    m.addParam("module",name());
    m.addParam("server",String::boolText(server));
    m.addParam("context",context,false);
    if (!server) {
	Lock lock(s_globalMutex);
	m.addParam("certificate",s_sslCertFile,false);
	m.addParam("key",s_sslKeyFile,false);
    }
    if (sock && *sock) {
	SocketRef* p = new SocketRef(sock);
	m.userData(p);
	TelEngine::destruct(p);
    }
    else
        m.addParam("test",String::boolText(true));
    return Engine::dispatch(m);
}

// Send a SIP method
bool SIPDriver::sendMethod(Message& msg, const char* method, bool msgExec,
    const char* target)
{
    Debug(this,DebugAll,"Sending method '%s'",method);
    if (!method)
	return false;
    RefPointer<YateSIPConnection> conn;
    String uri = target;
    const char* id = msg.getValue(YSTRING("id"));
    if (id) {
	lock();
	conn = static_cast<YateSIPConnection*>(find(id));
	unlock();
	if (!conn) {
	    msg.setParam("error","noconn");
	    return false;
	}
	if (!(msgExec || uri))
	    uri = conn->m_uri;
    }
    if (!msgExec)
	uri = msg.getValue(YSTRING("uri"),uri);
    else if (!uri)
	uri = msg.getValue(YSTRING("uri"));
    static const Regexp r("<\\([^>]\\+\\)>");
    if (uri.matches(r))
	uri = uri.matchString(1);
    if (!uri) {
	msg.setParam("error","failure");
	return false;
    }

    int maxf = msg.getIntValue(YSTRING("antiloop"),s_maxForwards);
    if (maxf <= 0) {
	Debug(this,DebugMild,"Blocking looping request '%s %s' [%p]",
	    method,uri.c_str(),this);
	msg.setParam("error","looping");
	return false;
    }

    SIPMessage* sip = 0;
    const char* domain = msg.getValue(YSTRING("domain"));
    if (conn) {
	sip = conn->createDlgMsg(method,uri);
	conn = 0;
    }
    else {
	YateSIPLine* line = findLine(msg.getValue(YSTRING("line")));
	if (line && !line->valid()) {
	    msg.setParam("error","offline");
	    return false;
	}
	sip = new SIPMessage(method,uri);
	YateSIPPartyHolder holder(this);
	URI rUri(uri);
	holder.sips(rUri.getProtocol() == YSTRING("sips"));
	const char* host = msg.getValue("host",rUri.getHost());
	int port = msg.getIntValue("port",rUri.getPort());
	// Force party creation for SIPS: it will force used protocol to TLS
	holder.setParty(msg,holder.sips(),String::empty(),host,port,true);
	holder.setSipParty(sip,line,true,host,port);
	if (line)
	    domain = line->domain(domain);
    }
    if (!sip->getParty()) {
	Debug(this,DebugWarn,"Could not create party to generate '%s'",
	    sip->method.c_str());
	TelEngine::destruct(sip);
	msg.setParam("error","notransport");
	return false;
    }
    sip->addHeader("Max-Forwards",String(maxf));
    copySipHeaders(*sip,msg,"sip_");
    copySipBody(*sip,msg);
    const char* user = msg.getValue(YSTRING("user"));
    sip->complete(ep()->engine(),user,domain,0,
	msg.getIntValue(YSTRING("xsip_flags"),-1));
    user = msg.getValue(YSTRING("authname"),user);
    const char* pass = msg.getValue(YSTRING("password"));
    if (user && pass)
	sip->setAutoAuth(user,pass);
    if (!msg.getBoolValue(YSTRING("wait"))) {
	// no answer requested - start transaction and forget
	ep()->engine()->addMessage(sip);
	sip->deref();
	return true;
    }
    YateSIPGenerate gen(sip,msg.getIntValue(YSTRING("xsip_trans_count"),-1));
    while (gen.busy())
	Thread::idle();
    if (gen.code()) {
	msg.setParam("code",String(gen.code()));
	msg.clearParam("sip",'_');
	msg.clearParam("xsip",'_');
	if (gen.answer()) {
	    copySipHeaders(msg,*gen.answer(),true,ep()->engine()->foreignAuth());
	    copySipBody(msg,*gen.answer());
	}
    }
    else
	msg.clearParam("code");
    return true;
}

bool SIPDriver::onHelp(Message& msg)
{
    static const char* s_cmds = "  sip drop transport <name>\r\n";
    static const char* s_cmdsDesc = "Drop a tcp/tls transport\r\n";
    
    if (msg[YSTRING("line")] == name()) {
	msg.retValue() << s_cmds << s_cmdsDesc;
	return true;
    }
    msg.retValue() << s_cmds;
    return false;
}

// Add accounts status
void SIPDriver::msgStatusAccounts(Message& msg)
{
    msg.retValue().clear();
    msg.retValue() << "module=" << name();
    msg.retValue() << ",protocol=SIP";
    msg.retValue() << ",format=Username|Status;";
    msg.retValue() << "accounts=" << s_lines.count();
    if (!msg.getBoolValue("details",true)) {
	msg.retValue() << "\r\n";
	return;
    }
    String accounts = "";
    for (ObjList* o = s_lines.skipNull(); o; o = o->skipNext()) {
	YateSIPLine* line = static_cast<YateSIPLine*>(o->get());
	accounts.append(line->c_str(),",") << "=";
	accounts.append(line->getUserName()) << "|";
	accounts << (line->valid() ? "online" : "offline");
    }
    msg.retValue().append(accounts,";");
    msg.retValue() << "\r\n";
}

// Add transports status
void SIPDriver::msgStatusTransports(Message& msg, bool showUdp, bool showTcp, bool showTls)
{
    msg.retValue().clear();
    msg.retValue() << "module=" << name();
    msg.retValue() << ",protocol=SIP";
    YateSIPUDPTransport* def = m_endpoint ? m_endpoint->defTransport() : 0;
    msg.retValue() << ",udp_default=" << (def ? def->toString() : String::empty());
    TelEngine::destruct(def);
    msg.retValue() << ",format=Proto|Status|Local|Remote|Outgoing|Reason;";
    String buf;
    unsigned int n = 0;
    if (m_endpoint) {
	Lock lock(m_endpoint->m_mutex);
	bool details = msg.getBoolValue("details",true);
	for (ObjList* o = m_endpoint->m_transports.skipNull(); o; o = o->skipNext()) {
	    YateSIPTransport* t = static_cast<YateSIPTransport*>(o->get());
	    YateSIPTCPTransport* tcp = t->tcpTransport();
	    if (!tcp) {
		if (!showUdp)
		    continue;
	    }
	    else if (!tcp->tls()) {
		if (!showTcp)
		    continue;
	    }
	    else if (!showTls)
		continue;
	    n++;
	    if (!details)
		continue;
	    Lock lck(t);
	    buf.append(String(n),",") << "=" << t->protoName() << "|";
	    buf << YateSIPTransport::statusName(t->status()) << "|";
	    buf << t->local().addr() << "|";
	    if (tcp) {
		buf << t->remote().addr() << "|";
		buf << String::boolText(tcp->outgoing());
	    }
	    else
		buf << "|";
	    buf << "|" << t->m_reason;
	}
    }
    msg.retValue() << "transports=" << n;
    msg.retValue().append(buf,";");
    msg.retValue() << "\r\n";
}

// Add listeners status
void SIPDriver::msgStatusListener(Message& msg)
{
    msg.retValue().clear();
    msg.retValue() << "module=" << name();
    msg.retValue() << ",protocol=SIP";
    msg.retValue() << ",format=Proto|Address|Status|Reason;";
    String buf;
    unsigned int n = 0;
    if (m_endpoint) {
	bool details = msg.getBoolValue("details",true);
	Lock lock(m_endpoint->m_mutex);
	for (ObjList* o = m_endpoint->m_transports.skipNull(); o; o = o->skipNext()) {
	    YateSIPTransport* t = static_cast<YateSIPTransport*>(o->get());
	    YateSIPUDPTransport* udp = t->udpTransport();
	    if (!udp)
		continue;
	    n++;
	    if (!details)
		continue;
	    Lock lck(udp);
	    buf.append(udp->toString(),",") << "=" << udp->protoName() << "|";
	    if (udp->status() == YateSIPTransport::Connected)
		buf << udp->local().addr() << "|Listening|";
	    else
		SocketAddr::appendTo(buf,udp->address(),udp->port()) << "|Idle|";
	    buf << udp->m_reason;
	}
	if (details) {
	    for (ObjList* o = m_endpoint->m_listeners.skipNull(); o; o = o->skipNext()) {
		YateSIPTCPListener* l = static_cast<YateSIPTCPListener*>(o->get());
		n++;
		buf.append(l->toString(),",") << "=";
		buf << l->protoName() << "|";
		Lock lck(l->m_mutex);
		if (l->listening())
		    buf << l->local().addr() << "|Listening|";
		else
		    SocketAddr::appendTo(buf,l->address(),l->port()) << "|Idle|";
		buf << l->m_reason;
	    }
	}
	else
	    n += m_endpoint->m_listeners.count();
    }
    msg.retValue() << "listeners=" << n;
    msg.retValue().append(buf,";");
    msg.retValue() << "\r\n";
}

// Add transport status
void SIPDriver::msgStatusTransport(Message& msg, const String& id)
{
    msg.retValue().clear();
    msg.retValue() << "module=" << name();
    msg.retValue() << ",protocol=SIP;";
    String tmp = id;
    tmp.trimBlanks();
    YateSIPTransport* t = m_endpoint ? m_endpoint->findTransport(tmp) : 0;
    if (t) {
	YateSIPTCPTransport* tcp = t->tcpTransport();
	t->lock();
	msg.retValue() << "name=" << t->toString();
	msg.retValue() << ",protocol=" << t->protoName();
	msg.retValue() << ",status=" << YateSIPTransport::statusName(t->status());
	msg.retValue() << ",statustime=" << (msg.msgTime().sec() - t->m_statusChgTime);
	msg.retValue() << ",local=" << t->local().addr();
	if (tcp) {
	    msg.retValue() << ",remote=" << t->remote().addr();
	    msg.retValue() << ",outgoing=" << String::boolText(tcp->outgoing());
	}
	String lines;
	for (ObjList* ol = s_lines.skipNull(); ol; ol = ol->skipNext()) {
	    YateSIPLine* line = static_cast<YateSIPLine*>(ol->get());
	    if (line->isTransport(t))
		lines.append(line->toString(),",");
	}
	msg.retValue() << ",lines=" << lines;
	msg.retValue() << ",references=" << (t->refcount() - 1);
	msg.retValue() << ",reason=" << t->m_reason;
	t->unlock();
	TelEngine::destruct(t);
    }
    msg.retValue() << "\r\n";
}

}; // anonymous namespace

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