Index: pjproject/trunk/pjnath/src/pjturn-client/client_main.c
===================================================================
--- pjproject/trunk/pjnath/src/pjstun-client/client_main.c	(revision 1611)
+++ pjproject/trunk/pjnath/src/pjturn-client/client_main.c	(revision 1867)
@@ -34,22 +34,23 @@
 
 
+struct peer
+{
+    pj_sock_t	sock;
+    pj_sockaddr	addr;
+};
+
+
 static struct global
 {
+    pj_caching_pool	 cp;
+    pj_pool_t		*pool;
     pj_stun_config	 stun_config;
-    pj_pool_t		*pool;
-    pj_caching_pool	 cp;
-    pj_timer_heap_t	*th;
-    pj_stun_session	*sess;
-    pj_sock_t		 sock;
-    pj_sock_t		 peer_sock;
     pj_thread_t		*thread;
     pj_bool_t		 quit;
-    pj_sockaddr_in	 peer_addr;
-    pj_sockaddr_in	 srv_addr;
-    pj_sockaddr_in	 relay_addr;
-    char		 data_buf[256];
-    char		*data;
-    pj_bool_t		 detect;
-    pj_status_t		 detect_result;
+
+    pj_turn_udp		*udp_rel;
+    pj_sockaddr		 relay_addr;
+
+    struct peer		 peer[2];
 } g;
 
@@ -62,10 +63,17 @@
     char    *password;
     char    *nonce;
-    char    *peer_addr;
     pj_bool_t use_fingerprint;
 } o;
 
 
+static int worker_thread(void *unused);
 static pj_status_t parse_addr(const char *input, pj_sockaddr_in *addr);
+static void turn_on_rx_data(pj_turn_udp *udp_rel,
+			    const pj_uint8_t *pkt,
+			    unsigned pkt_len,
+			    const pj_sockaddr_t *peer_addr,
+			    unsigned addr_len);
+static void turn_on_state(pj_turn_udp *udp_rel, pj_turn_state_t old_state,
+			  pj_turn_state_t new_state);
 
 
@@ -78,151 +86,154 @@
 }
 
