Index: pjproject/trunk/pjsip-apps/build/Samples-vc.mak
===================================================================
--- pjproject/trunk/pjsip-apps/build/Samples-vc.mak	(revision 2591)
+++ pjproject/trunk/pjsip-apps/build/Samples-vc.mak	(revision 2592)
@@ -62,4 +62,5 @@
 	  $(BINDIR)\confbench.exe \
 	  $(BINDIR)\encdec.exe \
+	  $(BINDIR)\icedemo.exe \
 	  $(BINDIR)\latency.exe \
 	  $(BINDIR)\level.exe \
Index: pjproject/trunk/pjsip-apps/build/Samples.mak
===================================================================
--- pjproject/trunk/pjsip-apps/build/Samples.mak	(revision 2591)
+++ pjproject/trunk/pjsip-apps/build/Samples.mak	(revision 2592)
@@ -17,4 +17,5 @@
 	   confsample \
 	   encdec \
+	   icedemo \
 	   latency \
 	   level \
Index: pjproject/trunk/pjsip-apps/build/samples.dsp
===================================================================
--- pjproject/trunk/pjsip-apps/build/samples.dsp	(revision 2591)
+++ pjproject/trunk/pjsip-apps/build/samples.dsp	(revision 2592)
@@ -111,4 +111,8 @@
 # Begin Source File
 
+SOURCE=..\src\samples\icedemo.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\samples\invtester.c
 # End Source File
Index: pjproject/trunk/pjsip-apps/build/samples.vcproj
===================================================================
--- pjproject/trunk/pjsip-apps/build/samples.vcproj	(revision 2591)
+++ pjproject/trunk/pjsip-apps/build/samples.vcproj	(revision 2592)
@@ -543,4 +543,8 @@
 			</File>
 			<File
+				RelativePath="..\src\samples\auddemo.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\samples\confbench.c"
 				>
@@ -556,4 +560,8 @@
 			<File
 				RelativePath="..\src\samples\footprint.c"
+				>
+			</File>
+			<File
+				RelativePath="..\src\samples\icedemo.c"
 				>
 			</File>
