/* $Id$ */
/*
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "test.h"
#include <pjsip.h>
#include <pjlib.h>

#define THIS_FILE   "tsx_uas_test.c"


/*****************************************************************************
 **
 ** UAS tests.
 **
 ** This file performs various tests for UAC transactions. Each test will have
 ** a different Via branch param so that message receiver module and
 ** transaction user module can identify which test is being carried out.
 **
 ** TEST1_BRANCH_ID
 **	Test that non-INVITE transaction returns 2xx response to the correct
 **	transport and correctly terminates the transaction.
 **	This also checks that transaction is destroyed immediately after
 **	it sends final response when reliable transport is used.
 **
 ** TEST2_BRANCH_ID
 **	As above, for non-2xx final response.
 **
 ** TEST3_BRANCH_ID
 **	Transaction correctly progressing to PROCEEDING state when provisional
 **	response is sent.
 **
 ** TEST4_BRANCH_ID
 **	Transaction retransmits last response (if any) without notifying
 **	transaction user upon receiving request  retransmissions on TRYING
 **	state
 **
 ** TEST5_BRANCH_ID
 **	As above, in PROCEEDING state.
 **
 ** TEST6_BRANCH_ID
 **	As above, in COMPLETED state, with first sending provisional response.
 **	(Only applicable for non-reliable transports).
 **
 ** TEST7_BRANCH_ID
 **	INVITE transaction MUST retransmit non-2xx final response.
 **
 ** TEST8_BRANCH_ID
 **	As above, for INVITE's 2xx final response (this is PJSIP specific).
 **
 ** TEST9_BRANCH_ID
 **	INVITE transaction MUST cease retransmission of final response when
 **	ACK is received. (Note: PJSIP also retransmit 2xx final response
 **	until it's terminated by user).
 **     Transaction also MUST terminate in T4 seconds.
 **	(Only applicable for non-reliable transports).
 **
 ** TEST11_BRANCH_ID
 **	Test scenario where transport fails before response is sent (i.e.
 **	in TRYING state).
 **
 ** TEST12_BRANCH_ID
 **	As above, after provisional response is sent but before final
 **	response is sent (i.e. in PROCEEDING state).
 **
 ** TEST13_BRANCH_ID
 **	As above, for INVITE, after final response has been sent but before
 **	ACK is received (i.e. in CONNECTED state).
 **
 ** TEST14_BRANCH_ID
 **	When UAS failed to deliver the response with the selected transport,
 **	it should try contacting the client with other transport or begin
 **	RFC 3263 server resolution procedure.
 **	This should be tested on:
 **	    a. TRYING state (when delivering first response).
 **	    b. PROCEEDING state (when failed to retransmit last response
 **	       upon receiving request retransmission).
 **	    c. COMPLETED state.
 **
 **/

#define TEST1_BRANCH_ID  (PJSIP_RFC3261_BRANCH_ID "-UAS-Test1")
#define TEST2_BRANCH_ID  (PJSIP_RFC3261_BRANCH_ID "-UAS-Test2")
#define TEST3_BRANCH_ID  (PJSIP_RFC3261_BRANCH_ID "-UAS-Test3")
#define TEST4_BRANCH_ID  (PJSIP_RFC3261_BRANCH_ID "-UAS-Test4")
#define TEST5_BRANCH_ID  (PJSIP_RFC3261_BRANCH_ID "-UAS-Test5")
#define TEST6_BRANCH_ID  (PJSIP_RFC3261_BRANCH_ID "-UAS-Test6")
#define TEST7_BRANCH_ID  (PJSIP_RFC3261_BRANCH_ID "-UAS-Test7")
#define TEST8_BRANCH_ID  (PJSIP_RFC3261_BRANCH_ID "-UAS-Test8")
#define TEST9_BRANCH_ID  (PJSIP_RFC3261_BRANCH_ID "-UAS-Test9")
#define TEST10_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test10")
#define TEST11_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test11")
#define TEST12_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test12")
//#define TEST13_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test13")

#define TEST1_STATUS_CODE	200
#define TEST2_STATUS_CODE	301
#define TEST3_PROVISIONAL_CODE	PJSIP_SC_QUEUED
#define TEST3_STATUS_CODE	202
#define TEST4_STATUS_CODE	200
#define TEST4_REQUEST_COUNT	2
#define TEST5_PROVISIONAL_CODE	100
#define TEST5_STATUS_CODE	200
#define TEST5_REQUEST_COUNT	2
#define TEST5_RESPONSE_COUNT	2
#define TEST6_PROVISIONAL_CODE	100
#define TEST6_STATUS_CODE	200	/* Must be final */
#define TEST6_REQUEST_COUNT	2
#define TEST6_RESPONSE_COUNT	3
#define TEST7_STATUS_CODE	301
#define TEST8_STATUS_CODE	302
#define TEST9_STATUS_CODE	301


#define TEST4_TITLE "test4: absorbing request retransmission"
#define TEST5_TITLE "test5: retransmit last response in PROCEEDING state"
#define TEST6_TITLE "test6: retransmit last response in COMPLETED state"


static char TARGET_URI[128];
static char FROM_URI[128];
static struct tsx_test_param *test_param;
static unsigned tp_flag;


#define TEST_TIMEOUT_ERROR	-30

// An effort to accommodate CPU load spike on some test machines.
#define MAX_ALLOWED_DIFF	500 //150

static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e);
static pj_bool_t on_rx_message(pjsip_rx_data *rdata);

/* UAC transaction user module. */
static pjsip_module tsx_user =
{
    NULL, NULL,				/* prev and next	*/
    { "Tsx-UAS-User", 12},		/* Name.		*/
    -1,					/* Id			*/
    PJSIP_MOD_PRIORITY_APPLICATION-1,	/* Priority		*/
    NULL,				/* load()		*/
    NULL,				/* start()		*/
    NULL,				/* stop()		*/
    NULL,				/* unload()		*/
    NULL,				/* on_rx_request()	*/
    NULL,				/* on_rx_response()	*/
    NULL,				/* on_tx_request()	*/
    NULL,				/* on_tx_response()	*/
    &tsx_user_on_tsx_state,		/* on_tsx_state()	*/
};

