/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_transaction.h>
#include <pjsip/sip_private.h>
#include <pjsip/sip_event.h>
#include <pjsip/sip_resolve.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_util.h>
#include <pjsip/sip_errno.h>
#include <pj/except.h>
#include <pj/log.h>
#include <pj/string.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/hash.h>
#include <pj/assert.h>
#include <pj/errno.h>
#include <pj/lock.h>
#include <pj/math.h>

#define PJSIP_EX_NO_MEMORY  pj_NO_MEMORY_EXCEPTION()
#define THIS_FILE           "sip_endpoint.c"

#define MAX_METHODS   32


/* List of SIP endpoint exit callback. */
typedef struct exit_cb
{
    PJ_DECL_LIST_MEMBER             (struct exit_cb);
    pjsip_endpt_exit_callback       func;
} exit_cb;


/**
 * The SIP endpoint.
 */
struct pjsip_endpoint
{
    /** Pool to allocate memory for the endpoint. */
    pj_pool_t           *pool;

    /** Mutex for the pool, hash table, and event list/queue. */
    pj_mutex_t          *mutex;

    /** Pool factory. */
    pj_pool_factory     *pf;

    /** Name. */
    pj_str_t             name;

    /** Timer heap. */
    pj_timer_heap_t     *timer_heap;

    /** Transport manager. */
    pjsip_tpmgr         *transport_mgr;

    /** Ioqueue. */
    pj_ioqueue_t        *ioqueue;

    /** Last ioqueue err */
    pj_status_t          ioq_last_err;

    /** DNS Resolver. */
    pjsip_resolver_t    *resolver;

    /** Modules lock. */
    pj_rwmutex_t        *mod_mutex;

    /** Modules. */
    pjsip_module        *modules[PJSIP_MAX_MODULE];

    /** Module list, sorted by priority. */
    pjsip_module         module_list;

    /** Capability header list. */
    pjsip_hdr            cap_hdr;

    /** Additional request headers. */
    pjsip_hdr            req_hdr;

    /** List of exit callback. */
    exit_cb              exit_cb_list;
};


#if defined(PJSIP_SAFE_MODULE) && PJSIP_SAFE_MODULE!=0
#   define LOCK_MODULE_ACCESS(ept)      pj_rwmutex_lock_read(ept->mod_mutex)
#   define UNLOCK_MODULE_ACCESS(ept)    pj_rwmutex_unlock_read(ept->mod_mutex)
#else
#   define LOCK_MODULE_ACCESS(endpt)
#   define UNLOCK_MODULE_ACCESS(endpt)
#endif



/*
 * Prototypes.
 */
static void endpt_on_rx_msg( pjsip_endpoint*, 
                             pj_status_t, pjsip_rx_data*);
static pj_status_t endpt_on_tx_msg( pjsip_endpoint *endpt,
                                    pjsip_tx_data *tdata );
static pj_status_t unload_module(pjsip_endpoint *endpt,
                                 pjsip_module *mod);

/* Defined in sip_parser.c */
void init_sip_parser(void);
void deinit_sip_parser(void);

/* Defined in sip_tel_uri.c */
pj_status_t pjsip_tel_uri_subsys_init(void);


/*
 * This is the global handler for memory allocation failure, for pools that
 * are created by the endpoint (by default, all pools ARE allocated by 
 * endpoint). The error is handled by throwing exception, and hopefully,
 * the exception will be handled by the application (or this library).
 */
static void pool_callback( pj_pool_t *pool, pj_size_t size )
{
    PJ_UNUSED_ARG(pool);
    PJ_UNUSED_ARG(size);

    PJ_THROW(PJSIP_EX_NO_MEMORY);
}


/* Compare module name, used for searching module based on name. */
static int cmp_mod_name(void *name, const void *mod)
{
    return pj_stricmp((const pj_str_t*)name, &((pjsip_module*)mod)->name);
}

/*
 * Register new module to the endpoint.
 * The endpoint will then call the load and start function in the module to 
 * properly initialize the module, and assign a unique module ID for the 
 * module.
 */
PJ_DEF(pj_status_t) pjsip_endpt_register_module( pjsip_endpoint *endpt,
                                                 pjsip_module *mod )
{
    pj_status_t status = PJ_SUCCESS;
    pjsip_module *m;
    unsigned i;

    pj_rwmutex_lock_write(endpt->mod_mutex);

    /* Make sure that this module has not been registered. */
    PJ_ASSERT_ON_FAIL(  pj_list_find_node(&endpt->module_list, mod) == NULL,
                        {status = PJ_EEXISTS; goto on_return;});

    /* Make sure that no module with the same name has been registered. */
    PJ_ASSERT_ON_FAIL(  pj_list_search(&endpt->module_list, &mod->name, 
                                       &cmp_mod_name)==NULL,
                        {status = PJ_EEXISTS; goto on_return; });

    /* Find unused ID for this module. */
    for (i=0; i<PJ_ARRAY_SIZE(endpt->modules); ++i) {
        if (endpt->modules[i] == NULL)
            break;
    }
    if (i == PJ_ARRAY_SIZE(endpt->modules)) {
        pj_assert(!"Too many modules registered!");
        status = PJ_ETOOMANY;
        goto on_return;
    }

    /* Assign the ID. */
    mod->id = i;

    /* Try to load the module. */
    if (mod->load) {
        status = (*mod->load)(endpt);
        if (status != PJ_SUCCESS)
            goto on_return;
    }

    /* Try to start the module. */
    if (mod->start) {
        status = (*mod->start)();
        if (status != PJ_SUCCESS)
            goto on_return;
    }

    /* Save the module. */
    endpt->modules[i] = mod;

    /* Put in the module list, sorted by priority. */
    m = endpt->module_list.next;
    while (m != &endpt->module_list) {
        if (m->priority > mod->priority)
            break;
        m = m->next;
    }
    pj_list_insert_before(m, mod);

    /* Done. */

    PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" registered", 
              (int)mod->name.slen, mod->name.ptr));

