Пример #1
0
int proxy_thread_callback(void* p)
{
    pj_time_val delay = {0, 10};
    while (!global.quit_flag) {
        pjsip_endpt_handle_events(global.endpt, &delay);
    }

    return 0;
}
Пример #2
0
/* Worker thread */
static int worker_thread(void *arg)
{
    PJ_UNUSED_ARG(arg);

    while (!quit_flag) {
	pj_time_val timeout = {0, 500};
	pjsip_endpt_handle_events(sip_endpt, &timeout);
    }

    return 0;
}
Пример #3
0
static int rt_worker_thread(void *arg)
{
    int i;
    pj_time_val poll_delay = { 0, 10 };

    PJ_UNUSED_ARG(arg);

    /* Sleep to allow main threads to run. */
    pj_thread_sleep(10);

    while (!rt_stop) {
	pjsip_endpt_handle_events(endpt, &poll_delay);
    }

    /* Exhaust responses. */
    for (i=0; i<100; ++i)
	pjsip_endpt_handle_events(endpt, &poll_delay);

    return 0;
}
Пример #4
0
static int worker_proc(void *arg)
{
    PJ_UNUSED_ARG(arg);

    while (!app.quit) {
	pj_time_val interval = { 0, 20 };
	pjsip_endpt_handle_events(app.sip_endpt, &interval);
    }

    return 0;
}
Пример #5
0
/// PJSIP threads are donated to PJSIP to handle receiving at transport level
/// and timers.
static int pjsip_thread(void *p)
{
  pj_time_val delay = {0, 10};

  PJ_UNUSED_ARG(p);

  LOG_DEBUG("PJSIP thread started");

  while (!quit_flag)
  {
    pjsip_endpt_handle_events(stack_data.endpt, &delay);
  }

  LOG_DEBUG("PJSIP thread ended");

  return 0;
}
Пример #6
0
void flush_events(unsigned duration)
{
    pj_time_val stop_time;

    pj_gettimeofday(&stop_time);
    stop_time.msec += duration;
    pj_time_val_normalize(&stop_time);

    /* Process all events for the specified duration. */
    for (;;) {
	pj_time_val timeout = {0, 1}, now;

	pjsip_endpt_handle_events(endpt, &timeout);

	pj_gettimeofday(&now);
	if (PJ_TIME_VAL_GTE(now, stop_time))
	    break;
    }
}
Пример #7
0
/*
 Cualquier REQUEST SIP pasa por aquí
 Aquí es dónde hacemos la magia negra de coger la header, desencapsular y tal
 */
static pj_bool_t on_rx_request( pjsip_rx_data *rdata )
{


	if (  (rdata->msg_info.msg->line.req.method.id != PJSIP_OPTIONS_METHOD))
	{
		debug(2,"Received no OPTIONS Request, reply 500 sent");
		pj_str_t reason = pj_str("Go home");
		pjsip_endpt_respond_stateless( g_endpt, rdata,500, &reason,NULL, NULL);
		return PJ_TRUE;
	}
	debug(1,"Received INFO/OPTIONS Request, lets parse it");
	
	pjsip_cid_hdr *headerMultivpn;
	pj_str_t hdrname = { .ptr = "Multivpn", .slen = 8 };
	
	headerMultivpn = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg ,&hdrname,NULL);
	
	if (!headerMultivpn)
	{
		debug(2,"No multivpn header found! Nothing to parse");
		pj_str_t reason = pj_str("Go home and sleep");
		pjsip_endpt_respond_stateless( g_endpt, rdata,500, &reason,NULL, NULL);
		return PJ_TRUE;
	}	
	else debug(1,"Header multivpn found ! Lets play with it");
	

    char payload[2048];
    memset(payload, 0, sizeof(payload));
    strncpy(payload, headerMultivpn->id.ptr, headerMultivpn->id.slen);
	
	debug(1,"Received payload is: %s with len %d",payload,strlen(payload));
	
	
	unsigned char *decodedpayload;
	size_t lenDecoded;
	decodedpayload = base64_decode(payload,strlen(payload),&lenDecoded);
	debug(1,"Decoded %d bytes",lenDecoded);
	debug(1,"Decoded payload with len %d and value %.3s",lenDecoded, decodedpayload);
	
	debug(1,"Now sending to PIPE for TUNDRIVER !");

	int nBytes = lenDecoded;
	
	nBytes=write(global_v.pipe_from_plugin[1],decodedpayload,nBytes);
	if (nBytes<=0)
		debug(3,"SIP Plugin: Failed Writing to Pipe");
	else    debug(3,"SIP Plugin: Write %d bytes to pipe",nBytes);
                
	// Tras el parsing, contestamos:
	pj_str_t reason = pj_str("Traffic Accepted");
   pjsip_endpt_respond_stateless( g_endpt, rdata,666, &reason,NULL, NULL);
   
   debug(1,"End process of request");
	return PJ_TRUE;



}

 
int sip_envia_datos(unsigned char *datos,int longitud)
{
	debug(1,"Starting function to send %d bytes", longitud);
	
	debug(1,"Now base64 encoding %d bytes",longitud);

	int longitudEncodeado;                    
	char * payload;
	payload = base64_encode(datos,longitud,(size_t *)&longitudEncodeado);
	                    	
	pj_status_t status;
	pjsip_tx_data *tdata;
	pj_str_t target = pj_str(global_v.sip_remoteuri);
	pj_str_t from = pj_str(global_v.sip_fromuri);

	status = pjsip_endpt_create_request(
			g_endpt,
			&pjsip_options_method,
			&target,
			&from,
			&target,
			NULL,
			NULL,
			-1,
			NULL,
			&tdata
	);
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

	pjsip_transaction *tsx;
	status = pjsip_tsx_create_uac(NULL, tdata, &tsx);
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

	// Create Custom header
	pj_str_t hdr_name = { .ptr = "Multivpn", .slen = 8 };
	pj_str_t hdr_value = { .ptr = payload, .slen = longitudEncodeado };
	pjsip_generic_string_hdr *multivpn_hdr = pjsip_generic_string_hdr_create(
			tdata->pool,
			&hdr_name,
			&hdr_value
	);

	// Add to message
	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) multivpn_hdr);


	status = pjsip_tsx_send_msg(tsx, NULL);
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

	return 0;
}



