Major major modifications related to ticket #485 (support for TURN-07):
 - Added STUN socket transport pj_stun_sock
 - Integration of TURN-07 to ICE
 - Major refactoring in ICE stream transport to make it simpler
 - Major modification (i.e. API change) in almost everywhere else
 - Much more elaborate STUN, TURN, and ICE tests in pjnath-test



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1988 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjnath/src/pjnath-test/turn_sock_test.c b/pjnath/src/pjnath-test/turn_sock_test.c
new file mode 100644
index 0000000..52d2242
--- /dev/null
+++ b/pjnath/src/pjnath-test/turn_sock_test.c
@@ -0,0 +1,515 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include "test.h"
+#include "server.h"
+
+#define SRV_DOMAIN	"pjsip.lab.domain"
+#define KA_INTERVAL	50
+
+struct test_result
+{
+    unsigned    state_called;
+    unsigned    rx_data_cnt;
+};
+
+struct test_session
+{
+    pj_pool_t		*pool;
+    pj_stun_config	*stun_cfg;
+    pj_turn_sock	*turn_sock;
+    pj_dns_resolver	*resolver;
+    test_server		*test_srv;
+
+    pj_bool_t		 destroy_called;
+    int			 destroy_on_state;
+    struct test_result	 result;
+};
+
+struct test_session_cfg
+{
+    struct {
+	pj_bool_t	enable_dns_srv;
+	int		destroy_on_state;
+    } client;
+
+    struct {
+	pj_uint32_t	flags;
+	pj_bool_t	respond_allocate;
+	pj_bool_t	respond_refresh;
+    } srv;
+};
+
+static void turn_on_rx_data(pj_turn_sock *turn_sock,
+			    void *pkt,
+			    unsigned pkt_len,
+			    const pj_sockaddr_t *peer_addr,
+			    unsigned addr_len);
+static void turn_on_state(pj_turn_sock *turn_sock, 
+			  pj_turn_state_t old_state,
+			  pj_turn_state_t new_state);
+
+static void destroy_session(struct test_session *sess)
+{
+    if (sess->resolver) {
+	pj_dns_resolver_destroy(sess->resolver, PJ_TRUE);
+	sess->resolver = NULL;
+    }
+
+    if (sess->turn_sock) {
+	if (!sess->destroy_called) {
+	    sess->destroy_called = PJ_TRUE;
+	    pj_turn_sock_destroy(sess->turn_sock);
+	}
+	sess->turn_sock = NULL;
+    }
+
+    if (sess->test_srv) {
+	destroy_test_server(sess->test_srv);
+	sess->test_srv = NULL;
+    }
+
+    if (sess->pool) {
+	pj_pool_release(sess->pool);
+    }
+}
+
+
+
+static int create_test_session(pj_stun_config  *stun_cfg,
+			       const struct test_session_cfg *cfg,
+			       struct test_session **p_sess)
+{
+    struct test_session *sess;
+    pj_pool_t *pool;
+    pj_turn_sock_cb turn_sock_cb;
+    pj_turn_alloc_param alloc_param;
+    pj_stun_auth_cred cred;
+    pj_status_t status;
+
+    /* Create client */
+    pool = pj_pool_create(mem, "turnclient", 512, 512, NULL);
+    sess = PJ_POOL_ZALLOC_T(pool, struct test_session);
+    sess->pool = pool;
+    sess->stun_cfg = stun_cfg;
+    sess->destroy_on_state = cfg->client.destroy_on_state;
+
+    pj_bzero(&turn_sock_cb, sizeof(turn_sock_cb));
+    turn_sock_cb.on_rx_data = &turn_on_rx_data;
+    turn_sock_cb.on_state = &turn_on_state;
+    status = pj_turn_sock_create(sess->stun_cfg, pj_AF_INET(), PJ_TURN_TP_UDP,
+				 &turn_sock_cb, 0, sess, &sess->turn_sock);
+    if (status != PJ_SUCCESS) {
+	destroy_session(sess);
+	return -20;
+    }
+
+    /* Create test server */
+    status = create_test_server(sess->stun_cfg, cfg->srv.flags,
+				SRV_DOMAIN, &sess->test_srv);
+    if (status != PJ_SUCCESS) {
+	destroy_session(sess);
+	return -30;
+    }
+
+    sess->test_srv->turn_respond_allocate = cfg->srv.respond_allocate;
+    sess->test_srv->turn_respond_refresh = cfg->srv.respond_refresh;
+
+    /* Create client resolver */
+    status = pj_dns_resolver_create(mem, "resolver", 0, sess->stun_cfg->timer_heap,
+				    sess->stun_cfg->ioqueue, &sess->resolver);
+    if (status != PJ_SUCCESS) {
+	destroy_session(sess);
+	return -40;
+
+    } else {
+	pj_str_t dns_srv = pj_str("127.0.0.1");
+	pj_uint16_t dns_srv_port = (pj_uint16_t) DNS_SERVER_PORT;
+	status = pj_dns_resolver_set_ns(sess->resolver, 1, &dns_srv, &dns_srv_port);
+
+	if (status != PJ_SUCCESS) {
+	    destroy_session(sess);
+	    return -50;
+	}
+    }
+
+    /* Init TURN credential */
+    pj_bzero(&cred, sizeof(cred));
+    cred.type = PJ_STUN_AUTH_CRED_STATIC;
+    cred.data.static_cred.realm = pj_str(SRV_DOMAIN);
+    cred.data.static_cred.username = pj_str(TURN_USERNAME);
+    cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
+    cred.data.static_cred.data = pj_str(TURN_PASSWD);
+
+    /* Init TURN allocate parameter */
+    pj_turn_alloc_param_default(&alloc_param);
+    alloc_param.ka_interval = KA_INTERVAL;
+
+    /* Start the client */
+    if (cfg->client.enable_dns_srv) {
+	/* Use DNS SRV to resolve server, may fallback to DNS A */
+	pj_str_t domain = pj_str(SRV_DOMAIN);
+	status = pj_turn_sock_alloc(sess->turn_sock, &domain, TURN_SERVER_PORT,
+				    sess->resolver, &cred, &alloc_param);
+
+    } else {
+	/* Explicitly specify server address */
+	pj_str_t host = pj_str("127.0.0.1");
+	status = pj_turn_sock_alloc(sess->turn_sock, &host, TURN_SERVER_PORT,
+				    NULL, &cred, &alloc_param);
+
+    }
+
+    if (status != PJ_SUCCESS) {
+	if (cfg->client.destroy_on_state >= PJ_TURN_STATE_READY) {
+	    destroy_session(sess);
+	    return -70;
+	}
+    }
+
+    *p_sess = sess;
+    return 0;
+}
+
+
+static void turn_on_rx_data(pj_turn_sock *turn_sock,
+			    void *pkt,
+			    unsigned pkt_len,
+			    const pj_sockaddr_t *peer_addr,
+			    unsigned addr_len)
+{
+    struct test_session *sess;
+
+    PJ_UNUSED_ARG(pkt);
+    PJ_UNUSED_ARG(pkt_len);
+    PJ_UNUSED_ARG(peer_addr);
+    PJ_UNUSED_ARG(addr_len);
+
+    sess = (struct test_session*) pj_turn_sock_get_user_data(turn_sock);
+    if (sess == NULL)
+	return;
+
+    sess->result.rx_data_cnt++;
+}
+
+
+static void turn_on_state(pj_turn_sock *turn_sock, 
+			  pj_turn_state_t old_state,
+			  pj_turn_state_t new_state)
+{
+    struct test_session *sess;
+    unsigned i, mask;
+
+    PJ_UNUSED_ARG(old_state);
+
+    sess = (struct test_session*) pj_turn_sock_get_user_data(turn_sock);
+    if (sess == NULL)
+	return;
+
+    /* This state must not be called before */
+    pj_assert((sess->result.state_called & (1<<new_state)) == 0);
+
+    /* new_state must be greater than old_state */
+    pj_assert(new_state > old_state);
+
+    /* must not call any greater state before */
+    mask = 0;
+    for (i=new_state+1; i<31; ++i) mask |= (1 << i);
+
+    pj_assert((sess->result.state_called & mask) == 0);
+
+    sess->result.state_called |= (1 << new_state);
+
+    if (new_state >= sess->destroy_on_state && !sess->destroy_called) {
+	sess->destroy_called = PJ_TRUE;
+	pj_turn_sock_destroy(turn_sock);
+    }
+
+    if (new_state >= PJ_TURN_STATE_DESTROYING) {
+	pj_turn_sock_set_user_data(sess->turn_sock, NULL);
+	sess->turn_sock = NULL;
+    }
+}
+
+
+/////////////////////////////////////////////////////////////////////
+
+static int state_progression_test(pj_stun_config  *stun_cfg)
+{
+    struct test_session_cfg test_cfg = 
+    {
+	{   /* Client cfg */
+	    /* DNS SRV */   /* Destroy on state */
+	    PJ_TRUE,	    0xFFFF
+	},
+	{   /* Server cfg */
+	    0xFFFFFFFF,	    /* flags */
+	    PJ_TRUE,	    /* respond to allocate  */
+	    PJ_TRUE	    /* respond to refresh   */
+	}
+    };
+    struct test_session *sess;
+    unsigned i;
+    int rc;
+
+    PJ_LOG(3,("", "  state progression tests"));
+
+    for (i=0; i<=1; ++i) {
+	enum { TIMEOUT = 60 };
+	pjlib_state pjlib_state;
+	pj_turn_session_info info;
+	struct test_result result;
+	pj_time_val tstart;
+
+	PJ_LOG(3,("", "   %s DNS SRV resolution",
+	              (i==0? "without" : "with")));
+
+	capture_pjlib_state(stun_cfg, &pjlib_state);
+
+	test_cfg.client.enable_dns_srv = i;
+
+	rc = create_test_session(stun_cfg, &test_cfg, &sess);
+	if (rc != 0)
+	    return rc;
+
+	pj_bzero(&info, sizeof(info));
+
+	/* Wait until state is READY */
+	pj_gettimeofday(&tstart);
+	while (sess->turn_sock) {
+	    pj_time_val now;
+
+	    poll_events(stun_cfg, 10, PJ_FALSE);
+	    rc = pj_turn_sock_get_info(sess->turn_sock, &info);
+	    if (rc!=PJ_SUCCESS)
+		break;
+
+	    if (info.state >= PJ_TURN_STATE_READY)
+		break;
+
+	    pj_gettimeofday(&now);
+	    if (now.sec - tstart.sec > TIMEOUT) {
+		PJ_LOG(3,("", "    timed-out"));
+		break;
+	    }
+	}
+
+	if (info.state != PJ_TURN_STATE_READY) {
+	    PJ_LOG(3,("", "    error: state is not READY"));
+	    destroy_session(sess);
+	    return -130;
+	}
+
+	/* Deallocate */
+	pj_turn_sock_destroy(sess->turn_sock);
+
+	/* Wait for couple of seconds.
+	 * We can't poll the session info since the session may have
+	 * been destroyed
+	 */
+	poll_events(stun_cfg, 2000, PJ_FALSE);
+	sess->turn_sock = NULL;
+	pj_memcpy(&result, &sess->result, sizeof(result));
+	destroy_session(sess);
+
+	/* Check the result */
+	if ((result.state_called & (1<<PJ_TURN_STATE_RESOLVING)) == 0) {
+	    PJ_LOG(3,("", "    error: PJ_TURN_STATE_RESOLVING is not called"));
+	    return -140;
+	}
+
+	if ((result.state_called & (1<<PJ_TURN_STATE_RESOLVED)) == 0) {
+	    PJ_LOG(3,("", "    error: PJ_TURN_STATE_RESOLVED is not called"));
+	    return -150;
+	}
+
+	if ((result.state_called & (1<<PJ_TURN_STATE_ALLOCATING)) == 0) {
+	    PJ_LOG(3,("", "    error: PJ_TURN_STATE_ALLOCATING is not called"));
+	    return -155;
+	}
+
+	if ((result.state_called & (1<<PJ_TURN_STATE_READY)) == 0) {
+	    PJ_LOG(3,("", "    error: PJ_TURN_STATE_READY is not called"));
+	    return -160;
+	}
+
+	if ((result.state_called & (1<<PJ_TURN_STATE_DEALLOCATING)) == 0) {
+	    PJ_LOG(3,("", "    error: PJ_TURN_STATE_DEALLOCATING is not called"));
+	    return -170;
+	}
+
+	if ((result.state_called & (1<<PJ_TURN_STATE_DEALLOCATED)) == 0) {
+	    PJ_LOG(3,("", "    error: PJ_TURN_STATE_DEALLOCATED is not called"));
+	    return -180;
+	}
+
+	if ((result.state_called & (1<<PJ_TURN_STATE_DESTROYING)) == 0) {
+	    PJ_LOG(3,("", "    error: PJ_TURN_STATE_DESTROYING is not called"));
+	    return -190;
+	}
+
+	poll_events(stun_cfg, 500, PJ_FALSE);
+	rc = check_pjlib_state(stun_cfg, &pjlib_state);
+	if (rc != 0) {
+	    PJ_LOG(3,("", "    error: memory/timer-heap leak detected"));
+	    return rc;
+	}
+    }
+
+    return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////
+
+static int destroy_test(pj_stun_config  *stun_cfg,
+			pj_bool_t with_dns_srv,
+			pj_bool_t in_callback)
+{
+    struct test_session_cfg test_cfg = 
+    {
+	{   /* Client cfg */
+	    /* DNS SRV */   /* Destroy on state */
+	    PJ_TRUE,	    0xFFFF
+	},
+	{   /* Server cfg */
+	    0xFFFFFFFF,	    /* flags */
+	    PJ_TRUE,	    /* respond to allocate  */
+	    PJ_TRUE	    /* respond to refresh   */
+	}
+    };
+    struct test_session *sess;
+    int target_state;
+    int rc;
+
+    PJ_LOG(3,("", "  destroy test %s %s",
+	          (in_callback? "in callback" : ""),
+		  (with_dns_srv? "with DNS srv" : "")
+		  ));
+
+    test_cfg.client.enable_dns_srv = with_dns_srv;
+
+    for (target_state=PJ_TURN_STATE_RESOLVING; target_state<=PJ_TURN_STATE_READY; ++target_state) {
+	enum { TIMEOUT = 60 };
+	pjlib_state pjlib_state;
+	pj_turn_session_info info;
+	pj_time_val tstart;
+
+	capture_pjlib_state(stun_cfg, &pjlib_state);
+
+	PJ_LOG(3,("", "   %s", pj_turn_state_name((pj_turn_state_t)target_state)));
+
+	if (in_callback)
+	    test_cfg.client.destroy_on_state = target_state;
+
+	rc = create_test_session(stun_cfg, &test_cfg, &sess);
+	if (rc != 0)
+	    return rc;
+
+	if (in_callback) {
+	    pj_gettimeofday(&tstart);
+	    rc = 0;
+	    while (sess->turn_sock) {
+		pj_time_val now;
+
+		poll_events(stun_cfg, 100, PJ_FALSE);
+
+		pj_gettimeofday(&now);
+		if (now.sec - tstart.sec > TIMEOUT) {
+		    rc = -7;
+		    break;
+		}
+	    }
+
+	} else {
+	    pj_gettimeofday(&tstart);
+	    rc = 0;
+	    while (sess->turn_sock) {
+		pj_time_val now;
+
+		poll_events(stun_cfg, 1, PJ_FALSE);
+
+		pj_turn_sock_get_info(sess->turn_sock, &info);
+		
+		if (info.state >= target_state) {
+		    pj_turn_sock_destroy(sess->turn_sock);
+		    break;
+		}
+
+		pj_gettimeofday(&now);
+		if (now.sec - tstart.sec > TIMEOUT) {
+		    rc = -8;
+		    break;
+		}
+	    }
+	}
+
+
+	if (rc != 0) {
+	    PJ_LOG(3,("", "    error: timeout"));
+	    return rc;
+	}
+
+	poll_events(stun_cfg, 1000, PJ_FALSE);
+	destroy_session(sess);
+
+	rc = check_pjlib_state(stun_cfg, &pjlib_state);
+	if (rc != 0) {
+	    PJ_LOG(3,("", "    error: memory/timer-heap leak detected"));
+	    return rc;
+	}
+    }
+
+    return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////
+
+int turn_sock_test(void)
+{
+    pj_pool_t *pool;
+    pj_stun_config stun_cfg;
+    int i, rc = 0;
+
+    pool = pj_pool_create(mem, "turntest", 512, 512, NULL);
+    rc = create_stun_config(pool, &stun_cfg);
+    if (rc != PJ_SUCCESS) {
+	pj_pool_release(pool);
+	return -2;
+    }
+
+    rc = state_progression_test(&stun_cfg);
+    if (rc != 0) 
+	goto on_return;
+
+    for (i=0; i<=1; ++i) {
+	int j;
+	for (j=0; j<=1; ++j) {
+	    rc = destroy_test(&stun_cfg, i, j);
+	    if (rc != 0)
+		goto on_return;
+	}
+    }
+
+on_return:
+    destroy_stun_config(&stun_cfg);
+    pj_pool_release(pool);
+    return rc;
+}
+