on_return:
    pj_rwmutex_unlock_write(endpt->mod_mutex);
    return status;
}

/*
 * Unregister a module from the endpoint.
 * The endpoint will then call the stop and unload function in the module to 
 * properly shutdown the module.
 */
PJ_DEF(pj_status_t) pjsip_endpt_unregister_module( pjsip_endpoint *endpt,
                                                   pjsip_module *mod )
{
    pj_status_t status;

    pj_rwmutex_lock_write(endpt->mod_mutex);

    /* Make sure the module exists in the list. */
    PJ_ASSERT_ON_FAIL(  pj_list_find_node(&endpt->module_list, mod) == mod,
                        {status = PJ_ENOTFOUND;goto on_return;} );

    /* Make sure the module exists in the array. */
    PJ_ASSERT_ON_FAIL(  mod->id>=0 && 
                        mod->id<(int)PJ_ARRAY_SIZE(endpt->modules) &&
                        endpt->modules[mod->id] == mod,
                        {status = PJ_ENOTFOUND; goto on_return;});

    /* Try to stop the module. */
    if (mod->stop) {
        status = (*mod->stop)();
        if (status != PJ_SUCCESS) goto on_return;
    }

    /* Unload module */
    status = unload_module(endpt, mod);

on_return:
    pj_rwmutex_unlock_write(endpt->mod_mutex);

    if (status != PJ_SUCCESS) {
        char errmsg[PJ_ERR_MSG_SIZE];

        pj_strerror(status, errmsg, sizeof(errmsg));
        PJ_LOG(3,(THIS_FILE, "Module \"%.*s\" can not be unregistered: %s",
                  (int)mod->name.slen, mod->name.ptr, errmsg));
    }

    return status;
}

static pj_status_t unload_module(pjsip_endpoint *endpt,
                                 pjsip_module *mod)
{
    pj_status_t status;

    /* Try to unload the module. */
    if (mod->unload) {
        status = (*mod->unload)();
        if (status != PJ_SUCCESS) 
            return status;
    }

    /* Module MUST NOT set module ID to -1. */
    pj_assert(mod->id >= 0);

    /* Remove module from array. */
    endpt->modules[mod->id] = NULL;

    /* Remove module from list. */
    pj_list_erase(mod);

    /* Set module Id to -1. */
    mod->id = -1;

    /* Done. */
    status = PJ_SUCCESS;

    PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" unregistered", 
              (int)mod->name.slen, mod->name.ptr));

    return status;
}


/*
 * Get the value of the specified capability header field.
 */
PJ_DEF(const pjsip_hdr*) pjsip_endpt_get_capability( pjsip_endpoint *endpt,
                                                     int htype,
                                                     const pj_str_t *hname)
{
    pjsip_hdr *hdr = endpt->cap_hdr.next;

    /* Check arguments. */
    PJ_ASSERT_RETURN(endpt != NULL, NULL);
    PJ_ASSERT_RETURN(htype != PJSIP_H_OTHER || hname, NULL);

    if (htype != PJSIP_H_OTHER) {
        while (hdr != &endpt->cap_hdr) {
            if ((int)hdr->type == htype)
                return hdr;
            hdr = hdr->next;
        }
    }
    return NULL;
}


/*
 * Check if the specified capability is supported.
 */
PJ_DEF(pj_bool_t) pjsip_endpt_has_capability( pjsip_endpoint *endpt,
                                              int htype,
                                              const pj_str_t *hname,
                                              const pj_str_t *token)
{
    const pjsip_generic_array_hdr *hdr;
    unsigned i;

    hdr = (const pjsip_generic_array_hdr*) 
           pjsip_endpt_get_capability(endpt, htype, hname);
    if (!hdr)
        return PJ_FALSE;

    PJ_ASSERT_RETURN(token != NULL, PJ_FALSE);

    for (i=0; i<hdr->count; ++i) {
        if (!pj_stricmp(&hdr->values[i], token))
            return PJ_TRUE;
    }

    return PJ_FALSE;
}

/*
 * Add or register new capabilities as indicated by the tags to the
 * appropriate header fields in the endpoint.
 */