void* sip_loop_sip_events(void *none)
{
    for (;!g_complete;) {
			//pj_time_val timeout = {0, 10};
			//pjsip_endpt_handle_events(g_endpt, &timeout);
			pjsip_endpt_handle_events(g_endpt, NULL);
			debug(1,"Looping SIP");
    }

    return NULL;
 }

void sip_loop_tun_events()
{
	 unsigned char buffer[BLOCK_SIZE];
    int nBytes;
    fd_set  setReading;
    int maxfd;
    
    debug(2,"SIP Plugin: Starting LOOPING for events from TUN");
     maxfd = global_v.pipe_to_plugin[0]+1;
    while (1)
    {
        FD_SET(global_v.pipe_to_plugin[0],&setReading);
        debug(3,"SIP Plugin: Blocking now, until data from tundriver is received");
        
        select(maxfd,&setReading,NULL,NULL,NULL);

			debug(1,"SIP PLUGIN: Select unlocked !");
			        
        if (FD_ISSET(global_v.pipe_to_plugin[0],&setReading))
        {
            // Message is from pipe
            nBytes=read(global_v.pipe_to_plugin[0],buffer, BLOCK_SIZE-1);
            if (nBytes<=0)
                debug(3,"SIP Plugin: failed reading From Pipe");
            else    
            {
                debug(3,"SIP Plugin: Read %d bytes from pipe",nBytes);
         		 sip_envia_datos(buffer,nBytes);
            
        		}
        
    		}
    }
 }
Пример #8
0
/* Test that we receive loopback message. */
int transport_send_recv_test( pjsip_transport_type_e tp_type,
			      pjsip_transport *ref_tp,
			      char *target_url,
			      int *p_usec_rtt)
{
    pj_bool_t msg_log_enabled;
    pj_status_t status;
    pj_str_t target, from, to, contact, call_id, body;
    pjsip_method method;
    pjsip_tx_data *tdata;
    pj_time_val timeout;

    PJ_UNUSED_ARG(tp_type);
    PJ_UNUSED_ARG(ref_tp);

    PJ_LOG(3,(THIS_FILE, "  single message round-trip test..."));

    /* Register out test module to receive the message (if necessary). */
    if (my_module.id == -1) {
	status = pjsip_endpt_register_module( endpt, &my_module );
	if (status != PJ_SUCCESS) {
	    app_perror("   error: unable to register module", status);
	    return -500;
	}
    }

    /* Disable message logging. */
    msg_log_enabled = msg_logger_set_enabled(0);

    /* Create a request message. */
    target = pj_str(target_url);
    from = pj_str(FROM_HDR);
    to = pj_str(target_url);
    contact = pj_str(CONTACT_HDR);
    call_id = pj_str(CALL_ID_HDR);
    body = pj_str(BODY);

    pjsip_method_set(&method, PJSIP_OPTIONS_METHOD);
    status = pjsip_endpt_create_request( endpt, &method, &target, &from, &to,
					 &contact, &call_id, CSEQ_VALUE, 
					 &body, &tdata );
    if (status != PJ_SUCCESS) {
	app_perror("   error: unable to create request", status);
	return -510;
    }

    /* Reset statuses */
    send_status = recv_status = NO_STATUS;

    /* Start time. */
    pj_get_timestamp(&my_send_time);

    /* Send the message (statelessly). */
    PJ_LOG(5,(THIS_FILE, "Sending request to %.*s", 
			 (int)target.slen, target.ptr));
    status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL,
					         &send_msg_callback);
    if (status != PJ_SUCCESS) {
	/* Immediate error! */
	pjsip_tx_data_dec_ref(tdata);
	send_status = status;
    }

    /* Set the timeout (2 seconds from now) */
    pj_gettimeofday(&timeout);
    timeout.sec += 2;

    /* Loop handling events until we get status */
    do {
	pj_time_val now;
	pj_time_val poll_interval = { 0, 10 };

	pj_gettimeofday(&now);
	if (PJ_TIME_VAL_GTE(now, timeout)) {
	    PJ_LOG(3,(THIS_FILE, "   error: timeout in send/recv test"));
	    status = -540;
	    goto on_return;
	}

	if (send_status!=NO_STATUS && send_status!=PJ_SUCCESS) {
	    app_perror("   error sending message", send_status);
	    status = -550;
	    goto on_return;
	}

	if (recv_status!=NO_STATUS && recv_status!=PJ_SUCCESS) {
	    app_perror("   error receiving message", recv_status);
	    status = -560;
	    goto on_return;
	}

	if (send_status!=NO_STATUS && recv_status!=NO_STATUS) {
	    /* Success! */
	    break;
	}

	pjsip_endpt_handle_events(endpt, &poll_interval);

    } while (1);

    if (status == PJ_SUCCESS) {
	unsigned usec_rt;
	usec_rt = pj_elapsed_usec(&my_send_time, &my_recv_time);

	PJ_LOG(3,(THIS_FILE, "    round-trip = %d usec", usec_rt));

	*p_usec_rtt = usec_rt;
    }

    /* Restore message logging. */
    msg_logger_set_enabled(msg_log_enabled);

    status = PJ_SUCCESS;