-static pj_status_t on_send_msg(pj_stun_session *sess,
-			       const void *pkt,
-			       pj_size_t pkt_size,
-			       const pj_sockaddr_t *srv_addr,
-			       unsigned addr_len)
-{
-    pj_ssize_t len;
+#define CHECK(expr)	status=expr; \
+			if (status!=PJ_SUCCESS) { \
+			    my_perror(#expr, status); \
+			    return status; \
+			}
+
+static int init()
+{
+    int i;
     pj_status_t status;
 
-    len = pkt_size;
-    status = pj_sock_sendto(g.sock, pkt, &len, 0, srv_addr, addr_len);
-
-    if (status != PJ_SUCCESS)
-	my_perror("Error sending packet", status);
-
-    return status;
-}
-
-static void on_request_complete(pj_stun_session *sess,
-			        pj_status_t status,
-			        pj_stun_tx_data *tdata,
-			        const pj_stun_msg *response,
-				const pj_sockaddr_t *src_addr,
-				unsigned src_addr_len)
-{
-    PJ_UNUSED_ARG(src_addr);
-    PJ_UNUSED_ARG(src_addr_len);
-
-    if (status == PJ_SUCCESS) {
-	switch (response->hdr.type) {
-	case PJ_STUN_ALLOCATE_RESPONSE:
-	    {
-		pj_stun_relay_addr_attr *ar;
-		pj_stun_lifetime_attr *al;
-
-		al = (pj_stun_lifetime_attr*)
-		     pj_stun_msg_find_attr(response, 
-					   PJ_STUN_ATTR_LIFETIME, 0);
-		if (!al) {
-		    PJ_LOG(1,(THIS_FILE, "Error: LIFETIME attribute not present"));
-		    return;
-		}
-
-		ar = (pj_stun_relay_addr_attr*)
-		     pj_stun_msg_find_attr(response, 
-					   PJ_STUN_ATTR_RELAY_ADDR, 0);
-		if (ar) {
-		    pj_memcpy(&g.relay_addr, &ar->sockaddr.ipv4,
-			      sizeof(pj_sockaddr_in));
-		    PJ_LOG(3,(THIS_FILE, "Relay address is %s:%d",
-			      pj_inet_ntoa(g.relay_addr.sin_addr),
-			      (int)pj_ntohs(g.relay_addr.sin_port)));
-		} else {
-		    pj_memset(&g.relay_addr, 0, sizeof(g.relay_addr));
-		}
-
-		if (al->value == 0) {
-		    PJ_LOG(3,(THIS_FILE, "Relay deallocated"));
-		}
+    CHECK( pj_init() );
+    CHECK( pjlib_util_init() );
+    CHECK( pjnath_init() );
+
+    /* Check that server is specified */
+    if (!o.srv_addr) {
+	printf("Error: server must be specified\n");
+	return PJ_EINVAL;
+    }
+
+    pj_caching_pool_init(&g.cp, &pj_pool_factory_default_policy, 0);
+
+    g.pool = pj_pool_create(&g.cp.factory, "main", 1000, 1000, NULL);
+
+    /* Init global STUN config */
+    pj_stun_config_init(&g.stun_config, &g.cp.factory, 0, NULL, NULL);
+
+    /* Create global timer heap */
+    CHECK( pj_timer_heap_create(g.pool, 1000, &g.stun_config.timer_heap) );
+
+    /* Create global ioqueue */
+    CHECK( pj_ioqueue_create(g.pool, 16, &g.stun_config.ioqueue) );
+
+    /* 
+     * Create peers
+     */
+    for (i=0; i<PJ_ARRAY_SIZE(g.peer); ++i) {
+	int len;
+	pj_sockaddr addr;
+	pj_uint16_t port;
+
+	CHECK( pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &g.peer[i].sock) );
+	CHECK( pj_sock_bind_in(g.peer[i].sock, 0, 0) );
+
+	len = sizeof(addr);
+	CHECK( pj_sock_getsockname(g.peer[i].sock, &addr, &len) );
+	port = pj_sockaddr_get_port(&g.peer[i].addr);
+
+	CHECK( pj_gethostip(pj_AF_INET(), &g.peer[i].addr) );
+	pj_sockaddr_set_port(&g.peer[0].addr, port);
+
+    }
+
+    /* Start the worker thread */
+    CHECK( pj_thread_create(g.pool, "stun", &worker_thread, NULL, 0, 0, &g.thread) );
+
+
+    return PJ_SUCCESS;
+}
+
+
+static int shutdown()
+{
+    unsigned i;
+
+    if (g.thread) {
+	g.quit = 1;
+	pj_thread_join(g.thread);
+	pj_thread_destroy(g.thread);
+	g.thread = NULL;
+    }
+    if (g.udp_rel) {
+	pj_turn_udp_destroy(g.udp_rel);
+	g.udp_rel = NULL;
+    }
+    for (i=0; i<PJ_ARRAY_SIZE(g.peer); ++i) {
+	if (g.peer[i].sock) {
+	    pj_sock_close(g.peer[i].sock);
+	    g.peer[i].sock = 0;
+	}
+    }
+    if (g.stun_config.timer_heap) {
+	pj_timer_heap_destroy(g.stun_config.timer_heap);
+	g.stun_config.timer_heap = NULL;
+    }
+    if (g.stun_config.ioqueue) {
+	pj_ioqueue_destroy(g.stun_config.ioqueue);
+	g.stun_config.ioqueue = NULL;
+    }
+    if (g.pool) {
+	pj_pool_release(g.pool);
+	g.pool = NULL;
+    }
+    pj_pool_factory_dump(&g.cp.factory, PJ_TRUE);
+    pj_caching_pool_destroy(&g.cp);
+
+    return PJ_SUCCESS;
+}
+
+
+static int worker_thread(void *unused)
+{
+    PJ_UNUSED_ARG(unused);
+
+    while (!g.quit) {
+	const pj_time_val delay = {0, 10};
+	int i;
+	pj_fd_set_t readset;
+
+	/* Poll ioqueue for the TURN client */
+	pj_ioqueue_poll(g.stun_config.ioqueue, &delay);
+
+	/* Poll the timer heap */
+	pj_timer_heap_poll(g.stun_config.timer_heap, NULL);
+
+	/* Poll peer sockets */
+	PJ_FD_ZERO(&readset);
+	for (i=0; i<PJ_ARRAY_SIZE(g.peer); ++i) {
+	    PJ_FD_SET(g.peer[i].sock, &readset);
+	}
+
+	if (pj_sock_select(64, &readset, NULL, NULL, &delay) <= 0)
+	    continue;
+
+	/* Handle incoming data on peer socket */
+	for (i=0; i<PJ_ARRAY_SIZE(g.peer); ++i) {
+	    char buf[128];
+	    pj_ssize_t len;
+	    pj_sockaddr src_addr;
+	    int src_addr_len;
+	    pj_status_t status;
+
+	    if (!PJ_FD_ISSET(g.peer[i].sock, &readset))
+		continue;
+
+	    len = sizeof(buf);
+	    src_addr_len = sizeof(src_addr);
+
+	    status = pj_sock_recvfrom(g.peer[i].sock, buf, &len, 0, 
+				      &src_addr, &src_addr_len);
+	    if (status != PJ_SUCCESS) {
+		my_perror("recvfrom error", status);
+	    } else {
+		char addrinfo[80];
+		pj_sockaddr_print(&src_addr, addrinfo, sizeof(addrinfo), 3);
+		PJ_LOG(3,(THIS_FILE, "Peer%d received %d bytes from %s: %.*s",
+			  i, len, addrinfo, len, buf));
 	    }
-	    break;
 	}
-    } else {
-	my_perror("Client transaction error", status);
-    }
-}
-
-static int worker_thread(void *unused)
-{
-    PJ_UNUSED_ARG(unused);
-
-    while (!g.quit) {
-	pj_time_val timeout =  {0, 50};
-	pj_fd_set_t readset;
-	int n;
-
-	pj_timer_heap_poll(g.th, NULL);
-	pj_ioqueue_poll(g.stun_config.ioqueue, &timeout);
-
-	PJ_FD_ZERO(&readset);
-	PJ_FD_SET(g.sock, &readset);
-	PJ_FD_SET(g.peer_sock, &readset);
-
-	n = (g.peer_sock > g.sock) ? g.peer_sock : g.sock;
-	n = pj_sock_select(n+1, &readset, NULL, NULL, &timeout);
-	if (n > 0) {
-	    if (PJ_FD_ISSET(g.sock, &readset)) {
-		pj_uint8_t buffer[512];
-		pj_ssize_t len;
-		pj_sockaddr_in addr;
-		int addrlen;
-		pj_status_t rc;
-
-		len = sizeof(buffer);
-		addrlen = sizeof(addr);
-		rc = pj_sock_recvfrom(g.sock, buffer, &len, 0, &addr, &addrlen);
-		if (rc != PJ_SUCCESS || len <= 0)
-		    continue;
-
-		if (pj_stun_msg_check(buffer, len, PJ_STUN_IS_DATAGRAM)==PJ_SUCCESS) {
-		    rc = pj_stun_session_on_rx_pkt(g.sess, buffer, len, 
-						   OPTIONS, 
-						   NULL, &addr, addrlen);
-		    if (rc != PJ_SUCCESS)
-			my_perror("Error processing packet", rc);
-
-		} else {
-		    buffer[len] = '\0';
-		    PJ_LOG(3,(THIS_FILE, "Received data on client sock: %s", (char*)buffer));
-		}
-
-	    } else if (PJ_FD_ISSET(g.peer_sock, &readset)) {
-		pj_uint8_t buffer[512];
-		pj_ssize_t len;
-		pj_sockaddr_in addr;
-		int addrlen;
-		pj_status_t rc;
-
-		len = sizeof(buffer);
-		addrlen = sizeof(addr);
-		rc = pj_sock_recvfrom(g.peer_sock, buffer, &len, 0, &addr, &addrlen);
-		if (rc != PJ_SUCCESS || len <= 0)
-		    continue;
-
-		buffer[len] = '\0';
-
-		if (pj_stun_msg_check(buffer, len, PJ_STUN_IS_DATAGRAM)==PJ_SUCCESS) {
-		    pj_stun_msg *msg;
-
-		    rc = pj_stun_msg_decode(g.pool, (pj_uint8_t*)buffer, len, 0,
-					    &msg, NULL, NULL);
-		    if (rc != PJ_SUCCESS) {
-			my_perror("Error decoding packet on peer sock", rc);
-		    } else {
-			pj_stun_msg_dump(msg, (char*)buffer, sizeof(buffer), NULL);
-			PJ_LOG(3,(THIS_FILE, "Received STUN packet on peer sock: %s",
-				  buffer));
-		    }
-
-		} else {
-		    PJ_LOG(3,(THIS_FILE, "Received data on peer sock: %s", (char*)buffer));
-		}
-
-	    }
-
-	} else if (n < 0)
-	    pj_thread_sleep(50);
     }
 
@@ -230,90 +241,24 @@
 }
 
-static int init()
-{
-    pj_sockaddr addr;
-    pj_stun_session_cb stun_cb;
-    int len;
+static pj_status_t create_relay(void)
+{
+    pj_turn_udp_cb rel_cb;
+    pj_stun_auth_cred cred;
+    pj_str_t srv;
     pj_status_t status;
 
-    g.sock = PJ_INVALID_SOCKET;
-
-    status = pj_init();
-    status = pjlib_util_init();
-    status = pjnath_init();
-
-    pj_caching_pool_init(&g.cp, &pj_pool_factory_default_policy, 0);
-
-    if (o.srv_addr) {
-	pj_str_t s;
-	pj_uint16_t port;
-
-	if (o.srv_port)
-	    port = (pj_uint16_t) atoi(o.srv_port);
-	else
-	    port = PJ_STUN_PORT;
-
-	status = pj_sockaddr_in_init(&g.srv_addr, pj_cstr(&s, o.srv_addr), port);
-	if (status != PJ_SUCCESS) {
-	    my_perror("Invalid address", status);
-	    return status;
-	}
-
-	printf("Destination address set to %s:%d\n", o.srv_addr, (int)port);
-    } else {
-	printf("Error: address must be specified\n");
-	return PJ_EINVAL;
-    }
-
-    g.pool = pj_pool_create(&g.cp.factory, NULL, 1000, 1000, NULL);
-
-    status = pj_timer_heap_create(g.pool, 1000, &g.th);
-    pj_assert(status == PJ_SUCCESS);
-
-    pj_stun_config_init(&g.stun_config, &g.cp.factory, 0, NULL, g.th);
-    pj_assert(status == PJ_SUCCESS);
-
-    status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &g.peer_sock);
-    pj_assert(status == PJ_SUCCESS);
-
-    status = pj_sock_bind_in(g.peer_sock, 0, 0);
-    pj_assert(status == PJ_SUCCESS);
-
-    status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &g.sock);
-    pj_assert(status == PJ_SUCCESS);
-
-    status = pj_sockaddr_in_init(&addr.ipv4, NULL, 0);
-    pj_assert(status == PJ_SUCCESS);
-
-    addr.ipv4.sin_port = pj_htons((pj_uint16_t)LOCAL_PORT);
-    status = pj_sock_bind(g.sock, &addr, sizeof(addr));
-    pj_assert(status == PJ_SUCCESS);
-
-    len = sizeof(addr);
-    status = pj_sock_getsockname(g.sock, &addr, &len);
-    pj_assert(status == PJ_SUCCESS);
-
-    PJ_LOG(3,(THIS_FILE, "Listening on port %d", (int)pj_ntohs(addr.ipv4.sin_port)));
-
-    len = sizeof(g.peer_addr);
-    status = pj_sock_getsockname(g.peer_sock, &g.peer_addr, &len);
-    if (g.peer_addr.sin_addr.s_addr == 0)
-	pj_gethostip(pj_AF_INET(), (pj_sockaddr*)&g.peer_addr.sin_addr);
-
-    PJ_LOG(3,(THIS_FILE, "Peer is on port %d", (int)pj_ntohs(g.peer_addr.sin_port)));
-
-    pj_memset(&stun_cb, 0, sizeof(stun_cb));
-    stun_cb.on_send_msg = &on_send_msg;
-    stun_cb.on_request_complete = &on_request_complete;
-
-    status = pj_stun_session_create(&g.stun_config, NULL, &stun_cb, 
-				    o.use_fingerprint!=0, &g.sess);
-    pj_assert(status == PJ_SUCCESS);
+    if (g.udp_rel) {
+	PJ_LOG(1,(THIS_FILE, "Relay already created"));
+	return -1;
+    }
+
+    pj_bzero(&rel_cb, sizeof(rel_cb));
+    rel_cb.on_rx_data = &turn_on_rx_data;
+    rel_cb.on_state = &turn_on_state;
+    CHECK( pj_turn_udp_create(&g.stun_config, pj_AF_INET(), &rel_cb, 0,
+			      NULL, &g.udp_rel) );
 
     if (o.user_name) {
-	pj_stun_auth_cred cred;
-
 	pj_bzero(&cred, sizeof(cred));
-
 	cred.type = PJ_STUN_AUTH_CRED_STATIC;
 	cred.data.static_cred.realm = pj_str(o.realm);
@@ -322,224 +267,52 @@
 	cred.data.static_cred.data = pj_str(o.password);
 	cred.data.static_cred.nonce = pj_str(o.nonce);
-
-	pj_stun_session_set_credential(g.sess, &cred);
-	puts("Session credential set");
     } else {
-	puts("Credential not set");
-    }
-
-    if (o.peer_addr) {
-	if (parse_addr(o.peer_addr, &g.peer_addr)!=PJ_SUCCESS)
-	    return -1;
-    }
-
-    status = pj_ioqueue_create(g.pool, 16, &g.stun_config.ioqueue);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    status = pj_thread_create(g.pool, "stun", &worker_thread, NULL, 
-			      0, 0, &g.thread);
-    if (status != PJ_SUCCESS)
-	return status;
+	PJ_LOG(2,(THIS_FILE, "Warning: no credential is set"));
+    }
+
+    srv = pj_str(o.srv_addr);
+    CHECK( pj_turn_udp_init(g.udp_rel,				 /* the relay */
+			    &srv,				 /* srv addr */
+			    (o.srv_port?atoi(o.srv_port):PJ_STUN_PORT),/* def port */
+			    NULL,				 /* resolver */
+			    (o.user_name?&cred:NULL),		 /* credential */
+			    NULL)				 /* alloc param */
+			    );
 
     return PJ_SUCCESS;
 }
 
-
-static int shutdown()
-{
-    if (g.thread) {
-	g.quit = 1;
-	pj_thread_join(g.thread);
-	pj_thread_destroy(g.thread);
-	g.thread = NULL;
-    }
-    if (g.sess)
-	pj_stun_session_destroy(g.sess);
-    if (g.sock != PJ_INVALID_SOCKET)
-	pj_sock_close(g.sock);
-    if (g.th)
-	pj_timer_heap_destroy(g.th);
-    if (g.pool)
-	pj_pool_release(g.pool);
-
-    pj_pool_factory_dump(&g.cp.factory, PJ_TRUE);
-    pj_caching_pool_destroy(&g.cp);
-
-    return PJ_SUCCESS;
-}
-
-static void send_bind_request(void)
-{
-    pj_stun_tx_data *tdata;
-    pj_status_t rc;
-
-    rc = pj_stun_session_create_req(g.sess, PJ_STUN_BINDING_REQUEST, 
-				    PJ_STUN_MAGIC, NULL, &tdata);
-    pj_assert(rc == PJ_SUCCESS);
-
-    rc = pj_stun_session_send_msg(g.sess, PJ_FALSE, 
-				  &g.srv_addr, sizeof(g.srv_addr),
-				  tdata);
-    if (rc != PJ_SUCCESS)
-	my_perror("Error sending STUN request", rc);
-}
-
-static void send_allocate_request(pj_bool_t allocate)
-{
-    pj_stun_tx_data *tdata;
-    pj_status_t rc;
-
-    rc = pj_stun_session_create_req(g.sess, PJ_STUN_ALLOCATE_REQUEST, 
-				    PJ_STUN_MAGIC, NULL, &tdata);
-    pj_assert(rc == PJ_SUCCESS);
-
-
-    if (BANDWIDTH != -1) {
-	pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, 
-				  PJ_STUN_ATTR_BANDWIDTH, BANDWIDTH);
-    }
-
-    if (!allocate) {
-	pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, 
-				  PJ_STUN_ATTR_LIFETIME, 0);
-
-    } else {
-	if (LIFETIME != -1) {
-	    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, 
-				      PJ_STUN_ATTR_LIFETIME, LIFETIME);
-	}
-
-	if (REQ_TRANSPORT != -1) {
-	    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, 
-				      PJ_STUN_ATTR_REQ_TRANSPORT, REQ_TRANSPORT);
-	}
-
-	if (REQ_PORT_PROPS != -1) {
-	    pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, 
-				      PJ_STUN_ATTR_REQ_PORT_PROPS, REQ_PORT_PROPS);
-	}
-
-	if (REQ_IP) {
-	    pj_sockaddr_in addr;
-	    pj_str_t tmp;
-
-	    pj_sockaddr_in_init(&addr, pj_cstr(&tmp, REQ_IP), 0);
-	    pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
-					 PJ_STUN_ATTR_REQ_IP, PJ_FALSE,
-					 &addr, sizeof(addr));
-	}
-    }
-
-    rc = pj_stun_session_send_msg(g.sess, PJ_FALSE, 
-				  &g.srv_addr, sizeof(g.srv_addr),
-				  tdata);
-    pj_assert(rc == PJ_SUCCESS);
-}
-
-static void send_sad_request(pj_bool_t set)
-{
-    pj_stun_tx_data *tdata;
-    pj_status_t rc;
-
-    if (g.peer_addr.sin_addr.s_addr == 0 ||
-	g.peer_addr.sin_port == 0)
-    {
-	puts("Error: peer address is not set");
-	return;
-    }
-
-    rc = pj_stun_session_create_req(g.sess, 
-				    PJ_STUN_SET_ACTIVE_DESTINATION_REQUEST, 
-				    PJ_STUN_MAGIC, NULL, &tdata);
-    pj_assert(rc == PJ_SUCCESS);
-
-    if (set) {
-	pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
-				     PJ_STUN_ATTR_REMOTE_ADDR, PJ_FALSE,
-				     &g.peer_addr, sizeof(g.peer_addr));
-    }
-
-    rc = pj_stun_session_send_msg(g.sess, PJ_FALSE, 
-				  &g.srv_addr, sizeof(g.srv_addr),
-				  tdata);
-    pj_assert(rc == PJ_SUCCESS);
-}
-
-static void send_send_ind(void)
-{
-    pj_stun_tx_data *tdata;
-    int len;
-    pj_status_t rc;
-
-    if (g.peer_addr.sin_addr.s_addr == 0 ||
-	g.peer_addr.sin_port == 0)
-    {
-	puts("Error: peer address is not set");
-	return;
-    }
-
-    len = strlen(g.data);
-    if (len==0) {
-	puts("Error: data is not set");
-	return;
-    }
-
-    rc = pj_stun_session_create_ind(g.sess, PJ_STUN_SEND_INDICATION, &tdata);
-    pj_assert(rc == PJ_SUCCESS);
-
-    pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
-				  PJ_STUN_ATTR_REMOTE_ADDR, PJ_FALSE,
-				  &g.peer_addr, sizeof(g.peer_addr));
-    pj_stun_msg_add_binary_attr(tdata->pool, tdata->msg,
-				PJ_STUN_ATTR_DATA, (pj_uint8_t*)g.data, len);
-
-    rc = pj_stun_session_send_msg(g.sess, PJ_FALSE, 
-				  &g.srv_addr, sizeof(g.srv_addr),
-				  tdata);
-    pj_assert(rc == PJ_SUCCESS);
-
-}
-
-static void send_raw_data_to_srv(void)
-{
-    pj_ssize_t len;
-
-    if (g.srv_addr.sin_addr.s_addr == 0 ||
-	g.srv_addr.sin_port == 0)
-    {
-	puts("Error: server address is not set");
-	return;
-    }
-
-    len = strlen(g.data);
-    if (len==0) {
-	puts("Error: data is not set");
-	return;
-    }
-
-    len = strlen(g.data);
-    pj_sock_sendto(g.sock, g.data, &len, 0, &g.srv_addr, sizeof(g.srv_addr));
-}
-
-static void send_raw_data_to_relay(void)
-{
-    pj_ssize_t len;
-
-    if (g.relay_addr.sin_addr.s_addr == 0 ||
-	g.relay_addr.sin_port == 0)
-    {
-	puts("Error: relay address is not set");
-	return;
-    }
-
-    len = strlen(g.data);
-    if (len==0) {
-	puts("Error: data is not set");
-	return;
-    }
-
-    len = strlen(g.data);
-    pj_sock_sendto(g.peer_sock, g.data, &len, 0, &g.relay_addr, sizeof(g.relay_addr));
+static void destroy_relay(void)
+{
+    if (g.udp_rel) {
+	pj_turn_udp_destroy(g.udp_rel);
+	g.udp_rel = NULL;
+    }
+}
+
+
+static void turn_on_rx_data(pj_turn_udp *udp_rel,
+			    const pj_uint8_t *pkt,
+			    unsigned pkt_len,
+			    const pj_sockaddr_t *peer_addr,
+			    unsigned addr_len)
+{
+    char addrinfo[80];
+
+    pj_sockaddr_print(peer_addr, addrinfo, sizeof(addrinfo), 3);
+
+    PJ_LOG(3,(THIS_FILE, "Client received %d bytes data from %s: %.*s",
+	      pkt_len, addrinfo, pkt_len, pkt));
+}
+
+
+static void turn_on_state(pj_turn_udp *udp_rel, pj_turn_state_t old_state,
+			  pj_turn_state_t new_state)
+{
+    if (new_state == PJ_TURN_STATE_READY) {
+	pj_turn_session_info info;
+	pj_turn_udp_get_info(udp_rel, &info);
+	pj_memcpy(&g.relay_addr, &info.relay_addr, sizeof(pj_sockaddr));
+    }
 }
 
@@ -577,72 +350,41 @@
 }
 