PJ_DEF(pj_status_t) pjsip_endpt_add_capability( pjsip_endpoint *endpt,
                                                pjsip_module *mod,
                                                int htype,
                                                const pj_str_t *hname,
                                                unsigned count,
                                                const pj_str_t tags[])
{
    pjsip_generic_array_hdr *hdr;
    unsigned i;

    PJ_UNUSED_ARG(mod);

    /* Check arguments. */
    PJ_ASSERT_RETURN(endpt!=NULL && count>0 && tags, PJ_EINVAL);
    PJ_ASSERT_RETURN(count <= PJSIP_GENERIC_ARRAY_MAX_COUNT, PJ_ETOOMANY);
    PJ_ASSERT_RETURN(htype==PJSIP_H_ACCEPT || 
                     htype==PJSIP_H_ALLOW ||
                     htype==PJSIP_H_SUPPORTED,
                     PJ_EINVAL);

    /* Find the header. */
    hdr = (pjsip_generic_array_hdr*) pjsip_endpt_get_capability(endpt, 
                                                                htype, hname);

    /* Create the header when it's not present */
    if (hdr == NULL) {
        switch (htype) {
        case PJSIP_H_ACCEPT:
            hdr = pjsip_accept_hdr_create(endpt->pool);
            break;
        case PJSIP_H_ALLOW:
            hdr = pjsip_allow_hdr_create(endpt->pool);
            break;
        case PJSIP_H_SUPPORTED:
            hdr = pjsip_supported_hdr_create(endpt->pool);
            break;
        default:
            return PJ_EINVAL;
        }

        if (hdr == NULL) {
            return PJ_ENOMEM;
        }
        pj_list_push_back(&endpt->cap_hdr, hdr);
    }

    /* Add the tags to the header. */
    for (i=0; i<count; ++i) {
        pj_strdup(endpt->pool, &hdr->values[hdr->count], &tags[i]);
        ++hdr->count;
    }

    /* Done. */
    return PJ_SUCCESS;
}

/*
 * Get additional headers to be put in outgoing request message.
 */
PJ_DEF(const pjsip_hdr*) pjsip_endpt_get_request_headers(pjsip_endpoint *endpt)
{
    return &endpt->req_hdr;
}


/*
 * Initialize endpoint.
 */
PJ_DEF(pj_status_t) pjsip_endpt_create(pj_pool_factory *pf,
                                       const char *name,
                                       pjsip_endpoint **p_endpt)
{
    pj_status_t status;
    pj_pool_t *pool;
    pjsip_endpoint *endpt;
    pjsip_max_fwd_hdr *mf_hdr;
    pj_lock_t *lock = NULL;


    status = pj_register_strerror(PJSIP_ERRNO_START, PJ_ERRNO_SPACE_SIZE,
                                  &pjsip_strerror);
    pj_assert(status == PJ_SUCCESS);

    PJ_LOG(5, (THIS_FILE, "Creating endpoint instance..."));

    *p_endpt = NULL;

    /* Create pool */
    pool = pj_pool_create(pf, "pept%p", 
                          PJSIP_POOL_LEN_ENDPT, PJSIP_POOL_INC_ENDPT,
                          &pool_callback);
    if (!pool)
        return PJ_ENOMEM;

    /* Create endpoint. */
    endpt = PJ_POOL_ZALLOC_T(pool, pjsip_endpoint);
    endpt->pool = pool;
    endpt->pf = pf;

    /* Init modules list. */
    pj_list_init(&endpt->module_list);

    /* Initialize exit callback list. */
    pj_list_init(&endpt->exit_cb_list);

    /* Create R/W mutex for module manipulation. */
    status = pj_rwmutex_create(endpt->pool, "ept%p", &endpt->mod_mutex);
    if (status != PJ_SUCCESS)
        goto on_error;

    /* Init parser. */
    init_sip_parser();

    /* Init tel: uri */
    pjsip_tel_uri_subsys_init();

    /* Get name. */
    if (name != NULL) {
        pj_str_t temp;
        pj_strdup_with_null(endpt->pool, &endpt->name, pj_cstr(&temp, name));
    } else {
        pj_strdup_with_null(endpt->pool, &endpt->name, pj_gethostname());
    }

    /* Create mutex for the events, etc. */
    status = pj_mutex_create_recursive( endpt->pool, "ept%p", &endpt->mutex );
    if (status != PJ_SUCCESS) {
        goto on_error;
    }

    /* Create timer heap to manage all timers within this endpoint. */
    status = pj_timer_heap_create( endpt->pool, PJSIP_MAX_TIMER_COUNT, 
                                   &endpt->timer_heap);
    if (status != PJ_SUCCESS) {
        goto on_error;
    }

    /* Set recursive lock for the timer heap. */
    status = pj_lock_create_recursive_mutex( endpt->pool, "edpt%p", &lock);
    if (status != PJ_SUCCESS) {
        goto on_error;
    }
    pj_timer_heap_set_lock(endpt->timer_heap, lock, PJ_TRUE);

    /* Set maximum timed out entries to process in a single poll. */
    pj_timer_heap_set_max_timed_out_per_poll(endpt->timer_heap, 
                                             PJSIP_MAX_TIMED_OUT_ENTRIES);

    /* Create ioqueue. */
    status = pj_ioqueue_create( endpt->pool, PJSIP_MAX_TRANSPORTS, &endpt->ioqueue);
    if (status != PJ_SUCCESS) {
        goto on_error;
    }

    /* Create transport manager. */
    status = pjsip_tpmgr_create( endpt->pool, endpt,
                                 &endpt_on_rx_msg,
                                 &endpt_on_tx_msg,
                                 &endpt->transport_mgr);
    if (status != PJ_SUCCESS) {
        goto on_error;
    }

    /* Create asynchronous DNS resolver. */
    status = pjsip_resolver_create(endpt->pool, &endpt->resolver);
    if (status != PJ_SUCCESS) {
        PJ_PERROR(4, (THIS_FILE, status,
                      "Error creating resolver instance"));
        goto on_error;
    }

    /* Initialize request headers. */
    pj_list_init(&endpt->req_hdr);

    /* Add "Max-Forwards" for request header. */
    mf_hdr = pjsip_max_fwd_hdr_create(endpt->pool,
                                      PJSIP_MAX_FORWARDS_VALUE);
    pj_list_insert_before( &endpt->req_hdr, mf_hdr);

    /* Initialize capability header list. */
    pj_list_init(&endpt->cap_hdr);


    /* Done. */
    *p_endpt = endpt;
    return status;

on_error:
    if (endpt->transport_mgr) {
        pjsip_tpmgr_destroy(endpt->transport_mgr);
        endpt->transport_mgr = NULL;
    }
    if (endpt->ioqueue) {
        pj_ioqueue_destroy(endpt->ioqueue);
        endpt->ioqueue = NULL;
    }
    if (endpt->timer_heap) {
        pj_timer_heap_destroy(endpt->timer_heap);
        endpt->timer_heap = NULL;
    }
    if (endpt->mutex) {
        pj_mutex_destroy(endpt->mutex);
        endpt->mutex = NULL;
    }
    deinit_sip_parser();
    if (endpt->mod_mutex) {
        pj_rwmutex_destroy(endpt->mod_mutex);
        endpt->mod_mutex = NULL;
    }
    pj_pool_release( endpt->pool );

    PJ_PERROR(4, (THIS_FILE, status, "Error creating endpoint"));
    return status;
}