on_return:
    return status;
}
Пример #9
0
static int perform_test(inv_test_param_t *param)
{
    pj_str_t uri;
    pjsip_dialog *dlg;
    pjmedia_sdp_session *sdp;
    pjsip_tx_data *tdata;
    pj_status_t status;

    PJ_LOG(3,(THIS_FILE, "  %s", param->title));

    pj_bzero(&inv_test, sizeof(inv_test));
    pj_memcpy(&inv_test.param, param, sizeof(*param));
    job_cnt = 0;

    uri = pj_str(CONTACT);

    /*  
     * Create UAC
     */
    status = pjsip_dlg_create_uac(pjsip_ua_instance(), 
				  &uri, &uri, &uri, &uri, &dlg);
    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -10);

    if (inv_test.param.oa[0] == OFFERER_UAC)
	sdp = create_sdp(dlg->pool, oa_sdp[0].offer);
    else
	sdp = NULL;

    status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac);
    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20);

    TRACE_((THIS_FILE, "    Sending INVITE %s offer", (sdp ? "with" : "without")));

    /*
     * Make call!
     */
    status = pjsip_inv_invite(inv_test.uac, &tdata);
    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);

    status = pjsip_inv_send_msg(inv_test.uac, tdata);
    PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);

    /*
     * Wait until test completes
     */
    while (!inv_test.complete) {
	pj_time_val delay = {0, 20};

	pjsip_endpt_handle_events(endpt, &delay);

	while (job_cnt) {
	    job_t j;

	    j = jobs[0];
	    pj_array_erase(jobs, sizeof(jobs[0]), job_cnt, 0);
	    --job_cnt;

	    run_job(&j);
	}
    }

    flush_events(100);

    /*
     * Hangup
     */
    TRACE_((THIS_FILE, "    Disconnecting call"));
    status = pjsip_inv_end_session(inv_test.uas, PJSIP_SC_DECLINE, 0, &tdata);
    pj_assert(status == PJ_SUCCESS);

    status = pjsip_inv_send_msg(inv_test.uas, tdata);
    pj_assert(status == PJ_SUCCESS);

    flush_events(500);

    return 0;
}
Пример #10
0
/*
 * main()
 *
 * If called with argument, treat argument as SIP URL to be called.
 * Otherwise wait for incoming calls.
 */