/* Module to send request. */
static pjsip_module msg_sender =
{
    NULL, NULL,				/* prev and next	*/
    { "Msg-Sender", 10},		/* Name.		*/
    -1,					/* Id			*/
    PJSIP_MOD_PRIORITY_APPLICATION-1,	/* Priority		*/
    NULL,				/* load()		*/
    NULL,				/* start()		*/
    NULL,				/* stop()		*/
    NULL,				/* unload()		*/
    &on_rx_message,			/* on_rx_request()	*/
    &on_rx_message,			/* on_rx_response()	*/
    NULL,				/* on_tx_request()	*/
    NULL,				/* on_tx_response()	*/
    NULL,				/* on_tsx_state()	*/
};

/* Static vars, which will be reset on each test. */
static int recv_count;
static pj_time_val recv_last;
static pj_bool_t test_complete;

/* Loop transport instance. */
static pjsip_transport *loop;

/* UAS transaction key. */
static char key_buf[64];
static pj_str_t tsx_key = { key_buf, 0 };


/* General timer entry to be used by tests. */
//static pj_timer_entry timer;

/* Timer to send response via transaction. */
struct response
{
    pj_str_t	     tsx_key;
    pjsip_tx_data   *tdata;
};

/* Timer callback to send response. */
static void send_response_timer( pj_timer_heap_t *timer_heap,
				 struct pj_timer_entry *entry)
{
    pjsip_transaction *tsx;
    struct response *r = (struct response*) entry->user_data;
    pj_status_t status;

    PJ_UNUSED_ARG(timer_heap);

    tsx = pjsip_tsx_layer_find_tsx(&r->tsx_key, PJ_TRUE);
    if (!tsx) {
	PJ_LOG(3,(THIS_FILE,"    error: timer unable to find transaction"));
	pjsip_tx_data_dec_ref(r->tdata);
	return;
    }

    status = pjsip_tsx_send_msg(tsx, r->tdata);
    if (status != PJ_SUCCESS) {
	// Some tests do expect failure!
	//PJ_LOG(3,(THIS_FILE,"    error: timer unable to send response"));
	pj_grp_lock_release(tsx->grp_lock);
	pjsip_tx_data_dec_ref(r->tdata);
	return;
    }

    pj_grp_lock_release(tsx->grp_lock);
}

/* Utility to send response. */
static void send_response( pjsip_rx_data *rdata,
			   pjsip_transaction *tsx,
			   int status_code )
{
    pj_status_t status;
    pjsip_tx_data *tdata;

    status = pjsip_endpt_create_response( endpt, rdata, status_code, NULL,
					  &tdata);
    if (status != PJ_SUCCESS) {
	app_perror("    error: unable to create response", status);
	test_complete = -196;
	return;
    }

    status = pjsip_tsx_send_msg(tsx, tdata);
    if (status != PJ_SUCCESS) {
	pjsip_tx_data_dec_ref(tdata);
	// Some tests do expect failure!
	//app_perror("    error: unable to send response", status);
	//test_complete = -197;
	return;
    }
}

/* Schedule timer to send response for the specified UAS transaction */
static void schedule_send_response( pjsip_rx_data *rdata,
				    const pj_str_t *tsx_key_,
				    int status_code,
				    int msec_delay )
{
    pj_status_t status;
    pjsip_tx_data *tdata;
    pj_timer_entry *t;
    struct response *r;
    pj_time_val delay;

    status = pjsip_endpt_create_response( endpt, rdata, status_code, NULL,
					  &tdata);
    if (status != PJ_SUCCESS) {
	app_perror("    error: unable to create response", status);
	test_complete = -198;
	return;
    }

    r = PJ_POOL_ALLOC_T(tdata->pool, struct response);
    pj_strdup(tdata->pool, &r->tsx_key, tsx_key_);
    r->tdata = tdata;

    delay.sec = 0;
    delay.msec = msec_delay;
    pj_time_val_normalize(&delay);

    t = PJ_POOL_ZALLOC_T(tdata->pool, pj_timer_entry);
    t->user_data = r;
    t->cb = &send_response_timer;

    status = pjsip_endpt_schedule_timer(endpt, t, &delay);
    if (status != PJ_SUCCESS) {
	pjsip_tx_data_dec_ref(tdata);
	app_perror("    error: unable to schedule timer", status);
	test_complete = -199;
	return;
    }
}


/* Find and terminate tsx with the specified key. */
static void terminate_our_tsx(int status_code)
{
    pjsip_transaction *tsx;

    tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE);
    if (!tsx) {
	PJ_LOG(3,(THIS_FILE,"    error: timer unable to find transaction"));
	return;
    }

    pjsip_tsx_terminate(tsx, status_code);
    pj_grp_lock_release(tsx->grp_lock);
}

#if 0	/* Unused for now */
/* Timer callback to terminate transaction. */
static void terminate_tsx_timer( pj_timer_heap_t *timer_heap,
				 struct pj_timer_entry *entry)
{
    terminate_our_tsx(entry->id);
}


/* Schedule timer to terminate transaction. */
static void schedule_terminate_tsx( pjsip_transaction *tsx,
				    int status_code,
				    int msec_delay )
{
    pj_time_val delay;

    delay.sec = 0;
    delay.msec = msec_delay;
    pj_time_val_normalize(&delay);

    pj_assert(pj_strcmp(&tsx->transaction_key, &tsx_key)==0);
    timer.user_data = NULL;
    timer.id = status_code;
    timer.cb = &terminate_tsx_timer;
    pjsip_endpt_schedule_timer(endpt, &timer, &delay);
}
#endif


/*
 * This is the handler to receive state changed notification from the
 * transaction. It is used to verify that the transaction behaves according
 * to the test scenario.
 */