Index: pjproject/trunk/pjsip-apps/src/samples/icedemo.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/icedemo.c	(revision 2592)
+++ pjproject/trunk/pjsip-apps/src/samples/icedemo.c	(revision 2592)
@@ -0,0 +1,1054 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjnath.h>
+
+
+#define THIS_FILE   "icedemo.c"
+
+static struct app_t
+{
+    struct options
+    {
+	unsigned    comp_cnt;
+	pj_str_t    ns;
+	pj_bool_t   no_host;
+	pj_str_t    stun_srv;
+	pj_str_t    turn_srv;
+	pj_bool_t   turn_tcp;
+	pj_str_t    turn_username;
+	pj_str_t    turn_password;
+	pj_bool_t   turn_fingerprint;
+    } opt;
+
+    pj_caching_pool	 cp;
+    pj_pool_t		*pool;
+    pj_thread_t		*thread;
+    pj_bool_t		 thread_quit_flag;
+    pj_ice_strans_cfg	 ice_cfg;
+    pj_ice_strans	*icest;
+
+    struct rem_info
+    {
+	char		 ufrag[80];
+	char		 pwd[80];
+	unsigned	 comp_cnt;
+	pj_sockaddr	 def_addr[PJ_ICE_MAX_COMP];
+	unsigned	 cand_cnt;
+	pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
+    } rem;
+
+} icedemo;
+
+static void icedemo_perror(const char *title, pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(status, errmsg, sizeof(errmsg));
+    PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
+}
+
+static void err_exit(const char *title, pj_status_t status)
+{
+    if (status != PJ_SUCCESS) {
+	icedemo_perror(title, status);
+    }
+    PJ_LOG(3,(THIS_FILE, "Shutting down.."));
+
+    if (icedemo.icest)
+	pj_ice_strans_destroy(icedemo.icest);
+    
+    pj_thread_sleep(500);
+
+    icedemo.thread_quit_flag = PJ_TRUE;
+    if (icedemo.thread) {
+	pj_thread_join(icedemo.thread);
+	pj_thread_destroy(icedemo.thread);
+    }
+
+    if (icedemo.ice_cfg.stun_cfg.ioqueue)
+	pj_ioqueue_destroy(icedemo.ice_cfg.stun_cfg.ioqueue);
+
+    if (icedemo.ice_cfg.stun_cfg.timer_heap)
+	pj_timer_heap_destroy(icedemo.ice_cfg.stun_cfg.timer_heap);
+
+    pj_caching_pool_destroy(&icedemo.cp);
+
+    pj_shutdown();
+    exit(status != PJ_SUCCESS);
+}
+
+#define CHECK(expr)	status=expr; \
+			if (status!=PJ_SUCCESS) { \
+			    err_exit(#expr, status); \
+			}
+
+static pj_status_t icedemo_handle_events(unsigned max_msec,
+					 unsigned *p_count)
+{
+    enum { MAX_NET_EVENTS = 1 };
+    pj_time_val max_timeout = {0, 0};
+    pj_time_val timeout = { 0, 0};
+    unsigned count = 0, net_event_count = 0;
+    int c;
+
+    max_timeout.msec = max_msec;
+
+    /* Poll the timer to run it and also to retrieve the earliest entry. */
+    timeout.sec = timeout.msec = 0;
+    c = pj_timer_heap_poll( icedemo.ice_cfg.stun_cfg.timer_heap, &timeout );
+    if (c > 0)
+	count += c;
+
+    /* timer_heap_poll should never ever returns negative value, or otherwise
+     * ioqueue_poll() will block forever!
+     */
+    pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
+    if (timeout.msec >= 1000) timeout.msec = 999;
+
+    /* compare the value with the timeout to wait from timer, and use the minimum value. */
+    if (PJ_TIME_VAL_GT(timeout, max_timeout))
+	timeout = max_timeout;
+
+    /* Poll ioqueue. 
+     * Repeat polling the ioqueue while we have immediate events, because
+     * timer heap may process more than one events, so if we only process
+     * one network events at a time (such as when IOCP backend is used),
+     * the ioqueue may have trouble keeping up with the request rate.
+     *
+     * For example, for each send() request, one network event will be
+     *   reported by ioqueue for the send() completion. If we don't poll
+     *   the ioqueue often enough, the send() completion will not be
+     *   reported in timely manner.
+     */
+    do {
+	c = pj_ioqueue_poll( icedemo.ice_cfg.stun_cfg.ioqueue, &timeout);
+	if (c < 0) {
+	    pj_status_t err = pj_get_netos_error();
+	    pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
+	    if (p_count)
+		*p_count = count;
+	    return err;
+	} else if (c == 0) {
+	    break;
+	} else {
+	    net_event_count += c;
+	    timeout.sec = timeout.msec = 0;
+	}
+    } while (c > 0 && net_event_count < MAX_NET_EVENTS);
+
+    count += net_event_count;
+    if (p_count)
+	*p_count = count;
+
+    return PJ_SUCCESS;
+
+}
+
+static int icedemo_worker_thread(void *unused)
+{
+    PJ_UNUSED_ARG(unused);
+
+    while (!icedemo.thread_quit_flag) {
+	icedemo_handle_events(500, NULL);
+    }
+
+    return 0;
+}
+
+static void icedemo_on_rx_data(pj_ice_strans *ice_st,
+			       unsigned comp_id, 
+			       void *pkt, pj_size_t size,
+			       const pj_sockaddr_t *src_addr,
+			       unsigned src_addr_len)
+{
+    char ipstr[PJ_INET6_ADDRSTRLEN+10];
+
+    PJ_UNUSED_ARG(ice_st);
+    PJ_UNUSED_ARG(src_addr_len);
+    PJ_UNUSED_ARG(pkt);
+
+    PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s",
+	      comp_id, size,
+	      pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3)));
+}
+
+static void icedemo_on_ice_complete(pj_ice_strans *ice_st, 
+				    pj_ice_strans_op op,
+				    pj_status_t status)
+{
+    const char *opname = 
+	(op==PJ_ICE_STRANS_OP_INIT? "initialization" :
+	    (op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));
+
+    PJ_UNUSED_ARG(ice_st);
+
+    if (status == PJ_SUCCESS) {
+	PJ_LOG(3,(THIS_FILE, "ICE %s successful", opname));
+    } else {
+	char errmsg[PJ_ERR_MSG_SIZE];
+
+	pj_strerror(status, errmsg, sizeof(errmsg));
+	PJ_LOG(1,(THIS_FILE, "ICE %s failed: %s", opname, errmsg));
+    }
+}
+
+static pj_status_t icedemo_init(void)
+{
+    pj_status_t status;
+
+    /* Initialize the libraries before anything else */
+    CHECK( pj_init() );
+    CHECK( pjlib_util_init() );
+    CHECK( pjnath_init() );
+
+    /* Must create pool factory, where memory allocations come from */
+    pj_caching_pool_init(&icedemo.cp, NULL, 0);
+
+    /* Init our ICE settings with null values */
+    pj_ice_strans_cfg_default(&icedemo.ice_cfg);
+
+    icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
+
+    /* Create application memory pool */
+    icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo", 512, 512, NULL);
+
+    /* Create timer heap for timer stuff */
+    CHECK( pj_timer_heap_create(icedemo.pool, 100, &icedemo.ice_cfg.stun_cfg.timer_heap) );
+
+    /* and create ioqueue for network I/O stuff */
+    CHECK( pj_ioqueue_create(icedemo.pool, 16, &icedemo.ice_cfg.stun_cfg.ioqueue) );
+
+    /* something must poll the timer heap and ioqueue, unless we're on Symbian */
+    CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread, NULL, 0, 0, &icedemo.thread) );
+
+    icedemo.ice_cfg.af = pj_AF_INET();
+
+    /* Create DNS resolver if nameserver is set */
+    if (icedemo.opt.ns.slen) {
+	CHECK( pj_dns_resolver_create(&icedemo.cp.factory, 
+				      "resolver", 
+				      0, 
+				      icedemo.ice_cfg.stun_cfg.timer_heap, 
+				      icedemo.ice_cfg.stun_cfg.ioqueue, 
+				      &icedemo.ice_cfg.resolver) );
+
+	CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1, 
+				      &icedemo.opt.ns, NULL) );
+    }
+
+    /* -= Start initializing ICE stream transport config =- */
+
+    /* Disable host candidates? */
+    icedemo.ice_cfg.stun.no_host_cands = icedemo.opt.no_host;
+
+    /* Configure STUN/srflx candidate resolution */
+    if (icedemo.opt.stun_srv.slen) {
+	char *pos;
+
+	/* Command line option may contain port number */
+	if ((pos=pj_strchr(&icedemo.opt.stun_srv, ':')) != NULL) {
+	    icedemo.ice_cfg.stun.server.ptr = icedemo.opt.stun_srv.ptr;
+	    icedemo.ice_cfg.stun.server.slen = (pos - icedemo.opt.stun_srv.ptr);
+
+	    icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1);
+	} else {
+	    icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv;
+	    icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
+	}
+    }
+
+    /* Configure TURN candidate */
+    if (icedemo.opt.turn_srv.slen) {
+	char *pos;
+
+	/* Command line option may contain port number */
+	if ((pos=pj_strchr(&icedemo.opt.turn_srv, ':')) != NULL) {
+	    icedemo.ice_cfg.turn.server.ptr = icedemo.opt.turn_srv.ptr;
+	    icedemo.ice_cfg.turn.server.slen = (pos - icedemo.opt.turn_srv.ptr);
+
+	    icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1);
+	} else {
+	    icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv;
+	    icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
+	}
+
+	/* TURN credential */
+	icedemo.ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
+	icedemo.ice_cfg.turn.auth_cred.data.static_cred.username = icedemo.opt.turn_username;
+	icedemo.ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
+	icedemo.ice_cfg.turn.auth_cred.data.static_cred.data = icedemo.opt.turn_password;
+
+	/* Connection type to TURN server */
+	if (icedemo.opt.turn_tcp)
+	    icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
+	else
+	    icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
+
+	/* You may further customize TURN settings, e.g.:
+	    icedemo.ice_cfg.turn.alloc_param.lifetime = 300
+	 */
+    }
+
+    /* -= That's it for now, initialization is complete =- */
+    return PJ_SUCCESS;
+}
+
+static void icedemo_create_instance(void)
+{
+    pj_ice_strans_cb icecb;
+    pj_status_t status;
+
+    if (icedemo.icest != NULL) {
+	puts("ICE instance already created, destroy it first");
+	return;
+    }
+
+    /* init the callback */
+    pj_bzero(&icecb, sizeof(icecb));
+    icecb.on_rx_data = icedemo_on_rx_data;
+    icecb.on_ice_complete = icedemo_on_ice_complete;
+
+    /* create the instance */
+    status = pj_ice_strans_create("icedemo",		    /* object name  */
+				&icedemo.ice_cfg,	    /* settings	    */
+				icedemo.opt.comp_cnt,	    /* comp_cnt	    */
+				NULL,			    /* user data    */
+				&icecb,			    /* callback	    */
+				&icedemo.icest)		    /* instance ptr */
+				;
+    if (status != PJ_SUCCESS)
+	icedemo_perror("error creating ice", status);
+}
+
+static void reset_rem_info(void)
+{
+    pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
+}
+
+static void icedemo_destroy_instance(void)
+{
+    if (icedemo.icest == NULL) {
+	PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+	return;
+    }
+
+    pj_ice_strans_destroy(icedemo.icest);
+    icedemo.icest = NULL;
+
+    reset_rem_info();
+}
+
+static void icedemo_init_session(unsigned rolechar)
+{
+    pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ? 
+				PJ_ICE_SESS_ROLE_CONTROLLING : 
+				PJ_ICE_SESS_ROLE_CONTROLLED);
+    pj_status_t status;
+
+    if (icedemo.icest == NULL) {
+	PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+	return;
+    }
+
+    if (pj_ice_strans_has_sess(icedemo.icest)) {
+	PJ_LOG(1,(THIS_FILE, "Error: Session already created"));
+	return;
+    }
+
+    status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL);
+    if (status != PJ_SUCCESS)
+	icedemo_perror("error creating session", status);
+
+    reset_rem_info();
+}
+
+static void icedemo_stop_session(void)
+{
+    pj_status_t status;
+
+    if (icedemo.icest == NULL) {
+	PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+	return;
+    }
+
+    if (!pj_ice_strans_has_sess(icedemo.icest)) {
+	PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
+	return;
+    }
+
+    status = pj_ice_strans_stop_ice(icedemo.icest);
+    if (status != PJ_SUCCESS)
+	icedemo_perror("error stopping session", status);
+
+    reset_rem_info();
+}
+
+
+#define PRINT(fmt, arg0, arg1, arg2, arg3, arg4, arg5)	    \
+	printed = pj_ansi_snprintf(p, maxlen - (p-buffer),  \
+				   fmt, arg0, arg1, arg2, arg3, arg4, arg5); \
+	if (printed <= 0) return -PJ_ETOOSMALL; \
+	p += printed
+
+static int print_cand(char buffer[], unsigned maxlen,
+		      const pj_ice_sess_cand *cand)
+{
+    char ipaddr[PJ_INET6_ADDRSTRLEN];
+    char *p = buffer;
+    int printed;
+
+    PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
+	  (int)cand->foundation.slen,
+	  cand->foundation.ptr,
+	  (unsigned)cand->comp_id,
+	  cand->prio,
+	  pj_sockaddr_print(&cand->addr, ipaddr, 
+			    sizeof(ipaddr), 0),
+	  (unsigned)pj_sockaddr_get_port(&cand->addr));
+
+    switch (cand->type) {
+    case PJ_ICE_CAND_TYPE_HOST:
+	PRINT("host\n", 0, 0, 0, 0, 0, 0);
+	break;
+    case PJ_ICE_CAND_TYPE_SRFLX:
+    case PJ_ICE_CAND_TYPE_RELAYED:
+    case PJ_ICE_CAND_TYPE_PRFLX:
+	PRINT("%s raddr %s rport %d\n",
+	      pj_ice_get_cand_type_name(cand->type),
+	      pj_sockaddr_print(&cand->rel_addr, ipaddr,
+				sizeof(ipaddr), 0),
+	      (int)pj_sockaddr_get_port(&cand->rel_addr),
+	      0, 0, 0);
+	break;
+    default:
+	pj_assert(!"Invalid candidate type");
+	return -PJ_EBUG;
+    }
+
+    if (p == buffer+maxlen)
+	return -PJ_ETOOSMALL;
+
+    *p = '\0';
+
+    return p-buffer;
+}
+
+/* Encode ICE information in SDP */
+static int encode_session(char buffer[], unsigned maxlen)
+{
+    char *p = buffer;
+    unsigned comp;
+    int printed;
+    pj_str_t local_ufrag, local_pwd;
+    pj_status_t status;
+
+    PRINT("v=0\no=- 3414953978 3414953978 IN IP4 127.0.0.1\ns=ice\nt=0 0\n", 
+	  0, 0, 0, 0, 0, 0);
+
+    /* Get ufrag and pwd from current session */
+    pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
+				NULL, NULL);
+
+    PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n",
+	   (int)local_ufrag.slen,
+	   local_ufrag.ptr,
+	   (int)local_pwd.slen,
+	   local_pwd.ptr, 
+	   0, 0);
+
+    for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) {
+	unsigned j, cand_cnt;
+	pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
+	char ipaddr[PJ_INET6_ADDRSTRLEN];
+
+	status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
+	if (status != PJ_SUCCESS)
+	    return -status;
+
+	if (comp==0) {
+	    PRINT("m=audio %d RTP/AVP 0\n"
+		  "c=IN IP4 %s\n",
+		  (int)pj_sockaddr_get_port(&cand[0].addr),
+		  pj_sockaddr_print(&cand[0].addr, ipaddr,
+				    sizeof(ipaddr), 0),
+		  0, 0, 0, 0);
+	} else if (comp==1) {
+	    PRINT("a=rtcp:%d IN IP4 %s\n",
+		  (int)pj_sockaddr_get_port(&cand[0].addr),
+		  pj_sockaddr_print(&cand[0].addr, ipaddr,
+				    sizeof(ipaddr), 0),
+		  0, 0, 0, 0);
+	} else {
+	    PRINT("a=Xice-defcand:%d IN IP4 %s\n",
+		  (int)pj_sockaddr_get_port(&cand[0].addr),
+		  pj_sockaddr_print(&cand[0].addr, ipaddr,
+				    sizeof(ipaddr), 0),
+		  0, 0, 0, 0);
+	}
+
+	status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
+					  &cand_cnt, cand);
+	if (status != PJ_SUCCESS)
+	    return -status;
+
+	for (j=0; j<cand_cnt; ++j) {
+	    printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
+	    if (printed < 0)
+		return -PJ_ETOOSMALL;
+	    p += printed;
+	}
+    }
+
+    if (p == buffer+maxlen)
+	return -PJ_ETOOSMALL;
+
+    *p = '\0';
+    return p - buffer;
+}
+
+static void icedemo_show_ice(void)
+{
+    static char buffer[1000];
+    int len;
+
+    if (icedemo.icest == NULL) {
+	PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+	return;
+    }
+
+    puts("General info");
+    puts("---------------");
+    printf("Component count    : %d\n", icedemo.opt.comp_cnt);
+    printf("Status             : ");
+    if (pj_ice_strans_sess_is_complete(icedemo.icest))
+	puts("negotiation complete");
+    else if (pj_ice_strans_sess_is_running(icedemo.icest))
+	puts("negotiation is in progress");
+    else if (pj_ice_strans_has_sess(icedemo.icest))
+	puts("session ready");
+    else
+	puts("session not created");
+
+    if (!pj_ice_strans_has_sess(icedemo.icest)) {
+	puts("Create the session first to see more info");
+	return;
+    }
+
+    printf("Negotiated comp_cnt: %d\n", 
+	   pj_ice_strans_get_running_comp_cnt(icedemo.icest));
+    printf("Role               : %s\n",
+	   pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
+	   "controlled" : "controlling");
+
+    len = encode_session(buffer, sizeof(buffer));
+    if (len < 0)
+	err_exit("not enough buffer to show ICE status", -len);
+
+    puts("");
+    printf("Local SDP (paste this to remote host):\n"
+	   "--------------------------------------\n"
+	   "%s\n", buffer);
+
+
+    puts("");
+    puts("Remote info:\n"
+	 "----------------------");
+    if (icedemo.rem.cand_cnt==0) {
+	puts("No remote info yet");
+    } else {
+	unsigned i;
+
+	printf("Remote ufrag       : %s\n", icedemo.rem.ufrag);
+	printf("Remote password    : %s\n", icedemo.rem.pwd);
+	printf("Remote cand. cnt.  : %d\n", icedemo.rem.cand_cnt);
+
+	for (i=0; i<icedemo.rem.cand_cnt; ++i) {
+	    len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
+	    if (len < 0)
+		err_exit("not enough buffer to show ICE status", -len);
+
+	    printf("  %s", buffer);
+	}
+    }
+}
+
+static void icedemo_input_remote(void)
+{
+    char linebuf[80];
+    unsigned comp0_port = 0;
+    char     comp0_addr[80];
+    pj_bool_t done = PJ_FALSE;
+
+    puts("Paste SDP from remote host, end with empty line");
+
+    reset_rem_info();
+
+    comp0_addr[0] = '\0';
+
+    while (!done) {
+	int len;
+	char *line;
+
+	printf(">");
+	if (stdout) fflush(stdout);
+
+	if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
+	    break;
+
+	len = strlen(linebuf);
+	while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
+	    linebuf[--len] = '\0';
+
+	line = linebuf;
+	while (len && pj_isspace(*line))
+	    ++line, --len;
+
+	if (len==0)
+	    break;
+
+	switch (line[0]) {
+	case 'm':
+	    {
+		int cnt;
+		char media[32], portstr[32];
+
+		cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
+		if (cnt != 2) {
+		    PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
+		    goto on_error;
+		}
+
+		comp0_port = atoi(portstr);
+	    }
+	    break;
+	case 'c':
+	    {
+		int cnt;
+		char c[32], net[32], ip[80];
+		
+		cnt = sscanf(line+2, "%s %s %s", c, net, ip);
+		if (cnt != 3) {
+		    PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
+		    goto on_error;
+		}
+
+		strcpy(comp0_addr, ip);
+	    }
+	    break;
+	case 'a':
+	    {
+		char *attr = strtok(line+2, ": \t\r\n");
+		if (strcmp(attr, "ice-ufrag")==0) {
+		    strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
+		} else if (strcmp(attr, "ice-pwd")==0) {
+		    strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
+		} else if (strcmp(attr, "rtcp")==0) {
+		    char *val = attr+strlen(attr)+1;
+		    int af, cnt;
+		    int port;
+		    char net[32], ip[64];
+		    pj_str_t tmp_addr;
+		    pj_status_t status;
+
+		    cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
+		    if (cnt != 3) {
+			PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
+			goto on_error;
+		    }
+
+		    if (strchr(ip, ':'))
+			af = pj_AF_INET6();
+		    else
+			af = pj_AF_INET();
+
+		    pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
+		    tmp_addr = pj_str(ip);
+		    status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
+						      &tmp_addr);
+		    if (status != PJ_SUCCESS) {
+			PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
+			goto on_error;
+		    }
+		    pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
+
+		} else if (strcmp(attr, "candidate")==0) {
+		    char *sdpcand = attr+strlen(attr)+1;
+		    int af, cnt;
+		    char foundation[32], transport[12], ipaddr[80], type[32];
+		    pj_str_t tmpaddr;
+		    int comp_id, prio, port;
+		    pj_ice_sess_cand *cand;
+		    pj_status_t status;
+
+		    cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
+				 foundation,
+				 &comp_id,
+				 transport,
+				 &prio,
+				 ipaddr,
+				 &port,
+				 type);
+		    if (cnt != 7) {
+			PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
+			goto on_error;
+		    }
+
+		    cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
+		    pj_bzero(cand, sizeof(*cand));
+		    
+		    if (strcmp(type, "host")==0)
+			cand->type = PJ_ICE_CAND_TYPE_HOST;
+		    else if (strcmp(type, "srflx")==0)
+			cand->type = PJ_ICE_CAND_TYPE_SRFLX;
+		    else if (strcmp(type, "relay")==0)
+			cand->type = PJ_ICE_CAND_TYPE_RELAYED;
+		    else {
+			PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'", 
+				   type));
+			goto on_error;
+		    }
+
+		    cand->comp_id = (pj_uint8_t)comp_id;
+		    pj_strdup2(icedemo.pool, &cand->foundation, foundation);
+		    cand->prio = prio;
+		    
+		    if (strchr(ipaddr, ':'))
+			af = pj_AF_INET6();
+		    else
+			af = pj_AF_INET();
+
+		    tmpaddr = pj_str(ipaddr);
+		    pj_sockaddr_init(af, &cand->addr, NULL, 0);
+		    status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
+		    if (status != PJ_SUCCESS) {
+			PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
+				  ipaddr));
+			goto on_error;
+		    }
+
+		    pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
+
+		    ++icedemo.rem.cand_cnt;
+
+		    if (cand->comp_id > icedemo.rem.comp_cnt)
+			icedemo.rem.comp_cnt = cand->comp_id;
+		}
+	    }
+	    break;
+	}
+    }
+
+    if (icedemo.rem.cand_cnt==0 ||
+	icedemo.rem.ufrag[0]==0 ||
+	icedemo.rem.pwd[0]==0 ||
+	icedemo.rem.comp_cnt == 0)
+    {
+	PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
+	goto on_error;
+    }
+
+    if (comp0_port==0 || comp0_addr[0]=='\0') {
+	PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
+	goto on_error;
+    } else {
+	int af;
+	pj_str_t tmp_addr;
+	pj_status_t status;
+
+	if (strchr(comp0_addr, ':'))
+	    af = pj_AF_INET6();
+	else
+	    af = pj_AF_INET();
+
+	pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
+	tmp_addr = pj_str(comp0_addr);
+	status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
+					  &tmp_addr);
+	if (status != PJ_SUCCESS) {
+	    PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
+	    goto on_error;
+	}
+	pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
+    }
+
+    printf("Done, %d remote candidates added\n", icedemo.rem.cand_cnt);
+    return;
+
+on_error:
+    reset_rem_info();
+}
+
+static void icedemo_start_nego(void)
+{
+    pj_str_t rufrag, rpwd;
+    pj_status_t status;
+
+    if (icedemo.icest == NULL) {
+	PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+	return;
+    }
+
+    if (!pj_ice_strans_has_sess(icedemo.icest)) {
+	PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
+	return;
+    }
+
+    if (icedemo.rem.cand_cnt == 0) {
+	PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
+	return;
+    }
+
+    status = pj_ice_strans_start_ice(icedemo.icest, 
+				     pj_cstr(&rufrag, icedemo.rem.ufrag),
+				     pj_cstr(&rpwd, icedemo.rem.pwd),
+				     icedemo.rem.cand_cnt,
+				     icedemo.rem.cand);
+    if (status != PJ_SUCCESS)
+	icedemo_perror("Error starting ICE", status);
+    else
+	PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
+}
+
+static void icedemo_send_data(unsigned comp_id, const char *data)
+{
+    pj_status_t status;
+
+    if (icedemo.icest == NULL) {
+	PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
+	return;
+    }
+
+    if (!pj_ice_strans_has_sess(icedemo.icest)) {
+	PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
+	return;
+    }
+
+    if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
+	PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
+	return;
+    }
+
+    if (comp_id > pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
+	PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
+	return;
+    }
+
+    status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
+				  &icedemo.rem.def_addr[comp_id-1],
+				  pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
+    if (status != PJ_SUCCESS)
+	icedemo_perror("Error sending data", status);
+    else
+	PJ_LOG(3,(THIS_FILE, "Data sent"));
+}
+
+static void icedemo_help_menu(void)
+{
+}
+
+
+static void icedemo_print_menu(void)
+{
+    puts("");
+    puts("+----------------------------------------------------------------------+");
+    puts("|                    M E N U                                           |");
+    puts("+---+------------------------------------------------------------------+");
+    puts("| c | create           Create the instance                             |");
+    puts("| d | destroy          Destroy the instance                            |");
+    puts("| i | init o|a         Initialize ICE session as offerer or answerer   |");
+    puts("| e | stop             End/stop ICE session                            |");
+    puts("| s | show             Display local ICE info                          |");
+    puts("| r | remote           Input remote ICE info                           |");
+    puts("| b | start            Begin ICE negotiation                           |");
+    puts("| x | send <compid> .. Send data to remote                             |");
+    puts("+---+------------------------------------------------------------------+");
+    puts("| h |  help            * Help! *                                       |");
+    puts("| q |  quit            Quit                                            |");
+    puts("+----------------------------------------------------------------------+");
+}
+
+
+static void icedemo_console(void)
+{
+    pj_bool_t app_quit = PJ_FALSE;
+
+    while (!app_quit) {
+	char input[80], *cmd;
+	const char *SEP = " \t\r\n";
+	int len;
+
+	icedemo_print_menu();
+
+	printf("Input: ");
+	if (stdout) fflush(stdout);
+
+	pj_bzero(input, sizeof(input));
+	if (fgets(input, sizeof(input), stdin) == NULL)
+	    break;
+
+	len = strlen(input);
+	while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
+	    input[--len] = '\0';
+
+	cmd = strtok(input, SEP);
+	if (!cmd)
+	    continue;
+
+	if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
+	    icedemo_create_instance();
+	} else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
+	    icedemo_destroy_instance();
+	} else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
+	    char *role = strtok(NULL, SEP);
+	    if (role)
+		icedemo_init_session(*role);
+	    else
+		puts("error: Role required");
+	} else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
+	    icedemo_stop_session();
+	} else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
+	    icedemo_show_ice();
+	} else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
+	    icedemo_input_remote();
+	} else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
+	    icedemo_start_nego();
+	} else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
+	    char *strcomp = strtok(NULL, SEP);
+
+	    if (!strcmp) {
+		PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
+	    } else {
+		char *data = strcomp + strlen(strcomp) + 1;
+		icedemo_send_data(atoi(strcomp), data);
+	    }
+	} else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
+	    icedemo_help_menu();
+	} else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
+	    app_quit = PJ_TRUE;
+	} else {
+	    printf("Invalid command '%s'\n", cmd);
+	}
+    }
+}
+
+
+static void icedemo_usage()
+{
+    puts("Usage: icedemo [optons]");
+    printf("icedemo v%s by pjsip.org\n", pj_get_version());
+    puts("");
+    puts("General options:");
+    puts(" --comp-cnt, -c N          Component count (default=1)");
+    puts(" --nameserver, -n IP       Configure nameserver to activate DNS SRV");
+    puts("                           resolution");
+    puts(" --no-host, -H             Disable host candidates");
+    puts(" --help, -h                Display this screen.");
+    puts("");
+    puts("STUN related options:");
+    puts(" --stun-srv, -s HOSTDOM    Enable srflx candidate by resolving to STUN server.");
+    puts("                           HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
+    puts("                           name if DNS SRV resolution is used.");
+    puts("");
+    puts("TURN related options:");
+    puts(" --turn-srv, -t HOSTDOM    Enable relayed candidate by using this TURN server.");
+    puts("                           HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
+    puts("                           name if DNS SRV resolution is used.");
+    puts(" --turn-tcp, -T            Use TCP to connect to TURN server");
+    puts(" --turn-username, -u UID   Set TURN username of the credential to UID");
+    puts(" --turn-password, -p PWD   Set password of the credential to WPWD");
+    puts(" --turn-fingerprint, -F    Use fingerprint for outgoing TURN requests");
+    puts("");
+}
+
+
+int main(int argc, char *argv[])
+{
+    struct pj_getopt_option long_options[] = {
+	{ "comp-cnt",           1, 0, 'c'},
+	{ "nameserver",		1, 0, 'n'},
+	{ "no-host",		0, 0, 'H'},
+	{ "help",		0, 0, 'h'},
+	{ "stun-srv",		1, 0, 's'},
+	{ "turn-srv",		1, 0, 't'},
+	{ "turn-tcp",		0, 0, 'T'},
+	{ "turn-username",	1, 0, 'u'},
+	{ "turn-password",	1, 0, 'p'},
+	{ "turn-fingerprint",	0, 0, 'F'}
+    };
+    int c, opt_id;
+    pj_status_t status;
+
+    icedemo.opt.comp_cnt = 1;
+
+    while((c=pj_getopt_long(argc,argv, "n:s:t:u:p:HhTF", long_options, &opt_id))!=-1) {
+	switch (c) {
+	case 'c':
+	    icedemo.opt.comp_cnt = atoi(pj_optarg);
+	    if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
+		puts("Invalid component count value");
+		return 1;
+	    }
+	    break;
+	case 'n':
+	    icedemo.opt.ns = pj_str(pj_optarg);
+	    break;
+	case 'H':
+	    icedemo.opt.no_host = PJ_TRUE;
+	    break;
+	case 'h':
+	    icedemo_usage();
+	    return 0;
+	case 's':
+	    icedemo.opt.stun_srv = pj_str(pj_optarg);
+	    break;
+	case 't':
+	    icedemo.opt.turn_srv = pj_str(pj_optarg);
+	    break;
+	case 'T':
+	    icedemo.opt.turn_tcp = PJ_TRUE;
+	    break;
+	case 'u':
+	    icedemo.opt.turn_username = pj_str(pj_optarg);
+	    break;
+	case 'p':
+	    icedemo.opt.turn_password = pj_str(pj_optarg);
+	    break;
+	case 'F':
+	    icedemo.opt.turn_fingerprint = PJ_TRUE;
+	    break;
+	default:
+	    printf("Argument \"%s\" is not valid. Use -h to see help",
+		   argv[pj_optind]);
+	    return 1;
+	}
+    }
+
+    status = icedemo_init();
+    if (status != PJ_SUCCESS)
+	return 1;
+
+    icedemo_console();
+
+    err_exit("Quitting..", PJ_SUCCESS);
+    return 0;
+}