int main(int argc, char *argv[])
{
    pj_pool_t *pool = NULL;
    pj_status_t status;
    unsigned i;

    /* Must init PJLIB first: */
    status = pj_init();
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    pj_log_set_level(5);

    /* Then init PJLIB-UTIL: */
    status = pjlib_util_init();
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);


    /* Must create a pool factory before we can allocate any memory. */
    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);


    /* Create global endpoint: */
    {
	const pj_str_t *hostname;
	const char *endpt_name;

	/* Endpoint MUST be assigned a globally unique name.
	 * The name will be used as the hostname in Warning header.
	 */

	/* For this implementation, we'll use hostname for simplicity */
	hostname = pj_gethostname();
	endpt_name = hostname->ptr;

	/* Create the endpoint: */

	status = pjsip_endpt_create(&cp.factory, endpt_name, 
				    &g_endpt);
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
    }


    /* 
     * Add UDP transport, with hard-coded port 
     * Alternatively, application can use pjsip_udp_transport_attach() to
     * start UDP transport, if it already has an UDP socket (e.g. after it
     * resolves the address with STUN).
     */
    {
	pj_sockaddr addr;

	pj_sockaddr_init(AF, &addr, NULL, (pj_uint16_t)SIP_PORT);
	
	if (AF == pj_AF_INET()) {
	    status = pjsip_udp_transport_start( g_endpt, &addr.ipv4, NULL, 
						1, NULL);
	} else if (AF == pj_AF_INET6()) {
	    status = pjsip_udp_transport_start6(g_endpt, &addr.ipv6, NULL,
						1, NULL);
	} else {
	    status = PJ_EAFNOTSUP;
	}

	if (status != PJ_SUCCESS) {
	    app_perror(THIS_FILE, "Unable to start UDP transport", status);
	    return 1;
	}
    }


    /* 
     * Init transaction layer.
     * This will create/initialize transaction hash tables etc.
     */
    status = pjsip_tsx_layer_init_module(g_endpt);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);


    /* 
     * Initialize UA layer module.
     * This will create/initialize dialog hash tables etc.
     */
    status = pjsip_ua_init_module( g_endpt, NULL );
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);


    /* 
     * Init invite session module.
     * The invite session module initialization takes additional argument,
     * i.e. a structure containing callbacks to be called on specific
     * occurence of events.
     *
     * The on_state_changed and on_new_session callbacks are mandatory.
     * Application must supply the callback function.
     *
     * We use on_media_update() callback in this application to start
     * media transmission.
     */
    {
	pjsip_inv_callback inv_cb;

	/* Init the callback for INVITE session: */
	pj_bzero(&inv_cb, sizeof(inv_cb));
	inv_cb.on_state_changed = &call_on_state_changed;
	inv_cb.on_new_session = &call_on_forked;
	inv_cb.on_media_update = &call_on_media_update;

	/* Initialize invite session module:  */
	status = pjsip_inv_usage_init(g_endpt, &inv_cb);
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
    }

    /* Initialize 100rel support */
    status = pjsip_100rel_init_module(g_endpt);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);

    /*
     * Register our module to receive incoming requests.
     */
    status = pjsip_endpt_register_module( g_endpt, &mod_simpleua);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    /*
     * Register message logger module.
     */
    status = pjsip_endpt_register_module( g_endpt, &msg_logger);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);


    /* 
     * Initialize media endpoint.
     * This will implicitly initialize PJMEDIA too.
     */
#if PJ_HAS_THREADS
    status = pjmedia_endpt_create(&cp.factory, NULL, 1, &g_med_endpt);
#else
    status = pjmedia_endpt_create(&cp.factory, 
				  pjsip_endpt_get_ioqueue(g_endpt), 
				  0, &g_med_endpt);
#endif
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    /* 
     * Add PCMA/PCMU codec to the media endpoint. 
     */
#if defined(PJMEDIA_HAS_G711_CODEC) && PJMEDIA_HAS_G711_CODEC!=0
    status = pjmedia_codec_g711_init(g_med_endpt);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
#endif


#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
    /* Init video subsystem */
    pool = pjmedia_endpt_create_pool(g_med_endpt, "Video subsystem", 512, 512);
    status = pjmedia_video_format_mgr_create(pool, 64, 0, NULL);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
    status = pjmedia_converter_mgr_create(pool, NULL);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
    status = pjmedia_vid_codec_mgr_create(pool, NULL);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
    status = pjmedia_vid_dev_subsys_init(&cp.factory);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

#  if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && PJMEDIA_HAS_FFMPEG_VID_CODEC!=0
    /* Init ffmpeg video codecs */
    status = pjmedia_codec_ffmpeg_vid_init(NULL, &cp.factory);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
#  endif  /* PJMEDIA_HAS_FFMPEG_VID_CODEC */