static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e)
{
    if (pj_stricmp2(&tsx->branch, TEST1_BRANCH_ID)==0 ||
	pj_stricmp2(&tsx->branch, TEST2_BRANCH_ID)==0)
    {
	/*
	 * TEST1_BRANCH_ID tests that non-INVITE transaction transmits final
	 * response using correct transport and terminates transaction after
	 * T4 (PJSIP_T4_TIMEOUT, 5 seconds).
	 *
	 * TEST2_BRANCH_ID does similar test for non-2xx final response.
	 */
	int status_code = (pj_stricmp2(&tsx->branch, TEST1_BRANCH_ID)==0) ?
			  TEST1_STATUS_CODE : TEST2_STATUS_CODE;

	if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

	    test_complete = 1;

	    /* Check that status code is status_code. */
	    if (tsx->status_code != status_code) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -100;
	    }

	    /* Previous state must be completed. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -101;
	    }

	} else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {

	    /* Previous state must be TRYING. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -102;
	    }
	}

    }
    else
    if (pj_stricmp2(&tsx->branch, TEST3_BRANCH_ID)==0) {
	/*
	 * TEST3_BRANCH_ID tests sending provisional response.
	 */
	if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

	    test_complete = 1;

	    /* Check that status code is status_code. */
	    if (tsx->status_code != TEST3_STATUS_CODE) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -110;
	    }

	    /* Previous state must be completed. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -111;
	    }

	} else if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) {

	    /* Previous state must be TRYING. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -112;
	    }

	    /* Check that status code is status_code. */
	    if (tsx->status_code != TEST3_PROVISIONAL_CODE) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -113;
	    }

	    /* Check that event must be TX_MSG */
	    if (e->body.tsx_state.type != PJSIP_EVENT_TX_MSG) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect event"));
		test_complete = -114;
	    }

	} else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {

	    /* Previous state must be PROCEEDING. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -115;
	    }

	    /* Check that status code is status_code. */
	    if (tsx->status_code != TEST3_STATUS_CODE) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -116;
	    }

	    /* Check that event must be TX_MSG */
	    if (e->body.tsx_state.type != PJSIP_EVENT_TX_MSG) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect event"));
		test_complete = -117;
	    }

	}

    } else
    if (pj_stricmp2(&tsx->branch, TEST4_BRANCH_ID)==0) {
	/*
	 * TEST4_BRANCH_ID tests receiving retransmissions in TRYING state.
	 */
	if (tsx->state == PJSIP_TSX_STATE_TRYING) {
	    /* Request is received. */
	} else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

	    /* Check that status code is status_code. */
	    if (tsx->status_code != TEST4_STATUS_CODE) {
		PJ_LOG(3,(THIS_FILE,
			  "    error: incorrect status code %d "
			  "(expecting %d)", tsx->status_code,
			  TEST4_STATUS_CODE));
		test_complete = -120;
	    }

	    /* Previous state. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -121;
	    }

	} else if (tsx->state != PJSIP_TSX_STATE_DESTROYED)
	{
	    PJ_LOG(3,(THIS_FILE, "    error: unexpected state %s (122)",
		      pjsip_tsx_state_str(tsx->state)));
	    test_complete = -122;

	}


    } else
    if (pj_stricmp2(&tsx->branch, TEST5_BRANCH_ID)==0) {
	/*
	 * TEST5_BRANCH_ID tests receiving retransmissions in PROCEEDING state
	 */
	if (tsx->state == PJSIP_TSX_STATE_TRYING) {
	    /* Request is received. */

	} else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

	    /* Check that status code is status_code. */
	    if (tsx->status_code != TEST5_STATUS_CODE) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -130;
	    }

	    /* Previous state. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -131;
	    }

	} else if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) {

	    /* Check status code. */
	    if (tsx->status_code != TEST5_PROVISIONAL_CODE) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -132;
	    }

	} else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) {
	    PJ_LOG(3,(THIS_FILE, "    error: unexpected state %s (133)",
		      pjsip_tsx_state_str(tsx->state)));
	    test_complete = -133;

	}

    } else
    if (pj_stricmp2(&tsx->branch, TEST6_BRANCH_ID)==0) {
	/*
	 * TEST6_BRANCH_ID tests receiving retransmissions in COMPLETED state
	 */
	if (tsx->state == PJSIP_TSX_STATE_TRYING) {
	    /* Request is received. */

	} else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

	    /* Check that status code is status_code. */
	    if (tsx->status_code != TEST6_STATUS_CODE) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code %d "
			  "(expecting %d)", tsx->status_code,
			  TEST6_STATUS_CODE));
		test_complete = -140;
	    }

	    /* Previous state. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -141;
	    }

	} else if (tsx->state != PJSIP_TSX_STATE_PROCEEDING &&
		   tsx->state != PJSIP_TSX_STATE_COMPLETED &&
		   tsx->state != PJSIP_TSX_STATE_DESTROYED)
	{
	    PJ_LOG(3,(THIS_FILE, "    error: unexpected state %s (142)",
		      pjsip_tsx_state_str(tsx->state)));
	    test_complete = -142;

	}


    } else
    if (pj_stricmp2(&tsx->branch, TEST7_BRANCH_ID)==0 ||
	pj_stricmp2(&tsx->branch, TEST8_BRANCH_ID)==0)
    {
	/*
	 * TEST7_BRANCH_ID and TEST8_BRANCH_ID test retransmission of
	 * INVITE final response
	 */
	int code;

	if (pj_stricmp2(&tsx->branch, TEST7_BRANCH_ID) == 0)
	    code = TEST7_STATUS_CODE;
	else
	    code = TEST8_STATUS_CODE;

	if (tsx->state == PJSIP_TSX_STATE_TRYING) {
	    /* Request is received. */

	} else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

	    if (test_complete == 0)
		test_complete = 1;

	    /* Check status code. */
	    if (tsx->status_code != PJSIP_SC_TSX_TIMEOUT) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -150;
	    }

	    /* Previous state. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -151;
	    }

	    /* Check the number of retransmissions */
	    if (tp_flag & PJSIP_TRANSPORT_RELIABLE) {

		if (tsx->retransmit_count != 0) {
		    PJ_LOG(3,(THIS_FILE, "    error: should not retransmit"));
		    test_complete = -1510;
		}

	    } else {

		if (tsx->retransmit_count != 10) {
		    PJ_LOG(3,(THIS_FILE,
			      "    error: incorrect retransmit count %d "
			      "(expecting 10)",
			      tsx->retransmit_count));
		    test_complete = -1510;
		}

	    }

	} else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {

	    /* Check that status code is status_code. */
	    if (tsx->status_code != code) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -152;
	    }

	    /* Previous state. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -153;
	    }

	} else if (tsx->state != PJSIP_TSX_STATE_DESTROYED)  {

	    PJ_LOG(3,(THIS_FILE, "    error: unexpected state (154)"));
	    test_complete = -154;

	}


    } else
    if (pj_stricmp2(&tsx->branch, TEST9_BRANCH_ID)==0)  {
	/*
	 * TEST9_BRANCH_ID tests that retransmission of INVITE final response
	 * must cease when ACK is received.
	 */

	if (tsx->state == PJSIP_TSX_STATE_TRYING) {
	    /* Request is received. */

	} else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

	    if (test_complete == 0)
		test_complete = 1;

	    /* Check status code. */
	    if (tsx->status_code != TEST9_STATUS_CODE) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -160;
	    }

	    /* Previous state. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_CONFIRMED) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -161;
	    }

	} else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {

	    /* Check that status code is status_code. */
	    if (tsx->status_code != TEST9_STATUS_CODE) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -162;
	    }

	    /* Previous state. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -163;
	    }


	} else if (tsx->state == PJSIP_TSX_STATE_CONFIRMED) {

	    /* Check that status code is status_code. */
	    if (tsx->status_code != TEST9_STATUS_CODE) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -164;
	    }

	    /* Previous state. */
	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
		test_complete = -165;
	    }

	} else if (tsx->state != PJSIP_TSX_STATE_DESTROYED)  {

	    PJ_LOG(3,(THIS_FILE, "    error: unexpected state (166)"));
	    test_complete = -166;

	}


    } else
    if (pj_stricmp2(&tsx->branch, TEST10_BRANCH_ID)==0 ||
	pj_stricmp2(&tsx->branch, TEST12_BRANCH_ID)==0)
    {
	if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

	    if (!test_complete)
		test_complete = 1;

	    if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) {
		PJ_LOG(3,(THIS_FILE,"    error: incorrect status code"
			  " (expecting %d, got %d)",
			  PJSIP_SC_TSX_TRANSPORT_ERROR,
			  tsx->status_code));
		test_complete = -170;
	    }
	}
    } else
    if (pj_stricmp2(&tsx->branch, TEST11_BRANCH_ID)==0)
    {
	if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

	    if (!test_complete)
		test_complete = 1;

	    if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR &&
		tsx->status_code != PJSIP_SC_OK)
	    {
		PJ_LOG(3,(THIS_FILE,"    error: incorrect status code"
			  " (expecting %d, got %d)",
			  PJSIP_SC_TSX_TRANSPORT_ERROR,
			  tsx->status_code));
		test_complete = -170;
	    }
	}
    }
}