-static void set_peer_addr(void)
-{
-    char addr[64];
-
-    printf("Current peer address: %s:%d\n", 
-	   pj_inet_ntoa(g.peer_addr.sin_addr), 
-	   pj_ntohs(g.peer_addr.sin_port));
-
-    printf("Input peer address in IP:PORT format: ");
+static void menu(void)
+{
+    pj_turn_session_info info;
+    char client_state[20], relay_addr[80], peer0_addr[80], peer1_addr[80];
+
+    if (g.udp_rel) {
+	pj_turn_udp_get_info(g.udp_rel, &info);
+	strcpy(client_state, pj_turn_state_name(info.state));
+	pj_sockaddr_print(&info.relay_addr, relay_addr, sizeof(relay_addr), 3);
+    } else {
+	strcpy(client_state, "NULL");
+	strcpy(relay_addr, "0.0.0.0:0");
+    }
+
+    pj_sockaddr_print(&g.peer[0].addr, peer0_addr, sizeof(peer0_addr), 3);
+    pj_sockaddr_print(&g.peer[1].addr, peer1_addr, sizeof(peer1_addr), 3);
+
+
+    puts("\n");
+    puts("+====================================================================+");
+    puts("|             CLIENT                |             PEER-0             |");
+    puts("|                                   |                                |");
+    printf("| State     : %12s          | Address: %21s |\n",
+	   client_state, peer0_addr);
+    printf("| Relay addr: %21s |                                |\n",
+	   relay_addr);
+    puts("|                                   | 0  Send data to relay address  |");
+    puts("| A      Allocate relay             +--------------------------------+	");
+    puts("| S[01]  Send data to peer 0/1      |             PEER-1             |");
+    puts("| B[01]  BindChannel to peer 0/1    |                                |");
+    printf("| X      Delete allocation          | Address: %21s |\n",
+	  peer1_addr);
+    puts("+-----------------------------------+                                |");
+    puts("| q  Quit                           | 1  Send data to relay adderss  |");
+    puts("+-----------------------------------+--------------------------------+");
+    printf(">>> ");
     fflush(stdout);
-    fgets(addr, sizeof(addr), stdin);
-
-    if (parse_addr(addr, &g.peer_addr) != PJ_SUCCESS) {
-	return;
-    }
-
-}
-
-
-static void nat_detect_cb(void *user_data,
-			  const pj_stun_nat_detect_result *res)
-{
-    g.detect_result = res->status;
-
-    if (res->status == PJ_SUCCESS) {
-	PJ_LOG(3,(THIS_FILE, "NAT successfully detected as %s", res->nat_type_name));
-    } else {
-	PJ_LOG(2,(THIS_FILE, "Error detecting NAT type: %s", res->status_text));
-    }
-}
-
-static pj_status_t perform_detection()
-{
-    pj_status_t status;
-
-    g.detect_result = PJ_EPENDING;
-    status = pj_stun_detect_nat_type(&g.srv_addr, &g.stun_config, NULL, 
-				     &nat_detect_cb);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    while (g.detect_result == PJ_EPENDING)
-	pj_thread_sleep(100);
-
-    status = g.detect_result;
-
-    return status;
-}
-
-
-static void menu(void)
-{
-    puts("Menu:");
-    puts("  d       Perform NAT detection");
-    printf("  pr      Set peer address (currently %s:%d)\n",
-	   pj_inet_ntoa(g.peer_addr.sin_addr), pj_ntohs(g.peer_addr.sin_port));
-    printf("  dt      Set data (currently \"%s\")\n", g.data);
-    puts("  br      Send Bind request");
-    puts("  ar      Send Allocate request");
-    puts("  dr      Send de-Allocate request");
-    puts("  sr      Send Set Active Destination request");
-    puts("  cr      Send clear Active Destination request");
-    puts("  si      Send data with Send Indication");
-    puts("  rw      Send raw data to TURN server");
-    puts("  rW      Send raw data to relay address");
-    puts("  q       Quit");
-    puts("");
-    printf("Choice: ");
 }
 
@@ -651,5 +393,8 @@
 {
     while (!g.quit) {
-	char input[10];
+	char input[32];
+	struct peer *peer;
+	pj_ssize_t len;
+	pj_status_t status;
 
 	menu();
@@ -657,41 +402,58 @@
 	fgets(input, sizeof(input), stdin);
 	
-	if (input[0] == 'd' && (input[1]=='\r' || input[1]=='\n')) {
-
-	    perform_detection();
-
-	} else if (input[0]=='d' && input[1]=='t') {
-	    printf("Input data: ");
-	    fgets(g.data, sizeof(g.data_buf), stdin);
-	    
-	} else if (input[0]=='p' && input[1]=='r') {
-	    set_peer_addr();
-	    
-	} else if (input[0]=='b' && input[1]=='r') {
-	    send_bind_request();
-	    
-	} else if (input[0]=='a' && input[1]=='r') {
-	    send_allocate_request(PJ_TRUE);
-	    
-	} else if (input[0]=='d' && input[1]=='r') {
-	    send_allocate_request(PJ_FALSE);
-	    
-	} else if (input[0]=='s' && input[1]=='r') {
-	    send_sad_request(PJ_TRUE);
-	    
-	} else if (input[0]=='c' && input[1]=='r') {
-	    send_sad_request(PJ_FALSE);
-	    
-	} else if (input[0]=='s' && input[1]=='i') {
-	    send_send_ind();
-	    
-	} else if (input[0]=='r' && input[1]=='w') {
-	    send_raw_data_to_srv();
-	    
-	} else if (input[0]=='r' && input[1]=='W') {
-	    send_raw_data_to_relay();
-	    
-	} else if (input[0]=='q') {
-	    g.quit = 1;
+	switch (input[0]) {
+	case 'A':
+	    create_relay();
+	    break;
+	case 'S':
+	    if (g.udp_rel == NULL) {
+		puts("Error: no relay");
+		continue;
+	    }
+	    if (input[1] != '0' && input[1] != '1') {
+		puts("Usage: S0 or S1");
+		continue;
+	    }
+	    peer = &g.peer[input[1]-'0'];
+	    strcpy(input, "Hello from client");
+	    status = pj_turn_udp_sendto(g.udp_rel, input, strlen(input)+1, 
+					&peer->addr, 
+					pj_sockaddr_get_len(&peer->addr));
+	    if (status != PJ_SUCCESS)
+		my_perror("turn_udp_sendto() failed", status);
+	    break;
+	case 'B':
+	    if (g.udp_rel == NULL) {
+		puts("Error: no relay");
+		continue;
+	    }
+	    if (input[1] != '0' && input[1] != '1') {
+		puts("Usage: B0 or B1");
+		continue;
+	    }
+	    peer = &g.peer[input[1]-'0'];
+	    status = pj_turn_udp_bind_channel(g.udp_rel, &peer->addr,
+					      pj_sockaddr_get_len(&peer->addr));
+	    if (status != PJ_SUCCESS)
+		my_perror("turn_udp_bind_channel() failed", status);
+	    break;
+	case 'X':
+	    if (g.udp_rel == NULL) {
+		puts("Error: no relay");
+		continue;
+	    }
+	    destroy_relay();
+	    break;
+	case '0':
+	case '1':
+	    peer = &g.peer[input[1]-'0'];
+	    sprintf(input, "Hello from peer%d", input[0]-'0');
+	    len = strlen(input)+1;
+	    pj_sock_sendto(peer->sock, input, &len, 0, &g.relay_addr, 
+			   pj_sockaddr_get_len(&g.relay_addr));
+	    break;
+	case 'q':
+	    g.quit = PJ_TRUE;
+	    break;
 	}
     }
@@ -701,10 +463,9 @@
 static void usage(void)
 {
-    puts("Usage: pjstun_client TARGET [OPTIONS]");
+    puts("Usage: pjturn_client TARGET [OPTIONS]");
     puts("");
     puts("where TARGET is \"host[:port]\"");
     puts("");
     puts("and OPTIONS:");
-    puts(" --detect, -d      Perform NAT type detection first");
     puts(" --realm, -r       Set realm of the credential");
     puts(" --username, -u    Set username of the credential");
@@ -712,6 +473,4 @@
     puts(" --nonce, -N       Set NONCE");   
     puts(" --fingerprint, -F Use fingerprint for outgoing requests");
-    puts(" --peer, -P        Set peer address (address is in HOST:PORT format)");
-    puts(" --data, -D        Set data");
     puts(" --help, -h");
 }
@@ -720,5 +479,4 @@
 {
     struct pj_getopt_option long_options[] = {
-	{ "detect",	0, 0, 'd'},
 	{ "realm",	1, 0, 'r'},
 	{ "username",	1, 0, 'u'},
@@ -726,5 +484,4 @@
 	{ "nonce",	1, 0, 'N'},
 	{ "fingerprint",0, 0, 'F'},
-	{ "peer",	1, 0, 'P'},
 	{ "data",	1, 0, 'D'},
 	{ "help",	0, 0, 'h'}
@@ -734,12 +491,6 @@
     pj_status_t status;
 
-    g.data = g.data_buf;
-    pj_ansi_strcpy(g.data, "Hello world");
-
-    while((c=pj_getopt_long(argc,argv, "r:u:p:N:dhF", long_options, &opt_id))!=-1) {
+    while((c=pj_getopt_long(argc,argv, "r:u:p:N:hF", long_options, &opt_id))!=-1) {
 	switch (c) {
-	case 'd':
-	    g.detect = PJ_TRUE;
-	    break;
 	case 'r':
 	    o.realm = pj_optarg;
@@ -759,10 +510,4 @@
 	case 'F':
 	    o.use_fingerprint = PJ_TRUE;
-	    break;
-	case 'P':
-	    o.peer_addr = pj_optarg;
-	    break;
-	case 'D':
-	    g.data = pj_optarg;
 	    break;
 
@@ -787,14 +532,10 @@
     }
 
-    status = init();
-    if (status != PJ_SUCCESS)
+    if ((status=init()) != 0)
 	goto on_return;
     
-    if (g.detect) {
-	status = perform_detection();
-	if (status != PJ_SUCCESS)
-	    goto on_return;
-    }
-
+    if ((status=create_relay()) != 0)
+	goto on_return;
+    
     console_main();
 