#endif	/* PJMEDIA_HAS_VIDEO */
    
    /* 
     * Create media transport used to send/receive RTP/RTCP socket.
     * One media transport is needed for each call. Application may
     * opt to re-use the same media transport for subsequent calls.
     */
    for (i = 0; i < PJ_ARRAY_SIZE(g_med_transport); ++i) {
	status = pjmedia_transport_udp_create3(g_med_endpt, AF, NULL, NULL, 
					       RTP_PORT + i*2, 0, 
					       &g_med_transport[i]);
	if (status != PJ_SUCCESS) {
	    app_perror(THIS_FILE, "Unable to create media transport", status);
	    return 1;
	}

	/* 
	 * Get socket info (address, port) of the media transport. We will
	 * need this info to create SDP (i.e. the address and port info in
	 * the SDP).
	 */
	pjmedia_transport_info_init(&g_med_tpinfo[i]);
	pjmedia_transport_get_info(g_med_transport[i], &g_med_tpinfo[i]);

	pj_memcpy(&g_sock_info[i], &g_med_tpinfo[i].sock_info,
		  sizeof(pjmedia_sock_info));
    }

    /*
     * If URL is specified, then make call immediately.
     */
    if (argc > 1) {
	pj_sockaddr hostaddr;
	char hostip[PJ_INET6_ADDRSTRLEN+2];
	char temp[80];
	pj_str_t dst_uri = pj_str(argv[1]);
	pj_str_t local_uri;
	pjsip_dialog *dlg;
	pjmedia_sdp_session *local_sdp;
	pjsip_tx_data *tdata;

	if (pj_gethostip(AF, &hostaddr) != PJ_SUCCESS) {
	    app_perror(THIS_FILE, "Unable to retrieve local host IP", status);
	    return 1;
	}
	pj_sockaddr_print(&hostaddr, hostip, sizeof(hostip), 2);

	pj_ansi_sprintf(temp, "<sip:simpleuac@%s:%d>", 
			hostip, SIP_PORT);
	local_uri = pj_str(temp);

	/* Create UAC dialog */
	status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
				       &local_uri,  /* local URI */
				       &local_uri,  /* local Contact */
				       &dst_uri,    /* remote URI */
				       &dst_uri,    /* remote target */
				       &dlg);	    /* dialog */
	if (status != PJ_SUCCESS) {
	    app_perror(THIS_FILE, "Unable to create UAC dialog", status);
	    return 1;
	}

	/* If we expect the outgoing INVITE to be challenged, then we should
	 * put the credentials in the dialog here, with something like this:
	 *
	    {
		pjsip_cred_info	cred[1];

		cred[0].realm	  = pj_str("sip.server.realm");
		cred[0].scheme    = pj_str("digest");
		cred[0].username  = pj_str("theuser");
		cred[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
		cred[0].data      = pj_str("thepassword");

		pjsip_auth_clt_set_credentials( &dlg->auth_sess, 1, cred);
	    }
	 *
	 */


	/* Get the SDP body to be put in the outgoing INVITE, by asking
	 * media endpoint to create one for us.
	 */
	status = pjmedia_endpt_create_sdp( g_med_endpt,	    /* the media endpt	*/
					   dlg->pool,	    /* pool.		*/
					   MAX_MEDIA_CNT,   /* # of streams	*/
					   g_sock_info,     /* RTP sock info	*/
					   &local_sdp);	    /* the SDP result	*/
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);



	/* Create the INVITE session, and pass the SDP returned earlier
	 * as the session's initial capability.
	 */
	status = pjsip_inv_create_uac( dlg, local_sdp, 0, &g_inv);
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

	/* If we want the initial INVITE to travel to specific SIP proxies,
	 * then we should put the initial dialog's route set here. The final
	 * route set will be updated once a dialog has been established.
	 * To set the dialog's initial route set, we do it with something
	 * like this:
	 *
	    {
		pjsip_route_hdr route_set;
		pjsip_route_hdr *route;
		const pj_str_t hname = { "Route", 5 };
		char *uri = "sip:proxy.server;lr";

		pj_list_init(&route_set);

		route = pjsip_parse_hdr( dlg->pool, &hname, 
					 uri, strlen(uri),
					 NULL);
		PJ_ASSERT_RETURN(route != NULL, 1);
		pj_list_push_back(&route_set, route);

		pjsip_dlg_set_route_set(dlg, &route_set);
	    }
	 *
	 * Note that Route URI SHOULD have an ";lr" parameter!
	 */

	/* Create initial INVITE request.
	 * This INVITE request will contain a perfectly good request and 
	 * an SDP body as well.
	 */
	status = pjsip_inv_invite(g_inv, &tdata);
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);



	/* Send initial INVITE request. 
	 * From now on, the invite session's state will be reported to us
	 * via the invite session callbacks.
	 */
	status = pjsip_inv_send_msg(g_inv, tdata);
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);


    } else {

	/* No URL to make call to */

	PJ_LOG(3,(THIS_FILE, "Ready to accept incoming calls..."));
    }


    /* Loop until one call is completed */
    for (;!g_complete;) {
	pj_time_val timeout = {0, 10};
	pjsip_endpt_handle_events(g_endpt, &timeout);
    }

    /* On exit, dump current memory usage: */
    dump_pool_usage(THIS_FILE, &cp);

    /* Destroy audio ports. Destroy the audio port first
     * before the stream since the audio port has threads
     * that get/put frames to the stream.
     */
    if (g_snd_port)
	pjmedia_snd_port_destroy(g_snd_port);

#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
    /* Destroy video ports */
    if (g_vid_capturer)
	pjmedia_vid_port_destroy(g_vid_capturer);
    if (g_vid_renderer)
	pjmedia_vid_port_destroy(g_vid_renderer);
#endif

    /* Destroy streams */
    if (g_med_stream)
	pjmedia_stream_destroy(g_med_stream);
#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
    if (g_med_vstream)
	pjmedia_vid_stream_destroy(g_med_vstream);

    /* Deinit ffmpeg codec */
#   if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && PJMEDIA_HAS_FFMPEG_VID_CODEC!=0
    pjmedia_codec_ffmpeg_vid_deinit();
#   endif