/*
 * Destroy endpoint.
 */
PJ_DEF(void) pjsip_endpt_destroy(pjsip_endpoint *endpt)
{
    pjsip_module *mod;
    exit_cb *ecb;

    PJ_LOG(5, (THIS_FILE, "Destroying endpoint instance.."));

    /* Phase 1: stop all modules */
    mod = endpt->module_list.prev;
    while (mod != &endpt->module_list) {
        pjsip_module *prev = mod->prev;
        if (mod->stop) {
            (*mod->stop)();
        }
        mod = prev;
    }

    /* Phase 2: unload modules. */
    mod = endpt->module_list.prev;
    while (mod != &endpt->module_list) {
        pjsip_module *prev = mod->prev;
        unload_module(endpt, mod);
        mod = prev;
    }

    /* Destroy resolver */
    pjsip_resolver_destroy(endpt->resolver);

    /* Shutdown and destroy all transports. */
    pjsip_tpmgr_destroy(endpt->transport_mgr);

    /* Destroy ioqueue */
    pj_ioqueue_destroy(endpt->ioqueue);

    /* Destroy timer heap */
#if PJ_TIMER_DEBUG
    pj_timer_heap_dump(endpt->timer_heap);
#endif
    pj_timer_heap_destroy(endpt->timer_heap);

    /* Call all registered exit callbacks */
    ecb = endpt->exit_cb_list.next;
    while (ecb != &endpt->exit_cb_list) {
        (*ecb->func)(endpt);
        ecb = ecb->next;
    }

    /* Delete endpoint mutex. */
    pj_mutex_destroy(endpt->mutex);

    /* Deinit parser */
    deinit_sip_parser();

    /* Delete module's mutex */
    pj_rwmutex_destroy(endpt->mod_mutex);

    /* Finally destroy pool. */
    pj_pool_release(endpt->pool);

    PJ_LOG(4, (THIS_FILE, "Endpoint %p destroyed", endpt));
}

/*
 * Get endpoint name.
 */
PJ_DEF(const pj_str_t*) pjsip_endpt_name(const pjsip_endpoint *endpt)
{
    return &endpt->name;
}


/*
 * Create new pool.
 */
PJ_DEF(pj_pool_t*) pjsip_endpt_create_pool( pjsip_endpoint *endpt,
                                               const char *pool_name,
                                               pj_size_t initial,
                                               pj_size_t increment )
{
    pj_pool_t *pool;

    /* Lock endpoint mutex. */
    /* No need to lock mutex. Factory is thread safe.
    pj_mutex_lock(endpt->mutex);
     */

    /* Create pool */
    pool = pj_pool_create( endpt->pf, pool_name,
                           initial, increment, &pool_callback);

    /* Unlock mutex. */
    /* No need to lock mutex. Factory is thread safe.
    pj_mutex_unlock(endpt->mutex);
     */

    if (!pool) {
        PJ_LOG(4, (THIS_FILE, "Unable to create pool %s!", pool_name));
    }

    return pool;
}

/*
 * Return back pool to endpoint's pool manager to be either destroyed or
 * recycled.
 */
PJ_DEF(void) pjsip_endpt_release_pool( pjsip_endpoint *endpt, pj_pool_t *pool )
{
    PJ_LOG(6, (THIS_FILE, "Releasing pool %s", pj_pool_getobjname(pool)));

    /* Don't need to acquire mutex since pool factory is thread safe
       pj_mutex_lock(endpt->mutex);
     */
    pj_pool_release( pool );

    PJ_UNUSED_ARG(endpt);
    /*
    pj_mutex_unlock(endpt->mutex);
     */
}