/* Save transaction key to global variables. */
static void save_key(pjsip_transaction *tsx)
{
    pj_str_t key;

    pj_strdup(tsx->pool, &key, &tsx->transaction_key);
    pj_strcpy(&tsx_key, &key);
}

#define DIFF(a,b)   ((a<b) ? (b-a) : (a-b))

/*
 * Message receiver handler.
 */
static pj_bool_t on_rx_message(pjsip_rx_data *rdata)
{
    pjsip_msg *msg = rdata->msg_info.msg;
    pj_str_t branch_param = rdata->msg_info.via->branch_param;
    pj_status_t status;

    if (pj_stricmp2(&branch_param, TEST1_BRANCH_ID) == 0 ||
	pj_stricmp2(&branch_param, TEST2_BRANCH_ID) == 0)
    {
	/*
	 * TEST1_BRANCH_ID tests that non-INVITE transaction transmits 2xx
	 * final response using correct transport and terminates transaction
	 * after 32 seconds.
	 *
	 * TEST2_BRANCH_ID performs similar test for non-2xx final response.
	 */
	int status_code = (pj_stricmp2(&branch_param, TEST1_BRANCH_ID) == 0) ?
			  TEST1_STATUS_CODE : TEST2_STATUS_CODE;

	if (msg->type == PJSIP_REQUEST_MSG) {
	    /* On received request, create UAS and respond with final
	     * response.
	     */
	    pjsip_transaction *tsx;

	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
	    if (status != PJ_SUCCESS) {
		app_perror("    error: unable to create transaction", status);
		test_complete = -110;
		return PJ_TRUE;
	    }
	    pjsip_tsx_recv_msg(tsx, rdata);

	    save_key(tsx);
	    send_response(rdata, tsx, status_code);

	} else {
	    /* Verify the response received. */

	    ++recv_count;

	    /* Verify status code. */
	    if (msg->line.status.code != status_code) {
		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		test_complete = -113;
	    }

	    /* Verify that no retransmissions is received. */
	    if (recv_count > 1) {
		PJ_LOG(3,(THIS_FILE, "    error: retransmission received"));
		test_complete = -114;
	    }

	}
	return PJ_TRUE;

    } else if (pj_stricmp2(&branch_param, TEST3_BRANCH_ID) == 0) {

	/* TEST3_BRANCH_ID tests provisional response. */

	if (msg->type == PJSIP_REQUEST_MSG) {
	    /* On received request, create UAS and respond with provisional
	     * response, then schedule timer to send final response.
	     */
	    pjsip_transaction *tsx;

	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
	    if (status != PJ_SUCCESS) {
		app_perror("    error: unable to create transaction", status);
		test_complete = -116;
		return PJ_TRUE;
	    }
	    pjsip_tsx_recv_msg(tsx, rdata);

	    save_key(tsx);

	    send_response(rdata, tsx, TEST3_PROVISIONAL_CODE);
	    schedule_send_response(rdata, &tsx->transaction_key,
				   TEST3_STATUS_CODE, 2000);

	} else {
	    /* Verify the response received. */

	    ++recv_count;

	    if (recv_count == 1) {
		/* Verify status code. */
		if (msg->line.status.code != TEST3_PROVISIONAL_CODE) {
		    PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		    test_complete = -123;
		}
	    } else if (recv_count == 2) {
		/* Verify status code. */
		if (msg->line.status.code != TEST3_STATUS_CODE) {
		    PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
		    test_complete = -124;
		}
	    } else {
		PJ_LOG(3,(THIS_FILE, "    error: retransmission received"));
		test_complete = -125;
	    }

	}
	return PJ_TRUE;

    } else if (pj_stricmp2(&branch_param, TEST4_BRANCH_ID) == 0 ||
	       pj_stricmp2(&branch_param, TEST5_BRANCH_ID) == 0 ||
	       pj_stricmp2(&branch_param, TEST6_BRANCH_ID) == 0)
    {

	/* TEST4_BRANCH_ID: absorbs retransmissions in TRYING state. */
	/* TEST5_BRANCH_ID: retransmit last response in PROCEEDING state. */
	/* TEST6_BRANCH_ID: retransmit last response in COMPLETED state. */

	if (msg->type == PJSIP_REQUEST_MSG) {
	    /* On received request, create UAS. */
	    pjsip_transaction *tsx;

	    PJ_LOG(4,(THIS_FILE, "    received request (probably retransmission)"));

	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
	    if (status != PJ_SUCCESS) {
		app_perror("    error: unable to create transaction", status);
		test_complete = -130;
		return PJ_TRUE;
	    }

	    pjsip_tsx_recv_msg(tsx, rdata);
	    save_key(tsx);

	    if (pj_stricmp2(&branch_param, TEST4_BRANCH_ID) == 0) {

	    } else if (pj_stricmp2(&branch_param, TEST5_BRANCH_ID) == 0) {
		send_response(rdata, tsx, TEST5_PROVISIONAL_CODE);

	    } else if (pj_stricmp2(&branch_param, TEST6_BRANCH_ID) == 0) {
		PJ_LOG(4,(THIS_FILE, "    sending provisional response"));
		send_response(rdata, tsx, TEST6_PROVISIONAL_CODE);
		PJ_LOG(4,(THIS_FILE, "    sending final response"));
		send_response(rdata, tsx, TEST6_STATUS_CODE);
	    }

	} else {
	    /* Verify the response received. */

	    PJ_LOG(4,(THIS_FILE, "    received response number %d", recv_count));

	    ++recv_count;

	    if (pj_stricmp2(&branch_param, TEST4_BRANCH_ID) == 0) {
		PJ_LOG(3,(THIS_FILE, "    error: not expecting response!"));
		test_complete = -132;

	    } else if (pj_stricmp2(&branch_param, TEST5_BRANCH_ID) == 0) {

		if (rdata->msg_info.msg->line.status.code!=TEST5_PROVISIONAL_CODE) {
		    PJ_LOG(3,(THIS_FILE, "    error: incorrect status code!"));
		    test_complete = -133;

		}
		if (recv_count > TEST5_RESPONSE_COUNT) {
		    PJ_LOG(3,(THIS_FILE, "    error: not expecting response!"));
		    test_complete = -134;
		}

	    } else if (pj_stricmp2(&branch_param, TEST6_BRANCH_ID) == 0) {

		int code = rdata->msg_info.msg->line.status.code;

		switch (recv_count) {
		case 1:
		    if (code != TEST6_PROVISIONAL_CODE) {
			PJ_LOG(3,(THIS_FILE, "    error: invalid code!"));
			test_complete = -135;
		    }
		    break;
		case 2:
		case 3:
		    if (code != TEST6_STATUS_CODE) {
			PJ_LOG(3,(THIS_FILE, "    error: invalid code %d "
				  "(expecting %d)", code, TEST6_STATUS_CODE));
			test_complete = -136;
		    }
		    break;
		default:
		    PJ_LOG(3,(THIS_FILE, "    error: not expecting response"));
		    test_complete = -137;
		    break;
		}
	    }
	}
	return PJ_TRUE;


    } else if (pj_stricmp2(&branch_param, TEST7_BRANCH_ID) == 0 ||
	       pj_stricmp2(&branch_param, TEST8_BRANCH_ID) == 0)
    {

	/*
	 * TEST7_BRANCH_ID and TEST8_BRANCH_ID test the retransmission
	 * of INVITE final response
	 */
	if (msg->type == PJSIP_REQUEST_MSG) {

	    /* On received request, create UAS. */
	    pjsip_transaction *tsx;

	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
	    if (status != PJ_SUCCESS) {
		app_perror("    error: unable to create transaction", status);
		test_complete = -140;
		return PJ_TRUE;
	    }

	    pjsip_tsx_recv_msg(tsx, rdata);
	    save_key(tsx);

	    if (pj_stricmp2(&branch_param, TEST7_BRANCH_ID) == 0) {

		send_response(rdata, tsx, TEST7_STATUS_CODE);

	    } else {

		send_response(rdata, tsx, TEST8_STATUS_CODE);

	    }

	} else {
	    int code;

	    ++recv_count;

	    if (pj_stricmp2(&branch_param, TEST7_BRANCH_ID) == 0)
		code = TEST7_STATUS_CODE;
	    else
		code = TEST8_STATUS_CODE;

	    if (recv_count==1) {

		if (rdata->msg_info.msg->line.status.code != code) {
		    PJ_LOG(3,(THIS_FILE,"    error: invalid status code"));
		    test_complete = -141;
		}

		recv_last = rdata->pkt_info.timestamp;

	    } else {

		pj_time_val now;
		unsigned msec, msec_expected;

		now = rdata->pkt_info.timestamp;

		PJ_TIME_VAL_SUB(now, recv_last);

		msec = now.sec*1000 + now.msec;
		msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1;
		if (msec_expected > pjsip_cfg()->tsx.t2)
		    msec_expected = pjsip_cfg()->tsx.t2;

		if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) {
		    PJ_LOG(3,(THIS_FILE,
			      "    error: incorrect retransmission "
			      "time (%d ms expected, %d ms received",
			      msec_expected, msec));
		    test_complete = -142;
		}

		if (recv_count > 11) {
		    PJ_LOG(3,(THIS_FILE,"    error: too many responses (%d)",
					recv_count));
		    test_complete = -143;
		}

		recv_last = rdata->pkt_info.timestamp;
	    }

	}
	return PJ_TRUE;

    } else if (pj_stricmp2(&branch_param, TEST9_BRANCH_ID) == 0) {

	/*
	 * TEST9_BRANCH_ID tests that the retransmission of INVITE final
	 * response should cease when ACK is received. Transaction also MUST
	 * terminate in T4 seconds.
	 */
	if (msg->type == PJSIP_REQUEST_MSG) {

	    /* On received request, create UAS. */
	    pjsip_transaction *tsx;

	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
	    if (status != PJ_SUCCESS) {
		app_perror("    error: unable to create transaction", status);
		test_complete = -150;
		return PJ_TRUE;
	    }

	    pjsip_tsx_recv_msg(tsx, rdata);
	    save_key(tsx);
	    send_response(rdata, tsx, TEST9_STATUS_CODE);


	} else {

	    ++recv_count;

	    if (rdata->msg_info.msg->line.status.code != TEST9_STATUS_CODE) {
		PJ_LOG(3,(THIS_FILE,"    error: invalid status code"));
		test_complete = -151;
	    }

	    if (recv_count==1) {

		recv_last = rdata->pkt_info.timestamp;

	    } else if (recv_count < 5) {

		/* Let UAS retransmit some messages before we send ACK. */
		pj_time_val now;
		unsigned msec, msec_expected;

		now = rdata->pkt_info.timestamp;

		PJ_TIME_VAL_SUB(now, recv_last);

		msec = now.sec*1000 + now.msec;
		msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1;
		if (msec_expected > pjsip_cfg()->tsx.t2)
		    msec_expected = pjsip_cfg()->tsx.t2;

		if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) {
		    PJ_LOG(3,(THIS_FILE,
			      "    error: incorrect retransmission "
			      "time (%d ms expected, %d ms received",
			      msec_expected, msec));
		    test_complete = -152;
		}

		recv_last = rdata->pkt_info.timestamp;

	    } else if (recv_count == 5) {
		pjsip_tx_data *tdata;
		pjsip_sip_uri *uri;
		pjsip_via_hdr *via;

		status = pjsip_endpt_create_request_from_hdr(
			    endpt, &pjsip_ack_method,
			    rdata->msg_info.to->uri,
			    rdata->msg_info.from,
			    rdata->msg_info.to,
			    NULL,
			    rdata->msg_info.cid,
			    rdata->msg_info.cseq->cseq,
			    NULL,
			    &tdata);
		if (status != PJ_SUCCESS) {
		    app_perror("    error: unable to create ACK", status);
		    test_complete = -153;
		    return PJ_TRUE;
		}

		uri=(pjsip_sip_uri*)pjsip_uri_get_uri(tdata->msg->line.req.uri);
		uri->transport_param = pj_str("loop-dgram");

		via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
		via->branch_param = pj_str(TEST9_BRANCH_ID);

		status = pjsip_endpt_send_request_stateless(endpt, tdata,
							    NULL, NULL);
		if (status != PJ_SUCCESS) {
		    app_perror("    error: unable to send ACK", status);
		    test_complete = -154;
		}

	    } else {
		PJ_LOG(3,(THIS_FILE,"    error: too many responses (%d)",
				    recv_count));
		test_complete = -155;
	    }

	}
	return PJ_TRUE;

    } else if (pj_stricmp2(&branch_param, TEST10_BRANCH_ID) == 0 ||
	       pj_stricmp2(&branch_param, TEST11_BRANCH_ID) == 0 ||
	       pj_stricmp2(&branch_param, TEST12_BRANCH_ID) == 0)
    {
	int test_num, code1, code2;

	if (pj_stricmp2(&branch_param, TEST10_BRANCH_ID) == 0)
	    test_num=10, code1 = 100, code2 = 0;
	else if (pj_stricmp2(&branch_param, TEST11_BRANCH_ID) == 0)
	    test_num=11, code1 = 100, code2 = 200;
	else
	    test_num=12, code1 = 200, code2 = 0;

	PJ_UNUSED_ARG(test_num);

	if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) {

	    /* On received response, create UAS. */
	    pjsip_transaction *tsx;

	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
	    if (status != PJ_SUCCESS) {
		app_perror("    error: unable to create transaction", status);
		test_complete = -150;
		return PJ_TRUE;
	    }

	    pjsip_tsx_recv_msg(tsx, rdata);
	    save_key(tsx);

	    schedule_send_response(rdata, &tsx_key, code1, 1000);

	    if (code2)
		schedule_send_response(rdata, &tsx_key, code2, 2000);

	} else {

	}

	return PJ_TRUE;
    }

    return PJ_FALSE;
}