#endif

    /* Destroy media transports */
    for (i = 0; i < MAX_MEDIA_CNT; ++i) {
	if (g_med_transport[i])
	    pjmedia_transport_close(g_med_transport[i]);
    }

    /* Deinit pjmedia endpoint */
    if (g_med_endpt)
	pjmedia_endpt_destroy(g_med_endpt);

    /* Deinit pjsip endpoint */
    if (g_endpt)
	pjsip_endpt_destroy(g_endpt);

    /* Release pool */
    if (pool)
	pj_pool_release(pool);

    return 0;
}
Пример #11
0
/*
 * main()
 */
int main(int argc, char *argv[])
{
    pj_status_t status;

    global.port = 5060;
    pj_log_set_level(4);

    status = init_options(argc, argv);
    if (status != PJ_SUCCESS)
	return 1;

    status = init_stack();
    if (status != PJ_SUCCESS) {
	app_perror("Error initializing stack", status);
	return 1;
    }

    status = init_proxy();
    if (status != PJ_SUCCESS) {
	app_perror("Error initializing proxy", status);
	return 1;
    }

    status = init_stateless_proxy();
    if (status != PJ_SUCCESS) {
	app_perror("Error initializing stateless proxy", status);
	return 1;
    }

#if PJ_HAS_THREADS
    status = pj_thread_create(global.pool, "sproxy", &worker_thread, 
			      NULL, 0, 0, &global.thread);
    if (status != PJ_SUCCESS) {
	app_perror("Error creating thread", status);
	return 1;
    }

    while (!global.quit_flag) {
	char line[10];

	puts("\n"
	     "Menu:\n"
	     "  q    quit\n"
	     "  d    dump status\n"
	     "  dd   dump detailed status\n"
	     "");

	if (fgets(line, sizeof(line), stdin) == NULL) {
	    puts("EOF while reading stdin, will quit now..");
	    global.quit_flag = PJ_TRUE;
	    break;
	}

	if (line[0] == 'q') {
	    global.quit_flag = PJ_TRUE;
	} else if (line[0] == 'd') {
	    pj_bool_t detail = (line[1] == 'd');
	    pjsip_endpt_dump(global.endpt, detail);
#if STATEFUL
	    pjsip_tsx_layer_dump(detail);
#endif
	}
    }

    pj_thread_join(global.thread);

#else
    puts("\nPress Ctrl-C to quit\n");
    for (;;) {
	pj_time_val delay = {0, 0};
	pjsip_endpt_handle_events(global.endpt, &delay);
    }
#endif

    destroy_stack();

    return 0;
}
Пример #12
0
/*
 * Perform server resolution where the results are expected to
 * come in strict order.
 */
static int test_resolve(const char *title,
			pj_pool_t *pool,
			pjsip_transport_type_e type,
			char *name,
			int port,
			pjsip_server_addresses *ref)
{
    pjsip_host_info dest;
    struct result result;

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

    dest.type = type;
    dest.flag = pjsip_transport_get_flag_from_type(type);
    dest.addr.host = pj_str(name);
    dest.addr.port = port;

    result.status = 0x12345678;

    pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb);

    while (result.status == 0x12345678) {
	int i = 0;
	pj_time_val timeout = { 1, 0 };
	pjsip_endpt_handle_events(endpt, &timeout);
	if (i == 1)
	    pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE);
    }

    if (result.status != PJ_SUCCESS) {
	app_perror("  pjsip_endpt_resolve() error", result.status);
	return result.status;
    }

    if (ref) {
	unsigned i;

	if (ref->count != result.servers.count) {
	    PJ_LOG(3,(THIS_FILE, "  test_resolve() error 10: result count mismatch"));
	    return 10;
	}
	
	for (i=0; i<ref->count; ++i) {
	    pj_sockaddr_in *ra = (pj_sockaddr_in *)&ref->entry[i].addr;
	    pj_sockaddr_in *rb = (pj_sockaddr_in *)&result.servers.entry[i].addr;

	    if (ra->sin_addr.s_addr != rb->sin_addr.s_addr) {
		PJ_LOG(3,(THIS_FILE, "  test_resolve() error 20: IP address mismatch"));
		return 20;
	    }
	    if (ra->sin_port != rb->sin_port) {
		PJ_LOG(3,(THIS_FILE, "  test_resolve() error 30: port mismatch"));
		return 30;
	    }
	    if (ref->entry[i].addr_len != result.servers.entry[i].addr_len) {
		PJ_LOG(3,(THIS_FILE, "  test_resolve() error 40: addr_len mismatch"));
		return 40;
	    }
	    if (ref->entry[i].type != result.servers.entry[i].type) {
		PJ_LOG(3,(THIS_FILE, "  test_resolve() error 50: transport type mismatch"));
		return 50;
	    }
	}
    }

    return PJ_SUCCESS;
}
Пример #13
0
/* 
 * The generic test framework, used by most of the tests. 
 */