PJ_DEF(pj_status_t) pjsip_endpt_handle_events2(pjsip_endpoint *endpt,
                                               const pj_time_val *max_timeout,
                                               unsigned *p_count)
{
    enum { MAX_TIMEOUT_ON_ERR = 10 };
    /* timeout is 'out' var. This just to make compiler happy. */
    pj_time_val timeout = { 0, 0};
    unsigned count = 0, net_event_count = 0;
    int c;

    PJ_LOG(6, (THIS_FILE, "pjsip_endpt_handle_events()"));

    /* Poll the timer. The timer heap has its own mutex for better 
     * granularity, so we don't need to lock end endpoint. 
     */
    timeout.sec = timeout.msec = 0;
    c = pj_timer_heap_poll( endpt->timer_heap, &timeout );
    if (c > 0)
        count += c;

    /* timer_heap_poll should never ever returns negative value, or otherwise
     * ioqueue_poll() will block forever!
     */
    pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
    if (timeout.msec >= 1000) timeout.msec = 999;

    /* If caller specifies maximum time to wait, then compare the value with
     * the timeout to wait from timer, and use the minimum value.
     */
    if (max_timeout && PJ_TIME_VAL_GT(timeout, *max_timeout)) {
        timeout = *max_timeout;
    }

    /* Poll ioqueue. 
     * Repeat polling the ioqueue while we have immediate events, because
     * timer heap may process more than one events, so if we only process
     * one network events at a time (such as when IOCP backend is used),
     * the ioqueue may have trouble keeping up with the request rate.
     *
     * For example, for each send() request, one network event will be
     *   reported by ioqueue for the send() completion. If we don't poll
     *   the ioqueue often enough, the send() completion will not be
     *   reported in timely manner.
     */
    do {
        c = pj_ioqueue_poll( endpt->ioqueue, &timeout);
        if (c < 0) {
            pj_status_t err = pj_get_netos_error();
#if PJSIP_HANDLE_EVENTS_HAS_SLEEP_ON_ERR
            unsigned msec = PJ_TIME_VAL_MSEC(timeout);
            pj_thread_sleep(PJ_MIN(msec, MAX_TIMEOUT_ON_ERR));
#endif

            if (p_count)
                *p_count = count;
            return err;
        } else if (c == 0) {
            break;
        } else {
            net_event_count += c;
            timeout.sec = timeout.msec = 0;
        }
    } while (c > 0 && net_event_count < PJSIP_MAX_NET_EVENTS);

    count += net_event_count;
    if (p_count)
        *p_count = count;

    return PJ_SUCCESS;
}

/*
 * Handle events.
 */
PJ_DEF(pj_status_t) pjsip_endpt_handle_events(pjsip_endpoint *endpt,
                                              const pj_time_val *max_timeout)
{
    return pjsip_endpt_handle_events2(endpt, max_timeout, NULL);
}

/*
 * Schedule timer.
 */
#if PJ_TIMER_DEBUG
PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer_dbg(pjsip_endpoint *endpt,
                                                    pj_timer_entry *entry,
                                                    const pj_time_val *delay,
                                                    const char *src_file,
                                                    int src_line)
{
    PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer(entry=%p, delay=%lu.%lu)",
                         entry, (unsigned long)delay->sec,
                         (unsigned long)delay->msec));
    return pj_timer_heap_schedule_dbg(endpt->timer_heap, entry, delay,
                                      src_file, src_line);
}
#else
PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer( pjsip_endpoint *endpt,
                                                pj_timer_entry *entry,
                                                const pj_time_val *delay )
{
    PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer(entry=%p, delay=%lu.%lu)",
                         entry, (unsigned long)delay->sec,
                         (unsigned long)delay->msec));
    return pj_timer_heap_schedule( endpt->timer_heap, entry, delay );
}
#endif

/*
 * Schedule timer with group lock.
 */
#if PJ_TIMER_DEBUG
PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer_w_grp_lock_dbg(
                                                    pjsip_endpoint *endpt,
                                                    pj_timer_entry *entry,
                                                    const pj_time_val *delay,
                                                    int id_val,
                                                    pj_grp_lock_t *grp_lock,
                                                    const char *src_file,
                                                    int src_line)
{
    PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer_w_grp_lock"
                          "(entry=%p, delay=%lu.%lu, grp_lock=%p)",
                          entry, (unsigned long)delay->sec,
                          (unsigned long)delay->msec, grp_lock));
    return pj_timer_heap_schedule_w_grp_lock_dbg(endpt->timer_heap, entry,
                                                 delay, id_val, grp_lock,
                                                 src_file, src_line);
}
#else
PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer_w_grp_lock(
                                                 pjsip_endpoint *endpt,
                                                 pj_timer_entry *entry,
                                                 const pj_time_val *delay,
                                                 int id_val,
                                                 pj_grp_lock_t *grp_lock )
{
    PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer_w_grp_lock"
                          "(entry=%p, delay=%lu.%lu, grp_lock=%p)",
                          entry, (unsigned long)delay->sec,
                          (unsigned long)delay->msec, grp_lock));
    return pj_timer_heap_schedule_w_grp_lock( endpt->timer_heap, entry,
                                              delay, id_val, grp_lock );
}
#endif

/*
 * Cancel the previously registered timer.
 */
PJ_DEF(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt, 
                                       pj_timer_entry *entry )
{
    PJ_LOG(6, (THIS_FILE, "pjsip_endpt_cancel_timer(entry=%p)", entry));
    pj_timer_heap_cancel( endpt->timer_heap, entry );
}

/*
 * Get the timer heap instance of the SIP endpoint.
 */
PJ_DEF(pj_timer_heap_t*) pjsip_endpt_get_timer_heap(pjsip_endpoint *endpt)
{
    return endpt->timer_heap;
}

/* Init with default */
PJ_DEF(void) pjsip_process_rdata_param_default(pjsip_process_rdata_param *p)
{
    pj_bzero(p, sizeof(*p));
}

