summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip/sip_transaction.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src/pjsip/sip_transaction.c')
-rw-r--r--pjsip/src/pjsip/sip_transaction.c3990
1 files changed, 1995 insertions, 1995 deletions
diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c
index bc4f21ff..eade0a4b 100644
--- a/pjsip/src/pjsip/sip_transaction.c
+++ b/pjsip/src/pjsip/sip_transaction.c
@@ -1,1995 +1,1995 @@
-/* $Id$ */
-/*
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-#include <pjsip/sip_transaction.h>
-#include <pjsip/sip_transport.h>
-#include <pjsip/sip_config.h>
-#include <pjsip/sip_util.h>
-#include <pjsip/sip_event.h>
-#include <pjsip/sip_endpoint.h>
-#include <pjsip/sip_errno.h>
-#include <pj/log.h>
-#include <pj/string.h>
-#include <pj/os.h>
-#include <pj/guid.h>
-#include <pj/pool.h>
-#include <pj/assert.h>
-
-/* Thread Local Storage ID for transaction lock (initialized by endpoint) */
-long pjsip_tsx_lock_tls_id;
-
-/* State names */
-static const char *state_str[] =
-{
- "Null",
- "Calling",
- "Trying",
- "Proceeding",
- "Completed",
- "Confirmed",
- "Terminated",
- "Destroyed",
-};
-
-/* Role names */
-static const char *role_name[] =
-{
- "Client",
- "Server"
-};
-
-/* 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 const pj_time_val t1_timer_val = { PJSIP_T1_TIMEOUT/1000,
- PJSIP_T1_TIMEOUT%1000 };
-static const pj_time_val t4_timer_val = { PJSIP_T4_TIMEOUT/1000,
- PJSIP_T4_TIMEOUT%1000 };
-static const pj_time_val td_timer_val = { PJSIP_TD_TIMEOUT/1000,
- PJSIP_TD_TIMEOUT%1000 };
-static const pj_time_val timeout_timer_val = { (64*PJSIP_T1_TIMEOUT)/1000,
- (64*PJSIP_T1_TIMEOUT)%1000 };
-
-/* Internal timer IDs */
-enum Transaction_Timer_Id
-{
- TSX_TIMER_RETRANSMISSION,
- TSX_TIMER_TIMEOUT,
-};
-
-/* Function Prototypes */
-static pj_status_t pjsip_tsx_on_state_null( pjsip_transaction *tsx,
- pjsip_event *event);
-static pj_status_t pjsip_tsx_on_state_calling( pjsip_transaction *tsx,
- pjsip_event *event);
-static pj_status_t pjsip_tsx_on_state_trying( pjsip_transaction *tsx,
- pjsip_event *event);
-static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx,
- pjsip_event *event);
-static pj_status_t pjsip_tsx_on_state_proceeding_uac( pjsip_transaction *tsx,
- pjsip_event *event);
-static pj_status_t pjsip_tsx_on_state_completed_uas( pjsip_transaction *tsx,
- pjsip_event *event);
-static pj_status_t pjsip_tsx_on_state_completed_uac( pjsip_transaction *tsx,
- pjsip_event *event);
-static pj_status_t pjsip_tsx_on_state_confirmed(pjsip_transaction *tsx,
- pjsip_event *event);
-static pj_status_t pjsip_tsx_on_state_terminated(pjsip_transaction *tsx,
- pjsip_event *event);
-static pj_status_t pjsip_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 int tsx_send_msg( pjsip_transaction *tsx,
- pjsip_tx_data *tdata);
-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 );
-
-/* State handlers for UAC, indexed by state */
-static int (*tsx_state_handler_uac[PJSIP_TSX_STATE_MAX])(pjsip_transaction *,
- pjsip_event *) =
-{
- &pjsip_tsx_on_state_null,
- &pjsip_tsx_on_state_calling,
- &pjsip_tsx_on_state_trying,
- &pjsip_tsx_on_state_proceeding_uac,
- &pjsip_tsx_on_state_completed_uac,
- &pjsip_tsx_on_state_confirmed,
- &pjsip_tsx_on_state_terminated,
- &pjsip_tsx_on_state_destroyed,
-};
-
-/* State handlers for UAS */
-static int (*tsx_state_handler_uas[PJSIP_TSX_STATE_MAX])(pjsip_transaction *,
- pjsip_event *) =
-{
- &pjsip_tsx_on_state_null,
- &pjsip_tsx_on_state_calling,
- &pjsip_tsx_on_state_trying,
- &pjsip_tsx_on_state_proceeding_uas,
- &pjsip_tsx_on_state_completed_uas,
- &pjsip_tsx_on_state_confirmed,
- &pjsip_tsx_on_state_terminated,
- &pjsip_tsx_on_state_destroyed,
-};
-
-/*
- * 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.call_id.slen + /* Call-ID */
- host->slen + /* Via host. */
- 9 + /* Via port. */
- 16; /* Separator+Allowance. */
- key = p = pj_pool_alloc(pool, len_required);
- end = p + len_required;
-
- /* Add role. */
- *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's');
- *p++ = SEPARATOR;
-
- /* Add Request-URI */
- /* This is BUG!
- * Response doesn't have Request-URI!
- *
- len = req_uri->vptr->print( PJSIP_URI_IN_REQ_URI, req_uri, p, end-p );
- p += len;
- *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.call_id.slen;
- pj_memcpy( p, rdata->msg_info.call_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 = 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 );
- }
-}
-
-
-/*
- * Create new transaction.
- */
-PJ_DEF(pj_status_t) pjsip_tsx_create( pj_pool_t *pool,
- pjsip_endpoint *endpt,
- pjsip_transaction **p_tsx)
-{
- pjsip_transaction *tsx;
- pj_status_t status;
-
- tsx = pj_pool_calloc(pool, 1, sizeof(pjsip_transaction));
-
- tsx->pool = pool;
- tsx->endpt = endpt;
- tsx->retransmit_timer.id = TSX_TIMER_RETRANSMISSION;
- tsx->retransmit_timer._timer_id = -1;
- tsx->retransmit_timer.user_data = tsx;
- tsx->retransmit_timer.cb = &tsx_timer_callback;
- tsx->timeout_timer.id = TSX_TIMER_TIMEOUT;
- tsx->timeout_timer._timer_id = -1;
- tsx->timeout_timer.user_data = tsx;
- tsx->timeout_timer.cb = &tsx_timer_callback;
- pj_sprintf(tsx->obj_name, "tsx%p", tsx);
- status = pj_mutex_create_recursive(pool, "mtsx%p", &tsx->mutex);
- if (status != PJ_SUCCESS) {
- return status;
- }
-
- *p_tsx = tsx;
- return PJ_SUCCESS;
-}
-
-/*
- * 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;
-}
-
-/*
- * 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_event e;
-
- PJ_LOG(4, (tsx->obj_name, "STATE %s-->%s, cause = %s",
- state_str[tsx->state], state_str[state],
- pjsip_event_str(event_src_type)));
-
- /* 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];
- }
-
- /* Inform TU */
- PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src);
- pjsip_endpt_send_tsx_event( tsx->endpt, &e );
-
- /* When the transaction is terminated, release transport, and free the
- * saved last transmitted message.
- */
- if (state == PJSIP_TSX_STATE_TERMINATED) {
-
- /* Decrement transport reference counter. */
- if (tsx->transport &&
- tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL)
- {
- pjsip_transport_dec_ref( tsx->transport );
- tsx->transport = NULL;
- }
- /* 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._timer_id != -1) {
- pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
- tsx->timeout_timer._timer_id = -1;
- }
- /* Cancel retransmission timer. */
- if (tsx->retransmit_timer._timer_id != -1) {
- pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
- tsx->retransmit_timer._timer_id = -1;
- }
-
- /* If transport is not pending, reschedule timeout timer to
- * destroy this transaction.
- */
- if (tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL) {
- pj_time_val timeout = {0, 0};
- pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
- &timeout);
- }
-
- } else if (state == PJSIP_TSX_STATE_DESTROYED) {
-
- /* Clear TLS, so that mutex will not be unlocked */
- struct tsx_lock_data *lck = pj_thread_local_get(pjsip_tsx_lock_tls_id);
- while (lck) {
- if (lck->tsx == tsx) {
- lck->is_alive = 0;
- }
- lck = lck->prev;
- }
- }
-}
-
-/*
- * Look-up destination address and select which transport to be used to send
- * the request message. The procedure used here follows the guidelines on
- * sending the request in RFC3261 chapter 8.1.2.
- *
- * This function also modifies the message (request line and Route headers)
- * accordingly.
- */
-static pj_status_t tsx_process_route( pjsip_transaction *tsx,
- pjsip_tx_data *tdata,
- pjsip_host_port *send_addr )
-{
- const pjsip_uri *new_request_uri, *target_uri;
- const pjsip_name_addr *topmost_route_uri;
- pjsip_route_hdr *first_route_hdr, *last_route_hdr;
-
- pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG);
-
- /* Get the first "Route" header from the message. If the message doesn't
- * have any "Route" headers but the endpoint has, then copy the "Route"
- * headers from the endpoint first.
- */
- last_route_hdr = first_route_hdr =
- pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
- if (first_route_hdr) {
- topmost_route_uri = &first_route_hdr->name_addr;
- while (last_route_hdr->next != (void*)&tdata->msg->hdr) {
- pjsip_route_hdr *hdr;
- hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE,
- last_route_hdr->next);
- if (!hdr)
- break;
- last_route_hdr = hdr;
- }
- } else {
- const pjsip_route_hdr *hdr_list;
- hdr_list = (pjsip_route_hdr*)pjsip_endpt_get_routing(tsx->endpt);
- if (hdr_list->next != hdr_list) {
- const pjsip_route_hdr *hdr = (pjsip_route_hdr*)hdr_list->next;
- first_route_hdr = NULL;
- topmost_route_uri = &hdr->name_addr;
- do {
- last_route_hdr = pjsip_hdr_shallow_clone(tdata->pool, hdr);
- if (first_route_hdr == NULL)
- first_route_hdr = last_route_hdr;
- pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)last_route_hdr);
- hdr = hdr->next;
- } while (hdr != hdr_list);
- } else {
- topmost_route_uri = NULL;
- }
- }
-
- /* If Route headers exist, and the first element indicates loose-route,
- * the URI is taken from the Request-URI, and we keep all existing Route
- * headers intact.
- * If Route headers exist, and the first element DOESN'T indicate loose
- * route, the URI is taken from the first Route header, and remove the
- * first Route header from the message.
- * Otherwise if there's no Route headers, the URI is taken from the
- * Request-URI.
- */
- if (topmost_route_uri) {
- pj_bool_t has_lr_param;
-
- if (PJSIP_URI_SCHEME_IS_SIP(topmost_route_uri) ||
- PJSIP_URI_SCHEME_IS_SIPS(topmost_route_uri))
- {
- const pjsip_url *url = pjsip_uri_get_uri((void*)topmost_route_uri);
- has_lr_param = url->lr_param;
- } else {
- has_lr_param = 0;
- }
-
- if (has_lr_param) {
- new_request_uri = tdata->msg->line.req.uri;
- /* We shouldn't need to delete topmost Route if it has lr param.
- * But seems like it breaks some proxy implementation, so we
- * delete it anyway.
- */
- /*
- pj_list_erase(first_route_hdr);
- if (first_route_hdr == last_route_hdr)
- last_route_hdr = NULL;
- */
- } else {
- new_request_uri = pjsip_uri_get_uri((void*)topmost_route_uri);
- pj_list_erase(first_route_hdr);
- if (first_route_hdr == last_route_hdr)
- last_route_hdr = NULL;
- }
-
- target_uri = (pjsip_uri*)topmost_route_uri;
-
- } else {
- target_uri = new_request_uri = tdata->msg->line.req.uri;
- }
-
- /* The target URI must be a SIP/SIPS URL so we can resolve it's address.
- * Otherwise we're in trouble (i.e. there's no host part in tel: URL).
- */
- pj_memset(send_addr, 0, sizeof(*send_addr));
-
- if (PJSIP_URI_SCHEME_IS_SIPS(target_uri)) {
- pjsip_uri *uri = (pjsip_uri*) target_uri;
- const pjsip_url *url = (const pjsip_url*)pjsip_uri_get_uri(uri);
- send_addr->flag |= (PJSIP_TRANSPORT_SECURE | PJSIP_TRANSPORT_RELIABLE);
- pj_strdup(tdata->pool, &send_addr->host, &url->host);
- send_addr->port = url->port;
- send_addr->type =
- pjsip_transport_get_type_from_name(&url->transport_param);
-
- } else if (PJSIP_URI_SCHEME_IS_SIP(target_uri)) {
- pjsip_uri *uri = (pjsip_uri*) target_uri;
- const pjsip_url *url = (const pjsip_url*)pjsip_uri_get_uri(uri);
- pj_strdup(tdata->pool, &send_addr->host, &url->host);
- send_addr->port = url->port;
- send_addr->type =
- pjsip_transport_get_type_from_name(&url->transport_param);
-#if PJ_HAS_TCP
- if (send_addr->type == PJSIP_TRANSPORT_TCP ||
- send_addr->type == PJSIP_TRANSPORT_SCTP)
- {
- send_addr->flag |= PJSIP_TRANSPORT_RELIABLE;
- }
-#endif
- } else {
- pj_assert(!"Unsupported URI scheme!");
- return PJSIP_EINVALIDSCHEME;
- }
-
- /* If target URI is different than request URI, replace
- * request URI add put the original URI in the last Route header.
- */
- if (new_request_uri && new_request_uri!=tdata->msg->line.req.uri) {
- pjsip_route_hdr *route = pjsip_route_hdr_create(tdata->pool);
- route->name_addr.uri = tdata->msg->line.req.uri;
- if (last_route_hdr)
- pj_list_insert_after(last_route_hdr, route);
- else
- pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)route);
- tdata->msg->line.req.uri = (pjsip_uri*)new_request_uri;
- }
-
- /* Success. */
- return PJ_SUCCESS;
-}
-
-
-/*
- * Callback from the transport job.
- * This callback is called when asychronous transport connect() operation
- * has completed, with or without error.
- */
-static void tsx_transport_callback(pjsip_transport *tr,
- void *token,
- pj_status_t status)
-{
- char addr[PJ_MAX_HOSTNAME];
- pjsip_transaction *tsx = token;
- struct tsx_lock_data lck;
-
- pj_memcpy(addr, tsx->dest_name.host.ptr, tsx->dest_name.host.slen);
- addr[tsx->dest_name.host.slen] = '\0';
-
-
- if (status == PJ_SUCCESS) {
- PJ_LOG(4, (tsx->obj_name, "%s connected to %s:%d",
- tr->type_name,
- addr, tsx->dest_name.port));
- } else {
- PJ_LOG(4, (tsx->obj_name, "%s unable to connect to %s:%d, status=%d",
- tr->type_name,
- addr, tsx->dest_name.port, status));
- }
-
- /* Lock transaction. */
- lock_tsx(tsx, &lck);
-
- if (status != PJ_SUCCESS) {
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
- tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
-
- tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED,
- PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
-
- /* Unlock transaction. */
- unlock_tsx(tsx, &lck);
- return;
- }
-
- /* See if transaction has already been terminated.
- * If so, schedule to destroy the transaction.
- */
- if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
- pj_time_val timeout = {0, 0};
- pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
- &timeout);
-
- /* Unlock transaction. */
- unlock_tsx(tsx, &lck);
- return;
- }
-
- /* Add reference counter to the transport. */
- pjsip_transport_add_ref(tr);
-
- /* Mark transport as ready. */
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
- tsx->transport = tr;
-
- /* If there's a pending message to send, send it now. */
- if (tsx->has_unsent_msg) {
- tsx_send_msg( tsx, tsx->last_tx );
- }
-
- /* Unlock transaction. */
- unlock_tsx(tsx, &lck);
-}
-
-/*
- * Callback from the resolver job.
- */
-static void tsx_resolver_callback(pj_status_t status,
- void *token,
- const struct pjsip_server_addresses *addr)
-{
- pjsip_transaction *tsx = token;
- struct tsx_lock_data lck;
- pjsip_transport *tp;
-
- PJ_LOG(4, (tsx->obj_name, "resolver job complete, status=%d", status));
-
- if (status != PJ_SUCCESS || addr->count == 0) {
- lock_tsx(tsx, &lck);
- tsx->status_code = PJSIP_SC_TSX_RESOLVE_ERROR;
- tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED,
- PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
- unlock_tsx(tsx, &lck);
- return;
- }
-
- /* Lock transaction. */
- lock_tsx(tsx, &lck);
-
- /* Copy server addresses. */
- pj_memcpy(&tsx->remote_addr, addr, sizeof(*addr));
-
- /* Create/find the transport for the remote address. */
- PJ_LOG(5,(tsx->obj_name, "tsx getting transport for %s:%d",
- pj_inet_ntoa(addr->entry[0].addr.sin_addr),
- pj_ntohs(addr->entry[0].addr.sin_port)));
-
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_CONNECTING;
- status = pjsip_endpt_alloc_transport( tsx->endpt, addr->entry[0].type,
- &addr->entry[0].addr,
- &tp);
- tsx_transport_callback(tp, tsx, status);
-
- /* Unlock transaction */
- unlock_tsx(tsx, &lck);
-
- /* There should be nothing to do after this point.
- * Execution for the transaction will resume when the callback for the
- * transport is called.
- */
-}
-
-/*
- * Initialize the transaction as UAC transaction.
- */
-PJ_DEF(pj_status_t) pjsip_tsx_init_uac( pjsip_transaction *tsx,
- pjsip_tx_data *tdata)
-{
- pjsip_msg *msg;
- pjsip_cseq_hdr *cseq;
- pjsip_via_hdr *via;
- pj_status_t status;
- struct tsx_lock_data lck;
-
- PJ_LOG(4,(tsx->obj_name, "initializing tsx as UAC (tdata=%p)", tdata));
-
- /* Lock transaction. */
- lock_tsx(tsx, &lck);
-
- /* Keep shortcut */
- msg = tdata->msg;
-
- /* Role is UAC. */
- tsx->role = PJSIP_ROLE_UAC;
-
- /* Save method. */
- pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method);
-
- /* Generate Via header if it doesn't exist. */
- via = 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);
- }
-
- if (via->branch_param.slen == 0) {
- pj_str_t tmp;
- via->branch_param.ptr = 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;
- 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. */
- status = create_tsx_key_3261( tsx->pool, &tsx->transaction_key,
- PJSIP_ROLE_UAC, &tsx->method,
- &via->branch_param);
- if (status != PJ_SUCCESS) {
- unlock_tsx(tsx, &lck);
- return status;
- }
-
- PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen,
- tsx->transaction_key.ptr));
-
- /* Save CSeq. */
- cseq = pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL);
- if (!cseq) {
- pj_assert(!"CSeq header not present in outgoing message!");
- unlock_tsx(tsx, &lck);
- return PJSIP_EMISSINGHDR;
- }
- tsx->cseq = cseq->cseq;
-
-
- /* 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 = &pjsip_tsx_on_state_null;
-
- /* Get destination name from the message. */
- status = tsx_process_route(tsx, tdata, &tsx->dest_name);
- if (status != PJ_SUCCESS) {
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
- tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
- tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED,
- PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
- unlock_tsx(tsx, &lck);
- return status;
- }
-
- /* Resolve destination.
- * This will start asynchronous resolver job, and when it finishes,
- * the callback will be called.
- */
- PJ_LOG(5,(tsx->obj_name, "tsx resolving destination %.*s:%d",
- tsx->dest_name.host.slen,
- tsx->dest_name.host.ptr,
- tsx->dest_name.port));
-
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING;
- pjsip_endpt_resolve( tsx->endpt, tsx->pool, &tsx->dest_name,
- tsx, &tsx_resolver_callback);
-
- /* There should be nothing to do after this point.
- * Execution for the transaction will resume when the resolver callback is
- * called.
- */
-
- /* Unlock transaction and return.
- * If transaction has been destroyed WITHIN the current thread, the
- * unlock_tsx() function will return -1.
- */
- return unlock_tsx(tsx, &lck);
-}
-
-
-/*
- * Initialize the transaction as UAS transaction.
- */
-PJ_DEF(pj_status_t) pjsip_tsx_init_uas( pjsip_transaction *tsx,
- pjsip_rx_data *rdata)
-{
- pjsip_msg *msg = rdata->msg_info.msg;
- pj_str_t *branch;
- pjsip_cseq_hdr *cseq;
- pj_status_t status;
- struct tsx_lock_data lck;
-
- PJ_LOG(4,(tsx->obj_name, "initializing tsx as UAS (rdata=%p)", rdata));
-
- /* Lock transaction. */
- lock_tsx(tsx, &lck);
-
- /* Keep shortcut to message */
- msg = rdata->msg_info.msg;
-
- /* Role is UAS */
- tsx->role = PJSIP_ROLE_UAS;
-
- /* Save method. */
- pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method);
-
- /* 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);
- return status;
- }
-
- /* 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));
-
- /* Save CSeq */
- cseq = rdata->msg_info.cseq;
- tsx->cseq = cseq->cseq;
-
- /* 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 = &pjsip_tsx_on_state_null;
-
- /* Get the transport to send the response.
- * According to section 18.2.2 of RFC3261, if the transport is reliable
- * then the response must be sent using that transport.
- */
- /* In addition, RFC 3581 says, if Via has "rport" parameter specified,
- * then return the response using the same transport.
- */
- if (PJSIP_TRANSPORT_IS_RELIABLE(rdata->tp_info.transport) ||
- rdata->msg_info.via->rport_param >= 0)
- {
- tsx->transport = rdata->tp_info.transport;
- pjsip_transport_add_ref(tsx->transport);
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
-
- tsx->current_addr = 0;
- tsx->remote_addr.count = 1;
- tsx->remote_addr.entry[0].type = tsx->transport->type;
- pj_memcpy(&tsx->remote_addr.entry[0].addr,
- &rdata->pkt_info.addr, rdata->pkt_info.addr_len);
-
- } else {
- pj_status_t status;
-
- status = pjsip_get_response_addr(tsx->pool, rdata->tp_info.transport,
- rdata->msg_info.via, &tsx->dest_name);
- if (status != PJ_SUCCESS) {
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
- tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
- tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED,
- PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
- unlock_tsx(tsx, &lck);
- return status;
- }
-
- /* Resolve destination.
- * This will start asynchronous resolver job, and when it finishes,
- * the callback will be called.
- */
- PJ_LOG(5,(tsx->obj_name, "tsx resolving destination %.*s:%d",
- tsx->dest_name.host.slen,
- tsx->dest_name.host.ptr,
- tsx->dest_name.port));
-
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING;
- pjsip_endpt_resolve( tsx->endpt, tsx->pool, &tsx->dest_name,
- tsx, &tsx_resolver_callback);
- }
-
- /* There should be nothing to do after this point.
- * Execution for the transaction will resume when the resolver callback is
- * called.
- */
-
- /* Unlock transaction and return.
- * If transaction has been destroyed WITHIN the current thread, the
- * unlock_tsx() function will return -1.
- */
- return unlock_tsx(tsx, &lck);
-}
-
-/*
- * Callback when timer expires.
- */
-static void tsx_timer_callback( pj_timer_heap_t *theap, pj_timer_entry *entry)
-{
- pjsip_event event;
- pjsip_transaction *tsx = entry->user_data;
- struct tsx_lock_data lck;
-
- PJ_UNUSED_ARG(theap);
-
- PJ_LOG(5,(tsx->obj_name, "got timer event (%s timer)",
- (entry->id==TSX_TIMER_RETRANSMISSION ? "Retransmit" : "Timeout")));
-
-
- if (entry->id == TSX_TIMER_RETRANSMISSION) {
- PJSIP_EVENT_INIT_TIMER(event, &tsx->retransmit_timer);
- } else {
- PJSIP_EVENT_INIT_TIMER(event, &tsx->timeout_timer);
- }
-
- /* Dispatch event to transaction. */
- lock_tsx(tsx, &lck);
- (*tsx->state_handler)(tsx, &event);
- unlock_tsx(tsx, &lck);
-}
-
-/*
- * Transmit ACK message for 2xx/INVITE with this transaction. The ACK for
- * non-2xx/INVITE is automatically sent by the transaction.
- * This operation is only valid if the transaction is configured to handle ACK
- * (tsx->handle_ack is non-zero). If this attribute is not set, then the
- * transaction will comply with RFC-3261, i.e. it will set itself to
- * TERMINATED state when it receives 2xx/INVITE.
- */
-PJ_DEF(void) pjsip_tsx_on_tx_ack( pjsip_transaction *tsx, pjsip_tx_data *tdata)
-{
- pjsip_msg *msg;
- pjsip_host_port dest_addr;
- pjsip_via_hdr *via;
- struct tsx_lock_data lck;
- pj_status_t status = PJ_SUCCESS;
-
- /* Lock tsx. */
- lock_tsx(tsx, &lck);
-
- pj_assert(tsx->handle_ack != 0);
-
- msg = tdata->msg;
-
- /* Generate branch parameter if it doesn't exist. */
- via = pjsip_msg_find_hdr(msg, PJSIP_H_VIA, NULL);
- if (via == NULL) {
- via = pjsip_via_hdr_create(tdata->pool);
- pjsip_msg_add_hdr(msg, (pjsip_hdr*) via);
- }
-
- if (via->branch_param.slen == 0) {
- via->branch_param = tsx->branch;
- } else {
- pj_assert( pj_strcmp(&via->branch_param, &tsx->branch) == 0 );
- }
-
- /* Get destination name from the message. */
- status = tsx_process_route(tsx, tdata, &dest_addr);
- if (status != 0){
- goto on_error;
- }
-
- /* Compare message's destination name with transaction's destination name.
- * If NOT equal, then we'll have to resolve the destination.
- */
- if (dest_addr.type == tsx->dest_name.type &&
- dest_addr.flag == tsx->dest_name.flag &&
- dest_addr.port == tsx->dest_name.port &&
- pj_stricmp(&dest_addr.host, &tsx->dest_name.host) == 0)
- {
- /* Equal destination. We can use current transport. */
- pjsip_tsx_on_tx_msg(tsx, tdata);
- unlock_tsx(tsx, &lck);
- return;
-
- }
-
- /* New destination; we'll have to resolve host and create new transport. */
- pj_memcpy(&tsx->dest_name, &dest_addr, sizeof(dest_addr));
- pj_strdup(tsx->pool, &tsx->dest_name.host, &dest_addr.host);
-
- PJ_LOG(5,(tsx->obj_name, "tsx resolving destination %.*s:%d",
- tsx->dest_name.host.slen,
- tsx->dest_name.host.ptr,
- tsx->dest_name.port));
-
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING;
- pjsip_transport_dec_ref(tsx->transport);
- tsx->transport = NULL;
-
- /* Put the message in queue. */
- pjsip_tsx_on_tx_msg(tsx, tdata);
-
- /* This is a bug!
- * We shouldn't change transaction's state before actually sending the
- * message. Otherwise transaction will terminate before message is sent,
- * and timeout timer will be scheduled.
- */
- PJ_TODO(TSX_DONT_CHANGE_STATE_BEFORE_SENDING_ACK)
-
- /*
- * This will start asynchronous resolver job, and when it finishes,
- * the callback will be called.
- */
-
- tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING;
- pjsip_endpt_resolve( tsx->endpt, tsx->pool, &tsx->dest_name,
- tsx, &tsx_resolver_callback);
-
- unlock_tsx(tsx, &lck);
-
- /* There should be nothing to do after this point.
- * Execution for the transaction will resume when the resolver callback is
- * called.
- */
- return;
-
-on_error:
- /* Failure condition.
- * Send TERMINATED event.
- */
- tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
-
- tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
- PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
-
- unlock_tsx(tsx, &lck);
-}
-
-
-/*
- * This function is called by TU to send a message.
- */
-PJ_DEF(void) pjsip_tsx_on_tx_msg( pjsip_transaction *tsx,
- pjsip_tx_data *tdata )
-{
- pjsip_event event;
- struct tsx_lock_data lck;
- pj_status_t status;
-
- PJ_LOG(5,(tsx->obj_name, "Request to transmit msg on state %s (tdata=%p)",
- state_str[tsx->state], tdata));
-
- PJSIP_EVENT_INIT_TX_MSG(event, tsx, tdata);
-
- /* Dispatch to transaction. */
- lock_tsx(tsx, &lck);
- status = (*tsx->state_handler)(tsx, &event);
- unlock_tsx(tsx, &lck);
-}
-
-/*
- * This function is called by endpoint when incoming message for the
- * transaction is received.
- */
-PJ_DEF(void) pjsip_tsx_on_rx_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 msg on state %s (rdata=%p)",
- state_str[tsx->state], rdata));
-
- PJSIP_EVENT_INIT_RX_MSG(event, tsx, rdata);
-
- /* Dispatch to transaction. */
- lock_tsx(tsx, &lck);
- status = (*tsx->state_handler)(tsx, &event);
- unlock_tsx(tsx, &lck);
-}
-
-/*
- * Forcely terminate transaction.
- */
-PJ_DEF(void) pjsip_tsx_terminate( pjsip_transaction *tsx, int code )
-{
- struct tsx_lock_data lck;
-
- lock_tsx(tsx, &lck);
- tsx->status_code = code;
- tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
- PJSIP_EVENT_USER, NULL);
-
- unlock_tsx(tsx, &lck);
-}
-
-/*
- * Transport send completion callback.
- */
-static void tsx_on_send_complete(void *token, pjsip_tx_data *tdata,
- pj_ssize_t bytes_sent)
-{
- PJ_UNUSED_ARG(token);
- PJ_UNUSED_ARG(tdata);
-
- if (bytes_sent <= 0) {
- PJ_TODO(HANDLE_TRANSPORT_ERROR);
- }
-}
-
-/*
- * Send message to the transport.
- * If transport is not yet available, then do nothing. The message will be
- * transmitted when transport connection completion callback is called.
- */
-static pj_status_t tsx_send_msg( pjsip_transaction *tsx,
- pjsip_tx_data *tdata)
-{
- pj_status_t status = PJ_SUCCESS;
-
- PJ_LOG(5,(tsx->obj_name, "sending msg (tdata=%p)", tdata));
-
- if (tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL) {
- pjsip_event before_tx_event;
-
- pj_assert(tsx->transport != NULL);
-
- /* Make sure Via transport info is filled up properly for
- * requests.
- */
- if (tdata->msg->type == PJSIP_REQUEST_MSG) {
- const pj_sockaddr_in *addr_name;
- pjsip_via_hdr *via = (pjsip_via_hdr*)
- pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
-
- /* For request message, set "rport" parameter by default. */
- if (tdata->msg->type == PJSIP_REQUEST_MSG)
- via->rport_param = 0;
-
- /* Don't update Via sent-by on retransmission. */
- if (via->sent_by.host.slen == 0) {
- addr_name = &tsx->transport->public_addr;
- pj_strdup2(tdata->pool, &via->transport,
- tsx->transport->type_name);
- pj_strdup2(tdata->pool, &via->sent_by.host,
- pj_inet_ntoa(addr_name->sin_addr));
- via->sent_by.port = pj_ntohs(addr_name->sin_port);
- }
- }
-
- /* Notify everybody we're about to send message. */
- PJSIP_EVENT_INIT_PRE_TX_MSG(before_tx_event, tsx, tdata,
- tsx->retransmit_count);
- pjsip_endpt_send_tsx_event( tsx->endpt, &before_tx_event );
-
- tsx->has_unsent_msg = 0;
- status = pjsip_transport_send(tsx->transport, tdata,
- &tsx->remote_addr.entry[tsx->current_addr].addr,
- tsx, &tsx_on_send_complete);
- if (status != PJ_SUCCESS && status != PJ_EPENDING) {
- PJ_TODO(HANDLE_TRANSPORT_ERROR);
- goto on_error;
- }
- } else {
- tsx->has_unsent_msg = 1;
- }
-
- return 0;
-
-on_error:
- tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
- tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
- PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
- return status;
-}
-
-/*
- * Retransmit last message sent.
- */
-static pj_status_t pjsip_tsx_retransmit( pjsip_transaction *tsx,
- int should_restart_timer)
-{
- pj_status_t status;
-
- PJ_LOG(4,(tsx->obj_name, "retransmiting (tdata=%p, count=%d, restart?=%d)",
- tsx->last_tx, tsx->retransmit_count, should_restart_timer));
-
- pj_assert(tsx->last_tx != NULL);
-
- ++tsx->retransmit_count;
-
- status = tsx_send_msg( tsx, tsx->last_tx);
- if (status != PJ_SUCCESS) {
- return status;
- }
-
- /* Restart timer T1. */
- if (should_restart_timer) {
- pj_time_val timeout;
- int msec_time = (1 << (tsx->retransmit_count)) * PJSIP_T1_TIMEOUT;
-
- if (tsx->method.id!=PJSIP_INVITE_METHOD && msec_time>PJSIP_T2_TIMEOUT)
- msec_time = PJSIP_T2_TIMEOUT;
-
- timeout.sec = msec_time / 1000;
- timeout.msec = msec_time % 1000;
- pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer,
- &timeout);
- }
-
- return PJ_SUCCESS;
-}
-
-/*
- * Handler for events in state Null.
- */
-static pj_status_t pjsip_tsx_on_state_null( pjsip_transaction *tsx,
- pjsip_event *event )
-{
- pj_status_t status;
-
- pj_assert( tsx->state == PJSIP_TSX_STATE_NULL);
- pj_assert( tsx->last_tx == NULL );
- pj_assert( tsx->has_unsent_msg == 0);
-
- if (tsx->role == PJSIP_ROLE_UAS) {
-
- /* Set state to Trying. */
- pj_assert(event->type == PJSIP_EVENT_RX_MSG);
- tsx_set_state( tsx, PJSIP_TSX_STATE_TRYING,
- PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
-
- } else {
- pjsip_tx_data *tdata = event->body.tx_msg.tdata;
-
- /* Save the message for retransmission. */
- 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.
- */
- pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
- &timeout_timer_val);
-
- /* Start Timer A (or timer E) for retransmission only if unreliable
- * transport is being used.
- */
- if (tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL &&
- PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0)
- {
- pjsip_endpt_schedule_timer(tsx->endpt, &tsx->retransmit_timer,
- &t1_timer_val);
- tsx->retransmit_count = 0;
- }
-
- /* 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 pjsip_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 = pjsip_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 (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) {
- pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
- }
-
- /* Set status code */
- tsx->status_code = PJSIP_SC_TSX_TIMEOUT;
-
- /* 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) {
- int code;
-
- /* Cancel retransmission timer A. */
- if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0)
- pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
-
- /* Cancel timer B (transaction timeout) */
- pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
-
- /* 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. */
- code = event->body.rx_msg.rdata->msg_info.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. */
- pjsip_tsx_on_state_proceeding_uac( tsx, event);
-
- } else {
- pj_assert(0);
- 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 pjsip_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!).
- */
- //pj_assert(event->type == PJSIP_EVENT_TX_MSG);
- 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 = pjsip_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 pjsip_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;
-
- /* Send last response. */
- status = pjsip_tsx_retransmit( tsx, 0 );
- 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(msg->type == PJSIP_RESPONSE_MSG);
-
- /* Status code must be higher than last sent. */
- pj_assert(msg->line.status.code >= tsx->status_code);
-
- /* Update last status */
- tsx->status_code = msg->line.status.code;
-
- /* 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 );
-
- } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
-
- if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->handle_ack==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;
- 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);
- }
-
- /* Start timer J at 64*T1 for unreliable transport or zero for
- * reliable transport.
- */
- if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) {
- timeout = timeout_timer_val;
- } else {
- timeout.sec = timeout.msec = 0;
- }
-
- pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
- &timeout);
-
- /* Set state to "Completed" */
- tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
- PJSIP_EVENT_TX_MSG, tdata );
- }
-
- } else if (tsx->status_code >= 300) {
-
- /* 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 );
- }
-
- /* Start timer H for transaction termination */
- pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer,
- &timeout_timer_val);
-
- /* For INVITE, if unreliable transport is used, retransmission
- * timer G will be scheduled (retransmission).
- */
- if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) {
- pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ,
- NULL);
- if (cseq->method.id == PJSIP_INVITE_METHOD) {
- tsx->retransmit_count = 0;
- 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 have last response to retransmit. */
- pj_assert(tsx->last_tx != NULL);
-
- /* Retransmit the last response. */
- status = pjsip_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(0);
-
- tsx->status_code = PJSIP_SC_TSX_TIMEOUT;
-
- tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
- PJSIP_EVENT_TIMER, &tsx->timeout_timer);
-
- return PJ_EBUG;
-
- } else {
- pj_assert(0);
- 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 pjsip_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) {
- /* 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;
- }
-
- tsx->status_code = event->body.rx_msg.rdata->msg_info.msg->line.status.code;
- } else {
- tsx->status_code = PJSIP_SC_TSX_TIMEOUT;
- }
-
- 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. */
- pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
-
- /* 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->handle_ack == 0) {
- 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 (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport) == 0) {
- if (tsx->method.id == PJSIP_INVITE_METHOD) {
- timeout = td_timer_val;
- } else {
- timeout = t4_timer_val;
- }
- } else {
- timeout.sec = timeout.msec = 0;
- }
- pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
- &timeout);
-
- /* 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 (tsx->status_code >= 300 && tsx->status_code <= 699) {
- 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_endpt_create_ack( tsx->endpt, tsx->last_tx,
- event->body.rx_msg.rdata );
- 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 (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport) == 0) {
- if (tsx->method.id == PJSIP_INVITE_METHOD) {
- timeout = td_timer_val;
- } else {
- timeout = t4_timer_val;
- }
- } else {
- timeout.sec = timeout.msec = 0;
- }
- pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout);
-
- /* Inform TU. */
- tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
- PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
-
- } else {
- // Shouldn't happen because there's no timer for this state.
- pj_assert(0);
- return PJ_EBUG;
- }
-
- return PJ_SUCCESS;
-}
-
-/*
- * Handler for events in Completed state for UAS
- */
-static pj_status_t pjsip_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;
- pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ, NULL );
-
- /* On receive request retransmission, retransmit last response. */
- if (cseq->method.id != PJSIP_ACK_METHOD) {
- pj_status_t status;
-
- status = pjsip_tsx_retransmit( tsx, 0 );
- if (status != PJ_SUCCESS) {
- return status;
- }
-
- } else {
- /* Process incoming ACK request. */
-
- /* Cease retransmission. */
- pjsip_endpt_cancel_timer( tsx->endpt, &tsx->retransmit_timer );
-
- /* Start timer I in T4 interval (transaction termination). */
- pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
- pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
- &t4_timer_val);
-
- /* 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 = pjsip_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->status_code = PJSIP_SC_TSX_TIMEOUT;
-
- 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(event->body.tx_msg.tdata == tsx->last_tx);
- }
-
- return PJ_SUCCESS;
-}
-
-/*
- * Handler for events in Completed state for UAC transaction.
- */
-static pj_status_t pjsip_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 = pjsip_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 if (tsx->method.id == PJSIP_INVITE_METHOD &&
- event->type == PJSIP_EVENT_TX_MSG &&
- event->body.tx_msg.tdata->msg->line.req.method.id==PJSIP_ACK_METHOD) {
-
- pj_status_t status;
-
- /* Set last transmitted message. */
- if (tsx->last_tx != event->body.tx_msg.tdata) {
- pjsip_tx_data_dec_ref( tsx->last_tx );
- tsx->last_tx = event->body.tx_msg.tdata;
- pjsip_tx_data_add_ref( tsx->last_tx );
- }
-
- /* No state changed, but notify app.
- * Must notify now, so app has chance to put SDP in outgoing ACK msg.
- */
- tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
- PJSIP_EVENT_TX_MSG, event->body.tx_msg.tdata );
-
- /* Send msg */
- status = tsx_send_msg(tsx, event->body.tx_msg.tdata);
- if (status != PJ_SUCCESS)
- return status;
-
- } else {
- pj_assert(0);
- return PJ_EBUG;
- }
-
- return PJ_SUCCESS;
-}
-
-/*
- * Handler for events in state Confirmed.
- */
-static pj_status_t pjsip_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_method_e method_id =
- event->body.rx_msg.rdata->msg_info.msg->line.req.method.id;
-
- /* Must be a request message. */
- pj_assert(event->body.rx_msg.rdata->msg_info.msg->type == PJSIP_REQUEST_MSG);
-
- /* Must be an ACK request or a late INVITE retransmission. */
- pj_assert(method_id == PJSIP_ACK_METHOD ||
- method_id == PJSIP_INVITE_METHOD);
-
- /* Just so that compiler won't complain about unused vars when
- * building release code.
- */
- PJ_UNUSED_ARG(method_id);
-
- } 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(0);
- return PJ_EBUG;
- }
-
- return PJ_SUCCESS;
-}
-
-/*
- * Handler for events in state Terminated.
- */
-static pj_status_t pjsip_tsx_on_state_terminated( pjsip_transaction *tsx,
- pjsip_event *event)
-{
- pj_assert(tsx->state == PJSIP_TSX_STATE_TERMINATED);
-
- PJ_UNUSED_ARG(event);
-
- /* Destroy this transaction */
- tsx_set_state(tsx, PJSIP_TSX_STATE_DESTROYED,
- event->type, event->body.user.user1 );
-
- return PJ_SUCCESS;
-}
-
-
-static pj_status_t pjsip_tsx_on_state_destroyed(pjsip_transaction *tsx,
- pjsip_event *event)
-{
- PJ_UNUSED_ARG(tsx);
- PJ_UNUSED_ARG(event);
- return PJ_SUCCESS;
-}
-
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_config.h>
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/os.h>
+#include <pj/guid.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+
+/* Thread Local Storage ID for transaction lock (initialized by endpoint) */
+long pjsip_tsx_lock_tls_id;
+
+/* State names */
+static const char *state_str[] =
+{
+ "Null",
+ "Calling",
+ "Trying",
+ "Proceeding",
+ "Completed",
+ "Confirmed",
+ "Terminated",
+ "Destroyed",
+};
+
+/* Role names */
+static const char *role_name[] =
+{
+ "Client",
+ "Server"
+};
+
+/* 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 const pj_time_val t1_timer_val = { PJSIP_T1_TIMEOUT/1000,
+ PJSIP_T1_TIMEOUT%1000 };
+static const pj_time_val t4_timer_val = { PJSIP_T4_TIMEOUT/1000,
+ PJSIP_T4_TIMEOUT%1000 };
+static const pj_time_val td_timer_val = { PJSIP_TD_TIMEOUT/1000,
+ PJSIP_TD_TIMEOUT%1000 };
+static const pj_time_val timeout_timer_val = { (64*PJSIP_T1_TIMEOUT)/1000,
+ (64*PJSIP_T1_TIMEOUT)%1000 };
+
+/* Internal timer IDs */
+enum Transaction_Timer_Id
+{
+ TSX_TIMER_RETRANSMISSION,
+ TSX_TIMER_TIMEOUT,
+};
+
+/* Function Prototypes */
+static pj_status_t pjsip_tsx_on_state_null( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t pjsip_tsx_on_state_calling( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t pjsip_tsx_on_state_trying( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t pjsip_tsx_on_state_proceeding_uas( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t pjsip_tsx_on_state_proceeding_uac( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t pjsip_tsx_on_state_completed_uas( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t pjsip_tsx_on_state_completed_uac( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t pjsip_tsx_on_state_confirmed(pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t pjsip_tsx_on_state_terminated(pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t pjsip_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 int tsx_send_msg( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata);
+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 );
+
+/* State handlers for UAC, indexed by state */
+static int (*tsx_state_handler_uac[PJSIP_TSX_STATE_MAX])(pjsip_transaction *,
+ pjsip_event *) =
+{
+ &pjsip_tsx_on_state_null,
+ &pjsip_tsx_on_state_calling,
+ &pjsip_tsx_on_state_trying,
+ &pjsip_tsx_on_state_proceeding_uac,
+ &pjsip_tsx_on_state_completed_uac,
+ &pjsip_tsx_on_state_confirmed,
+ &pjsip_tsx_on_state_terminated,
+ &pjsip_tsx_on_state_destroyed,
+};
+
+/* State handlers for UAS */
+static int (*tsx_state_handler_uas[PJSIP_TSX_STATE_MAX])(pjsip_transaction *,
+ pjsip_event *) =
+{
+ &pjsip_tsx_on_state_null,
+ &pjsip_tsx_on_state_calling,
+ &pjsip_tsx_on_state_trying,
+ &pjsip_tsx_on_state_proceeding_uas,
+ &pjsip_tsx_on_state_completed_uas,
+ &pjsip_tsx_on_state_confirmed,
+ &pjsip_tsx_on_state_terminated,
+ &pjsip_tsx_on_state_destroyed,
+};
+
+/*
+ * 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.call_id.slen + /* Call-ID */
+ host->slen + /* Via host. */
+ 9 + /* Via port. */
+ 16; /* Separator+Allowance. */
+ key = p = pj_pool_alloc(pool, len_required);
+ end = p + len_required;
+
+ /* Add role. */
+ *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's');
+ *p++ = SEPARATOR;
+
+ /* Add Request-URI */
+ /* This is BUG!
+ * Response doesn't have Request-URI!
+ *
+ len = req_uri->vptr->print( PJSIP_URI_IN_REQ_URI, req_uri, p, end-p );
+ p += len;
+ *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.call_id.slen;
+ pj_memcpy( p, rdata->msg_info.call_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 = 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 );
+ }
+}
+
+
+/*
+ * Create new transaction.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_create( pj_pool_t *pool,
+ pjsip_endpoint *endpt,
+ pjsip_transaction **p_tsx)
+{
+ pjsip_transaction *tsx;
+ pj_status_t status;
+
+ tsx = pj_pool_calloc(pool, 1, sizeof(pjsip_transaction));
+
+ tsx->pool = pool;
+ tsx->endpt = endpt;
+ tsx->retransmit_timer.id = TSX_TIMER_RETRANSMISSION;
+ tsx->retransmit_timer._timer_id = -1;
+ tsx->retransmit_timer.user_data = tsx;
+ tsx->retransmit_timer.cb = &tsx_timer_callback;
+ tsx->timeout_timer.id = TSX_TIMER_TIMEOUT;
+ tsx->timeout_timer._timer_id = -1;
+ tsx->timeout_timer.user_data = tsx;
+ tsx->timeout_timer.cb = &tsx_timer_callback;
+ pj_sprintf(tsx->obj_name, "tsx%p", tsx);
+ status = pj_mutex_create_recursive(pool, "mtsx%p", &tsx->mutex);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ *p_tsx = tsx;
+ return PJ_SUCCESS;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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_event e;
+
+ PJ_LOG(4, (tsx->obj_name, "STATE %s-->%s, cause = %s",
+ state_str[tsx->state], state_str[state],
+ pjsip_event_str(event_src_type)));
+
+ /* 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];
+ }
+
+ /* Inform TU */
+ PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src);
+ pjsip_endpt_send_tsx_event( tsx->endpt, &e );
+
+ /* When the transaction is terminated, release transport, and free the
+ * saved last transmitted message.
+ */
+ if (state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Decrement transport reference counter. */
+ if (tsx->transport &&
+ tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL)
+ {
+ pjsip_transport_dec_ref( tsx->transport );
+ tsx->transport = NULL;
+ }
+ /* 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._timer_id != -1) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer._timer_id = -1;
+ }
+ /* Cancel retransmission timer. */
+ if (tsx->retransmit_timer._timer_id != -1) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer._timer_id = -1;
+ }
+
+ /* If transport is not pending, reschedule timeout timer to
+ * destroy this transaction.
+ */
+ if (tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL) {
+ pj_time_val timeout = {0, 0};
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+ }
+
+ } else if (state == PJSIP_TSX_STATE_DESTROYED) {
+
+ /* Clear TLS, so that mutex will not be unlocked */
+ struct tsx_lock_data *lck = pj_thread_local_get(pjsip_tsx_lock_tls_id);
+ while (lck) {
+ if (lck->tsx == tsx) {
+ lck->is_alive = 0;
+ }
+ lck = lck->prev;
+ }
+ }
+}
+
+/*
+ * Look-up destination address and select which transport to be used to send
+ * the request message. The procedure used here follows the guidelines on
+ * sending the request in RFC3261 chapter 8.1.2.
+ *
+ * This function also modifies the message (request line and Route headers)
+ * accordingly.
+ */
+static pj_status_t tsx_process_route( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata,
+ pjsip_host_port *send_addr )
+{
+ const pjsip_uri *new_request_uri, *target_uri;
+ const pjsip_name_addr *topmost_route_uri;
+ pjsip_route_hdr *first_route_hdr, *last_route_hdr;
+
+ pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG);
+
+ /* Get the first "Route" header from the message. If the message doesn't
+ * have any "Route" headers but the endpoint has, then copy the "Route"
+ * headers from the endpoint first.
+ */
+ last_route_hdr = first_route_hdr =
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+ if (first_route_hdr) {
+ topmost_route_uri = &first_route_hdr->name_addr;
+ while (last_route_hdr->next != (void*)&tdata->msg->hdr) {
+ pjsip_route_hdr *hdr;
+ hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE,
+ last_route_hdr->next);
+ if (!hdr)
+ break;
+ last_route_hdr = hdr;
+ }
+ } else {
+ const pjsip_route_hdr *hdr_list;
+ hdr_list = (pjsip_route_hdr*)pjsip_endpt_get_routing(tsx->endpt);
+ if (hdr_list->next != hdr_list) {
+ const pjsip_route_hdr *hdr = (pjsip_route_hdr*)hdr_list->next;
+ first_route_hdr = NULL;
+ topmost_route_uri = &hdr->name_addr;
+ do {
+ last_route_hdr = pjsip_hdr_shallow_clone(tdata->pool, hdr);
+ if (first_route_hdr == NULL)
+ first_route_hdr = last_route_hdr;
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)last_route_hdr);
+ hdr = hdr->next;
+ } while (hdr != hdr_list);
+ } else {
+ topmost_route_uri = NULL;
+ }
+ }
+
+ /* If Route headers exist, and the first element indicates loose-route,
+ * the URI is taken from the Request-URI, and we keep all existing Route
+ * headers intact.
+ * If Route headers exist, and the first element DOESN'T indicate loose
+ * route, the URI is taken from the first Route header, and remove the
+ * first Route header from the message.
+ * Otherwise if there's no Route headers, the URI is taken from the
+ * Request-URI.
+ */
+ if (topmost_route_uri) {
+ pj_bool_t has_lr_param;
+
+ if (PJSIP_URI_SCHEME_IS_SIP(topmost_route_uri) ||
+ PJSIP_URI_SCHEME_IS_SIPS(topmost_route_uri))
+ {
+ const pjsip_url *url = pjsip_uri_get_uri((void*)topmost_route_uri);
+ has_lr_param = url->lr_param;
+ } else {
+ has_lr_param = 0;
+ }
+
+ if (has_lr_param) {
+ new_request_uri = tdata->msg->line.req.uri;
+ /* We shouldn't need to delete topmost Route if it has lr param.
+ * But seems like it breaks some proxy implementation, so we
+ * delete it anyway.
+ */
+ /*
+ pj_list_erase(first_route_hdr);
+ if (first_route_hdr == last_route_hdr)
+ last_route_hdr = NULL;
+ */
+ } else {
+ new_request_uri = pjsip_uri_get_uri((void*)topmost_route_uri);
+ pj_list_erase(first_route_hdr);
+ if (first_route_hdr == last_route_hdr)
+ last_route_hdr = NULL;
+ }
+
+ target_uri = (pjsip_uri*)topmost_route_uri;
+
+ } else {
+ target_uri = new_request_uri = tdata->msg->line.req.uri;
+ }
+
+ /* The target URI must be a SIP/SIPS URL so we can resolve it's address.
+ * Otherwise we're in trouble (i.e. there's no host part in tel: URL).
+ */
+ pj_memset(send_addr, 0, sizeof(*send_addr));
+
+ if (PJSIP_URI_SCHEME_IS_SIPS(target_uri)) {
+ pjsip_uri *uri = (pjsip_uri*) target_uri;
+ const pjsip_url *url = (const pjsip_url*)pjsip_uri_get_uri(uri);
+ send_addr->flag |= (PJSIP_TRANSPORT_SECURE | PJSIP_TRANSPORT_RELIABLE);
+ pj_strdup(tdata->pool, &send_addr->host, &url->host);
+ send_addr->port = url->port;
+ send_addr->type =
+ pjsip_transport_get_type_from_name(&url->transport_param);
+
+ } else if (PJSIP_URI_SCHEME_IS_SIP(target_uri)) {
+ pjsip_uri *uri = (pjsip_uri*) target_uri;
+ const pjsip_url *url = (const pjsip_url*)pjsip_uri_get_uri(uri);
+ pj_strdup(tdata->pool, &send_addr->host, &url->host);
+ send_addr->port = url->port;
+ send_addr->type =
+ pjsip_transport_get_type_from_name(&url->transport_param);
+#if PJ_HAS_TCP
+ if (send_addr->type == PJSIP_TRANSPORT_TCP ||
+ send_addr->type == PJSIP_TRANSPORT_SCTP)
+ {
+ send_addr->flag |= PJSIP_TRANSPORT_RELIABLE;
+ }
+#endif
+ } else {
+ pj_assert(!"Unsupported URI scheme!");
+ return PJSIP_EINVALIDSCHEME;
+ }
+
+ /* If target URI is different than request URI, replace
+ * request URI add put the original URI in the last Route header.
+ */
+ if (new_request_uri && new_request_uri!=tdata->msg->line.req.uri) {
+ pjsip_route_hdr *route = pjsip_route_hdr_create(tdata->pool);
+ route->name_addr.uri = tdata->msg->line.req.uri;
+ if (last_route_hdr)
+ pj_list_insert_after(last_route_hdr, route);
+ else
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)route);
+ tdata->msg->line.req.uri = (pjsip_uri*)new_request_uri;
+ }
+
+ /* Success. */
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Callback from the transport job.
+ * This callback is called when asychronous transport connect() operation
+ * has completed, with or without error.
+ */
+static void tsx_transport_callback(pjsip_transport *tr,
+ void *token,
+ pj_status_t status)
+{
+ char addr[PJ_MAX_HOSTNAME];
+ pjsip_transaction *tsx = token;
+ struct tsx_lock_data lck;
+
+ pj_memcpy(addr, tsx->dest_name.host.ptr, tsx->dest_name.host.slen);
+ addr[tsx->dest_name.host.slen] = '\0';
+
+
+ if (status == PJ_SUCCESS) {
+ PJ_LOG(4, (tsx->obj_name, "%s connected to %s:%d",
+ tr->type_name,
+ addr, tsx->dest_name.port));
+ } else {
+ PJ_LOG(4, (tsx->obj_name, "%s unable to connect to %s:%d, status=%d",
+ tr->type_name,
+ addr, tsx->dest_name.port, status));
+ }
+
+ /* Lock transaction. */
+ lock_tsx(tsx, &lck);
+
+ if (status != PJ_SUCCESS) {
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
+ tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
+
+ tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
+
+ /* Unlock transaction. */
+ unlock_tsx(tsx, &lck);
+ return;
+ }
+
+ /* See if transaction has already been terminated.
+ * If so, schedule to destroy the transaction.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+ pj_time_val timeout = {0, 0};
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+
+ /* Unlock transaction. */
+ unlock_tsx(tsx, &lck);
+ return;
+ }
+
+ /* Add reference counter to the transport. */
+ pjsip_transport_add_ref(tr);
+
+ /* Mark transport as ready. */
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
+ tsx->transport = tr;
+
+ /* If there's a pending message to send, send it now. */
+ if (tsx->has_unsent_msg) {
+ tsx_send_msg( tsx, tsx->last_tx );
+ }
+
+ /* Unlock transaction. */
+ unlock_tsx(tsx, &lck);
+}
+
+/*
+ * Callback from the resolver job.
+ */
+static void tsx_resolver_callback(pj_status_t status,
+ void *token,
+ const struct pjsip_server_addresses *addr)
+{
+ pjsip_transaction *tsx = token;
+ struct tsx_lock_data lck;
+ pjsip_transport *tp;
+
+ PJ_LOG(4, (tsx->obj_name, "resolver job complete, status=%d", status));
+
+ if (status != PJ_SUCCESS || addr->count == 0) {
+ lock_tsx(tsx, &lck);
+ tsx->status_code = PJSIP_SC_TSX_RESOLVE_ERROR;
+ tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
+ unlock_tsx(tsx, &lck);
+ return;
+ }
+
+ /* Lock transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Copy server addresses. */
+ pj_memcpy(&tsx->remote_addr, addr, sizeof(*addr));
+
+ /* Create/find the transport for the remote address. */
+ PJ_LOG(5,(tsx->obj_name, "tsx getting transport for %s:%d",
+ pj_inet_ntoa(addr->entry[0].addr.sin_addr),
+ pj_ntohs(addr->entry[0].addr.sin_port)));
+
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_CONNECTING;
+ status = pjsip_endpt_alloc_transport( tsx->endpt, addr->entry[0].type,
+ &addr->entry[0].addr,
+ &tp);
+ tsx_transport_callback(tp, tsx, status);
+
+ /* Unlock transaction */
+ unlock_tsx(tsx, &lck);
+
+ /* There should be nothing to do after this point.
+ * Execution for the transaction will resume when the callback for the
+ * transport is called.
+ */
+}
+
+/*
+ * Initialize the transaction as UAC transaction.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_init_uac( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata)
+{
+ pjsip_msg *msg;
+ pjsip_cseq_hdr *cseq;
+ pjsip_via_hdr *via;
+ pj_status_t status;
+ struct tsx_lock_data lck;
+
+ PJ_LOG(4,(tsx->obj_name, "initializing tsx as UAC (tdata=%p)", tdata));
+
+ /* Lock transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Keep shortcut */
+ msg = tdata->msg;
+
+ /* Role is UAC. */
+ tsx->role = PJSIP_ROLE_UAC;
+
+ /* Save method. */
+ pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method);
+
+ /* Generate Via header if it doesn't exist. */
+ via = 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);
+ }
+
+ if (via->branch_param.slen == 0) {
+ pj_str_t tmp;
+ via->branch_param.ptr = 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;
+ 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. */
+ status = create_tsx_key_3261( tsx->pool, &tsx->transaction_key,
+ PJSIP_ROLE_UAC, &tsx->method,
+ &via->branch_param);
+ if (status != PJ_SUCCESS) {
+ unlock_tsx(tsx, &lck);
+ return status;
+ }
+
+ PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen,
+ tsx->transaction_key.ptr));
+
+ /* Save CSeq. */
+ cseq = pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL);
+ if (!cseq) {
+ pj_assert(!"CSeq header not present in outgoing message!");
+ unlock_tsx(tsx, &lck);
+ return PJSIP_EMISSINGHDR;
+ }
+ tsx->cseq = cseq->cseq;
+
+
+ /* 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 = &pjsip_tsx_on_state_null;
+
+ /* Get destination name from the message. */
+ status = tsx_process_route(tsx, tdata, &tsx->dest_name);
+ if (status != PJ_SUCCESS) {
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
+ tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
+ tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
+ unlock_tsx(tsx, &lck);
+ return status;
+ }
+
+ /* Resolve destination.
+ * This will start asynchronous resolver job, and when it finishes,
+ * the callback will be called.
+ */
+ PJ_LOG(5,(tsx->obj_name, "tsx resolving destination %.*s:%d",
+ tsx->dest_name.host.slen,
+ tsx->dest_name.host.ptr,
+ tsx->dest_name.port));
+
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING;
+ pjsip_endpt_resolve( tsx->endpt, tsx->pool, &tsx->dest_name,
+ tsx, &tsx_resolver_callback);
+
+ /* There should be nothing to do after this point.
+ * Execution for the transaction will resume when the resolver callback is
+ * called.
+ */
+
+ /* Unlock transaction and return.
+ * If transaction has been destroyed WITHIN the current thread, the
+ * unlock_tsx() function will return -1.
+ */
+ return unlock_tsx(tsx, &lck);
+}
+
+
+/*
+ * Initialize the transaction as UAS transaction.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_init_uas( pjsip_transaction *tsx,
+ pjsip_rx_data *rdata)
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+ pj_str_t *branch;
+ pjsip_cseq_hdr *cseq;
+ pj_status_t status;
+ struct tsx_lock_data lck;
+
+ PJ_LOG(4,(tsx->obj_name, "initializing tsx as UAS (rdata=%p)", rdata));
+
+ /* Lock transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Keep shortcut to message */
+ msg = rdata->msg_info.msg;
+
+ /* Role is UAS */
+ tsx->role = PJSIP_ROLE_UAS;
+
+ /* Save method. */
+ pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method);
+
+ /* 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);
+ return status;
+ }
+
+ /* 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));
+
+ /* Save CSeq */
+ cseq = rdata->msg_info.cseq;
+ tsx->cseq = cseq->cseq;
+
+ /* 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 = &pjsip_tsx_on_state_null;
+
+ /* Get the transport to send the response.
+ * According to section 18.2.2 of RFC3261, if the transport is reliable
+ * then the response must be sent using that transport.
+ */
+ /* In addition, RFC 3581 says, if Via has "rport" parameter specified,
+ * then return the response using the same transport.
+ */
+ if (PJSIP_TRANSPORT_IS_RELIABLE(rdata->tp_info.transport) ||
+ rdata->msg_info.via->rport_param >= 0)
+ {
+ tsx->transport = rdata->tp_info.transport;
+ pjsip_transport_add_ref(tsx->transport);
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
+
+ tsx->current_addr = 0;
+ tsx->remote_addr.count = 1;
+ tsx->remote_addr.entry[0].type = tsx->transport->type;
+ pj_memcpy(&tsx->remote_addr.entry[0].addr,
+ &rdata->pkt_info.addr, rdata->pkt_info.addr_len);
+
+ } else {
+ pj_status_t status;
+
+ status = pjsip_get_response_addr(tsx->pool, rdata->tp_info.transport,
+ rdata->msg_info.via, &tsx->dest_name);
+ if (status != PJ_SUCCESS) {
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_FINAL;
+ tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
+ tsx_set_state(tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
+ unlock_tsx(tsx, &lck);
+ return status;
+ }
+
+ /* Resolve destination.
+ * This will start asynchronous resolver job, and when it finishes,
+ * the callback will be called.
+ */
+ PJ_LOG(5,(tsx->obj_name, "tsx resolving destination %.*s:%d",
+ tsx->dest_name.host.slen,
+ tsx->dest_name.host.ptr,
+ tsx->dest_name.port));
+
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING;
+ pjsip_endpt_resolve( tsx->endpt, tsx->pool, &tsx->dest_name,
+ tsx, &tsx_resolver_callback);
+ }
+
+ /* There should be nothing to do after this point.
+ * Execution for the transaction will resume when the resolver callback is
+ * called.
+ */
+
+ /* Unlock transaction and return.
+ * If transaction has been destroyed WITHIN the current thread, the
+ * unlock_tsx() function will return -1.
+ */
+ return unlock_tsx(tsx, &lck);
+}
+
+/*
+ * Callback when timer expires.
+ */
+static void tsx_timer_callback( pj_timer_heap_t *theap, pj_timer_entry *entry)
+{
+ pjsip_event event;
+ pjsip_transaction *tsx = entry->user_data;
+ struct tsx_lock_data lck;
+
+ PJ_UNUSED_ARG(theap);
+
+ PJ_LOG(5,(tsx->obj_name, "got timer event (%s timer)",
+ (entry->id==TSX_TIMER_RETRANSMISSION ? "Retransmit" : "Timeout")));
+
+
+ if (entry->id == TSX_TIMER_RETRANSMISSION) {
+ PJSIP_EVENT_INIT_TIMER(event, &tsx->retransmit_timer);
+ } else {
+ PJSIP_EVENT_INIT_TIMER(event, &tsx->timeout_timer);
+ }
+
+ /* Dispatch event to transaction. */
+ lock_tsx(tsx, &lck);
+ (*tsx->state_handler)(tsx, &event);
+ unlock_tsx(tsx, &lck);
+}
+
+/*
+ * Transmit ACK message for 2xx/INVITE with this transaction. The ACK for
+ * non-2xx/INVITE is automatically sent by the transaction.
+ * This operation is only valid if the transaction is configured to handle ACK
+ * (tsx->handle_ack is non-zero). If this attribute is not set, then the
+ * transaction will comply with RFC-3261, i.e. it will set itself to
+ * TERMINATED state when it receives 2xx/INVITE.
+ */
+PJ_DEF(void) pjsip_tsx_on_tx_ack( pjsip_transaction *tsx, pjsip_tx_data *tdata)
+{
+ pjsip_msg *msg;
+ pjsip_host_port dest_addr;
+ pjsip_via_hdr *via;
+ struct tsx_lock_data lck;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Lock tsx. */
+ lock_tsx(tsx, &lck);
+
+ pj_assert(tsx->handle_ack != 0);
+
+ msg = tdata->msg;
+
+ /* Generate branch parameter if it doesn't exist. */
+ via = pjsip_msg_find_hdr(msg, PJSIP_H_VIA, NULL);
+ if (via == NULL) {
+ via = pjsip_via_hdr_create(tdata->pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*) via);
+ }
+
+ if (via->branch_param.slen == 0) {
+ via->branch_param = tsx->branch;
+ } else {
+ pj_assert( pj_strcmp(&via->branch_param, &tsx->branch) == 0 );
+ }
+
+ /* Get destination name from the message. */
+ status = tsx_process_route(tsx, tdata, &dest_addr);
+ if (status != 0){
+ goto on_error;
+ }
+
+ /* Compare message's destination name with transaction's destination name.
+ * If NOT equal, then we'll have to resolve the destination.
+ */
+ if (dest_addr.type == tsx->dest_name.type &&
+ dest_addr.flag == tsx->dest_name.flag &&
+ dest_addr.port == tsx->dest_name.port &&
+ pj_stricmp(&dest_addr.host, &tsx->dest_name.host) == 0)
+ {
+ /* Equal destination. We can use current transport. */
+ pjsip_tsx_on_tx_msg(tsx, tdata);
+ unlock_tsx(tsx, &lck);
+ return;
+
+ }
+
+ /* New destination; we'll have to resolve host and create new transport. */
+ pj_memcpy(&tsx->dest_name, &dest_addr, sizeof(dest_addr));
+ pj_strdup(tsx->pool, &tsx->dest_name.host, &dest_addr.host);
+
+ PJ_LOG(5,(tsx->obj_name, "tsx resolving destination %.*s:%d",
+ tsx->dest_name.host.slen,
+ tsx->dest_name.host.ptr,
+ tsx->dest_name.port));
+
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING;
+ pjsip_transport_dec_ref(tsx->transport);
+ tsx->transport = NULL;
+
+ /* Put the message in queue. */
+ pjsip_tsx_on_tx_msg(tsx, tdata);
+
+ /* This is a bug!
+ * We shouldn't change transaction's state before actually sending the
+ * message. Otherwise transaction will terminate before message is sent,
+ * and timeout timer will be scheduled.
+ */
+ PJ_TODO(TSX_DONT_CHANGE_STATE_BEFORE_SENDING_ACK)
+
+ /*
+ * This will start asynchronous resolver job, and when it finishes,
+ * the callback will be called.
+ */
+
+ tsx->transport_state = PJSIP_TSX_TRANSPORT_STATE_RESOLVING;
+ pjsip_endpt_resolve( tsx->endpt, tsx->pool, &tsx->dest_name,
+ tsx, &tsx_resolver_callback);
+
+ unlock_tsx(tsx, &lck);
+
+ /* There should be nothing to do after this point.
+ * Execution for the transaction will resume when the resolver callback is
+ * called.
+ */
+ return;
+
+on_error:
+ /* Failure condition.
+ * Send TERMINATED event.
+ */
+ tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
+
+ unlock_tsx(tsx, &lck);
+}
+
+
+/*
+ * This function is called by TU to send a message.
+ */
+PJ_DEF(void) pjsip_tsx_on_tx_msg( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata )
+{
+ pjsip_event event;
+ struct tsx_lock_data lck;
+ pj_status_t status;
+
+ PJ_LOG(5,(tsx->obj_name, "Request to transmit msg on state %s (tdata=%p)",
+ state_str[tsx->state], tdata));
+
+ PJSIP_EVENT_INIT_TX_MSG(event, tsx, tdata);
+
+ /* Dispatch to transaction. */
+ lock_tsx(tsx, &lck);
+ status = (*tsx->state_handler)(tsx, &event);
+ unlock_tsx(tsx, &lck);
+}
+
+/*
+ * This function is called by endpoint when incoming message for the
+ * transaction is received.
+ */
+PJ_DEF(void) pjsip_tsx_on_rx_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 msg on state %s (rdata=%p)",
+ state_str[tsx->state], rdata));
+
+ PJSIP_EVENT_INIT_RX_MSG(event, tsx, rdata);
+
+ /* Dispatch to transaction. */
+ lock_tsx(tsx, &lck);
+ status = (*tsx->state_handler)(tsx, &event);
+ unlock_tsx(tsx, &lck);
+}
+
+/*
+ * Forcely terminate transaction.
+ */
+PJ_DEF(void) pjsip_tsx_terminate( pjsip_transaction *tsx, int code )
+{
+ struct tsx_lock_data lck;
+
+ lock_tsx(tsx, &lck);
+ tsx->status_code = code;
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_USER, NULL);
+
+ unlock_tsx(tsx, &lck);
+}
+
+/*
+ * Transport send completion callback.
+ */
+static void tsx_on_send_complete(void *token, pjsip_tx_data *tdata,
+ pj_ssize_t bytes_sent)
+{
+ PJ_UNUSED_ARG(token);
+ PJ_UNUSED_ARG(tdata);
+
+ if (bytes_sent <= 0) {
+ PJ_TODO(HANDLE_TRANSPORT_ERROR);
+ }
+}
+
+/*
+ * Send message to the transport.
+ * If transport is not yet available, then do nothing. The message will be
+ * transmitted when transport connection completion callback is called.
+ */
+static pj_status_t tsx_send_msg( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_LOG(5,(tsx->obj_name, "sending msg (tdata=%p)", tdata));
+
+ if (tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL) {
+ pjsip_event before_tx_event;
+
+ pj_assert(tsx->transport != NULL);
+
+ /* Make sure Via transport info is filled up properly for
+ * requests.
+ */
+ if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+ const pj_sockaddr_in *addr_name;
+ pjsip_via_hdr *via = (pjsip_via_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+
+ /* For request message, set "rport" parameter by default. */
+ if (tdata->msg->type == PJSIP_REQUEST_MSG)
+ via->rport_param = 0;
+
+ /* Don't update Via sent-by on retransmission. */
+ if (via->sent_by.host.slen == 0) {
+ addr_name = &tsx->transport->public_addr;
+ pj_strdup2(tdata->pool, &via->transport,
+ tsx->transport->type_name);
+ pj_strdup2(tdata->pool, &via->sent_by.host,
+ pj_inet_ntoa(addr_name->sin_addr));
+ via->sent_by.port = pj_ntohs(addr_name->sin_port);
+ }
+ }
+
+ /* Notify everybody we're about to send message. */
+ PJSIP_EVENT_INIT_PRE_TX_MSG(before_tx_event, tsx, tdata,
+ tsx->retransmit_count);
+ pjsip_endpt_send_tsx_event( tsx->endpt, &before_tx_event );
+
+ tsx->has_unsent_msg = 0;
+ status = pjsip_transport_send(tsx->transport, tdata,
+ &tsx->remote_addr.entry[tsx->current_addr].addr,
+ tsx, &tsx_on_send_complete);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ PJ_TODO(HANDLE_TRANSPORT_ERROR);
+ goto on_error;
+ }
+ } else {
+ tsx->has_unsent_msg = 1;
+ }
+
+ return 0;
+
+on_error:
+ tsx->status_code = PJSIP_SC_TSX_TRANSPORT_ERROR;
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, (void*)status);
+ return status;
+}
+
+/*
+ * Retransmit last message sent.
+ */
+static pj_status_t pjsip_tsx_retransmit( pjsip_transaction *tsx,
+ int should_restart_timer)
+{
+ pj_status_t status;
+
+ PJ_LOG(4,(tsx->obj_name, "retransmiting (tdata=%p, count=%d, restart?=%d)",
+ tsx->last_tx, tsx->retransmit_count, should_restart_timer));
+
+ pj_assert(tsx->last_tx != NULL);
+
+ ++tsx->retransmit_count;
+
+ status = tsx_send_msg( tsx, tsx->last_tx);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Restart timer T1. */
+ if (should_restart_timer) {
+ pj_time_val timeout;
+ int msec_time = (1 << (tsx->retransmit_count)) * PJSIP_T1_TIMEOUT;
+
+ if (tsx->method.id!=PJSIP_INVITE_METHOD && msec_time>PJSIP_T2_TIMEOUT)
+ msec_time = PJSIP_T2_TIMEOUT;
+
+ timeout.sec = msec_time / 1000;
+ timeout.msec = msec_time % 1000;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer,
+ &timeout);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Handler for events in state Null.
+ */
+static pj_status_t pjsip_tsx_on_state_null( pjsip_transaction *tsx,
+ pjsip_event *event )
+{
+ pj_status_t status;
+
+ pj_assert( tsx->state == PJSIP_TSX_STATE_NULL);
+ pj_assert( tsx->last_tx == NULL );
+ pj_assert( tsx->has_unsent_msg == 0);
+
+ if (tsx->role == PJSIP_ROLE_UAS) {
+
+ /* Set state to Trying. */
+ pj_assert(event->type == PJSIP_EVENT_RX_MSG);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TRYING,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+
+ } else {
+ pjsip_tx_data *tdata = event->body.tx_msg.tdata;
+
+ /* Save the message for retransmission. */
+ 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.
+ */
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout_timer_val);
+
+ /* Start Timer A (or timer E) for retransmission only if unreliable
+ * transport is being used.
+ */
+ if (tsx->transport_state == PJSIP_TSX_TRANSPORT_STATE_FINAL &&
+ PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0)
+ {
+ pjsip_endpt_schedule_timer(tsx->endpt, &tsx->retransmit_timer,
+ &t1_timer_val);
+ tsx->retransmit_count = 0;
+ }
+
+ /* 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 pjsip_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 = pjsip_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 (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ }
+
+ /* Set status code */
+ tsx->status_code = PJSIP_SC_TSX_TIMEOUT;
+
+ /* 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) {
+ int code;
+
+ /* Cancel retransmission timer A. */
+ if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0)
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+
+ /* Cancel timer B (transaction timeout) */
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+
+ /* 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. */
+ code = event->body.rx_msg.rdata->msg_info.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. */
+ pjsip_tsx_on_state_proceeding_uac( tsx, event);
+
+ } else {
+ pj_assert(0);
+ 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 pjsip_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!).
+ */
+ //pj_assert(event->type == PJSIP_EVENT_TX_MSG);
+ 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 = pjsip_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 pjsip_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;
+
+ /* Send last response. */
+ status = pjsip_tsx_retransmit( tsx, 0 );
+ 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(msg->type == PJSIP_RESPONSE_MSG);
+
+ /* Status code must be higher than last sent. */
+ pj_assert(msg->line.status.code >= tsx->status_code);
+
+ /* Update last status */
+ tsx->status_code = msg->line.status.code;
+
+ /* 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 );
+
+ } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
+
+ if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->handle_ack==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;
+ 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);
+ }
+
+ /* Start timer J at 64*T1 for unreliable transport or zero for
+ * reliable transport.
+ */
+ if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) {
+ timeout = timeout_timer_val;
+ } else {
+ timeout.sec = timeout.msec = 0;
+ }
+
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+
+ /* Set state to "Completed" */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_TX_MSG, tdata );
+ }
+
+ } else if (tsx->status_code >= 300) {
+
+ /* 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 );
+ }
+
+ /* Start timer H for transaction termination */
+ pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer,
+ &timeout_timer_val);
+
+ /* For INVITE, if unreliable transport is used, retransmission
+ * timer G will be scheduled (retransmission).
+ */
+ if (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport)==0) {
+ pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ,
+ NULL);
+ if (cseq->method.id == PJSIP_INVITE_METHOD) {
+ tsx->retransmit_count = 0;
+ 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 have last response to retransmit. */
+ pj_assert(tsx->last_tx != NULL);
+
+ /* Retransmit the last response. */
+ status = pjsip_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(0);
+
+ tsx->status_code = PJSIP_SC_TSX_TIMEOUT;
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer);
+
+ return PJ_EBUG;
+
+ } else {
+ pj_assert(0);
+ 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 pjsip_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) {
+ /* 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;
+ }
+
+ tsx->status_code = event->body.rx_msg.rdata->msg_info.msg->line.status.code;
+ } else {
+ tsx->status_code = PJSIP_SC_TSX_TIMEOUT;
+ }
+
+ 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. */
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+
+ /* 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->handle_ack == 0) {
+ 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 (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport) == 0) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ timeout = td_timer_val;
+ } else {
+ timeout = t4_timer_val;
+ }
+ } else {
+ timeout.sec = timeout.msec = 0;
+ }
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+
+ /* 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 (tsx->status_code >= 300 && tsx->status_code <= 699) {
+ 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_endpt_create_ack( tsx->endpt, tsx->last_tx,
+ event->body.rx_msg.rdata );
+ 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 (PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport) == 0) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ timeout = td_timer_val;
+ } else {
+ timeout = t4_timer_val;
+ }
+ } else {
+ timeout.sec = timeout.msec = 0;
+ }
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout);
+
+ /* Inform TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+
+ } else {
+ // Shouldn't happen because there's no timer for this state.
+ pj_assert(0);
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Handler for events in Completed state for UAS
+ */
+static pj_status_t pjsip_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;
+ pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ, NULL );
+
+ /* On receive request retransmission, retransmit last response. */
+ if (cseq->method.id != PJSIP_ACK_METHOD) {
+ pj_status_t status;
+
+ status = pjsip_tsx_retransmit( tsx, 0 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ } else {
+ /* Process incoming ACK request. */
+
+ /* Cease retransmission. */
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->retransmit_timer );
+
+ /* Start timer I in T4 interval (transaction termination). */
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &t4_timer_val);
+
+ /* 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 = pjsip_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->status_code = PJSIP_SC_TSX_TIMEOUT;
+
+ 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(event->body.tx_msg.tdata == tsx->last_tx);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Handler for events in Completed state for UAC transaction.
+ */
+static pj_status_t pjsip_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 = pjsip_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 if (tsx->method.id == PJSIP_INVITE_METHOD &&
+ event->type == PJSIP_EVENT_TX_MSG &&
+ event->body.tx_msg.tdata->msg->line.req.method.id==PJSIP_ACK_METHOD) {
+
+ pj_status_t status;
+
+ /* Set last transmitted message. */
+ if (tsx->last_tx != event->body.tx_msg.tdata) {
+ pjsip_tx_data_dec_ref( tsx->last_tx );
+ tsx->last_tx = event->body.tx_msg.tdata;
+ pjsip_tx_data_add_ref( tsx->last_tx );
+ }
+
+ /* No state changed, but notify app.
+ * Must notify now, so app has chance to put SDP in outgoing ACK msg.
+ */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_TX_MSG, event->body.tx_msg.tdata );
+
+ /* Send msg */
+ status = tsx_send_msg(tsx, event->body.tx_msg.tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ } else {
+ pj_assert(0);
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Handler for events in state Confirmed.
+ */
+static pj_status_t pjsip_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_method_e method_id =
+ event->body.rx_msg.rdata->msg_info.msg->line.req.method.id;
+
+ /* Must be a request message. */
+ pj_assert(event->body.rx_msg.rdata->msg_info.msg->type == PJSIP_REQUEST_MSG);
+
+ /* Must be an ACK request or a late INVITE retransmission. */
+ pj_assert(method_id == PJSIP_ACK_METHOD ||
+ method_id == PJSIP_INVITE_METHOD);
+
+ /* Just so that compiler won't complain about unused vars when
+ * building release code.
+ */
+ PJ_UNUSED_ARG(method_id);
+
+ } 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(0);
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Handler for events in state Terminated.
+ */
+static pj_status_t pjsip_tsx_on_state_terminated( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_TERMINATED);
+
+ PJ_UNUSED_ARG(event);
+
+ /* Destroy this transaction */
+ tsx_set_state(tsx, PJSIP_TSX_STATE_DESTROYED,
+ event->type, event->body.user.user1 );
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t pjsip_tsx_on_state_destroyed(pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ PJ_UNUSED_ARG(tsx);
+ PJ_UNUSED_ARG(event);
+ return PJ_SUCCESS;
+}
+