static int perform_tsx_test(int dummy, char *target_uri, char *from_uri, 
			    char *branch_param, int test_time, 
			    const pjsip_method *method)
{
    pjsip_tx_data *tdata;
    pjsip_transaction *tsx;
    pj_str_t target, from, tsx_key;
    pjsip_via_hdr *via;
    pj_time_val timeout;
    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;

    /* 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 -100;
    }

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

    /* Add additional reference to tdata to prevent transaction from
     * deleting it.
     */
    pjsip_tx_data_add_ref(tdata);

    /* Create transaction. */
    status = pjsip_tsx_create_uac( &tsx_user, tdata, &tsx);
    if (status != PJ_SUCCESS) {
	app_perror("   Error: unable to create UAC transaction", status);
	pjsip_tx_data_dec_ref(tdata);
	return -110;
    }

    /* Get transaction key. */
    pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key);

    /* Send the message. */
    status = pjsip_tsx_send_msg(tsx, NULL);
    // Ignore send result. Some tests do deliberately triggers error
    // when sending message.
    if (status != PJ_SUCCESS) {
	// app_perror("   Error: unable to send request", status);
        pjsip_tx_data_dec_ref(tdata);
	// return -120;
    }


    /* 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 (now.sec > timeout.sec) {
	    PJ_LOG(3,(THIS_FILE, "   Error: test has timed out"));
	    pjsip_tx_data_dec_ref(tdata);
	    return -130;
	}
    }

    if (test_complete < 0) {
	tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE);
	if (tsx) {
	    pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
	    pj_mutex_unlock(tsx->mutex);
	    flush_events(1000);
	}
	pjsip_tx_data_dec_ref(tdata);
	return test_complete;

    } else {
	pj_time_val now;

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

	/* Wait until test completes */
	pj_gettimeofday(&now);

	if (PJ_TIME_VAL_LT(now, timeout)) {
	    pj_time_val interval;
	    interval = timeout;
	    PJ_TIME_VAL_SUB(interval, now);
	    flush_events(PJ_TIME_VAL_MSEC(interval));
	}
    }

    /* 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 -140;
    }

    /* 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 -150;
    }

    /* Destroy txdata */
    pjsip_tx_data_dec_ref(tdata);

    return PJ_SUCCESS;
}
Пример #14
0
/*
 * main()
 *
 */
int main(int argc, char *argv[])
{
    pj_caching_pool cp;
    pj_pool_t *pool = NULL;
    pjsip_module mod_app =
    {
	NULL, NULL,		    /* prev, next.		*/
	{ "mod-app", 7 },	    /* Name.			*/
	-1,				    /* Id		*/
	PJSIP_MOD_PRIORITY_APPLICATION, /* Priority		*/
	NULL,			    /* load()			*/
	NULL,			    /* start()			*/
	NULL,			    /* stop()			*/
	NULL,			    /* unload()			*/
	&on_rx_request,		    /* on_rx_request()		*/
	NULL,			    /* on_rx_response()		*/
	NULL,			    /* on_tx_request.		*/
	NULL,			    /* on_tx_response()		*/
	NULL,			    /* on_tsx_state()		*/
    };
    int c;
    pj_status_t status;
    
    /* Must init PJLIB first: */
    status = pj_init();
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);


    /* Then init PJLIB-UTIL: */
    status = pjlib_util_init();
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);

    /* Must create a pool factory before we can allocate any memory. */
    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);

    /* Create global endpoint: */
    {
	/* Endpoint MUST be assigned a globally unique name.
	 * Ideally we should put hostname or public IP address, but
	 * we'll just use an arbitrary name here.
	 */

	/* Create the endpoint: */
	status = pjsip_endpt_create(&cp.factory, "sipstateless", 
				    &sip_endpt);
	PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
    }

    /* Parse arguments */
    pj_optind = 0;
    pj_list_init(&hdr_list);
    while ((c=pj_getopt(argc, argv , "H:")) != -1) {
	switch (c) {
	case 'H':
	    if (pool == NULL) {
		pool = pj_pool_create(&cp.factory, "sipstateless", 1000, 
				      1000, NULL);
	    } 
	    
	    if (pool) {
		char *name;
		name = strtok(pj_optarg, ":");
		if (name == NULL) {
		    puts("Error: invalid header format");
		    return 1;
		} else {
		    char *val = strtok(NULL, "\r\n");
		    pjsip_generic_string_hdr *h;
		    pj_str_t hname, hvalue;

		    hname = pj_str(name);
		    hvalue = pj_str(val);

		    h = pjsip_generic_string_hdr_create(pool, &hname, &hvalue);

		    pj_list_push_back(&hdr_list, h);

		    PJ_LOG(4,(THIS_FILE, "Header %s: %s added", name, val));
		}
	    }
	    break;
	default:
	    puts("Error: invalid argument");
	    usage();
	    return 1;
	}
    }

    if (pj_optind != argc) {
	code = atoi(argv[pj_optind]);
	if (code < 200 || code > 699) {
	    puts("Error: invalid status code");
	    usage();
	    return 1;
	}
    }

    PJ_LOG(4,(THIS_FILE, "Returning %d to incoming requests", code));


    /* 
     * Add UDP transport, with hard-coded port 
     */