/*
 * The generic test framework, used by most of the tests.
 */
static int perform_test( char *target_uri, char *from_uri,
			 char *branch_param, int test_time,
			 const pjsip_method *method,
			 int request_cnt, int request_interval_msec,
			 int expecting_timeout)
{
    pjsip_tx_data *tdata;
    pj_str_t target, from;
    pjsip_via_hdr *via;
    pj_time_val timeout, next_send;
    int sent_cnt;
    pj_status_t status;

    PJ_LOG(3,(THIS_FILE,
	      "   please standby, this will take at most %d seconds..",
	      test_time));

    /* Reset test. */
    recv_count = 0;
    test_complete = 0;
    tsx_key.slen = 0;

    /* Init headers. */
    target = pj_str(target_uri);
    from = pj_str(from_uri);

    /* Create request. */
    status = pjsip_endpt_create_request( endpt, method, &target,
					 &from, &target, NULL, NULL, -1,
					 NULL, &tdata);
    if (status != PJ_SUCCESS) {
	app_perror("   Error: unable to create request", status);
	return -10;
    }

    /* Set the branch param for test 1. */
    via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
    via->branch_param = pj_str(branch_param);

    /* Schedule first send. */
    sent_cnt = 0;
    pj_gettimeofday(&next_send);
    pj_time_val_normalize(&next_send);

    /* Set test completion time. */
    pj_gettimeofday(&timeout);
    timeout.sec += test_time;

    /* Wait until test complete. */
    while (!test_complete) {
	pj_time_val now, poll_delay = {0, 10};

	pjsip_endpt_handle_events(endpt, &poll_delay);

	pj_gettimeofday(&now);

	if (sent_cnt < request_cnt && PJ_TIME_VAL_GTE(now, next_send)) {
	    /* Add additional reference to tdata to prevent transaction from
	     * deleting it.
	     */
	    pjsip_tx_data_add_ref(tdata);

	    /* (Re)Send the request. */
	    PJ_LOG(4,(THIS_FILE, "    (re)sending request %d", sent_cnt));

	    status = pjsip_endpt_send_request_stateless(endpt, tdata, 0, 0);
	    if (status != PJ_SUCCESS) {
		app_perror("   Error: unable to send request", status);
		pjsip_tx_data_dec_ref(tdata);
		return -20;
	    }

	    /* Schedule next send, if any. */
	    sent_cnt++;
	    if (sent_cnt < request_cnt) {
		pj_gettimeofday(&next_send);
		next_send.msec += request_interval_msec;
		pj_time_val_normalize(&next_send);
	    }
	}

	if (now.sec > timeout.sec) {
	    if (!expecting_timeout)
		PJ_LOG(3,(THIS_FILE, "   Error: test has timed out"));
	    pjsip_tx_data_dec_ref(tdata);
	    return TEST_TIMEOUT_ERROR;
	}
    }

    if (test_complete < 0) {
	pjsip_transaction *tsx;

	tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE);
	if (tsx) {
	    pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
	    pj_grp_lock_release(tsx->grp_lock);
	    flush_events(1000);
	}
	pjsip_tx_data_dec_ref(tdata);
	return test_complete;
    }

    /* Allow transaction to destroy itself */
    flush_events(500);

    /* Make sure transaction has been destroyed. */
    if (pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE) != NULL) {
	PJ_LOG(3,(THIS_FILE, "   Error: transaction has not been destroyed"));
	pjsip_tx_data_dec_ref(tdata);
	return -40;
    }

    /* Check tdata reference counter. */
    if (pj_atomic_get(tdata->ref_cnt) != 1) {
	PJ_LOG(3,(THIS_FILE, "   Error: tdata reference counter is %d",
		      pj_atomic_get(tdata->ref_cnt)));
	pjsip_tx_data_dec_ref(tdata);
	return -50;
    }

    /* Destroy txdata */
    pjsip_tx_data_dec_ref(tdata);

    return PJ_SUCCESS;

}


