pjproject/pjsip/src/pjsip/sip_endpoint.c

1369 lines
40 KiB
C

/*
* 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;
}