#ifdef HAS_UDP_TRANSPORT
    {
	pj_sockaddr_in addr;

	addr.sin_family = pj_AF_INET();
	addr.sin_addr.s_addr = 0;
	addr.sin_port = pj_htons(5060);

	status = pjsip_udp_transport_start( sip_endpt, &addr, NULL, 1, NULL);
	if (status != PJ_SUCCESS) {
	    PJ_LOG(3,(THIS_FILE, 
		      "Error starting UDP transport (port in use?)"));
	    return 1;
	}
    }
#endif

#if HAS_TCP_TRANSPORT
    /* 
     * Add UDP transport, with hard-coded port 
     */
    {
	pj_sockaddr_in addr;

	addr.sin_family = pj_AF_INET();
	addr.sin_addr.s_addr = 0;
	addr.sin_port = pj_htons(5060);

	status = pjsip_tcp_transport_start(sip_endpt, &addr, 1, NULL);
	if (status != PJ_SUCCESS) {
	    PJ_LOG(3,(THIS_FILE, 
		      "Error starting TCP transport (port in use?)"));
	    return 1;
	}
    }
#endif

    /*
     * Register our module to receive incoming requests.
     */
    status = pjsip_endpt_register_module( sip_endpt, &mod_app);
    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);


    /* Done. Loop forever to handle incoming events. */
    PJ_LOG(3,(THIS_FILE, "Press Ctrl-C to quit.."));

    for (;;) {
	pjsip_endpt_handle_events(sip_endpt, NULL);
    }
}
Пример #15
0
/*
 * Perform round-robin/load balance test.
 */
static int round_robin_test(pj_pool_t *pool)
{
    enum { COUNT = 400, PCT_ALLOWANCE = 5 };
    unsigned i;
    struct server_hit
    {
	char *ip_addr;
	unsigned percent;
	unsigned hits;
    } server_hit[] =
    {
	{ "1.1.1.1", 3,  0 },
	{ "2.2.2.2", 65, 0 },
	{ "3.3.3.3", 32, 0 },
	{ "4.4.4.4", 0,  0 }
    };

    PJ_LOG(3,(THIS_FILE, " Performing round-robin/load-balance test.."));

    /* Do multiple resolve request to "example.com".
     * The resolver should select the server based on the weight proportion
     * the the servers in the SRV entry.
     */
    for (i=0; i<COUNT; ++i) {
	pjsip_host_info dest;
	struct result result;
	unsigned j;

	dest.type = PJSIP_TRANSPORT_UDP;
	dest.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP);
	dest.addr.host = pj_str("example.com");
	dest.addr.port = 0;

	result.status = 0x12345678;

	pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb);

	while (result.status == 0x12345678) {
	    int i = 0;
	    pj_time_val timeout = { 1, 0 };
	    pjsip_endpt_handle_events(endpt, &timeout);
	    if (i == 1)
		pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE);
	}

	/* Find which server was "hit" */
	for (j=0; j<PJ_ARRAY_SIZE(server_hit); ++j) {
	    pj_str_t tmp;
	    pj_in_addr a1;
	    pj_sockaddr_in *a2;

	    tmp = pj_str(server_hit[j].ip_addr);
	    a1 = pj_inet_addr(&tmp);
	    a2 = (pj_sockaddr_in*) &result.servers.entry[0].addr;

	    if (a1.s_addr == a2->sin_addr.s_addr) {
		server_hit[j].hits++;
		break;
	    }
	}

	if (j == PJ_ARRAY_SIZE(server_hit)) {
	    PJ_LOG(1,(THIS_FILE, "..round_robin_test() error 10: returned address mismatch"));
	    return 10;
	}
    }

    /* Print the actual hit rate */
    for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) {
	PJ_LOG(3,(THIS_FILE, " ..Server %s: weight=%d%%, hit %d%% times",
		  server_hit[i].ip_addr, server_hit[i].percent,
		  (server_hit[i].hits * 100) / COUNT));
    }

    /* Compare the actual hit with the weight proportion */
    for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) {
	int actual_pct = (server_hit[i].hits * 100) / COUNT;

	if (actual_pct + PCT_ALLOWANCE < (int)server_hit[i].percent ||
	    actual_pct - PCT_ALLOWANCE > (int)server_hit[i].percent)
	{
	    PJ_LOG(1,(THIS_FILE, 
		      "..round_robin_test() error 20: "
		      "hit rate difference for server %s (%d%%) is more than "
		      "tolerable allowance (%d%%)",
		      server_hit[i].ip_addr, 
		      actual_pct - server_hit[i].percent, 
		      PCT_ALLOWANCE));
	    return 20;
	}
    }

    PJ_LOG(3,(THIS_FILE, 
	      " Load balance test success, hit-rate is "
	      "within %d%% allowance", PCT_ALLOWANCE));
    return PJ_SUCCESS;
}
Пример #16
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.
 **
 *****************************************************************************
 */
static 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)" },
	{ 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)" },
	{ 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);

	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;
}
Пример #17
0
/* 
 * 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_mutex_unlock(tsx->mutex);
	    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;

}