/*****************************************************************************
 **
 ** TEST1_BRANCH_ID: Basic 2xx final response
 ** TEST2_BRANCH_ID: Basic non-2xx final response
 **
 *****************************************************************************
 */
static int tsx_basic_final_response_test(void)
{
    unsigned duration;
    int status;

    PJ_LOG(3,(THIS_FILE,"  test1: basic sending 2xx final response"));

    /* Test duration must be greater than 32 secs if unreliable transport
     * is used.
     */
    duration = (tp_flag & PJSIP_TRANSPORT_RELIABLE) ? 1 : 33;

    status = perform_test(TARGET_URI, FROM_URI, TEST1_BRANCH_ID,
			  duration,  &pjsip_options_method, 1, 0, 0);
    if (status != 0)
	return status;

    PJ_LOG(3,(THIS_FILE,"  test2: basic sending non-2xx final response"));

    status = perform_test(TARGET_URI, FROM_URI, TEST2_BRANCH_ID,
			  duration, &pjsip_options_method, 1, 0, 0);
    if (status != 0)
	return status;

    return 0;
}


/*****************************************************************************
 **
 ** TEST3_BRANCH_ID: Sending provisional response
 **
 *****************************************************************************
 */
static int tsx_basic_provisional_response_test(void)
{
    unsigned duration;
    int status;

    PJ_LOG(3,(THIS_FILE,"  test3: basic sending 2xx final response"));

    duration = (tp_flag & PJSIP_TRANSPORT_RELIABLE) ? 1 : 33;
    duration += 2;

    status = perform_test(TARGET_URI, FROM_URI, TEST3_BRANCH_ID, duration,
			  &pjsip_options_method, 1, 0, 0);

    return status;
}