/* Distribute rdata */
PJ_DEF(pj_status_t) pjsip_endpt_process_rx_data( pjsip_endpoint *endpt,
                                                 pjsip_rx_data *rdata,
                                                 pjsip_process_rdata_param *p,
                                                 pj_bool_t *p_handled)
{
    pjsip_msg *msg;
    pjsip_process_rdata_param def_prm;
    pjsip_module *mod;
    pj_bool_t handled = PJ_FALSE;
    unsigned i;
    pj_status_t status;

    PJ_ASSERT_RETURN(endpt && rdata, PJ_EINVAL);

    if (p==NULL) {
        p = &def_prm;
        pjsip_process_rdata_param_default(p);
    }

    msg = rdata->msg_info.msg;

    if (p_handled)
        *p_handled = PJ_FALSE;

    if (!p->silent) {
        PJ_LOG(5, (THIS_FILE, "Distributing rdata to modules: %s",
                   pjsip_rx_data_get_info(rdata)));
        pj_log_push_indent();
    }

    LOCK_MODULE_ACCESS(endpt);

    /* Find start module */
    if (p->start_mod) {
        mod = (pjsip_module*)
              pj_list_find_node(&endpt->module_list, p->start_mod);
        if (!mod) {
            status = PJ_ENOTFOUND;
            goto on_return;
        }
    } else {
        mod = endpt->module_list.next;
    }

    /* Start after the specified index */
    for (i=0; i < p->idx_after_start && mod != &endpt->module_list; ++i) {
        mod = mod->next;
    }

    /* Start with the specified priority */
    while (mod != &endpt->module_list && mod->priority < (int)p->start_prio) {
        mod = mod->next;
    }

    if (mod == &endpt->module_list) {
        status = PJ_ENOTFOUND;
        goto on_return;
    }

    /* Distribute */
    if (msg->type == PJSIP_REQUEST_MSG) {
        do {
            if (mod->on_rx_request)
                handled = (*mod->on_rx_request)(rdata);
            if (handled)
                break;
            mod = mod->next;
        } while (mod != &endpt->module_list);
    } else {
        do {
            if (mod->on_rx_response)
                handled = (*mod->on_rx_response)(rdata);
            if (handled)
                break;
            mod = mod->next;
        } while (mod != &endpt->module_list);
    }

    status = PJ_SUCCESS;

on_return:
    if (p_handled)
        *p_handled = handled;

    UNLOCK_MODULE_ACCESS(endpt);
    if (!p->silent) {
        pj_log_pop_indent();
    }
    return status;
}

/*
 * This is the callback that is called by the transport manager when it 
 * receives a message from the network.
 */
static void endpt_on_rx_msg( pjsip_endpoint *endpt,
                             pj_status_t status,
                             pjsip_rx_data *rdata )
{
    pjsip_msg *msg = rdata->msg_info.msg;
    pjsip_process_rdata_param proc_prm;
    pj_bool_t handled = PJ_FALSE;

    PJ_UNUSED_ARG(msg);

    if (status != PJ_SUCCESS) {
        char info[30];
        char errmsg[PJ_ERR_MSG_SIZE];

        info[0] = '\0';

        if (status == PJSIP_EMISSINGHDR) {
            pj_str_t p;

            p.ptr = info; p.slen = 0;

            if (rdata->msg_info.cid == NULL || rdata->msg_info.cid->id.slen)
                pj_strcpy2(&p, "Call-ID");
            if (rdata->msg_info.from == NULL)
                pj_strcpy2(&p, " From");
            if (rdata->msg_info.to == NULL)
                pj_strcpy2(&p, " To");
            if (rdata->msg_info.via == NULL)
                pj_strcpy2(&p, " Via");
            if (rdata->msg_info.cseq == NULL) 
                pj_strcpy2(&p, " CSeq");

            p.ptr[p.slen] = '\0';
        }

        pj_strerror(status, errmsg, sizeof(errmsg));

        PJ_LOG(1, (THIS_FILE, 
                  "Error processing packet from %s:%d: %s %s [code %d]:\n"
                  "%.*s\n"
                  "-- end of packet.",
                  rdata->pkt_info.src_name, 
                  rdata->pkt_info.src_port,
                  errmsg,
                  info,
                  status,
                  (int)rdata->msg_info.len,     
                  rdata->msg_info.msg_buf));
        return;
    }

    PJ_LOG(5, (THIS_FILE, "Processing incoming message: %s", 
               pjsip_rx_data_get_info(rdata)));
    pj_log_push_indent();

#if defined(PJSIP_CHECK_VIA_SENT_BY) && PJSIP_CHECK_VIA_SENT_BY != 0
    /* For response, check that the value in Via sent-by match the transport.
     * If not matched, silently drop the response.
     * Ref: RFC3261 Section 18.1.2 Receiving Response
     */
    if (msg->type == PJSIP_RESPONSE_MSG) {
        const pj_str_t *local_addr;
        int port = rdata->msg_info.via->sent_by.port;
        pj_bool_t mismatch = PJ_FALSE;
        if (port == 0) {
            pjsip_transport_type_e type;
            type = (pjsip_transport_type_e)rdata->tp_info.transport->key.type;
            port = pjsip_transport_get_default_port_for_type(type);
        }
        local_addr = &rdata->tp_info.transport->local_name.host;

        if (pj_strcmp(&rdata->msg_info.via->sent_by.host, local_addr) != 0) {

            /* The RFC says that we should drop response when sent-by
             * address mismatch. But it could happen (e.g. with SER) when
             * endpoint with private IP is sending request to public
             * server.

            mismatch = PJ_TRUE;

             */

        } else if (port != rdata->tp_info.transport->local_name.port) {
            /* Port or address mismatch, we should discard response */
            /* But we saw one implementation (we don't want to name it to 
             * protect the innocence) which put wrong sent-by port although
             * the "rport" parameter is correct.
             * So we discard the response only if the port doesn't match
             * both the port in sent-by and rport. We try to be lenient here!
             */
            if (rdata->msg_info.via->rport_param != 
                rdata->tp_info.transport->local_name.port)
                mismatch = PJ_TRUE;
            else {
                PJ_LOG(4,(THIS_FILE, "Message %s from %s has mismatch port in "
                                     "sent-by but the rport parameter is "
                                     "correct",
                                     pjsip_rx_data_get_info(rdata), 
                                     rdata->pkt_info.src_name));
            }
        }

        if (mismatch) {
            PJ_TODO(ENDPT_REPORT_WHEN_DROPPING_MESSAGE);
            PJ_LOG(4,(THIS_FILE, "Dropping response %s from %s:%d because "
                                 "sent-by is mismatch", 
                                 pjsip_rx_data_get_info(rdata),
                                 rdata->pkt_info.src_name, 
                                 rdata->pkt_info.src_port));
            pj_log_pop_indent();
            return;
        }
    }
#endif

    pjsip_process_rdata_param_default(&proc_prm);
    proc_prm.silent = PJ_TRUE;

    pjsip_endpt_process_rx_data(endpt, rdata, &proc_prm, &handled);

    /* No module is able to handle the message */
    if (!handled) {
        PJ_LOG(4,(THIS_FILE, "%s from %s:%d was dropped/unhandled by"
                             " any modules",
                             pjsip_rx_data_get_info(rdata),
                             rdata->pkt_info.src_name,
                             rdata->pkt_info.src_port));
    }

    /* Must clear mod_data before returning rdata to transport, since
     * rdata may be reused.
     */
    pj_bzero(&rdata->endpt_info, sizeof(rdata->endpt_info));

    pj_log_pop_indent();
}

