From f3ab456a17af1c89a6e3be4d20c5944853df1cb0 Mon Sep 17 00:00:00 2001 From: "David M. Lee" Date: Mon, 7 Jan 2013 14:24:28 -0600 Subject: Import pjproject-2.0.1 --- pjsip/src/pjsip/sip_transaction.c | 3277 +++++++++++++++++++++++++++++++++++++ 1 file changed, 3277 insertions(+) create mode 100644 pjsip/src/pjsip/sip_transaction.c (limited to 'pjsip/src/pjsip/sip_transaction.c') diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c new file mode 100644 index 0000000..4b3dd12 --- /dev/null +++ b/pjsip/src/pjsip/sip_transaction.c @@ -0,0 +1,3277 @@ +/* $Id: sip_transaction.c 4165 2012-06-14 09:04:20Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "sip_transaction.c" + +#if 0 +#define TSX_TRACE_(expr) PJ_LOG(3,expr) +#else +#define TSX_TRACE_(expr) +#endif + +/* When this macro is set, transaction will keep the hashed value + * so that future lookup (to unregister transaction) does not need + * to recalculate the hash again. It should gains a little bit of + * performance, so generally we'd want this. + */ +#define PRECALC_HASH + + +/* Defined in sip_util_statefull.c */ +extern pjsip_module mod_stateful_util; + + +/***************************************************************************** + ** + ** Declarations and static variable definitions section. + ** + ***************************************************************************** + **/ +/* Prototypes. */ +static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt); +static pj_status_t mod_tsx_layer_start(void); +static pj_status_t mod_tsx_layer_stop(void); +static pj_status_t mod_tsx_layer_unload(void); +static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata); + +/* Transaction layer module definition. */ +static struct mod_tsx_layer +{ + struct pjsip_module mod; + pj_pool_t *pool; + pjsip_endpoint *endpt; + pj_mutex_t *mutex; + pj_hash_table_t *htable; +} mod_tsx_layer = +{ { + NULL, NULL, /* List's prev and next. */ + { "mod-tsx-layer", 13 }, /* Module name. */ + -1, /* Module ID */ + PJSIP_MOD_PRIORITY_TSX_LAYER, /* Priority. */ + mod_tsx_layer_load, /* load(). */ + mod_tsx_layer_start, /* start() */ + mod_tsx_layer_stop, /* stop() */ + mod_tsx_layer_unload, /* unload() */ + mod_tsx_layer_on_rx_request, /* on_rx_request() */ + mod_tsx_layer_on_rx_response, /* on_rx_response() */ + NULL + } +}; + +/* Thread Local Storage ID for transaction lock */ +static long pjsip_tsx_lock_tls_id; + +/* Transaction state names */ +static const char *state_str[] = +{ + "Null", + "Calling", + "Trying", + "Proceeding", + "Completed", + "Confirmed", + "Terminated", + "Destroyed", +}; + +/* Role names */ +static const char *role_name[] = +{ + "UAC", + "UAS" +}; + +/* Transport flag. */ +enum +{ + TSX_HAS_PENDING_TRANSPORT = 1, + TSX_HAS_PENDING_RESCHED = 2, + TSX_HAS_PENDING_SEND = 4, + TSX_HAS_PENDING_DESTROY = 8, + TSX_HAS_RESOLVED_SERVER = 16, +}; + +/* Transaction lock. */ +typedef struct tsx_lock_data { + struct tsx_lock_data *prev; + pjsip_transaction *tsx; + int is_alive; +} tsx_lock_data; + + +/* Timer timeout value constants */ +static pj_time_val t1_timer_val = { PJSIP_T1_TIMEOUT/1000, + PJSIP_T1_TIMEOUT%1000 }; +static pj_time_val t2_timer_val = { PJSIP_T2_TIMEOUT/1000, + PJSIP_T2_TIMEOUT%1000 }; +static pj_time_val t4_timer_val = { PJSIP_T4_TIMEOUT/1000, + PJSIP_T4_TIMEOUT%1000 }; +static pj_time_val td_timer_val = { PJSIP_TD_TIMEOUT/1000, + PJSIP_TD_TIMEOUT%1000 }; +static pj_time_val timeout_timer_val = { (64*PJSIP_T1_TIMEOUT)/1000, + (64*PJSIP_T1_TIMEOUT)%1000 }; + +#define TIMER_INACTIVE 0 +#define TIMER_ACTIVE 1 + + +/* Prototypes. */ +static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck); +static pj_status_t unlock_tsx( pjsip_transaction *tsx, + struct tsx_lock_data *lck); +static pj_status_t tsx_on_state_null( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_proceeding_uac( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_destroyed( pjsip_transaction *tsx, + pjsip_event *event); +static void tsx_timer_callback( pj_timer_heap_t *theap, + pj_timer_entry *entry); +static void tsx_tp_state_callback( + pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info); +static pj_status_t tsx_create( pjsip_module *tsx_user, + pjsip_transaction **p_tsx); +static pj_status_t tsx_destroy( pjsip_transaction *tsx ); +static void tsx_resched_retransmission( pjsip_transaction *tsx ); +static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched); +static int tsx_send_msg( pjsip_transaction *tsx, + pjsip_tx_data *tdata); +static void tsx_update_transport( pjsip_transaction *tsx, + pjsip_transport *tp); + + +/* State handlers for UAC, indexed by state */ +static int (*tsx_state_handler_uac[PJSIP_TSX_STATE_MAX])(pjsip_transaction *, + pjsip_event *) = +{ + &tsx_on_state_null, + &tsx_on_state_calling, + NULL, + &tsx_on_state_proceeding_uac, + &tsx_on_state_completed_uac, + &tsx_on_state_confirmed, + &tsx_on_state_terminated, + &tsx_on_state_destroyed, +}; + +/* State handlers for UAS */ +static int (*tsx_state_handler_uas[PJSIP_TSX_STATE_MAX])(pjsip_transaction *, + pjsip_event *) = +{ + &tsx_on_state_null, + NULL, + &tsx_on_state_trying, + &tsx_on_state_proceeding_uas, + &tsx_on_state_completed_uas, + &tsx_on_state_confirmed, + &tsx_on_state_terminated, + &tsx_on_state_destroyed, +}; + +/***************************************************************************** + ** + ** Utilities + ** + ***************************************************************************** + */ +/* + * Get transaction state name. + */ +PJ_DEF(const char *) pjsip_tsx_state_str(pjsip_tsx_state_e state) +{ + return state_str[state]; +} + +/* + * Get the role name. + */ +PJ_DEF(const char *) pjsip_role_name(pjsip_role_e role) +{ + return role_name[role]; +} + + +/* + * Create transaction key for RFC2543 compliant messages, which don't have + * unique branch parameter in the top most Via header. + * + * INVITE requests matches a transaction if the following attributes + * match the original request: + * - Request-URI + * - To tag + * - From tag + * - Call-ID + * - CSeq + * - top Via header + * + * CANCEL matching is done similarly as INVITE, except: + * - CSeq method will differ + * - To tag is not matched. + * + * ACK matching is done similarly, except that: + * - method of the CSeq will differ, + * - To tag is matched to the response sent by the server transaction. + * + * The transaction key is constructed from the common components of above + * components. Additional comparison is needed to fully match a transaction. + */ +static pj_status_t create_tsx_key_2543( pj_pool_t *pool, + pj_str_t *str, + pjsip_role_e role, + const pjsip_method *method, + const pjsip_rx_data *rdata ) +{ +#define SEPARATOR '$' + char *key, *p, *end; + int len; + pj_size_t len_required; + pjsip_uri *req_uri; + pj_str_t *host; + + PJ_ASSERT_RETURN(pool && str && method && rdata, PJ_EINVAL); + PJ_ASSERT_RETURN(rdata->msg_info.msg, PJ_EINVAL); + PJ_ASSERT_RETURN(rdata->msg_info.via, PJSIP_EMISSINGHDR); + PJ_ASSERT_RETURN(rdata->msg_info.cseq, PJSIP_EMISSINGHDR); + PJ_ASSERT_RETURN(rdata->msg_info.from, PJSIP_EMISSINGHDR); + + host = &rdata->msg_info.via->sent_by.host; + req_uri = (pjsip_uri*)rdata->msg_info.msg->line.req.uri; + + /* Calculate length required. */ + len_required = 9 + /* CSeq number */ + rdata->msg_info.from->tag.slen + /* From tag. */ + rdata->msg_info.cid->id.slen + /* Call-ID */ + host->slen + /* Via host. */ + 9 + /* Via port. */ + 16; /* Separator+Allowance. */ + key = p = (char*) pj_pool_alloc(pool, len_required); + end = p + len_required; + + /* Add role. */ + *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's'); + *p++ = SEPARATOR; + + /* Add method, except when method is INVITE or ACK. */ + if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) { + pj_memcpy(p, method->name.ptr, method->name.slen); + p += method->name.slen; + *p++ = '$'; + } + + /* Add CSeq (only the number). */ + len = pj_utoa(rdata->msg_info.cseq->cseq, p); + p += len; + *p++ = SEPARATOR; + + /* Add From tag. */ + len = rdata->msg_info.from->tag.slen; + pj_memcpy( p, rdata->msg_info.from->tag.ptr, len); + p += len; + *p++ = SEPARATOR; + + /* Add Call-ID. */ + len = rdata->msg_info.cid->id.slen; + pj_memcpy( p, rdata->msg_info.cid->id.ptr, len ); + p += len; + *p++ = SEPARATOR; + + /* Add top Via header. + * We don't really care whether the port contains the real port (because + * it can be omited if default port is used). Anyway this function is + * only used to match request retransmission, and we expect that the + * request retransmissions will contain the same port. + */ + pj_memcpy(p, host->ptr, host->slen); + p += host->slen; + *p++ = ':'; + + len = pj_utoa(rdata->msg_info.via->sent_by.port, p); + p += len; + *p++ = SEPARATOR; + + *p++ = '\0'; + + /* Done. */ + str->ptr = key; + str->slen = p-key; + + return PJ_SUCCESS; +} + +/* + * Create transaction key for RFC3161 compliant system. + */ +static pj_status_t create_tsx_key_3261( pj_pool_t *pool, + pj_str_t *key, + pjsip_role_e role, + const pjsip_method *method, + const pj_str_t *branch) +{ + char *p; + + PJ_ASSERT_RETURN(pool && key && method && branch, PJ_EINVAL); + + p = key->ptr = (char*) + pj_pool_alloc(pool, branch->slen + method->name.slen + 4 ); + + /* Add role. */ + *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's'); + *p++ = SEPARATOR; + + /* Add method, except when method is INVITE or ACK. */ + if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) { + pj_memcpy(p, method->name.ptr, method->name.slen); + p += method->name.slen; + *p++ = '$'; + } + + /* Add branch ID. */ + pj_memcpy(p, branch->ptr, branch->slen); + p += branch->slen; + + /* Set length */ + key->slen = p - key->ptr; + + return PJ_SUCCESS; +} + +/* + * Create key from the incoming data, to be used to search the transaction + * in the transaction hash table. + */ +PJ_DEF(pj_status_t) pjsip_tsx_create_key( pj_pool_t *pool, pj_str_t *key, + pjsip_role_e role, + const pjsip_method *method, + const pjsip_rx_data *rdata) +{ + pj_str_t rfc3261_branch = {PJSIP_RFC3261_BRANCH_ID, + PJSIP_RFC3261_BRANCH_LEN}; + + + /* Get the branch parameter in the top-most Via. + * If branch parameter is started with "z9hG4bK", then the message was + * generated by agent compliant with RFC3261. Otherwise, it will be + * handled as RFC2543. + */ + const pj_str_t *branch = &rdata->msg_info.via->branch_param; + + if (pj_strncmp(branch,&rfc3261_branch,PJSIP_RFC3261_BRANCH_LEN)==0) { + + /* Create transaction key. */ + return create_tsx_key_3261(pool, key, role, method, branch); + + } else { + /* Create the key for the message. This key will be matched up + * with the transaction key. For RFC2563 transactions, the + * transaction key was created by the same function, so it will + * match the message. + */ + return create_tsx_key_2543( pool, key, role, method, rdata ); + } +} + +/***************************************************************************** + ** + ** Transaction layer module + ** + ***************************************************************************** + **/ +/* + * Create transaction layer module and registers it to the endpoint. + */ +PJ_DEF(pj_status_t) pjsip_tsx_layer_init_module(pjsip_endpoint *endpt) +{ + pj_pool_t *pool; + pj_status_t status; + + + PJ_ASSERT_RETURN(mod_tsx_layer.endpt==NULL, PJ_EINVALIDOP); + + /* Initialize timer values */ + t1_timer_val.sec = pjsip_cfg()->tsx.t1 / 1000; + t1_timer_val.msec = pjsip_cfg()->tsx.t1 % 1000; + t2_timer_val.sec = pjsip_cfg()->tsx.t2 / 1000; + t2_timer_val.msec = pjsip_cfg()->tsx.t2 % 1000; + t4_timer_val.sec = pjsip_cfg()->tsx.t4 / 1000; + t4_timer_val.msec = pjsip_cfg()->tsx.t4 % 1000; + td_timer_val.sec = pjsip_cfg()->tsx.td / 1000; + td_timer_val.msec = pjsip_cfg()->tsx.td % 1000; + /* Changed the initialization below to use td_timer_val instead, to enable + * customization to the timeout value. + */ + //timeout_timer_val.sec = (64 * pjsip_cfg()->tsx.t1) / 1000; + //timeout_timer_val.msec = (64 * pjsip_cfg()->tsx.t1) % 1000; + timeout_timer_val = td_timer_val; + + /* Initialize TLS ID for transaction lock. */ + status = pj_thread_local_alloc(&pjsip_tsx_lock_tls_id); + if (status != PJ_SUCCESS) + return status; + + pj_thread_local_set(pjsip_tsx_lock_tls_id, NULL); + + /* + * Initialize transaction layer structure. + */ + + /* Create pool for the module. */ + pool = pjsip_endpt_create_pool(endpt, "tsxlayer", + PJSIP_POOL_TSX_LAYER_LEN, + PJSIP_POOL_TSX_LAYER_INC ); + if (!pool) + return PJ_ENOMEM; + + + /* Initialize some attributes. */ + mod_tsx_layer.pool = pool; + mod_tsx_layer.endpt = endpt; + + + /* Create hash table. */ + mod_tsx_layer.htable = pj_hash_create( pool, pjsip_cfg()->tsx.max_count ); + if (!mod_tsx_layer.htable) { + pjsip_endpt_release_pool(endpt, pool); + return PJ_ENOMEM; + } + + /* Create mutex. */ + status = pj_mutex_create_recursive(pool, "tsxlayer", &mod_tsx_layer.mutex); + if (status != PJ_SUCCESS) { + pjsip_endpt_release_pool(endpt, pool); + return status; + } + + /* + * Register transaction layer module to endpoint. + */ + status = pjsip_endpt_register_module( endpt, &mod_tsx_layer.mod ); + if (status != PJ_SUCCESS) { + pj_mutex_destroy(mod_tsx_layer.mutex); + pjsip_endpt_release_pool(endpt, pool); + return status; + } + + /* Register mod_stateful_util module (sip_util_statefull.c) */ + status = pjsip_endpt_register_module(endpt, &mod_stateful_util); + if (status != PJ_SUCCESS) { + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Get the instance of transaction layer module. + */ +PJ_DEF(pjsip_module*) pjsip_tsx_layer_instance(void) +{ + return &mod_tsx_layer.mod; +} + + +/* + * Unregister and destroy transaction layer module. + */ +PJ_DEF(pj_status_t) pjsip_tsx_layer_destroy(void) +{ + /* Are we registered? */ + PJ_ASSERT_RETURN(mod_tsx_layer.endpt!=NULL, PJ_EINVALIDOP); + + /* Unregister from endpoint. + * Clean-ups will be done in the unload() module callback. + */ + return pjsip_endpt_unregister_module( mod_tsx_layer.endpt, + &mod_tsx_layer.mod); +} + + +/* + * Register the transaction to the hash table. + */ +static pj_status_t mod_tsx_layer_register_tsx( pjsip_transaction *tsx) +{ + pj_assert(tsx->transaction_key.slen != 0); + + /* Lock hash table mutex. */ + pj_mutex_lock(mod_tsx_layer.mutex); + + /* Check if no transaction with the same key exists. + * Do not use PJ_ASSERT_RETURN since it evaluates the expression + * twice! + */ + if(pj_hash_get(mod_tsx_layer.htable, + tsx->transaction_key.ptr, + tsx->transaction_key.slen, + NULL)) + { + pj_mutex_unlock(mod_tsx_layer.mutex); + PJ_LOG(2,(THIS_FILE, + "Unable to register %.*s transaction (key exists)", + (int)tsx->method.name.slen, + tsx->method.name.ptr)); + return PJ_EEXISTS; + } + + TSX_TRACE_((THIS_FILE, + "Transaction %p registered with hkey=0x%p and key=%.*s", + tsx, tsx->hashed_key, tsx->transaction_key.slen, + tsx->transaction_key.ptr)); + + /* Register the transaction to the hash table. */ +#ifdef PRECALC_HASH + pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, tsx->hashed_key, tsx); +#else + pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, 0, tsx); +#endif + + /* Unlock mutex. */ + pj_mutex_unlock(mod_tsx_layer.mutex); + + return PJ_SUCCESS; +} + + +/* + * Unregister the transaction from the hash table. + */ +static void mod_tsx_layer_unregister_tsx( pjsip_transaction *tsx) +{ + if (mod_tsx_layer.mod.id == -1) { + /* The transaction layer has been unregistered. This could happen + * if the transaction was pending on transport and the application + * is shutdown. See http://trac.pjsip.org/repos/ticket/1033. In + * this case just do nothing. + */ + return; + } + + pj_assert(tsx->transaction_key.slen != 0); + //pj_assert(tsx->state != PJSIP_TSX_STATE_NULL); + + /* Lock hash table mutex. */ + pj_mutex_lock(mod_tsx_layer.mutex); + + /* Register the transaction to the hash table. */ +#ifdef PRECALC_HASH + pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, tsx->hashed_key, NULL); +#else + pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, 0, NULL); +#endif + + TSX_TRACE_((THIS_FILE, + "Transaction %p unregistered, hkey=0x%p and key=%.*s", + tsx, tsx->hashed_key, tsx->transaction_key.slen, + tsx->transaction_key.ptr)); + + /* Unlock mutex. */ + pj_mutex_unlock(mod_tsx_layer.mutex); +} + + +/* + * Retrieve the current number of transactions currently registered in + * the hash table. + */ +PJ_DEF(unsigned) pjsip_tsx_layer_get_tsx_count(void) +{ + unsigned count; + + /* Are we registered? */ + PJ_ASSERT_RETURN(mod_tsx_layer.endpt!=NULL, 0); + + pj_mutex_lock(mod_tsx_layer.mutex); + count = pj_hash_count(mod_tsx_layer.htable); + pj_mutex_unlock(mod_tsx_layer.mutex); + + return count; +} + + +/* + * Find a transaction. + */ +PJ_DEF(pjsip_transaction*) pjsip_tsx_layer_find_tsx( const pj_str_t *key, + pj_bool_t lock ) +{ + pjsip_transaction *tsx; + pj_uint32_t hval = 0; + + pj_mutex_lock(mod_tsx_layer.mutex); + tsx = (pjsip_transaction*) + pj_hash_get( mod_tsx_layer.htable, key->ptr, key->slen, &hval ); + pj_mutex_unlock(mod_tsx_layer.mutex); + + TSX_TRACE_((THIS_FILE, + "Finding tsx with hkey=0x%p and key=%.*s: found %p", + hval, key->slen, key->ptr, tsx)); + + /* Race condition! + * Transaction may gets deleted before we have chance to lock it. + */ + PJ_TODO(FIX_RACE_CONDITION_HERE); + if (tsx && lock) + pj_mutex_lock(tsx->mutex); + + return tsx; +} + + +/* This module callback is called when module is being loaded by + * endpoint. It does nothing for this module. + */ +static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt) +{ + PJ_UNUSED_ARG(endpt); + return PJ_SUCCESS; +} + + +/* This module callback is called when module is being started by + * endpoint. It does nothing for this module. + */ +static pj_status_t mod_tsx_layer_start(void) +{ + return PJ_SUCCESS; +} + + +/* This module callback is called when module is being stopped by + * endpoint. + */ +static pj_status_t mod_tsx_layer_stop(void) +{ + pj_hash_iterator_t it_buf, *it; + + PJ_LOG(4,(THIS_FILE, "Stopping transaction layer module")); + + pj_mutex_lock(mod_tsx_layer.mutex); + + /* Destroy all transactions. */ + it = pj_hash_first(mod_tsx_layer.htable, &it_buf); + while (it) { + pjsip_transaction *tsx = (pjsip_transaction*) + pj_hash_this(mod_tsx_layer.htable, it); + pj_hash_iterator_t *next = pj_hash_next(mod_tsx_layer.htable, it); + if (tsx) { + pjsip_tsx_terminate(tsx, PJSIP_SC_SERVICE_UNAVAILABLE); + mod_tsx_layer_unregister_tsx(tsx); + tsx_destroy(tsx); + } + it = next; + } + + pj_mutex_unlock(mod_tsx_layer.mutex); + + PJ_LOG(4,(THIS_FILE, "Stopped transaction layer module")); + + return PJ_SUCCESS; +} + + +/* Destroy this module */ +static void tsx_layer_destroy(pjsip_endpoint *endpt) +{ + PJ_UNUSED_ARG(endpt); + + /* Destroy mutex. */ + pj_mutex_destroy(mod_tsx_layer.mutex); + + /* Release pool. */ + pjsip_endpt_release_pool(mod_tsx_layer.endpt, mod_tsx_layer.pool); + + /* Free TLS */ + pj_thread_local_free(pjsip_tsx_lock_tls_id); + + /* Mark as unregistered. */ + mod_tsx_layer.endpt = NULL; + + PJ_LOG(4,(THIS_FILE, "Transaction layer module destroyed")); +} + + +/* This module callback is called when module is being unloaded by + * endpoint. + */ +static pj_status_t mod_tsx_layer_unload(void) +{ + /* Only self destroy when there's no transaction in the table. + * Transaction may refuse to destroy when it has pending + * transmission. If we destroy the module now, application will + * crash when the pending transaction finally got error response + * from transport and when it tries to unregister itself. + */ + if (pj_hash_count(mod_tsx_layer.htable) != 0) { + if (pjsip_endpt_atexit(mod_tsx_layer.endpt, &tsx_layer_destroy) != + PJ_SUCCESS) + { + PJ_LOG(3,(THIS_FILE, "Failed to register transaction layer " + "module destroy.")); + } + return PJ_EBUSY; + } + + tsx_layer_destroy(mod_tsx_layer.endpt); + + return PJ_SUCCESS; +} + + +/* This module callback is called when endpoint has received an + * incoming request message. + */ +static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata) +{ + pj_str_t key; + pj_uint32_t hval = 0; + pjsip_transaction *tsx; + + pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS, + &rdata->msg_info.cseq->method, rdata); + + /* Find transaction. */ + pj_mutex_lock( mod_tsx_layer.mutex ); + + tsx = (pjsip_transaction*) + pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval ); + + + TSX_TRACE_((THIS_FILE, + "Finding tsx for request, hkey=0x%p and key=%.*s, found %p", + hval, key.slen, key.ptr, tsx)); + + + if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Transaction not found. + * Reject the request so that endpoint passes the request to + * upper layer modules. + */ + pj_mutex_unlock( mod_tsx_layer.mutex); + return PJ_FALSE; + } + + /* Unlock hash table. */ + pj_mutex_unlock( mod_tsx_layer.mutex ); + + /* Race condition! + * Transaction may gets deleted before we have chance to lock it + * in pjsip_tsx_recv_msg(). + */ + PJ_TODO(FIX_RACE_CONDITION_HERE); + + /* Pass the message to the transaction. */ + pjsip_tsx_recv_msg(tsx, rdata ); + + return PJ_TRUE; +} + + +/* This module callback is called when endpoint has received an + * incoming response message. + */ +static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata) +{ + pj_str_t key; + pj_uint32_t hval = 0; + pjsip_transaction *tsx; + + pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, + &rdata->msg_info.cseq->method, rdata); + + /* Find transaction. */ + pj_mutex_lock( mod_tsx_layer.mutex ); + + tsx = (pjsip_transaction*) + pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval ); + + + TSX_TRACE_((THIS_FILE, + "Finding tsx for response, hkey=0x%p and key=%.*s, found %p", + hval, key.slen, key.ptr, tsx)); + + + if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Transaction not found. + * Reject the request so that endpoint passes the request to + * upper layer modules. + */ + pj_mutex_unlock( mod_tsx_layer.mutex); + return PJ_FALSE; + } + + /* Unlock hash table. */ + pj_mutex_unlock( mod_tsx_layer.mutex ); + + /* Race condition! + * Transaction may gets deleted before we have chance to lock it + * in pjsip_tsx_recv_msg(). + */ + PJ_TODO(FIX_RACE_CONDITION_HERE); + + /* Pass the message to the transaction. */ + pjsip_tsx_recv_msg(tsx, rdata ); + + return PJ_TRUE; +} + + +/* + * Get transaction instance in the rdata. + */ +PJ_DEF(pjsip_transaction*) pjsip_rdata_get_tsx( pjsip_rx_data *rdata ) +{ + return (pjsip_transaction*) + rdata->endpt_info.mod_data[mod_tsx_layer.mod.id]; +} + + +/* + * Dump transaction layer. + */ +PJ_DEF(void) pjsip_tsx_layer_dump(pj_bool_t detail) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + pj_hash_iterator_t itbuf, *it; + + /* Lock mutex. */ + pj_mutex_lock(mod_tsx_layer.mutex); + + PJ_LOG(3, (THIS_FILE, "Dumping transaction table:")); + PJ_LOG(3, (THIS_FILE, " Total %d transactions", + pj_hash_count(mod_tsx_layer.htable))); + + if (detail) { + it = pj_hash_first(mod_tsx_layer.htable, &itbuf); + if (it == NULL) { + PJ_LOG(3, (THIS_FILE, " - none - ")); + } else { + while (it != NULL) { + pjsip_transaction *tsx = (pjsip_transaction*) + pj_hash_this(mod_tsx_layer.htable,it); + + PJ_LOG(3, (THIS_FILE, " %s %s|%d|%s", + tsx->obj_name, + (tsx->last_tx? + pjsip_tx_data_get_info(tsx->last_tx): + "none"), + tsx->status_code, + pjsip_tsx_state_str(tsx->state))); + + it = pj_hash_next(mod_tsx_layer.htable, it); + } + } + } + + /* Unlock mutex. */ + pj_mutex_unlock(mod_tsx_layer.mutex); +#endif +} + +/***************************************************************************** + ** + ** Transaction + ** + ***************************************************************************** + **/ +/* + * Lock transaction and set the value of Thread Local Storage. + */ +static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck) +{ + struct tsx_lock_data *prev_data; + + pj_mutex_lock(tsx->mutex); + prev_data = (struct tsx_lock_data *) + pj_thread_local_get(pjsip_tsx_lock_tls_id); + lck->prev = prev_data; + lck->tsx = tsx; + lck->is_alive = 1; + pj_thread_local_set(pjsip_tsx_lock_tls_id, lck); +} + + +/* + * Unlock transaction. + * This will selectively unlock the mutex ONLY IF the transaction has not been + * destroyed. The function knows whether the transaction has been destroyed + * because when transaction is destroyed the is_alive flag for the transaction + * will be set to zero. + */ +static pj_status_t unlock_tsx( pjsip_transaction *tsx, + struct tsx_lock_data *lck) +{ + pj_assert( (void*)pj_thread_local_get(pjsip_tsx_lock_tls_id) == lck); + pj_assert( lck->tsx == tsx ); + pj_thread_local_set(pjsip_tsx_lock_tls_id, lck->prev); + if (lck->is_alive) + pj_mutex_unlock(tsx->mutex); + + return lck->is_alive ? PJ_SUCCESS : PJSIP_ETSXDESTROYED; +} + + +/* Lock transaction for accessing the timeout timer only. */ +static void lock_timer(pjsip_transaction *tsx) +{ + pj_mutex_lock(tsx->mutex_b); +} + +/* Unlock timer */ +static void unlock_timer(pjsip_transaction *tsx) +{ + pj_mutex_unlock(tsx->mutex_b); +} + +/* Create and initialize basic transaction structure. + * This function is called by both UAC and UAS creation. + */ +static pj_status_t tsx_create( pjsip_module *tsx_user, + pjsip_transaction **p_tsx) +{ + pj_pool_t *pool; + pjsip_transaction *tsx; + pj_status_t status; + + pool = pjsip_endpt_create_pool( mod_tsx_layer.endpt, "tsx", + PJSIP_POOL_TSX_LEN, PJSIP_POOL_TSX_INC ); + if (!pool) + return PJ_ENOMEM; + + tsx = PJ_POOL_ZALLOC_T(pool, pjsip_transaction); + tsx->pool = pool; + tsx->tsx_user = tsx_user; + tsx->endpt = mod_tsx_layer.endpt; + + pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name), + "tsx%p", tsx); + pj_memcpy(pool->obj_name, tsx->obj_name, sizeof(pool->obj_name)); + + tsx->handle_200resp = 1; + tsx->retransmit_timer.id = 0; + tsx->retransmit_timer.user_data = tsx; + tsx->retransmit_timer.cb = &tsx_timer_callback; + tsx->timeout_timer.id = 0; + tsx->timeout_timer.user_data = tsx; + tsx->timeout_timer.cb = &tsx_timer_callback; + + status = pj_mutex_create_recursive(pool, tsx->obj_name, &tsx->mutex); + if (status != PJ_SUCCESS) { + pjsip_endpt_release_pool(mod_tsx_layer.endpt, pool); + return status; + } + + status = pj_mutex_create_simple(pool, tsx->obj_name, &tsx->mutex_b); + if (status != PJ_SUCCESS) { + pj_mutex_destroy(tsx->mutex); + pjsip_endpt_release_pool(mod_tsx_layer.endpt, pool); + return status; + } + + *p_tsx = tsx; + return PJ_SUCCESS; +} + + +/* Destroy transaction. */ +static pj_status_t tsx_destroy( pjsip_transaction *tsx ) +{ + struct tsx_lock_data *lck; + + /* Release the transport */ + tsx_update_transport(tsx, NULL); + + /* Decrement reference counter in transport selector */ + pjsip_tpselector_dec_ref(&tsx->tp_sel); + + /* Free last transmitted message. */ + if (tsx->last_tx) { + pjsip_tx_data_dec_ref( tsx->last_tx ); + tsx->last_tx = NULL; + } + /* Cancel timeout timer. */ + if (tsx->timeout_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = 0; + } + /* Cancel retransmission timer. */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* Clear some pending flags. */ + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED | TSX_HAS_PENDING_SEND); + + /* Refuse to destroy transaction if it has pending resolving. */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_DESTROY; + tsx->tsx_user = NULL; + PJ_LOG(4,(tsx->obj_name, "Will destroy later because transport is " + "in progress")); + return PJ_EBUSY; + } + + /* Clear TLS, so that mutex will not be unlocked */ + lck = (struct tsx_lock_data*) pj_thread_local_get(pjsip_tsx_lock_tls_id); + while (lck) { + if (lck->tsx == tsx) { + lck->is_alive = 0; + } + lck = lck->prev; + } + + pj_mutex_destroy(tsx->mutex_b); + pj_mutex_destroy(tsx->mutex); + + PJ_LOG(5,(tsx->obj_name, "Transaction destroyed!")); + + pjsip_endpt_release_pool(tsx->endpt, tsx->pool); + + return PJ_SUCCESS; +} + + +/* + * Callback when timer expires. + */ +static void tsx_timer_callback( pj_timer_heap_t *theap, pj_timer_entry *entry) +{ + pjsip_event event; + pjsip_transaction *tsx = (pjsip_transaction*) entry->user_data; + struct tsx_lock_data lck; + + PJ_UNUSED_ARG(theap); + + entry->id = 0; + + PJ_LOG(5,(tsx->obj_name, "%s timer event", + (entry==&tsx->retransmit_timer ? "Retransmit":"Timeout"))); + pj_log_push_indent(); + + + PJSIP_EVENT_INIT_TIMER(event, entry); + + /* Dispatch event to transaction. */ + lock_tsx(tsx, &lck); + (*tsx->state_handler)(tsx, &event); + unlock_tsx(tsx, &lck); + + pj_log_pop_indent(); +} + + +/* + * Set transaction state, and inform TU about the transaction state change. + */ +static void tsx_set_state( pjsip_transaction *tsx, + pjsip_tsx_state_e state, + pjsip_event_id_e event_src_type, + void *event_src ) +{ + pjsip_tsx_state_e prev_state = tsx->state; + + /* New state must be greater than previous state */ + pj_assert(state >= tsx->state); + + PJ_LOG(5, (tsx->obj_name, "State changed from %s to %s, event=%s", + state_str[tsx->state], state_str[state], + pjsip_event_str(event_src_type))); + pj_log_push_indent(); + + /* Change state. */ + tsx->state = state; + + /* Update the state handlers. */ + if (tsx->role == PJSIP_ROLE_UAC) { + tsx->state_handler = tsx_state_handler_uac[state]; + } else { + tsx->state_handler = tsx_state_handler_uas[state]; + } + + /* Before informing TU about state changed, inform TU about + * rx event. + */ + if (event_src_type==PJSIP_EVENT_RX_MSG && tsx->tsx_user) { + pjsip_rx_data *rdata = (pjsip_rx_data*) event_src; + + pj_assert(rdata != NULL); + + if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG && + tsx->tsx_user->on_rx_response) + { + (*tsx->tsx_user->on_rx_response)(rdata); + } + + } + + /* Inform TU about state changed. */ + if (tsx->tsx_user && tsx->tsx_user->on_tsx_state) { + pjsip_event e; + PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src, + prev_state); + (*tsx->tsx_user->on_tsx_state)(tsx, &e); + } + + + /* When the transaction is terminated, release transport, and free the + * saved last transmitted message. + */ + if (state == PJSIP_TSX_STATE_TERMINATED) { + pj_time_val timeout = {0, 0}; + + /* If we're still waiting for a message to be sent.. */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + /* Disassociate ourselves from the outstanding transmit data + * so that when the send callback is called we will be able + * to ignore that (otherwise we'll get assertion, see + * http://trac.pjsip.org/repos/ticket/1033) + */ + if (tsx->pending_tx) { + tsx->pending_tx->mod_data[mod_tsx_layer.mod.id] = NULL; + tsx->pending_tx = NULL; + } + tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT); + } + + lock_timer(tsx); + + /* Cancel timeout timer. */ + if (tsx->timeout_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = 0; + } + + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout); + + unlock_timer(tsx); + + } else if (state == PJSIP_TSX_STATE_DESTROYED) { + + /* Unregister transaction. */ + mod_tsx_layer_unregister_tsx(tsx); + + /* Destroy transaction. */ + tsx_destroy(tsx); + } + + pj_log_pop_indent(); +} + + +/* + * Create, initialize, and register UAC transaction. + */ +PJ_DEF(pj_status_t) pjsip_tsx_create_uac( pjsip_module *tsx_user, + pjsip_tx_data *tdata, + pjsip_transaction **p_tsx) +{ + pjsip_transaction *tsx; + pjsip_msg *msg; + pjsip_cseq_hdr *cseq; + pjsip_via_hdr *via; + pjsip_host_info dst_info; + struct tsx_lock_data lck; + pj_status_t status; + + /* Validate arguments. */ + PJ_ASSERT_RETURN(tdata && tdata->msg && p_tsx, PJ_EINVAL); + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Method MUST NOT be ACK! */ + PJ_ASSERT_RETURN(tdata->msg->line.req.method.id != PJSIP_ACK_METHOD, + PJ_EINVALIDOP); + + /* Keep shortcut */ + msg = tdata->msg; + + /* Make sure CSeq header is present. */ + cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL); + if (!cseq) { + pj_assert(!"CSeq header not present in outgoing message!"); + return PJSIP_EMISSINGHDR; + } + + + /* Create transaction instance. */ + status = tsx_create( tsx_user, &tsx); + if (status != PJ_SUCCESS) + return status; + + + /* Lock transaction. */ + lock_tsx(tsx, &lck); + + /* Role is UAC. */ + tsx->role = PJSIP_ROLE_UAC; + + /* Save method. */ + pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method); + + /* Save CSeq. */ + tsx->cseq = cseq->cseq; + + /* Generate Via header if it doesn't exist. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_VIA, NULL); + if (via == NULL) { + via = pjsip_via_hdr_create(tdata->pool); + pjsip_msg_insert_first_hdr(msg, (pjsip_hdr*) via); + } + + /* Generate branch parameter if it doesn't exist. */ + if (via->branch_param.slen == 0) { + pj_str_t tmp; + via->branch_param.ptr = (char*) + pj_pool_alloc(tsx->pool, PJSIP_MAX_BRANCH_LEN); + via->branch_param.slen = PJSIP_MAX_BRANCH_LEN; + pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID, + PJSIP_RFC3261_BRANCH_LEN); + tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN + 2; + *(tmp.ptr-2) = 80; *(tmp.ptr-1) = 106; + pj_generate_unique_string( &tmp ); + + /* Save branch parameter. */ + tsx->branch = via->branch_param; + + } else { + /* Copy branch parameter. */ + pj_strdup(tsx->pool, &tsx->branch, &via->branch_param); + } + + /* Generate transaction key. */ + create_tsx_key_3261( tsx->pool, &tsx->transaction_key, + PJSIP_ROLE_UAC, &tsx->method, + &via->branch_param); + + /* Calculate hashed key value. */ +#ifdef PRECALC_HASH + tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr, + tsx->transaction_key.slen); +#endif + + PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen, + tsx->transaction_key.ptr)); + + /* Begin with State_Null. + * Manually set-up the state becase we don't want to call the callback. + */ + tsx->state = PJSIP_TSX_STATE_NULL; + tsx->state_handler = &tsx_on_state_null; + + /* Save the message. */ + tsx->last_tx = tdata; + pjsip_tx_data_add_ref(tsx->last_tx); + + /* Determine whether reliable transport should be used initially. + * This will be updated whenever transport has changed. + */ + status = pjsip_get_request_dest(tdata, &dst_info); + if (status != PJ_SUCCESS) { + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + tsx->is_reliable = (dst_info.flag & PJSIP_TRANSPORT_RELIABLE); + + /* Register transaction to hash table. */ + status = mod_tsx_layer_register_tsx(tsx); + if (status != PJ_SUCCESS) { + /* The assertion is removed by #1090: + pj_assert(!"Bug in branch_param generator (i.e. not unique)"); + */ + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + + + /* Unlock transaction and return. */ + unlock_tsx(tsx, &lck); + + pj_log_push_indent(); + PJ_LOG(5,(tsx->obj_name, "Transaction created for %s", + pjsip_tx_data_get_info(tdata))); + pj_log_pop_indent(); + + *p_tsx = tsx; + return PJ_SUCCESS; +} + + +/* + * Create, initialize, and register UAS transaction. + */ +PJ_DEF(pj_status_t) pjsip_tsx_create_uas( pjsip_module *tsx_user, + pjsip_rx_data *rdata, + pjsip_transaction **p_tsx) +{ + pjsip_transaction *tsx; + pjsip_msg *msg; + pj_str_t *branch; + pjsip_cseq_hdr *cseq; + pj_status_t status; + struct tsx_lock_data lck; + + /* Validate arguments. */ + PJ_ASSERT_RETURN(rdata && rdata->msg_info.msg && p_tsx, PJ_EINVAL); + + /* Keep shortcut to message */ + msg = rdata->msg_info.msg; + + /* Make sure this is a request message. */ + PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG); + + /* Make sure method is not ACK */ + PJ_ASSERT_RETURN(msg->line.req.method.id != PJSIP_ACK_METHOD, + PJ_EINVALIDOP); + + /* Make sure CSeq header is present. */ + cseq = rdata->msg_info.cseq; + if (!cseq) + return PJSIP_EMISSINGHDR; + + /* Make sure Via header is present. */ + if (rdata->msg_info.via == NULL) + return PJSIP_EMISSINGHDR; + + /* Check that method in CSeq header match request method. + * Reference: PROTOS #1922 + */ + if (pjsip_method_cmp(&msg->line.req.method, + &rdata->msg_info.cseq->method) != 0) + { + PJ_LOG(4,(THIS_FILE, "Error: CSeq header contains different " + "method than the request line")); + return PJSIP_EINVALIDHDR; + } + + /* + * Create transaction instance. + */ + status = tsx_create( tsx_user, &tsx); + if (status != PJ_SUCCESS) + return status; + + + /* Lock transaction. */ + lock_tsx(tsx, &lck); + + /* Role is UAS */ + tsx->role = PJSIP_ROLE_UAS; + + /* Save method. */ + pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method); + + /* Save CSeq */ + tsx->cseq = cseq->cseq; + + /* Get transaction key either from branch for RFC3261 message, or + * create transaction key. + */ + status = pjsip_tsx_create_key(tsx->pool, &tsx->transaction_key, + PJSIP_ROLE_UAS, &tsx->method, rdata); + if (status != PJ_SUCCESS) { + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + + /* Calculate hashed key value. */ +#ifdef PRECALC_HASH + tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr, + tsx->transaction_key.slen); +#endif + + /* Duplicate branch parameter for transaction. */ + branch = &rdata->msg_info.via->branch_param; + pj_strdup(tsx->pool, &tsx->branch, branch); + + PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen, + tsx->transaction_key.ptr)); + + + /* Begin with state NULL. + * Manually set-up the state becase we don't want to call the callback. + */ + tsx->state = PJSIP_TSX_STATE_NULL; + tsx->state_handler = &tsx_on_state_null; + + /* Get response address. */ + status = pjsip_get_response_addr( tsx->pool, rdata, &tsx->res_addr ); + if (status != PJ_SUCCESS) { + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + + /* If it's decided that we should use current transport, keep the + * transport. + */ + if (tsx->res_addr.transport) { + tsx_update_transport(tsx, tsx->res_addr.transport); + pj_memcpy(&tsx->addr, &tsx->res_addr.addr, tsx->res_addr.addr_len); + tsx->addr_len = tsx->res_addr.addr_len; + tsx->is_reliable = PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport); + } else { + tsx->is_reliable = + (tsx->res_addr.dst_host.flag & PJSIP_TRANSPORT_RELIABLE); + } + + + /* Register the transaction. */ + status = mod_tsx_layer_register_tsx(tsx); + if (status != PJ_SUCCESS) { + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + + /* Put this transaction in rdata's mod_data. */ + rdata->endpt_info.mod_data[mod_tsx_layer.mod.id] = tsx; + + /* Unlock transaction and return. */ + unlock_tsx(tsx, &lck); + + pj_log_push_indent(); + PJ_LOG(5,(tsx->obj_name, "Transaction created for %s", + pjsip_rx_data_get_info(rdata))); + pj_log_pop_indent(); + + + *p_tsx = tsx; + return PJ_SUCCESS; +} + + +/* + * Bind transaction to a specific transport/listener. + */ +PJ_DEF(pj_status_t) pjsip_tsx_set_transport(pjsip_transaction *tsx, + const pjsip_tpselector *sel) +{ + struct tsx_lock_data lck; + + /* Must be UAC transaction */ + PJ_ASSERT_RETURN(tsx && sel, PJ_EINVAL); + + /* Start locking the transaction. */ + lock_tsx(tsx, &lck); + + /* Decrement reference counter of previous transport selector */ + pjsip_tpselector_dec_ref(&tsx->tp_sel); + + /* Copy transport selector structure .*/ + pj_memcpy(&tsx->tp_sel, sel, sizeof(*sel)); + + /* Increment reference counter */ + pjsip_tpselector_add_ref(&tsx->tp_sel); + + /* Unlock transaction. */ + unlock_tsx(tsx, &lck); + + return PJ_SUCCESS; +} + + +/* + * Set transaction status code and reason. + */ +static void tsx_set_status_code(pjsip_transaction *tsx, + int code, const pj_str_t *reason) +{ + tsx->status_code = code; + if (reason) + pj_strdup(tsx->pool, &tsx->status_text, reason); + else + tsx->status_text = *pjsip_get_status_text(code); +} + + +/* + * Forcely terminate transaction. + */ +PJ_DEF(pj_status_t) pjsip_tsx_terminate( pjsip_transaction *tsx, int code ) +{ + struct tsx_lock_data lck; + + PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL); + + PJ_LOG(5,(tsx->obj_name, "Request to terminate transaction")); + + PJ_ASSERT_RETURN(code >= 200, PJ_EINVAL); + + if (tsx->state >= PJSIP_TSX_STATE_TERMINATED) + return PJ_SUCCESS; + + pj_log_push_indent(); + + lock_tsx(tsx, &lck); + tsx_set_status_code(tsx, code, NULL); + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, PJSIP_EVENT_USER, NULL); + unlock_tsx(tsx, &lck); + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + + +/* + * Cease retransmission on the UAC transaction. The UAC transaction is + * still considered running, and it will complete when either final + * response is received or the transaction times out. + */ +PJ_DEF(pj_status_t) pjsip_tsx_stop_retransmit(pjsip_transaction *tsx) +{ + struct tsx_lock_data lck; + + PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(tsx->role == PJSIP_ROLE_UAC && + tsx->method.id == PJSIP_INVITE_METHOD, + PJ_EINVALIDOP); + + PJ_LOG(5,(tsx->obj_name, "Request to stop retransmission")); + + pj_log_push_indent(); + + lock_tsx(tsx, &lck); + /* Cancel retransmission timer. */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + unlock_tsx(tsx, &lck); + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + + +/* + * Start a timer to terminate transaction after the specified time + * has elapsed. + */ +PJ_DEF(pj_status_t) pjsip_tsx_set_timeout( pjsip_transaction *tsx, + unsigned millisec) +{ + pj_time_val timeout; + + PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(tsx->role == PJSIP_ROLE_UAC && + tsx->method.id == PJSIP_INVITE_METHOD, + PJ_EINVALIDOP); + + /* Note: must not call lock_tsx() as that would introduce deadlock. + * See #1121. + */ + lock_timer(tsx); + + /* Transaction should normally not have final response, but as + * #1121 says there is a (tolerable) window of race condition + * where this might happen. + */ + if (tsx->status_code >= 200 && tsx->timeout_timer.id != 0) { + /* Timeout is already set */ + unlock_timer(tsx); + return PJ_EEXISTS; + } + + if (tsx->timeout_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = 0; + } + + timeout.sec = 0; + timeout.msec = millisec; + pj_time_val_normalize(&timeout); + + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt, &tsx->timeout_timer, + &timeout); + + + unlock_timer(tsx); + + return PJ_SUCCESS; +} + + +/* + * This function is called by TU to send a message. + */ +PJ_DEF(pj_status_t) pjsip_tsx_send_msg( pjsip_transaction *tsx, + pjsip_tx_data *tdata ) +{ + pjsip_event event; + struct tsx_lock_data lck; + pj_status_t status; + + if (tdata == NULL) + tdata = tsx->last_tx; + + PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP); + + PJ_LOG(5,(tsx->obj_name, "Sending %s in state %s", + pjsip_tx_data_get_info(tdata), + state_str[tsx->state])); + pj_log_push_indent(); + + PJSIP_EVENT_INIT_TX_MSG(event, tdata); + + /* Dispatch to transaction. */ + lock_tsx(tsx, &lck); + + /* Set transport selector to tdata */ + pjsip_tx_data_set_transport(tdata, &tsx->tp_sel); + + /* Dispatch to state handler */ + status = (*tsx->state_handler)(tsx, &event); + + unlock_tsx(tsx, &lck); + + /* Only decrement reference counter when it returns success. + * (This is the specification from the .PDF design document). + */ + if (status == PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + } + + pj_log_pop_indent(); + + return status; +} + + +/* + * This function is called by endpoint when incoming message for the + * transaction is received. + */ +PJ_DEF(void) pjsip_tsx_recv_msg( pjsip_transaction *tsx, + pjsip_rx_data *rdata) +{ + pjsip_event event; + struct tsx_lock_data lck; + pj_status_t status; + + PJ_LOG(5,(tsx->obj_name, "Incoming %s in state %s", + pjsip_rx_data_get_info(rdata), state_str[tsx->state])); + pj_log_push_indent(); + + /* Put the transaction in the rdata's mod_data. */ + rdata->endpt_info.mod_data[mod_tsx_layer.mod.id] = tsx; + + /* Init event. */ + PJSIP_EVENT_INIT_RX_MSG(event, rdata); + + /* Dispatch to transaction. */ + lock_tsx(tsx, &lck); + status = (*tsx->state_handler)(tsx, &event); + unlock_tsx(tsx, &lck); + + pj_log_pop_indent(); +} + + +/* Callback called by send message framework */ +static void send_msg_callback( pjsip_send_state *send_state, + pj_ssize_t sent, pj_bool_t *cont ) +{ + pjsip_transaction *tsx = (pjsip_transaction*) send_state->token; + pjsip_tx_data *tdata = send_state->tdata; + struct tsx_lock_data lck; + + /* Check if transaction has cancelled itself from this transmit + * notification (https://trac.pjsip.org/repos/ticket/1033). + * Also check if the transaction layer itself may have been shutdown + * (https://trac.pjsip.org/repos/ticket/1535) + */ + if (mod_tsx_layer.mod.id < 0 || + tdata->mod_data[mod_tsx_layer.mod.id] == NULL) + { + *cont = PJ_FALSE; + return; + } + + /* Reset */ + tdata->mod_data[mod_tsx_layer.mod.id] = NULL; + tsx->pending_tx = NULL; + + lock_tsx(tsx, &lck); + + if (sent > 0) { + /* Successfully sent! */ + pj_assert(send_state->cur_transport != NULL); + + if (tsx->transport != send_state->cur_transport) { + /* Update transport. */ + tsx_update_transport(tsx, send_state->cur_transport); + + /* Update remote address. */ + tsx->addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len; + pj_memcpy(&tsx->addr, + &tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr, + tsx->addr_len); + + /* Update is_reliable flag. */ + tsx->is_reliable = PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport); + } + + /* Clear pending transport flag. */ + tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT); + + /* Mark that we have resolved the addresses. */ + tsx->transport_flag |= TSX_HAS_RESOLVED_SERVER; + + /* Pending destroy? */ + if (tsx->transport_flag & TSX_HAS_PENDING_DESTROY) { + tsx_set_state( tsx, PJSIP_TSX_STATE_DESTROYED, + PJSIP_EVENT_UNKNOWN, NULL ); + unlock_tsx(tsx, &lck); + return; + } + + /* Need to transmit a message? */ + if (tsx->transport_flag & TSX_HAS_PENDING_SEND) { + tsx->transport_flag &= ~(TSX_HAS_PENDING_SEND); + tsx_send_msg(tsx, tsx->last_tx); + } + + /* Need to reschedule retransmission? */ + if (tsx->transport_flag & TSX_HAS_PENDING_RESCHED) { + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED); + + /* Only update when transport turns out to be unreliable. */ + if (!tsx->is_reliable) { + tsx_resched_retransmission(tsx); + } + } + + } else { + /* Failed to send! */ + pj_assert(sent != 0); + + /* If transaction is using the same transport as the failed one, + * release the transport. + */ + if (send_state->cur_transport==tsx->transport) + tsx_update_transport(tsx, NULL); + + /* Also stop processing if transaction has been flagged with + * pending destroy (http://trac.pjsip.org/repos/ticket/906) + */ + if ((!*cont) || (tsx->transport_flag & TSX_HAS_PENDING_DESTROY)) { + char errmsg[PJ_ERR_MSG_SIZE]; + pjsip_status_code sc; + pj_str_t err; + + tsx->transport_err = -sent; + + err =pj_strerror(-sent, errmsg, sizeof(errmsg)); + + PJ_LOG(2,(tsx->obj_name, + "Failed to send %s! err=%d (%s)", + pjsip_tx_data_get_info(send_state->tdata), -sent, + errmsg)); + + /* Clear pending transport flag. */ + tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT); + + /* Mark that we have resolved the addresses. */ + tsx->transport_flag |= TSX_HAS_RESOLVED_SERVER; + + /* Server resolution error is now mapped to 502 instead of 503, + * since with 503 normally client should try again. + * See http://trac.pjsip.org/repos/ticket/870 + */ + if (-sent==PJ_ERESOLVE || -sent==PJLIB_UTIL_EDNS_NXDOMAIN) + sc = PJSIP_SC_BAD_GATEWAY; + else + sc = PJSIP_SC_TSX_TRANSPORT_ERROR; + + /* Terminate transaction, if it's not already terminated. */ + tsx_set_status_code(tsx, sc, &err); + if (tsx->state != PJSIP_TSX_STATE_TERMINATED && + tsx->state != PJSIP_TSX_STATE_DESTROYED) + { + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, send_state->tdata); + } + /* Don't forget to destroy if we have pending destroy flag + * (http://trac.pjsip.org/repos/ticket/906) + */ + else if (tsx->transport_flag & TSX_HAS_PENDING_DESTROY) + { + tsx_set_state( tsx, PJSIP_TSX_STATE_DESTROYED, + PJSIP_EVENT_TRANSPORT_ERROR, send_state->tdata); + } + + } else { + char errmsg[PJ_ERR_MSG_SIZE]; + + PJ_LOG(2,(tsx->obj_name, + "Temporary failure in sending %s, " + "will try next server. Err=%d (%s)", + pjsip_tx_data_get_info(send_state->tdata), -sent, + pj_strerror(-sent, errmsg, sizeof(errmsg)).ptr)); + + /* Reset retransmission count */ + tsx->retransmit_count = 0; + + /* And reset timeout timer */ + if (tsx->timeout_timer.id) { + lock_timer(tsx); + + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = TIMER_INACTIVE; + + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout_timer_val); + + unlock_timer(tsx); + } + + /* Put again pending tdata */ + tdata->mod_data[mod_tsx_layer.mod.id] = tsx; + tsx->pending_tx = tdata; + } + } + + unlock_tsx(tsx, &lck); +} + + +/* Transport callback. */ +static void transport_callback(void *token, pjsip_tx_data *tdata, + pj_ssize_t sent) +{ + if (sent < 0) { + pjsip_transaction *tsx = (pjsip_transaction*) token; + struct tsx_lock_data lck; + char errmsg[PJ_ERR_MSG_SIZE]; + pj_str_t err; + + tsx->transport_err = -sent; + + err = pj_strerror(-sent, errmsg, sizeof(errmsg)); + + PJ_LOG(2,(tsx->obj_name, "Transport failed to send %s! Err=%d (%s)", + pjsip_tx_data_get_info(tdata), -sent, errmsg)); + + lock_tsx(tsx, &lck); + + /* Release transport. */ + tsx_update_transport(tsx, NULL); + + /* Terminate transaction. */ + tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err); + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, tdata ); + + unlock_tsx(tsx, &lck); + } +} + + +/* + * Callback when transport state changes. + */ +static void tsx_tp_state_callback( pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info) +{ + PJ_UNUSED_ARG(tp); + + if (state == PJSIP_TP_STATE_DISCONNECTED) { + pjsip_transaction *tsx; + struct tsx_lock_data lck; + + pj_assert(tp && info && info->user_data); + + tsx = (pjsip_transaction*)info->user_data; + + lock_tsx(tsx, &lck); + + /* Terminate transaction when transport disconnected */ + if (tsx->state < PJSIP_TSX_STATE_TERMINATED) { + pj_str_t err; + char errmsg[PJ_ERR_MSG_SIZE]; + + err = pj_strerror(info->status, errmsg, sizeof(errmsg)); + tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err); + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, NULL); + } + + unlock_tsx(tsx, &lck); + } +} + + +/* + * Send message to the transport. + */ +static pj_status_t tsx_send_msg( pjsip_transaction *tsx, + pjsip_tx_data *tdata) +{ + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(tsx && tdata, PJ_EINVAL); + + /* Send later if transport is still pending. */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_SEND; + return PJ_SUCCESS; + } + + /* If we have the transport, send the message using that transport. + * Otherwise perform full transport resolution. + */ + if (tsx->transport) { + status = pjsip_transport_send( tsx->transport, tdata, &tsx->addr, + tsx->addr_len, tsx, + &transport_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + PJ_LOG(2,(tsx->obj_name, + "Error sending %s: Err=%d (%s)", + pjsip_tx_data_get_info(tdata), status, + pj_strerror(status, errmsg, sizeof(errmsg)).ptr)); + + /* On error, release transport to force using full transport + * resolution procedure. + */ + tsx_update_transport(tsx, NULL); + + tsx->addr_len = 0; + tsx->res_addr.transport = NULL; + tsx->res_addr.addr_len = 0; + } else { + return PJ_SUCCESS; + } + } + + /* We are here because we don't have transport, or we failed to send + * the message using existing transport. If we haven't resolved the + * server before, then begin the long process of resolving the server + * and send the message with possibly new server. + */ + pj_assert(status != PJ_SUCCESS || tsx->transport == NULL); + + /* If we have resolved the server, we treat the error as permanent error. + * Terminate transaction with transport error failure. + */ + if (tsx->transport_flag & TSX_HAS_RESOLVED_SERVER) { + + char errmsg[PJ_ERR_MSG_SIZE]; + pj_str_t err; + + if (status == PJ_SUCCESS) { + pj_assert(!"Unexpected status!"); + status = PJ_EUNKNOWN; + } + + /* We have resolved the server!. + * Treat this as permanent transport error. + */ + err = pj_strerror(status, errmsg, sizeof(errmsg)); + + PJ_LOG(2,(tsx->obj_name, + "Transport error, terminating transaction. " + "Err=%d (%s)", + status, errmsg)); + + tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err); + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, NULL ); + + return status; + } + + /* Must add reference counter because the send request functions + * decrement the reference counter. + */ + pjsip_tx_data_add_ref(tdata); + + /* Also attach ourselves to the transmit data so that we'll be able + * to unregister ourselves from the send notification of this + * transmit data. + */ + tdata->mod_data[mod_tsx_layer.mod.id] = tsx; + tsx->pending_tx = tdata; + + /* Begin resolving destination etc to send the message. */ + if (tdata->msg->type == PJSIP_REQUEST_MSG) { + + tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT; + status = pjsip_endpt_send_request_stateless(tsx->endpt, tdata, tsx, + &send_msg_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + tdata->mod_data[mod_tsx_layer.mod.id] = NULL; + tsx->pending_tx = NULL; + } + + /* Check if transaction is terminated. */ + if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TERMINATED) + status = tsx->transport_err; + + } else { + + tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT; + status = pjsip_endpt_send_response( tsx->endpt, &tsx->res_addr, + tdata, tsx, + &send_msg_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + tdata->mod_data[mod_tsx_layer.mod.id] = NULL; + tsx->pending_tx = NULL; + } + + /* Check if transaction is terminated. */ + if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TERMINATED) + status = tsx->transport_err; + + } + + + return status; +} + + +/* + * Manually retransmit the last messagewithout updating the transaction state. + */ +PJ_DEF(pj_status_t) pjsip_tsx_retransmit_no_state(pjsip_transaction *tsx, + pjsip_tx_data *tdata) +{ + struct tsx_lock_data lck; + pj_status_t status; + + lock_tsx(tsx, &lck); + if (tdata == NULL) { + tdata = tsx->last_tx; + } + status = tsx_send_msg(tsx, tdata); + unlock_tsx(tsx, &lck); + + /* Only decrement reference counter when it returns success. + * (This is the specification from the .PDF design document). + */ + if (status == PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + } + + return status; +} + + +/* + * Retransmit last message sent. + */ +static void tsx_resched_retransmission( pjsip_transaction *tsx ) +{ + pj_uint32_t msec_time; + + pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0); + + if (tsx->role==PJSIP_ROLE_UAC && tsx->status_code >= 100) + msec_time = pjsip_cfg()->tsx.t2; + else + msec_time = (1 << (tsx->retransmit_count)) * pjsip_cfg()->tsx.t1; + + if (tsx->role == PJSIP_ROLE_UAC) { + pj_assert(tsx->status_code < 200); + /* Retransmission for non-INVITE transaction caps-off at T2 */ + if (msec_time > pjsip_cfg()->tsx.t2 && + tsx->method.id != PJSIP_INVITE_METHOD) + { + msec_time = pjsip_cfg()->tsx.t2; + } + } else { + /* For UAS, this can be retransmission of 2xx response for INVITE + * or non-100 1xx response. + */ + if (tsx->status_code < 200) { + /* non-100 1xx retransmission is at 60 seconds */ + msec_time = PJSIP_TSX_1XX_RETRANS_DELAY * 1000; + } else { + /* Retransmission of INVITE final response also caps-off at T2 */ + pj_assert(tsx->status_code >= 200); + if (msec_time > pjsip_cfg()->tsx.t2) + msec_time = pjsip_cfg()->tsx.t2; + } + } + + if (msec_time != 0) { + pj_time_val timeout; + + timeout.sec = msec_time / 1000; + timeout.msec = msec_time % 1000; + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer, + &timeout); + } +} + +/* + * Retransmit last message sent. + */ +static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(tsx->last_tx!=NULL, PJ_EBUG); + + PJ_LOG(5,(tsx->obj_name, "Retransmiting %s, count=%d, restart?=%d", + pjsip_tx_data_get_info(tsx->last_tx), + tsx->retransmit_count, resched)); + + ++tsx->retransmit_count; + + /* Restart timer T1 first before sending the message to ensure that + * retransmission timer is not engaged when loop transport is used. + */ + if (resched) { + pj_assert(tsx->state != PJSIP_TSX_STATE_CONFIRMED); + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + tsx_resched_retransmission(tsx); + } + } + + status = tsx_send_msg( tsx, tsx->last_tx); + if (status != PJ_SUCCESS) { + return status; + } + + return PJ_SUCCESS; +} + +static void tsx_update_transport( pjsip_transaction *tsx, + pjsip_transport *tp) +{ + pj_assert(tsx); + + if (tsx->transport) { + pjsip_transport_remove_state_listener(tsx->transport, + tsx->tp_st_key, tsx); + pjsip_transport_dec_ref( tsx->transport ); + tsx->transport = NULL; + } + + if (tp) { + tsx->transport = tp; + pjsip_transport_add_ref(tp); + pjsip_transport_add_state_listener(tp, &tsx_tp_state_callback, tsx, + &tsx->tp_st_key); + } +} + + +/* + * Handler for events in state Null. + */ +static pj_status_t tsx_on_state_null( pjsip_transaction *tsx, + pjsip_event *event ) +{ + pj_status_t status; + + pj_assert(tsx->state == PJSIP_TSX_STATE_NULL); + + if (tsx->role == PJSIP_ROLE_UAS) { + + /* Set state to Trying. */ + pj_assert(event->type == PJSIP_EVENT_RX_MSG && + event->body.rx_msg.rdata->msg_info.msg->type == + PJSIP_REQUEST_MSG); + tsx_set_state( tsx, PJSIP_TSX_STATE_TRYING, PJSIP_EVENT_RX_MSG, + event->body.rx_msg.rdata); + + } else { + pjsip_tx_data *tdata; + + /* Must be transmit event. + * You may got this assertion when using loop transport with delay + * set to zero. That would cause on_rx_response() callback to be + * called before tsx_send_msg() has completed. + */ + PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG, PJ_EBUG); + + /* Get the txdata */ + tdata = event->body.tx_msg.tdata; + + /* Save the message for retransmission. */ + if (tsx->last_tx && tsx->last_tx != tdata) { + pjsip_tx_data_dec_ref(tsx->last_tx); + tsx->last_tx = NULL; + } + if (tsx->last_tx != tdata) { + tsx->last_tx = tdata; + pjsip_tx_data_add_ref(tdata); + } + + /* Send the message. */ + status = tsx_send_msg( tsx, tdata); + if (status != PJ_SUCCESS) { + return status; + } + + /* Start Timer B (or called timer F for non-INVITE) for transaction + * timeout. + */ + lock_timer(tsx); + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout_timer_val); + unlock_timer(tsx); + + /* Start Timer A (or timer E) for retransmission only if unreliable + * transport is being used. + */ + if (!tsx->is_reliable) { + tsx->retransmit_count = 0; + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt, &tsx->retransmit_timer, + &t1_timer_val); + } + } + + /* Move state. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_CALLING, + PJSIP_EVENT_TX_MSG, tdata); + } + + return PJ_SUCCESS; +} + + +/* + * State Calling is for UAC after it sends request but before any responses + * is received. + */ +static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx, + pjsip_event *event ) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_CALLING); + pj_assert(tsx->role == PJSIP_ROLE_UAC); + + if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->retransmit_timer) + { + pj_status_t status; + + /* Retransmit the request. */ + status = tsx_retransmit( tsx, 1 ); + if (status != PJ_SUCCESS) { + return status; + } + + } else if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->timeout_timer) + { + + /* Cancel retransmission timer. */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED); + + /* Set status code */ + tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL); + + /* Inform TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer); + + /* Transaction is destroyed */ + //return PJSIP_ETSXDESTROYED; + + } else if (event->type == PJSIP_EVENT_RX_MSG) { + pjsip_msg *msg; + int code; + + /* Get message instance */ + msg = event->body.rx_msg.rdata->msg_info.msg; + + /* Better be a response message. */ + if (msg->type != PJSIP_RESPONSE_MSG) + return PJSIP_ENOTRESPONSEMSG; + + code = msg->line.status.code; + + /* If the response is final, cancel both retransmission and timeout + * timer. + */ + if (code >= 200) { + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + if (tsx->timeout_timer.id != 0) { + lock_timer(tsx); + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = 0; + unlock_timer(tsx); + } + + } else { + /* Cancel retransmit timer (for non-INVITE transaction, the + * retransmit timer will be rescheduled at T2. + */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* For provisional response, only cancel retransmit when this + * is an INVITE transaction. For non-INVITE, section 17.1.2.1 + * of RFC 3261 says that: + * - retransmit timer is set to T2 + * - timeout timer F is not deleted. + */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + /* Cancel timeout timer */ + lock_timer(tsx); + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + unlock_timer(tsx); + + } else { + if (!tsx->is_reliable) { + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt, + &tsx->retransmit_timer, + &t2_timer_val); + } + } + } + + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED); + + + /* Discard retransmission message if it is not INVITE. + * The INVITE tdata is needed in case we have to generate ACK for + * the final response. + */ + /* Keep last_tx for authorization. */ + //blp: always keep last_tx until transaction is destroyed + //code = msg->line.status.code; + //if (tsx->method.id != PJSIP_INVITE_METHOD && code!=401 && code!=407) { + // pjsip_tx_data_dec_ref(tsx->last_tx); + // tsx->last_tx = NULL; + //} + + /* Processing is similar to state Proceeding. */ + tsx_on_state_proceeding_uac( tsx, event); + + } else { + pj_assert(!"Unexpected event"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + + +/* + * State Trying is for UAS after it received request but before any responses + * is sent. + * Note: this is different than RFC3261, which can use Trying state for + * non-INVITE client transaction (bug in RFC?). + */ +static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_status_t status; + + pj_assert(tsx->state == PJSIP_TSX_STATE_TRYING); + + /* This state is only for UAS */ + pj_assert(tsx->role == PJSIP_ROLE_UAS); + + /* Better be transmission of response message. + * If we've got request retransmission, this means that the TU hasn't + * transmitted any responses within 500 ms, which is not allowed. If + * this happens, just ignore the event (we couldn't retransmit last + * response because we haven't sent any!). + */ + if (event->type != PJSIP_EVENT_TX_MSG) { + return PJ_SUCCESS; + } + + /* The rest of the processing of the event is exactly the same as in + * "Proceeding" state. + */ + status = tsx_on_state_proceeding_uas( tsx, event); + + /* Inform the TU of the state transision if state is still State_Trying */ + if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TRYING) { + + tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING, + PJSIP_EVENT_TX_MSG, event->body.tx_msg.tdata); + + } + + return status; +} + + +/* + * Handler for events in Proceeding for UAS + * This state happens after the TU sends provisional response. + */ +static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING || + tsx->state == PJSIP_TSX_STATE_TRYING); + + /* This state is only for UAS. */ + pj_assert(tsx->role == PJSIP_ROLE_UAS); + + /* Receive request retransmission. */ + if (event->type == PJSIP_EVENT_RX_MSG) { + + pj_status_t status; + + /* Must have last response sent. */ + PJ_ASSERT_RETURN(tsx->last_tx != NULL, PJ_EBUG); + + /* Send last response */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_SEND; + } else { + status = tsx_send_msg(tsx, tsx->last_tx); + if (status != PJ_SUCCESS) + return status; + } + + } else if (event->type == PJSIP_EVENT_TX_MSG ) { + pjsip_tx_data *tdata = event->body.tx_msg.tdata; + pj_status_t status; + + /* The TU sends response message to the request. Save this message so + * that we can retransmit the last response in case we receive request + * retransmission. + */ + pjsip_msg *msg = tdata->msg; + + /* This can only be a response message. */ + PJ_ASSERT_RETURN(msg->type==PJSIP_RESPONSE_MSG, PJSIP_ENOTRESPONSEMSG); + + /* Update last status */ + tsx_set_status_code(tsx, msg->line.status.code, + &msg->line.status.reason); + + /* Discard the saved last response (it will be updated later as + * necessary). + */ + if (tsx->last_tx && tsx->last_tx != tdata) { + pjsip_tx_data_dec_ref( tsx->last_tx ); + tsx->last_tx = NULL; + } + + /* Send the message. */ + status = tsx_send_msg(tsx, tdata); + if (status != PJ_SUCCESS) { + return status; + } + + // Update To tag header for RFC2543 transaction. + // TODO: + + /* Update transaction state */ + if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 100)) { + + if (tsx->last_tx != tdata) { + tsx->last_tx = tdata; + pjsip_tx_data_add_ref( tdata ); + } + + tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING, + PJSIP_EVENT_TX_MSG, tdata ); + + /* Retransmit provisional response every 1 minute if this is + * an INVITE provisional response greater than 100. + */ + if (PJSIP_TSX_1XX_RETRANS_DELAY > 0 && + tsx->method.id==PJSIP_INVITE_METHOD && tsx->status_code>100) + { + + /* Stop 1xx retransmission timer, if any */ + if (tsx->retransmit_timer.id) { + pjsip_endpt_cancel_timer(tsx->endpt, + &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* Schedule retransmission */ + tsx->retransmit_count = 0; + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + pj_time_val delay = {PJSIP_TSX_1XX_RETRANS_DELAY, 0}; + + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, + &tsx->retransmit_timer, + &delay); + } + } + + } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { + + /* Stop 1xx retransmission timer, if any */ + if (tsx->retransmit_timer.id) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->handle_200resp==0) { + + /* 2xx class message is not saved, because retransmission + * is handled by TU. + */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TX_MSG, tdata ); + + /* Transaction is destroyed. */ + //return PJSIP_ETSXDESTROYED; + + } else { + pj_time_val timeout; + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + tsx->retransmit_count = 0; + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, + &tsx->retransmit_timer, + &t1_timer_val); + } + } + + /* Save last response sent for retransmission when request + * retransmission is received. + */ + if (tsx->last_tx != tdata) { + tsx->last_tx = tdata; + pjsip_tx_data_add_ref(tdata); + } + + /* Setup timeout timer: */ + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + /* Start Timer H at 64*T1 for INVITE server transaction, + * regardless of transport. + */ + timeout = timeout_timer_val; + + } else if (!tsx->is_reliable) { + + /* For non-INVITE, start timer J at 64*T1 for unreliable + * transport. + */ + timeout = timeout_timer_val; + + } else { + + /* Transaction terminates immediately for non-INVITE when + * reliable transport is used. + */ + timeout.sec = timeout.msec = 0; + } + + lock_timer(tsx); + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout); + unlock_timer(tsx); + + /* Set state to "Completed" */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_TX_MSG, tdata ); + } + + } else if (tsx->status_code >= 300) { + + /* Stop 1xx retransmission timer, if any */ + if (tsx->retransmit_timer.id) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* 3xx-6xx class message causes transaction to move to + * "Completed" state. + */ + if (tsx->last_tx != tdata) { + tsx->last_tx = tdata; + pjsip_tx_data_add_ref( tdata ); + } + + /* For INVITE, start timer H for transaction termination + * regardless whether transport is reliable or not. + * For non-INVITE, start timer J with the value of 64*T1 for + * non-reliable transports, and zero for reliable transports. + */ + lock_timer(tsx); + if (tsx->method.id == PJSIP_INVITE_METHOD) { + /* Start timer H for INVITE */ + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, + &timeout_timer_val); + } else if (!tsx->is_reliable) { + /* Start timer J on 64*T1 seconds for non-INVITE */ + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, + &timeout_timer_val); + } else { + /* Start timer J on zero seconds for non-INVITE */ + pj_time_val zero_time = { 0, 0 }; + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, + &zero_time); + } + unlock_timer(tsx); + + /* For INVITE, if unreliable transport is used, retransmission + * timer G will be scheduled (retransmission). + */ + if (!tsx->is_reliable) { + pjsip_cseq_hdr *cseq = (pjsip_cseq_hdr*) + pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ, + NULL); + if (cseq->method.id == PJSIP_INVITE_METHOD) { + tsx->retransmit_count = 0; + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt, + &tsx->retransmit_timer, + &t1_timer_val); + } + } + } + + /* Inform TU */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_TX_MSG, tdata ); + + } else { + pj_assert(0); + } + + + } else if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->retransmit_timer) { + + /* Retransmission timer elapsed. */ + pj_status_t status; + + /* Must not be triggered while transport is pending. */ + pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0); + + /* Must have last response to retransmit. */ + pj_assert(tsx->last_tx != NULL); + + /* Retransmit the last response. */ + status = tsx_retransmit( tsx, 1 ); + if (status != PJ_SUCCESS) { + return status; + } + + } else if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->timeout_timer) { + + /* Timeout timer. should not happen? */ + pj_assert(!"Should not happen(?)"); + + tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL); + + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer); + + return PJ_EBUG; + + } else { + pj_assert(!"Unexpected event"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in Proceeding for UAC + * This state happens after provisional response(s) has been received from + * UAS. + */ +static pj_status_t tsx_on_state_proceeding_uac(pjsip_transaction *tsx, + pjsip_event *event) +{ + + pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING || + tsx->state == PJSIP_TSX_STATE_CALLING); + + if (event->type != PJSIP_EVENT_TIMER) { + pjsip_msg *msg; + + /* Must be incoming response, because we should not retransmit + * request once response has been received. + */ + pj_assert(event->type == PJSIP_EVENT_RX_MSG); + if (event->type != PJSIP_EVENT_RX_MSG) { + return PJ_EINVALIDOP; + } + + msg = event->body.rx_msg.rdata->msg_info.msg; + + /* Must be a response message. */ + if (msg->type != PJSIP_RESPONSE_MSG) { + pj_assert(!"Expecting response message!"); + return PJSIP_ENOTRESPONSEMSG; + } + + tsx_set_status_code(tsx, msg->line.status.code, + &msg->line.status.reason); + + } else { + if (event->body.timer.entry == &tsx->retransmit_timer) { + /* Retransmit message. */ + pj_status_t status; + + status = tsx_retransmit( tsx, 1 ); + + return status; + + } else { + tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL); + } + } + + if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 100)) { + + /* Inform the message to TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + + } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code,200)) { + + /* Stop timeout timer B/F. */ + lock_timer(tsx); + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + unlock_timer(tsx); + + /* For INVITE, the state moves to Terminated state (because ACK is + * handled in TU). For non-INVITE, state moves to Completed. + */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + //return PJSIP_ETSXDESTROYED; + + } else { + pj_time_val timeout; + + /* For unreliable transport, start timer D (for INVITE) or + * timer K for non-INVITE. */ + if (!tsx->is_reliable) { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + timeout = td_timer_val; + } else { + timeout = t4_timer_val; + } + } else { + timeout.sec = timeout.msec = 0; + } + lock_timer(tsx); + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout); + unlock_timer(tsx); + + /* Cancel retransmission timer */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* Move state to Completed, inform TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + } + + } else if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->timeout_timer) { + + /* Inform TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer); + + + } else if (tsx->status_code >= 300 && tsx->status_code <= 699) { + + +#if 0 + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ + /* + * This is the old code; it's broken for authentication. + */ + pj_time_val timeout; + pj_status_t status; + + /* Stop timer B. */ + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + + /* Generate and send ACK for INVITE. */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + pjsip_tx_data *ack; + + status = pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx, + event->body.rx_msg.rdata, + &ack); + if (status != PJ_SUCCESS) + return status; + + if (ack != tsx->last_tx) { + pjsip_tx_data_dec_ref(tsx->last_tx); + tsx->last_tx = ack; + } + + status = tsx_send_msg( tsx, tsx->last_tx); + if (status != PJ_SUCCESS) { + return status; + } + } + + /* Start Timer D with TD/T4 timer if unreliable transport is used. */ + if (!tsx->is_reliable) { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + timeout = td_timer_val; + } else { + timeout = t4_timer_val; + } + } else { + timeout.sec = timeout.msec = 0; + } + tsx->timeout->timer.id = TSX_TIMER_TIMEOUT; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout); + + /* Inform TU. + * blp: You might be tempted to move this notification before + * sending ACK, but I think you shouldn't. Better set-up + * everything before calling tsx_user's callback to avoid + * mess up. + */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ +#endif + + /* New code, taken from 0.2.9.x branch */ + pj_time_val timeout; + pjsip_tx_data *ack_tdata = NULL; + + /* Cancel retransmission timer */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* Stop timer B. */ + lock_timer(tsx); + tsx->timeout_timer.id = 0; + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + unlock_timer(tsx); + + /* Generate and send ACK (for INVITE) */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + pj_status_t status; + + status = pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx, + event->body.rx_msg.rdata, + &ack_tdata); + if (status != PJ_SUCCESS) + return status; + + status = tsx_send_msg( tsx, ack_tdata); + if (status != PJ_SUCCESS) + return status; + } + + /* Inform TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata); + + /* Generate and send ACK for INVITE. */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + if (ack_tdata != tsx->last_tx) { + pjsip_tx_data_dec_ref(tsx->last_tx); + tsx->last_tx = ack_tdata; + + /* This is a bug. + tsx_send_msg() does NOT decrement tdata's reference counter, + so if we add the reference counter here, tdata will have + reference counter 2, causing it to leak. + pjsip_tx_data_add_ref(ack_tdata); + */ + } + } + + /* Start Timer D with TD/T4 timer if unreliable transport is used. */ + /* Note: tsx->transport may be NULL! */ + if (!tsx->is_reliable) { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + timeout = td_timer_val; + } else { + timeout = t4_timer_val; + } + } else { + timeout.sec = timeout.msec = 0; + } + lock_timer(tsx); + /* In the short period above timer may have been inserted + * by set_timeout() (by CANCEL). Cancel it if necessary. See: + * https://trac.pjsip.org/repos/ticket/1374 + */ + if (tsx->timeout_timer.id) + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout); + unlock_timer(tsx); + + } else { + // Shouldn't happen because there's no timer for this state. + pj_assert(!"Unexpected event"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in Completed state for UAS + */ +static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED); + + if (event->type == PJSIP_EVENT_RX_MSG) { + pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg; + + /* This must be a request message retransmission. */ + if (msg->type != PJSIP_REQUEST_MSG) + return PJSIP_ENOTREQUESTMSG; + + /* On receive request retransmission, retransmit last response. */ + if (msg->line.req.method.id != PJSIP_ACK_METHOD) { + pj_status_t status; + + status = tsx_retransmit( tsx, 0 ); + if (status != PJ_SUCCESS) { + return status; + } + + } else { + pj_time_val timeout; + + /* Process incoming ACK request. */ + + /* Verify that this is an INVITE transaction */ + if (tsx->method.id != PJSIP_INVITE_METHOD) { + PJ_LOG(2, (tsx->obj_name, + "Received illegal ACK for %.*s transaction", + (int)tsx->method.name.slen, + tsx->method.name.ptr)); + return PJSIP_EINVALIDMETHOD; + } + + /* Cease retransmission. */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED); + + /* Reschedule timeout timer. */ + lock_timer(tsx); + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + tsx->timeout_timer.id = TIMER_ACTIVE; + + /* Timer I is T4 timer for unreliable transports, and + * zero seconds for reliable transports. + */ + if (!tsx->is_reliable) { + timeout.sec = 0; + timeout.msec = 0; + } else { + timeout.sec = t4_timer_val.sec; + timeout.msec = t4_timer_val.msec; + } + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout); + unlock_timer(tsx); + + /* Move state to "Confirmed" */ + tsx_set_state( tsx, PJSIP_TSX_STATE_CONFIRMED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + } + + } else if (event->type == PJSIP_EVENT_TIMER) { + + if (event->body.timer.entry == &tsx->retransmit_timer) { + /* Retransmit message. */ + pj_status_t status; + + status = tsx_retransmit( tsx, 1 ); + if (status != PJ_SUCCESS) { + return status; + } + + } else { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + /* For INVITE, this means that ACK was never received. + * Set state to Terminated, and inform TU. + */ + + tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL); + + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer ); + + //return PJSIP_ETSXDESTROYED; + + } else { + /* Transaction terminated, it can now be deleted. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer ); + //return PJSIP_ETSXDESTROYED; + } + } + + } else { + /* Ignore request to transmit. */ + PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG && + event->body.tx_msg.tdata == tsx->last_tx, + PJ_EINVALIDOP); + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in Completed state for UAC transaction. + */ +static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED); + + if (event->type == PJSIP_EVENT_TIMER) { + /* Must be the timeout timer. */ + pj_assert(event->body.timer.entry == &tsx->timeout_timer); + + /* Move to Terminated state. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, event->body.timer.entry ); + + /* Transaction has been destroyed. */ + //return PJSIP_ETSXDESTROYED; + + } else if (event->type == PJSIP_EVENT_RX_MSG) { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + /* On received of final response retransmission, retransmit the ACK. + * TU doesn't need to be informed. + */ + pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg; + pj_assert(msg->type == PJSIP_RESPONSE_MSG); + if (msg->type==PJSIP_RESPONSE_MSG && + msg->line.status.code >= 200) + { + pj_status_t status; + + status = tsx_retransmit( tsx, 0 ); + if (status != PJ_SUCCESS) { + return status; + } + } else { + /* Very late retransmission of privisional response. */ + pj_assert( msg->type == PJSIP_RESPONSE_MSG ); + } + } else { + /* Just drop the response. */ + } + + } else { + pj_assert(!"Unexpected event"); + return PJ_EINVALIDOP; + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in state Confirmed. + */ +static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_CONFIRMED); + + /* This state is only for UAS for INVITE. */ + pj_assert(tsx->role == PJSIP_ROLE_UAS); + pj_assert(tsx->method.id == PJSIP_INVITE_METHOD); + + /* Absorb any ACK received. */ + if (event->type == PJSIP_EVENT_RX_MSG) { + + pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg; + + /* Only expecting request message. */ + if (msg->type != PJSIP_REQUEST_MSG) + return PJSIP_ENOTREQUESTMSG; + + /* Must be an ACK request or a late INVITE retransmission. */ + pj_assert(msg->line.req.method.id == PJSIP_ACK_METHOD || + msg->line.req.method.id == PJSIP_INVITE_METHOD); + + } else if (event->type == PJSIP_EVENT_TIMER) { + /* Must be from timeout_timer_. */ + pj_assert(event->body.timer.entry == &tsx->timeout_timer); + + /* Move to Terminated state. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer ); + + /* Transaction has been destroyed. */ + //return PJSIP_ETSXDESTROYED; + + } else { + pj_assert(!"Unexpected event"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in state Terminated. + */ +static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_TERMINATED); + + /* Ignore events other than timer. This used to be an assertion but + * events may genuinely arrive at this state. + */ + if (event->type != PJSIP_EVENT_TIMER) { + return PJ_EIGNORED; + } + + /* Destroy this transaction */ + tsx_set_state(tsx, PJSIP_TSX_STATE_DESTROYED, + event->type, event->body.user.user1 ); + + return PJ_SUCCESS; +} + + +/* + * Handler for events in state Destroyed. + * Shouldn't happen! + */ +static pj_status_t tsx_on_state_destroyed(pjsip_transaction *tsx, + pjsip_event *event) +{ + PJ_UNUSED_ARG(tsx); + PJ_UNUSED_ARG(event); + + // See https://trac.pjsip.org/repos/ticket/1432 + //pj_assert(!"Not expecting any events!!"); + + return PJ_EIGNORED; +} + -- cgit v1.2.3