/*****************************************************************************
 **
 ** TEST4_BRANCH_ID: Absorbs retransmissions in TRYING state
 ** TEST5_BRANCH_ID: Absorbs retransmissions in PROCEEDING state
 ** TEST6_BRANCH_ID: Absorbs retransmissions in COMPLETED state
 **
 *****************************************************************************
 */
static int tsx_retransmit_last_response_test(const char *title,
					     char *branch_id,
					     int request_cnt,
					     int status_code)
{
    int status;

    PJ_LOG(3,(THIS_FILE,"  %s", title));

    status = perform_test(TARGET_URI, FROM_URI, branch_id, 5,
			  &pjsip_options_method,
			  request_cnt, 1000, 1);
    if (status && status != TEST_TIMEOUT_ERROR)
	return status;
    if (!status) {
	PJ_LOG(3,(THIS_FILE, "   error: expecting timeout"));
	return -31;
    }

    terminate_our_tsx(status_code);
    flush_events(100);

    if (test_complete != 1)
	return test_complete;

    flush_events(100);
    return 0;
}

/*****************************************************************************
 **
 ** TEST7_BRANCH_ID: INVITE non-2xx final response retransmission test
 ** TEST8_BRANCH_ID: INVITE 2xx final response retransmission test
 **
 *****************************************************************************
 */
static int tsx_final_response_retransmission_test(void)
{
    int status;

    PJ_LOG(3,(THIS_FILE,
	      "  test7: INVITE non-2xx final response retransmission"));

    status = perform_test(TARGET_URI, FROM_URI, TEST7_BRANCH_ID,
			  33, /* Test duration must be greater than 32 secs */
			  &pjsip_invite_method, 1, 0, 0);
    if (status != 0)
	return status;

    PJ_LOG(3,(THIS_FILE,
	      "  test8: INVITE 2xx final response retransmission"));

    status = perform_test(TARGET_URI, FROM_URI, TEST8_BRANCH_ID,
			  33, /* Test duration must be greater than 32 secs */
			  &pjsip_invite_method, 1, 0, 0);
    if (status != 0)
	return status;

    return 0;
}


/*****************************************************************************
 **
 ** TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must
 ** cease when ACK is received
 **
 *****************************************************************************
 */
static int tsx_ack_test(void)
{
    int status;

    PJ_LOG(3,(THIS_FILE,
	      "  test9: receiving ACK for non-2xx final response"));

    status = perform_test(TARGET_URI, FROM_URI, TEST9_BRANCH_ID,
			  20, /* allow 5 retransmissions */
			  &pjsip_invite_method, 1, 0, 0);
    if (status != 0)
	return status;


    return 0;
}



/*****************************************************************************
 **
 ** TEST10_BRANCH_ID: test transport failure in TRYING state.
 ** TEST11_BRANCH_ID: test transport failure in PROCEEDING state.
 ** TEST12_BRANCH_ID: test transport failure in CONNECTED state.
 ** TEST13_BRANCH_ID: test transport failure in CONFIRMED state.
 **
 *****************************************************************************
 */