/*
 * This callback is called by transport manager before message is sent.
 * Modules may inspect the message before it's actually sent.
 */
static pj_status_t endpt_on_tx_msg( pjsip_endpoint *endpt,
                                    pjsip_tx_data *tdata )
{
    pj_status_t status = PJ_SUCCESS;
    pjsip_module *mod;

    /* Distribute to modules, starting from modules with LOWEST priority */
    LOCK_MODULE_ACCESS(endpt);

    mod = endpt->module_list.prev;
    if (tdata->msg->type == PJSIP_REQUEST_MSG) {
        while (mod != &endpt->module_list) {
            if (mod->on_tx_request)
                status = (*mod->on_tx_request)(tdata);
            if (status != PJ_SUCCESS)
                break;
            mod = mod->prev;
        }

    } else {
        while (mod != &endpt->module_list) {
            if (mod->on_tx_response)
                status = (*mod->on_tx_response)(tdata);
            if (status != PJ_SUCCESS)
                break;
            mod = mod->prev;
        }
    }

    UNLOCK_MODULE_ACCESS(endpt);

    return status;
}


/*
 * Create transmit data buffer.
 */
PJ_DEF(pj_status_t) pjsip_endpt_create_tdata(  pjsip_endpoint *endpt,
                                               pjsip_tx_data **p_tdata)
{
    return pjsip_tx_data_create(endpt->transport_mgr, p_tdata);
}

/*
 * Create the DNS resolver instance. 
 */
PJ_DEF(pj_status_t) pjsip_endpt_create_resolver(pjsip_endpoint *endpt,
                                                pj_dns_resolver **p_resv)
{
#if PJSIP_HAS_RESOLVER
    PJ_ASSERT_RETURN(endpt && p_resv, PJ_EINVAL);
    return pj_dns_resolver_create( endpt->pf, NULL, 0, endpt->timer_heap,
                                   endpt->ioqueue, p_resv);
#else
    PJ_UNUSED_ARG(endpt);
    PJ_UNUSED_ARG(p_resv);
    pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)");
    return PJ_EINVALIDOP;
#endif
}

/*
 * Set DNS resolver to be used by the SIP resolver.
 */
PJ_DEF(pj_status_t) pjsip_endpt_set_resolver( pjsip_endpoint *endpt,
                                              pj_dns_resolver *resv)
{
    return pjsip_resolver_set_resolver(endpt->resolver, resv);
}

/*
 * Set DNS external resolver implementation to be used by the SIP resolver.
 */
PJ_DEF(pj_status_t) pjsip_endpt_set_ext_resolver(pjsip_endpoint *endpt,
                                                 pjsip_ext_resolver *ext_res)
{
    return pjsip_resolver_set_ext_resolver(endpt->resolver, ext_res);
}

/*
 * Get the DNS resolver being used by the SIP resolver.
 */
PJ_DEF(pj_dns_resolver*) pjsip_endpt_get_resolver(pjsip_endpoint *endpt)
{
    PJ_ASSERT_RETURN(endpt, NULL);
    return pjsip_resolver_get_resolver(endpt->resolver);
}

/*
 * Resolve
 */
