| /* $Header: /pjproject-0.3/pjsip/src/pjsip_mod_ua/sip_ua.c 16 10/14/05 12:23a Bennylp $ */ |
| #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 |
| } |
| |