diff options
Diffstat (limited to 'pjsip/src/pjsip-ua/sip_ua.c')
-rw-r--r-- | pjsip/src/pjsip-ua/sip_ua.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/pjsip/src/pjsip-ua/sip_ua.c b/pjsip/src/pjsip-ua/sip_ua.c new file mode 100644 index 00000000..6502a5e2 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_ua.c @@ -0,0 +1,491 @@ +/* $Id$ + * + */ +#include <pjsip_mod_ua/sip_ua.h> +#include <pjsip_mod_ua/sip_dialog.h> +#include <pjsip_mod_ua/sip_ua_private.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_misc.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_transaction.h> +#include <pj/list.h> +#include <pj/log.h> +#include <pj/string.h> +#include <pj/guid.h> +#include <pj/os.h> +#include <pj/hash.h> +#include <pj/pool.h> + +#define PJSIP_POOL_LEN_USER_AGENT 1024 +#define PJSIP_POOL_INC_USER_AGENT 0 + + +#define LOG_THIS "useragent.." + +/* + * Static prototypes. + */ +static pj_status_t ua_init( pjsip_endpoint *endpt, + struct pjsip_module *mod, pj_uint32_t id ); +static pj_status_t ua_start( struct pjsip_module *mod ); +static pj_status_t ua_deinit( struct pjsip_module *mod ); +static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *evt ); +static pjsip_dlg *find_dialog( pjsip_user_agent *ua, + pjsip_rx_data *rdata ); +static pj_status_t ua_register_dialog( pjsip_user_agent *ua, pjsip_dlg *dlg, + pj_str_t *key ); +PJ_DECL(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg ); + +/* + * Default UA instance. + */ +static pjsip_user_agent ua_instance; + +/* + * Module interface. + */ +static struct pjsip_module mod_ua = +{ + { "User-Agent", 10 }, /* Name. */ + 0, /* Flag */ + 128, /* Priority */ + NULL, /* User agent instance, initialized by APP. */ + 0, /* Number of methods supported (will be initialized later). */ + { 0 }, /* Array of methods (will be initialized later) */ + &ua_init, /* init_module() */ + &ua_start, /* start_module() */ + &ua_deinit, /* deinit_module() */ + &ua_tsx_handler, /* tsx_handler() */ +}; + +/* + * Initialize user agent instance. + */ +static pj_status_t ua_init( pjsip_endpoint *endpt, + struct pjsip_module *mod, pj_uint32_t id ) +{ + static pjsip_method m_invite, m_ack, m_cancel, m_bye; + pjsip_user_agent *ua = mod->mod_data; + extern int pjsip_dlg_lock_tls_id; /* defined in sip_dialog.c */ + + pjsip_method_set( &m_invite, PJSIP_INVITE_METHOD ); + pjsip_method_set( &m_ack, PJSIP_ACK_METHOD ); + pjsip_method_set( &m_cancel, PJSIP_CANCEL_METHOD ); + pjsip_method_set( &m_bye, PJSIP_BYE_METHOD ); + + mod->method_cnt = 4; + mod->methods[0] = &m_invite; + mod->methods[1] = &m_ack; + mod->methods[2] = &m_cancel; + mod->methods[3] = &m_bye; + + /* Initialize the user agent. */ + ua->endpt = endpt; + ua->pool = pjsip_endpt_create_pool(endpt, "pua%p", PJSIP_POOL_LEN_UA, + PJSIP_POOL_INC_UA); + if (!ua->pool) { + return -1; + } + ua->mod_id = id; + ua->mutex = pj_mutex_create(ua->pool, " ua%p", 0); + if (!ua->mutex) { + return -1; + } + ua->dlg_table = pj_hash_create(ua->pool, PJSIP_MAX_DIALOG_COUNT); + if (ua->dlg_table == NULL) { + return -1; + } + pj_list_init(&ua->dlg_list); + + /* Initialize dialog lock. */ + pjsip_dlg_lock_tls_id = pj_thread_local_alloc(); + if (pjsip_dlg_lock_tls_id == -1) { + return -1; + } + pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL); + + return 0; +} + +/* + * Start user agent instance. + */ +static pj_status_t ua_start( struct pjsip_module *mod ) +{ + PJ_UNUSED_ARG(mod) + return 0; +} + +/* + * Destroy user agent. + */ +static pj_status_t ua_deinit( struct pjsip_module *mod ) +{ + pjsip_user_agent *ua = mod->mod_data; + + pj_mutex_unlock(ua->mutex); + + /* Release pool */ + if (ua->pool) { + pjsip_endpt_destroy_pool( ua->endpt, ua->pool ); + } + return 0; +} + +/* + * Get the module interface for the UA module. + */ +PJ_DEF(pjsip_module*) pjsip_ua_get_module(void) +{ + mod_ua.mod_data = &ua_instance; + return &mod_ua; +} + +/* + * Register callback to receive dialog notifications. + */ +PJ_DEF(void) pjsip_ua_set_dialog_callback( pjsip_user_agent *ua, + pjsip_dlg_callback *cb ) +{ + ua->dlg_cb = cb; +} + +/* + * Find dialog. + * This function is called for a new transactions, which a dialog hasn't been + * 'attached' to the transaction. + */ +static pjsip_dlg *find_dialog( pjsip_user_agent *ua, pjsip_rx_data *rdata ) +{ + pjsip_dlg *dlg; + pj_str_t *tag; + + /* Non-CANCEL requests/response can be found by looking at the tag in the + * hash table. CANCEL requests don't have tags, so instead we'll try to + * find the UAS INVITE transaction in endpoint's hash table + */ + if (rdata->cseq->method.id == PJSIP_CANCEL_METHOD) { + + /* Create key for the rdata, but this time, use INVITE as the + * method. + */ + pj_str_t key; + pjsip_role_e role; + pjsip_method invite_method; + pjsip_transaction *invite_tsx; + + if (rdata->msg->type == PJSIP_REQUEST_MSG) { + role = PJSIP_ROLE_UAS; + } else { + role = PJSIP_ROLE_UAC; + } + pjsip_method_set(&invite_method, PJSIP_INVITE_METHOD); + pjsip_tsx_create_key(rdata->pool, &key, role, &invite_method, rdata); + + /* Lookup the INVITE transaction */ + invite_tsx = pjsip_endpt_find_tsx(ua->endpt, &key); + + /* We should find the dialog attached to the INVITE transaction */ + return invite_tsx ? + (pjsip_dlg*) invite_tsx->module_data[ua->mod_id] : NULL; + + } else { + if (rdata->msg->type == PJSIP_REQUEST_MSG) { + tag = &rdata->to_tag; + } else { + tag = &rdata->from_tag; + } + /* Find the dialog in UA hash table */ + pj_mutex_lock(ua->mutex); + dlg = pj_hash_get( ua->dlg_table, tag->ptr, tag->slen ); + pj_mutex_unlock(ua->mutex); + } + + return dlg; +} + +/* + * This function receives event notification from transactions. It is called by + * endpoint. + */ +static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *event ) +{ + pjsip_user_agent *ua = mod->mod_data; + pjsip_dlg *dlg = NULL; + pjsip_transaction *tsx = event->obj.tsx; + + PJ_LOG(5, (LOG_THIS, "ua_tsx_handler(tsx=%s, evt=%s, src=%s, data=%p)", + (tsx ? tsx->obj_name : "NULL"), pjsip_event_str(event->type), + pjsip_event_str(event->src_type), event->src.data)); + + /* Special case to handle ACK which doesn't match any INVITE transactions. */ + if (event->type == PJSIP_EVENT_RX_ACK_MSG) { + /* Find the dialog based on the "tag". */ + dlg = find_dialog( ua, event->src.rdata ); + + /* We should be able to find it. */ + if (!dlg) { + PJ_LOG(4,(LOG_THIS, "Unable to find dialog for incoming ACK")); + return; + } + + /* Match CSeq with pending INVITE in dialog. */ + if (dlg->invite_tsx && dlg->invite_tsx->cseq==event->src.rdata->cseq->cseq) { + /* A match found. */ + tsx = dlg->invite_tsx; + + /* Pass the event to transaction if transaction handles ACK. */ + if (tsx->handle_ack) { + PJ_LOG(4,(LOG_THIS, "Re-routing strandled ACK to transaction")); + pjsip_tsx_on_rx_msg(tsx, event->src.rdata); + return; + } + } else { + tsx = NULL; + PJ_LOG(4,(LOG_THIS, "Unable to find INVITE tsx for incoming ACK")); + return; + } + } + + /* For discard event, transaction is NULL. */ + if (tsx == NULL) { + return; + } + + /* Try to pickup the dlg from the transaction. */ + dlg = (pjsip_dlg*) tsx->module_data[ua->mod_id]; + + if (dlg != NULL) { + + /* Nothing to do now. */ + + } else if (event->src_type == PJSIP_EVENT_RX_MSG) { + + /* This must be a new UAS transaction. */ + + /* Finds dlg that can handle this transaction. */ + dlg = find_dialog( ua, event->src.rdata); + + /* Create a new dlg if there's no existing dlg that can handle + the request, ONLY if the incoming message is an INVITE request. + */ + if (dlg==NULL && event->src.rdata->msg->type == PJSIP_REQUEST_MSG) { + + if (event->src.rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD) { + /* Create new dialog. */ + dlg = pjsip_ua_create_dialog( ua, PJSIP_ROLE_UAS ); + + if (dlg == NULL || + pjsip_dlg_init_from_rdata( dlg, event->src.rdata) != 0) + { + pjsip_tx_data *tdata; + + /* Dialog initialization has failed. Respond request with 500 */ + if (dlg) { + pjsip_ua_destroy_dialog(dlg); + } + tdata = pjsip_endpt_create_response(ua->endpt, event->src.rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR); + if (tdata) { + pjsip_tsx_on_tx_msg( event->obj.tsx, tdata ); + } + return; + } + + } else { + pjsip_tx_data *tdata; + + /* Check the method */ + switch (tsx->method.id) { + case PJSIP_INVITE_METHOD: + case PJSIP_ACK_METHOD: + case PJSIP_BYE_METHOD: + case PJSIP_CANCEL_METHOD: + /* Stale non-INVITE request. + * For now, respond all stale requests with 481 (?). + */ + tdata = pjsip_endpt_create_response(ua->endpt, event->src.rdata, + PJSIP_SC_CALL_TSX_DOES_NOT_EXIST); + if (tdata) { + pjsip_tsx_on_tx_msg( event->obj.tsx, tdata ); + } + break; + } + + return; + } + } else { + /* Check the method */ + switch (tsx->method.id) { + case PJSIP_INVITE_METHOD: + case PJSIP_ACK_METHOD: + case PJSIP_BYE_METHOD: + case PJSIP_CANCEL_METHOD: + /* These methods belongs to dialog. + * If we receive these methods while no dialog is found, + * then it must be a stale responses. + */ + break; + default: + return; + } + + } + + if (dlg == NULL) { + PJ_LOG(3, (LOG_THIS, "Receives spurious rdata %p from %s:%d", + event->src.rdata, + pj_sockaddr_get_str_addr(&event->src.rdata->addr), + pj_sockaddr_get_port(&event->src.rdata->addr))); + } + + /* Set the dlg in the transaction (dlg can be NULL). */ + tsx->module_data[ua->mod_id] = dlg; + + } else { + /* This CAN happen with event->src_type == PJSIP_EVENT_TX_MSG + * if UAS is responding to a transaction which does not exist. + * Just ignore. + */ + return; + } + + /* Pass the event to the dlg. */ + if (dlg) { + pjsip_dlg_on_tsx_event(dlg, tsx, event); + } +} + +/* + * Register dialog to UA. + */ +static pj_status_t ua_register_dialog( pjsip_user_agent *ua, pjsip_dlg *dlg, + pj_str_t *key ) +{ + /* Assure that no entry with similar key exists in the hash table. */ + pj_assert( pj_hash_get( ua->dlg_table, key->ptr, key->slen) == 0); + + /* Insert entry to hash table. */ + pj_hash_set( dlg->pool, ua->dlg_table, + key->ptr, key->slen, dlg); + + /* Insert to the list. */ + pj_list_insert_before(&ua->dlg_list, dlg); + return PJ_SUCCESS; +} + +/* + * Create a new dialog. + */ +PJ_DEF(pjsip_dlg*) pjsip_ua_create_dialog( pjsip_user_agent *ua, + pjsip_role_e role ) +{ + pj_pool_t *pool; + pjsip_dlg *dlg; + + PJ_UNUSED_ARG(ua) + + /* Create pool for the dialog. */ + pool = pjsip_endpt_create_pool( ua->endpt, "pdlg%p", + PJSIP_POOL_LEN_DIALOG, + PJSIP_POOL_INC_DIALOG); + + /* Create the dialog. */ + dlg = pj_pool_calloc(pool, 1, sizeof(pjsip_dlg)); + dlg->pool = pool; + dlg->ua = ua; + dlg->role = role; + sprintf(dlg->obj_name, "dlg%p", dlg); + + /* Create mutex for the dialog. */ + dlg->mutex = pj_mutex_create(dlg->pool, "mdlg%p", 0); + if (!dlg->mutex) { + pjsip_endpt_destroy_pool(ua->endpt, pool); + return NULL; + } + + /* Create unique tag for the dialog. */ + pj_create_unique_string( pool, &dlg->local.tag ); + + /* Register dialog. */ + pj_mutex_lock(ua->mutex); + if (ua_register_dialog(ua, dlg, &dlg->local.tag) != PJ_SUCCESS) { + pj_mutex_unlock(ua->mutex); + pj_mutex_destroy(dlg->mutex); + pjsip_endpt_destroy_pool( ua->endpt, pool ); + return NULL; + } + pj_mutex_unlock(ua->mutex); + + PJ_LOG(4, (dlg->obj_name, "new %s dialog created", pjsip_role_name(role))); + return dlg; +} + +/* + * Destroy dialog. + */ +PJ_DEF(void) pjsip_ua_destroy_dialog( pjsip_dlg *dlg ) +{ + PJ_LOG(5, (dlg->obj_name, "destroying..")); + + /* Lock dialog's mutex. + * Check the mutex validity first since this function can be called + * on dialog initialization failure (which might be because mutex could not + * be allocated in the first place). + */ + if (dlg->mutex) { + pj_mutex_lock(dlg->mutex); + } + + /* This must be called while holding dialog's mutex, if any. */ + pjsip_on_dialog_destroyed(dlg); + + /* Lock UA. */ + pj_mutex_lock(dlg->ua->mutex); + + /* Erase from hash table. */ + pj_hash_set( dlg->pool, dlg->ua->dlg_table, + dlg->local.tag.ptr, dlg->local.tag.slen, NULL); + + /* Erase from the list. */ + pj_list_erase(dlg); + + /* Unlock UA. */ + pj_mutex_unlock(dlg->ua->mutex); + + /* Unlock mutex. */ + if (dlg->mutex) { + pj_mutex_unlock(dlg->mutex); + } + + /* Destroy the pool. */ + pjsip_endpt_destroy_pool( dlg->ua->endpt, dlg->pool); +} + +/* + * Dump user agent state to log file. + */ +PJ_DEF(void) pjsip_ua_dump(pjsip_user_agent *ua) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + PJ_LOG(3,(LOG_THIS, "Dumping user agent")); + PJ_LOG(3,(LOG_THIS, " Pool capacity=%u, used=%u", + pj_pool_get_capacity(ua->pool), + pj_pool_get_used_size(ua->pool))); + PJ_LOG(3,(LOG_THIS, " Number of dialogs=%u", pj_hash_count(ua->dlg_table))); + + if (pj_hash_count(ua->dlg_table)) { + pjsip_dlg *dlg; + + PJ_LOG(3,(LOG_THIS, " Dumping dialog list:")); + dlg = ua->dlg_list.next; + while (dlg != (pjsip_dlg*) &ua->dlg_list) { + PJ_LOG(3, (LOG_THIS, " %s %s", dlg->obj_name, + pjsip_dlg_state_str(dlg->state))); + dlg = dlg->next; + } + } +#endif +} + |