int tsx_transport_failure_test(void)
{
    struct test_desc
    {
	int transport_delay;
	int fail_delay;
	char *branch_id;
	char *title;
    } tests[] =
    {
	{ 0,  10,   TEST10_BRANCH_ID, "test10: failed transport in TRYING state (no delay)" },
	{ 50, 10,   TEST10_BRANCH_ID, "test10: failed transport in TRYING state (50 ms delay)" },
	{ 0,  1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (no delay)" },
	// After ticket #2076, transport error will be ignored if tsx is in COMPLETED state, note that
	// tsx state will be shifted to COMPLETED state once 200 response is sent due to transport delay.
	//{ 50, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (50 ms delay)" },
	{ 0,  2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (no delay)" },
	//Not applicable (maybe)
	//This test may expect transport failure notification in COMPLETED state. This may not be
	//possible because the loop transport can only notify failure when it has something to send,
	//while in this case, there is nothing to send because UAS already sends 200/OK
	//{ 50, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (50 ms delay)" },
    };
    int i, status;

    for (i=0; i<(int)PJ_ARRAY_SIZE(tests); ++i) {
	pj_time_val fail_time, end_test, now;

	PJ_LOG(3,(THIS_FILE, "  %s", tests[i].title));
	pjsip_loop_set_failure(loop, 0, NULL);
	pjsip_loop_set_delay(loop, tests[i].transport_delay);

	status = perform_test(TARGET_URI, FROM_URI, tests[i].branch_id,
			      0, &pjsip_invite_method, 1, 0, 1);
	if (status && status != TEST_TIMEOUT_ERROR)
	    return status;
	if (!status) {
	    PJ_LOG(3,(THIS_FILE, "   error: expecting timeout"));
	    return -40;
	}

	pj_gettimeofday(&fail_time);
	fail_time.msec += tests[i].fail_delay;
	pj_time_val_normalize(&fail_time);

	do {
	    pj_time_val interval = { 0, 1 };
	    pj_gettimeofday(&now);
	    pjsip_endpt_handle_events(endpt, &interval);
	} while (PJ_TIME_VAL_LT(now, fail_time));

	pjsip_loop_set_failure(loop, 1, NULL);
	PJ_LOG(5,(THIS_FILE, "   transport loop fail mode set"));

	end_test = now;
	end_test.sec += 5;

	do {
	    pj_time_val interval = { 0, 1 };
	    pj_gettimeofday(&now);
	    pjsip_endpt_handle_events(endpt, &interval);
	} while (!test_complete && PJ_TIME_VAL_LT(now, end_test));

	if (test_complete == 0) {
	    PJ_LOG(3,(THIS_FILE, "   error: test has timed out"));
	    return -41;
	}

	if (test_complete != 1)
	    return test_complete;
    }

    return 0;
}

/*****************************************************************************
 **
 ** UAS Transaction Test.
 **
 *****************************************************************************
 */
int tsx_uas_test(struct tsx_test_param *param)
{
    pj_sockaddr_in addr;
    pj_status_t status;

    test_param = param;
    tp_flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)param->type);

    pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s",
		    param->port, param->tp_type);
    pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s",
		    param->port, param->tp_type);

    /* Check if loop transport is configured. */
    status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM,
				      &addr, sizeof(addr), NULL, &loop);
    if (status != PJ_SUCCESS) {
	PJ_LOG(3,(THIS_FILE, "  Error: loop transport is not configured!"));
	return -10;
    }
    /* Register modules. */
    status = pjsip_endpt_register_module(endpt, &tsx_user);
    if (status != PJ_SUCCESS) {
	app_perror("   Error: unable to register module", status);
	return -3;
    }
    status = pjsip_endpt_register_module(endpt, &msg_sender);
    if (status != PJ_SUCCESS) {
	app_perror("   Error: unable to register module", status);
	return -4;
    }

    /* TEST1_BRANCH_ID: Basic 2xx final response.
     * TEST2_BRANCH_ID: Basic non-2xx final response.
     */
    status = tsx_basic_final_response_test();
    if (status != 0)
	return status;

    /* TEST3_BRANCH_ID: with provisional response
     */
    status = tsx_basic_provisional_response_test();
    if (status != 0)
	return status;

    /* TEST4_BRANCH_ID: absorbs retransmissions in TRYING state
     */
    status = tsx_retransmit_last_response_test(TEST4_TITLE,
					       TEST4_BRANCH_ID,
					       TEST4_REQUEST_COUNT,
					       TEST4_STATUS_CODE);
    if (status != 0)
	return status;

    /* TEST5_BRANCH_ID: retransmit last response in PROCEEDING state
     */
    status = tsx_retransmit_last_response_test(TEST5_TITLE,
					       TEST5_BRANCH_ID,
					       TEST5_REQUEST_COUNT,
					       TEST5_STATUS_CODE);
    if (status != 0)
	return status;

    /* TEST6_BRANCH_ID: retransmit last response in COMPLETED state
     *                  This only applies to non-reliable transports,
     *			since UAS transaction is destroyed as soon
     *			as final response is sent for reliable transports.
     */
    if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) {
	status = tsx_retransmit_last_response_test(TEST6_TITLE,
						   TEST6_BRANCH_ID,
						   TEST6_REQUEST_COUNT,
						   TEST6_STATUS_CODE);
	if (status != 0)
	    return status;
    }

    /* TEST7_BRANCH_ID: INVITE non-2xx final response retransmission test
     * TEST8_BRANCH_ID: INVITE 2xx final response retransmission test
     */
    status = tsx_final_response_retransmission_test();
    if (status != 0)
	return status;

    /* TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must
     * cease when ACK is received
     * Only applicable for non-reliable transports.
     */
    if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) {
	status = tsx_ack_test();
	if (status != 0)
	    return status;
    }

    /* TEST10_BRANCH_ID: test transport failure in TRYING state.
     * TEST11_BRANCH_ID: test transport failure in PROCEEDING state.
     * TEST12_BRANCH_ID: test transport failure in CONNECTED state.
     * TEST13_BRANCH_ID: test transport failure in CONFIRMED state.
     */
    /* Only valid for loop-dgram */
    if (param->type == PJSIP_TRANSPORT_LOOP_DGRAM) {
	status = tsx_transport_failure_test();
	if (status != 0)
	    return status;
    }


    /* Register modules. */
    status = pjsip_endpt_unregister_module(endpt, &tsx_user);
    if (status != PJ_SUCCESS) {
	app_perror("   Error: unable to unregister module", status);
	return -8;
    }
    status = pjsip_endpt_unregister_module(endpt, &msg_sender);
    if (status != PJ_SUCCESS) {
	app_perror("   Error: unable to unregister module", status);
	return -9;
    }


    if (loop)
	pjsip_transport_dec_ref(loop);

    return 0;
}