PJ_DEF(void) pjsip_endpt_resolve( pjsip_endpoint *endpt,
                                  pj_pool_t *pool,
                                  pjsip_host_info *target,
                                  void *token,
                                  pjsip_resolver_callback *cb)
{
    pjsip_resolve( endpt->resolver, pool, target, token, cb);
}

/*
 * Get transport manager.
 */
PJ_DEF(pjsip_tpmgr*) pjsip_endpt_get_tpmgr(pjsip_endpoint *endpt)
{
    return endpt->transport_mgr;
}

/*
 * Get ioqueue instance.
 */
PJ_DEF(pj_ioqueue_t*) pjsip_endpt_get_ioqueue(pjsip_endpoint *endpt)
{
    return endpt->ioqueue;
}

/*
 * Find/create transport.
 */
PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport(pjsip_endpoint *endpt,
                                                  pjsip_transport_type_e type,
                                                  const pj_sockaddr_t *remote,
                                                  int addr_len,
                                                  const pjsip_tpselector *sel,
                                                  pjsip_transport **transport)
{
    return pjsip_tpmgr_acquire_transport(endpt->transport_mgr, type, 
                                         remote, addr_len, sel, transport);
}


/*
 * Find/create transport.
 */
PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport2(pjsip_endpoint *endpt,
                                                   pjsip_transport_type_e type,
                                                   const pj_sockaddr_t *remote,
                                                   int addr_len,
                                                   const pjsip_tpselector *sel,
                                                   pjsip_tx_data *tdata,
                                                   pjsip_transport **transport)
{
    return pjsip_tpmgr_acquire_transport2(endpt->transport_mgr, type, remote, 
                                          addr_len, sel, tdata, transport);
}


/*
 * Report error.
 */
PJ_DEF(void) pjsip_endpt_log_error(  pjsip_endpoint *endpt,
                                     const char *sender,
                                     pj_status_t error_code,
                                     const char *format,
                                     ... )
{
#if PJ_LOG_MAX_LEVEL > 0
    char newformat[256];
    pj_size_t len;
    va_list marker;

    va_start(marker, format);

    PJ_UNUSED_ARG(endpt);

    len = pj_ansi_strlen(format);
    if (len < (int)sizeof(newformat)-30) {
        pj_str_t errstr;

        pj_ansi_strxcpy(newformat, format, sizeof(newformat));
        pj_ansi_snprintf(newformat+len, sizeof(newformat)-len-1,
                         ": [err %d] ", error_code);
        len += pj_ansi_strlen(newformat+len);

        errstr = pj_strerror( error_code, newformat+len, 
                              sizeof(newformat)-len-1);

        len += errstr.slen;
        newformat[len] = '\0';

        pj_log(sender, 1, newformat, marker);
    } else {
        pj_log(sender, 1, format, marker);
    }

    va_end(marker);
#else
    PJ_UNUSED_ARG(format);
    PJ_UNUSED_ARG(error_code);
    PJ_UNUSED_ARG(sender);
    PJ_UNUSED_ARG(endpt);
#endif
}


/*
 * Dump endpoint.
 */
PJ_DEF(void) pjsip_endpt_dump( pjsip_endpoint *endpt, pj_bool_t detail )
{
#if PJ_LOG_MAX_LEVEL >= 3
    PJ_LOG(5, (THIS_FILE, "pjsip_endpt_dump()"));

    /* Lock mutex. */
    pj_mutex_lock(endpt->mutex);

    PJ_LOG(3, (THIS_FILE, "Dumping endpoint %p:", endpt));
    
    /* Dumping pool factory. */
    pj_pool_factory_dump(endpt->pf, detail);

    /* Pool health. */
    PJ_LOG(3, (THIS_FILE," Endpoint pool capacity=%lu, used_size=%lu",
               (unsigned long)pj_pool_get_capacity(endpt->pool),
               (unsigned long)pj_pool_get_used_size(endpt->pool)));

    /* Resolver */
#if PJSIP_HAS_RESOLVER
    if (pjsip_endpt_get_resolver(endpt)) {
        pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), detail);
    }
#endif

    /* Transports. 
     */
    pjsip_tpmgr_dump_transports( endpt->transport_mgr );

    /* Timer. */
#if PJ_TIMER_DEBUG
    pj_timer_heap_dump(endpt->timer_heap);
#else
    PJ_LOG(3,(THIS_FILE, " Timer heap has %lu entries",
              (unsigned long)pj_timer_heap_count(endpt->timer_heap)));
#endif

    /* Unlock mutex. */
    pj_mutex_unlock(endpt->mutex);
#else
    PJ_UNUSED_ARG(endpt);
    PJ_UNUSED_ARG(detail);
    PJ_LOG(3,(THIS_FILE, "pjsip_end_dump: can't dump because it's disabled."));
#endif
}


PJ_DEF(pj_status_t) pjsip_endpt_atexit( pjsip_endpoint *endpt,
                                        pjsip_endpt_exit_callback func)
{
    exit_cb *new_cb;

    PJ_ASSERT_RETURN(endpt && func, PJ_EINVAL);

    new_cb = PJ_POOL_ZALLOC_T(endpt->pool, exit_cb);
    new_cb->func = func;

    pj_mutex_lock(endpt->mutex);
    pj_list_push_back(&endpt->exit_cb_list, new_cb);
    pj_mutex_unlock(endpt->mutex);

    return PJ_SUCCESS;
}
