| /* $Id$ */ |
| /* |
| * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| #include "test.h" |
| #include "server.h" |
| |
| enum |
| { |
| NO = 0, |
| YES = 1, |
| SRV = 3, |
| }; |
| |
| #define NODELAY 0xFFFFFFFF |
| #define SRV_DOMAIN "pjsip.lab.domain" |
| #define MAX_THREADS 16 |
| |
| #define THIS_FILE "ice_test.c" |
| #define INDENT " " |
| |
| /* Client flags */ |
| enum |
| { |
| WRONG_TURN = 1, |
| DEL_ON_ERR = 2, |
| }; |
| |
| |
| /* Test results */ |
| struct test_result |
| { |
| pj_status_t init_status; /* init successful? */ |
| pj_status_t nego_status; /* negotiation successful? */ |
| unsigned rx_cnt[4]; /* Number of data received */ |
| }; |
| |
| /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ |
| /* Test session configuration */ |
| struct test_cfg |
| { |
| pj_ice_sess_role role; /* Role. */ |
| unsigned comp_cnt; /* Component count */ |
| unsigned enable_host; /* Enable host candidates */ |
| unsigned enable_stun; /* Enable srflx candidates */ |
| unsigned enable_turn; /* Enable turn candidates */ |
| unsigned client_flag; /* Client flags */ |
| |
| unsigned answer_delay; /* Delay before sending SDP */ |
| unsigned send_delay; /* unused */ |
| unsigned destroy_delay; /* unused */ |
| |
| struct test_result expected;/* Expected result */ |
| |
| pj_bool_t nom_regular; /* Use regular nomination? */ |
| }; |
| |
| /* ICE endpoint state */ |
| struct ice_ept |
| { |
| struct test_cfg cfg; /* Configuratino. */ |
| pj_ice_strans *ice; /* ICE stream transport */ |
| struct test_result result;/* Test result. */ |
| |
| pj_str_t ufrag; /* username fragment. */ |
| pj_str_t pass; /* password */ |
| }; |
| |
| /* Session param */ |
| struct sess_param |
| { |
| unsigned worker_cnt; |
| unsigned worker_timeout; |
| pj_bool_t worker_quit; |
| |
| pj_bool_t destroy_after_create; |
| pj_bool_t destroy_after_one_done; |
| }; |
| |
| /* The test session */ |
| struct test_sess |
| { |
| pj_pool_t *pool; |
| pj_stun_config *stun_cfg; |
| pj_dns_resolver *resolver; |
| |
| struct sess_param *param; |
| |
| test_server *server; |
| |
| pj_thread_t *worker_threads[MAX_THREADS]; |
| |
| unsigned server_flag; |
| struct ice_ept caller; |
| struct ice_ept callee; |
| }; |
| |
| |
| static void ice_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); |
| static void ice_on_ice_complete(pj_ice_strans *ice_st, |
| pj_ice_strans_op op, |
| pj_status_t status); |
| static void destroy_sess(struct test_sess *sess, unsigned wait_msec); |
| |
| /* Create ICE stream transport */ |
| static int create_ice_strans(struct test_sess *test_sess, |
| struct ice_ept *ept, |
| pj_ice_strans **p_ice) |
| { |
| pj_ice_strans *ice; |
| pj_ice_strans_cb ice_cb; |
| pj_ice_strans_cfg ice_cfg; |
| pj_sockaddr hostip; |
| char serverip[PJ_INET6_ADDRSTRLEN]; |
| pj_status_t status; |
| |
| status = pj_gethostip(pj_AF_INET(), &hostip); |
| if (status != PJ_SUCCESS) |
| return -1030; |
| |
| pj_sockaddr_print(&hostip, serverip, sizeof(serverip), 0); |
| |
| /* Init callback structure */ |
| pj_bzero(&ice_cb, sizeof(ice_cb)); |
| ice_cb.on_rx_data = &ice_on_rx_data; |
| ice_cb.on_ice_complete = &ice_on_ice_complete; |
| |
| /* Init ICE stream transport configuration structure */ |
| pj_ice_strans_cfg_default(&ice_cfg); |
| pj_memcpy(&ice_cfg.stun_cfg, test_sess->stun_cfg, sizeof(pj_stun_config)); |
| if ((ept->cfg.enable_stun & SRV)==SRV || (ept->cfg.enable_turn & SRV)==SRV) |
| ice_cfg.resolver = test_sess->resolver; |
| |
| if (ept->cfg.enable_stun & YES) { |
| if ((ept->cfg.enable_stun & SRV) == SRV) { |
| ice_cfg.stun.server = pj_str(SRV_DOMAIN); |
| } else { |
| ice_cfg.stun.server = pj_str(serverip); |
| } |
| ice_cfg.stun.port = STUN_SERVER_PORT; |
| } |
| |
| if (ept->cfg.enable_host == 0) { |
| ice_cfg.stun.max_host_cands = 0; |
| } else { |
| //ice_cfg.stun.no_host_cands = PJ_FALSE; |
| ice_cfg.stun.loop_addr = PJ_TRUE; |
| } |
| |
| |
| if (ept->cfg.enable_turn & YES) { |
| if ((ept->cfg.enable_turn & SRV) == SRV) { |
| ice_cfg.turn.server = pj_str(SRV_DOMAIN); |
| } else { |
| ice_cfg.turn.server = pj_str(serverip); |
| } |
| ice_cfg.turn.port = TURN_SERVER_PORT; |
| ice_cfg.turn.conn_type = PJ_TURN_TP_UDP; |
| ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC; |
| ice_cfg.turn.auth_cred.data.static_cred.realm = pj_str(SRV_DOMAIN); |
| if (ept->cfg.client_flag & WRONG_TURN) |
| ice_cfg.turn.auth_cred.data.static_cred.username = pj_str("xxx"); |
| else |
| ice_cfg.turn.auth_cred.data.static_cred.username = pj_str(TURN_USERNAME); |
| ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN; |
| ice_cfg.turn.auth_cred.data.static_cred.data = pj_str(TURN_PASSWD); |
| } |
| |
| /* Create ICE stream transport */ |
| status = pj_ice_strans_create(NULL, &ice_cfg, ept->cfg.comp_cnt, |
| (void*)ept, &ice_cb, |
| &ice); |
| if (status != PJ_SUCCESS) { |
| app_perror(INDENT "err: pj_ice_strans_create()", status); |
| return status; |
| } |
| |
| pj_create_unique_string(test_sess->pool, &ept->ufrag); |
| pj_create_unique_string(test_sess->pool, &ept->pass); |
| |
| /* Looks alright */ |
| *p_ice = ice; |
| return PJ_SUCCESS; |
| } |
| |
| /* Create test session */ |
| static int create_sess(pj_stun_config *stun_cfg, |
| unsigned server_flag, |
| struct test_cfg *caller_cfg, |
| struct test_cfg *callee_cfg, |
| struct sess_param *test_param, |
| struct test_sess **p_sess) |
| { |
| pj_pool_t *pool; |
| struct test_sess *sess; |
| pj_str_t ns_ip; |
| pj_uint16_t ns_port; |
| unsigned flags; |
| pj_status_t status; |
| |
| /* Create session structure */ |
| pool = pj_pool_create(mem, "testsess", 512, 512, NULL); |
| sess = PJ_POOL_ZALLOC_T(pool, struct test_sess); |
| sess->pool = pool; |
| sess->stun_cfg = stun_cfg; |
| sess->param = test_param; |
| |
| pj_memcpy(&sess->caller.cfg, caller_cfg, sizeof(*caller_cfg)); |
| sess->caller.result.init_status = sess->caller.result.nego_status = PJ_EPENDING; |
| |
| pj_memcpy(&sess->callee.cfg, callee_cfg, sizeof(*callee_cfg)); |
| sess->callee.result.init_status = sess->callee.result.nego_status = PJ_EPENDING; |
| |
| /* Create server */ |
| flags = server_flag; |
| status = create_test_server(stun_cfg, flags, SRV_DOMAIN, &sess->server); |
| if (status != PJ_SUCCESS) { |
| app_perror(INDENT "error: create_test_server()", status); |
| destroy_sess(sess, 500); |
| return -10; |
| } |
| sess->server->turn_respond_allocate = |
| sess->server->turn_respond_refresh = PJ_TRUE; |
| |
| /* Create resolver */ |
| status = pj_dns_resolver_create(mem, NULL, 0, stun_cfg->timer_heap, |
| stun_cfg->ioqueue, &sess->resolver); |
| if (status != PJ_SUCCESS) { |
| app_perror(INDENT "error: pj_dns_resolver_create()", status); |
| destroy_sess(sess, 500); |
| return -20; |
| } |
| |
| ns_ip = pj_str("127.0.0.1"); |
| ns_port = (pj_uint16_t)DNS_SERVER_PORT; |
| status = pj_dns_resolver_set_ns(sess->resolver, 1, &ns_ip, &ns_port); |
| if (status != PJ_SUCCESS) { |
| app_perror( INDENT "error: pj_dns_resolver_set_ns()", status); |
| destroy_sess(sess, 500); |
| return -21; |
| } |
| |
| /* Create caller ICE stream transport */ |
| status = create_ice_strans(sess, &sess->caller, &sess->caller.ice); |
| if (status != PJ_SUCCESS) { |
| destroy_sess(sess, 500); |
| return -30; |
| } |
| |
| /* Create callee ICE stream transport */ |
| status = create_ice_strans(sess, &sess->callee, &sess->callee.ice); |
| if (status != PJ_SUCCESS) { |
| destroy_sess(sess, 500); |
| return -40; |
| } |
| |
| *p_sess = sess; |
| return 0; |
| } |
| |
| /* Destroy test session */ |
| static void destroy_sess(struct test_sess *sess, unsigned wait_msec) |
| { |
| unsigned i; |
| |
| if (sess->caller.ice) { |
| pj_ice_strans_destroy(sess->caller.ice); |
| sess->caller.ice = NULL; |
| } |
| |
| if (sess->callee.ice) { |
| pj_ice_strans_destroy(sess->callee.ice); |
| sess->callee.ice = NULL; |
| } |
| |
| sess->param->worker_quit = PJ_TRUE; |
| for (i=0; i<sess->param->worker_cnt; ++i) { |
| if (sess->worker_threads[i]) |
| pj_thread_join(sess->worker_threads[i]); |
| } |
| |
| poll_events(sess->stun_cfg, wait_msec, PJ_FALSE); |
| |
| if (sess->resolver) { |
| pj_dns_resolver_destroy(sess->resolver, PJ_FALSE); |
| sess->resolver = NULL; |
| } |
| |
| if (sess->server) { |
| destroy_test_server(sess->server); |
| sess->server = NULL; |
| } |
| |
| if (sess->pool) { |
| pj_pool_t *pool = sess->pool; |
| sess->pool = NULL; |
| pj_pool_release(pool); |
| } |
| } |
| |
| static void ice_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) |
| { |
| struct ice_ept *ept; |
| |
| PJ_UNUSED_ARG(pkt); |
| PJ_UNUSED_ARG(size); |
| PJ_UNUSED_ARG(src_addr); |
| PJ_UNUSED_ARG(src_addr_len); |
| |
| ept = (struct ice_ept*) pj_ice_strans_get_user_data(ice_st); |
| ept->result.rx_cnt[comp_id]++; |
| } |
| |
| |
| static void ice_on_ice_complete(pj_ice_strans *ice_st, |
| pj_ice_strans_op op, |
| pj_status_t status) |
| { |
| struct ice_ept *ept; |
| |
| ept = (struct ice_ept*) pj_ice_strans_get_user_data(ice_st); |
| switch (op) { |
| case PJ_ICE_STRANS_OP_INIT: |
| ept->result.init_status = status; |
| if (status != PJ_SUCCESS && (ept->cfg.client_flag & DEL_ON_ERR)) { |
| pj_ice_strans_destroy(ice_st); |
| ept->ice = NULL; |
| } |
| break; |
| case PJ_ICE_STRANS_OP_NEGOTIATION: |
| ept->result.nego_status = status; |
| break; |
| case PJ_ICE_STRANS_OP_KEEP_ALIVE: |
| /* keep alive failed? */ |
| break; |
| default: |
| pj_assert(!"Unknown op"); |
| } |
| } |
| |
| |
| /* Start ICE negotiation on the endpoint, based on parameter from |
| * the other endpoint. |
| */ |
| static pj_status_t start_ice(struct ice_ept *ept, const struct ice_ept *remote) |
| { |
| pj_ice_sess_cand rcand[32]; |
| unsigned i, rcand_cnt = 0; |
| pj_status_t status; |
| |
| /* Enum remote candidates */ |
| for (i=0; i<remote->cfg.comp_cnt; ++i) { |
| unsigned cnt = PJ_ARRAY_SIZE(rcand) - rcand_cnt; |
| status = pj_ice_strans_enum_cands(remote->ice, i+1, &cnt, rcand+rcand_cnt); |
| if (status != PJ_SUCCESS) { |
| app_perror(INDENT "err: pj_ice_strans_enum_cands()", status); |
| return status; |
| } |
| rcand_cnt += cnt; |
| } |
| |
| status = pj_ice_strans_start_ice(ept->ice, &remote->ufrag, &remote->pass, |
| rcand_cnt, rcand); |
| if (status != PJ_SUCCESS) { |
| app_perror(INDENT "err: pj_ice_strans_start_ice()", status); |
| return status; |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* Check that the pair in both agents are matched */ |
| static int check_pair(const struct ice_ept *ept1, const struct ice_ept *ept2, |
| int start_err) |
| { |
| unsigned i, min_cnt, max_cnt; |
| |
| if (ept1->cfg.comp_cnt < ept2->cfg.comp_cnt) { |
| min_cnt = ept1->cfg.comp_cnt; |
| max_cnt = ept2->cfg.comp_cnt; |
| } else { |
| min_cnt = ept2->cfg.comp_cnt; |
| max_cnt = ept1->cfg.comp_cnt; |
| } |
| |
| /* Must have valid pair for common components */ |
| for (i=0; i<min_cnt; ++i) { |
| const pj_ice_sess_check *c1; |
| const pj_ice_sess_check *c2; |
| |
| c1 = pj_ice_strans_get_valid_pair(ept1->ice, i+1); |
| if (c1 == NULL) { |
| PJ_LOG(3,(THIS_FILE, INDENT "err: unable to get valid pair for ice1 " |
| "component %d", i+1)); |
| return start_err - 2; |
| } |
| |
| c2 = pj_ice_strans_get_valid_pair(ept2->ice, i+1); |
| if (c2 == NULL) { |
| PJ_LOG(3,(THIS_FILE, INDENT "err: unable to get valid pair for ice2 " |
| "component %d", i+1)); |
| return start_err - 4; |
| } |
| |
| if (pj_sockaddr_cmp(&c1->rcand->addr, &c2->lcand->addr) != 0) { |
| PJ_LOG(3,(THIS_FILE, INDENT "err: candidate pair does not match " |
| "for component %d", i+1)); |
| return start_err - 6; |
| } |
| } |
| |
| /* Extra components must not have valid pair */ |
| for (; i<max_cnt; ++i) { |
| if (ept1->cfg.comp_cnt>i && |
| pj_ice_strans_get_valid_pair(ept1->ice, i+1) != NULL) |
| { |
| PJ_LOG(3,(THIS_FILE, INDENT "err: ice1 shouldn't have valid pair " |
| "for component %d", i+1)); |
| return start_err - 8; |
| } |
| if (ept2->cfg.comp_cnt>i && |
| pj_ice_strans_get_valid_pair(ept2->ice, i+1) != NULL) |
| { |
| PJ_LOG(3,(THIS_FILE, INDENT "err: ice2 shouldn't have valid pair " |
| "for component %d", i+1)); |
| return start_err - 9; |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| #define WAIT_UNTIL(timeout,expr, RC) { \ |
| pj_time_val t0, t; \ |
| pj_gettimeofday(&t0); \ |
| RC = -1; \ |
| for (;;) { \ |
| poll_events(stun_cfg, 10, PJ_FALSE); \ |
| pj_gettimeofday(&t); \ |
| if (expr) { \ |
| rc = PJ_SUCCESS; \ |
| break; \ |
| } \ |
| PJ_TIME_VAL_SUB(t, t0); \ |
| if ((unsigned)PJ_TIME_VAL_MSEC(t) >= (timeout)) \ |
| break; \ |
| } \ |
| } |
| |
| int worker_thread_proc(void *data) |
| { |
| pj_status_t rc; |
| struct test_sess *sess = (struct test_sess *) data; |
| pj_stun_config *stun_cfg = sess->stun_cfg; |
| |
| /* Wait until negotiation is complete on both endpoints */ |
| #define ALL_DONE (sess->param->worker_quit || \ |
| (sess->caller.result.nego_status!=PJ_EPENDING && \ |
| sess->callee.result.nego_status!=PJ_EPENDING)) |
| WAIT_UNTIL(sess->param->worker_timeout, ALL_DONE, rc); |
| |
| return 0; |
| } |
| |
| static int perform_test2(const char *title, |
| pj_stun_config *stun_cfg, |
| unsigned server_flag, |
| struct test_cfg *caller_cfg, |
| struct test_cfg *callee_cfg, |
| struct sess_param *test_param) |
| { |
| pjlib_state pjlib_state; |
| struct test_sess *sess; |
| unsigned i; |
| int rc; |
| |
| PJ_LOG(3,(THIS_FILE, INDENT "%s", title)); |
| |
| capture_pjlib_state(stun_cfg, &pjlib_state); |
| |
| rc = create_sess(stun_cfg, server_flag, caller_cfg, callee_cfg, test_param, &sess); |
| if (rc != 0) |
| return rc; |
| |
| #define ALL_READY (sess->caller.result.init_status!=PJ_EPENDING && \ |
| sess->callee.result.init_status!=PJ_EPENDING) |
| |
| /* Wait until both ICE transports are initialized */ |
| WAIT_UNTIL(30000, ALL_READY, rc); |
| |
| if (!ALL_READY) { |
| PJ_LOG(3,(THIS_FILE, INDENT "err: init timed-out")); |
| destroy_sess(sess, 500); |
| return -100; |
| } |
| |
| if (sess->caller.result.init_status != sess->caller.cfg.expected.init_status) { |
| app_perror(INDENT "err: caller init", sess->caller.result.init_status); |
| destroy_sess(sess, 500); |
| return -102; |
| } |
| if (sess->callee.result.init_status != sess->callee.cfg.expected.init_status) { |
| app_perror(INDENT "err: callee init", sess->callee.result.init_status); |
| destroy_sess(sess, 500); |
| return -104; |
| } |
| |
| /* Failure condition */ |
| if (sess->caller.result.init_status != PJ_SUCCESS || |
| sess->callee.result.init_status != PJ_SUCCESS) |
| { |
| rc = 0; |
| goto on_return; |
| } |
| /* Init ICE on caller */ |
| rc = pj_ice_strans_init_ice(sess->caller.ice, sess->caller.cfg.role, |
| &sess->caller.ufrag, &sess->caller.pass); |
| if (rc != PJ_SUCCESS) { |
| app_perror(INDENT "err: caller pj_ice_strans_init_ice()", rc); |
| destroy_sess(sess, 500); |
| return -100; |
| } |
| |
| /* Init ICE on callee */ |
| rc = pj_ice_strans_init_ice(sess->callee.ice, sess->callee.cfg.role, |
| &sess->callee.ufrag, &sess->callee.pass); |
| if (rc != PJ_SUCCESS) { |
| app_perror(INDENT "err: callee pj_ice_strans_init_ice()", rc); |
| destroy_sess(sess, 500); |
| return -110; |
| } |
| /* Start ICE on callee */ |
| rc = start_ice(&sess->callee, &sess->caller); |
| if (rc != PJ_SUCCESS) { |
| destroy_sess(sess, 500); |
| return -120; |
| } |
| /* Wait for callee's answer_delay */ |
| poll_events(stun_cfg, sess->callee.cfg.answer_delay, PJ_FALSE); |
| /* Start ICE on caller */ |
| rc = start_ice(&sess->caller, &sess->callee); |
| if (rc != PJ_SUCCESS) { |
| destroy_sess(sess, 500); |
| return -130; |
| } |
| |
| for (i=0; i<sess->param->worker_cnt; ++i) { |
| pj_status_t status; |
| |
| status = pj_thread_create(sess->pool, "worker_thread", |
| worker_thread_proc, sess, 0, 0, |
| &sess->worker_threads[i]); |
| if (status != PJ_SUCCESS) { |
| PJ_LOG(3,(THIS_FILE, INDENT "err: create thread")); |
| destroy_sess(sess, 500); |
| return -135; |
| } |
| } |
| |
| if (sess->param->destroy_after_create) |
| goto on_destroy; |
| |
| if (sess->param->destroy_after_one_done) { |
| while (sess->caller.result.init_status==PJ_EPENDING && |
| sess->callee.result.init_status==PJ_EPENDING) |
| { |
| if (sess->param->worker_cnt) |
| pj_thread_sleep(0); |
| else |
| poll_events(stun_cfg, 0, PJ_FALSE); |
| } |
| goto on_destroy; |
| } |
| |
| WAIT_UNTIL(30000, ALL_DONE, rc); |
| if (!ALL_DONE) { |
| PJ_LOG(3,(THIS_FILE, INDENT "err: negotiation timed-out")); |
| destroy_sess(sess, 500); |
| return -140; |
| } |
| |
| if (sess->caller.result.nego_status != sess->caller.cfg.expected.nego_status) { |
| app_perror(INDENT "err: caller negotiation failed", sess->caller.result.nego_status); |
| destroy_sess(sess, 500); |
| return -150; |
| } |
| |
| if (sess->callee.result.nego_status != sess->callee.cfg.expected.nego_status) { |
| app_perror(INDENT "err: callee negotiation failed", sess->callee.result.nego_status); |
| destroy_sess(sess, 500); |
| return -160; |
| } |
| |
| /* Verify that both agents have agreed on the same pair */ |
| rc = check_pair(&sess->caller, &sess->callee, -170); |
| if (rc != 0) { |
| destroy_sess(sess, 500); |
| return rc; |
| } |
| rc = check_pair(&sess->callee, &sess->caller, -180); |
| if (rc != 0) { |
| destroy_sess(sess, 500); |
| return rc; |
| } |
| |
| /* Looks like everything is okay */ |
| on_destroy: |
| |
| /* Destroy ICE stream transports first to let it de-allocate |
| * TURN relay (otherwise there'll be timer/memory leak, unless |
| * we wait for long time in the last poll_events() below). |
| */ |
| if (sess->caller.ice) { |
| pj_ice_strans_destroy(sess->caller.ice); |
| sess->caller.ice = NULL; |
| } |
| |
| if (sess->callee.ice) { |
| pj_ice_strans_destroy(sess->callee.ice); |
| sess->callee.ice = NULL; |
| } |
| |
| on_return: |
| /* Wait.. */ |
| poll_events(stun_cfg, 200, PJ_FALSE); |
| |
| /* Now destroy everything */ |
| destroy_sess(sess, 500); |
| |
| /* Flush events */ |
| poll_events(stun_cfg, 100, PJ_FALSE); |
| |
| rc = check_pjlib_state(stun_cfg, &pjlib_state); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| static int perform_test(const char *title, |
| pj_stun_config *stun_cfg, |
| unsigned server_flag, |
| struct test_cfg *caller_cfg, |
| struct test_cfg *callee_cfg) |
| { |
| struct sess_param test_param; |
| |
| pj_bzero(&test_param, sizeof(test_param)); |
| return perform_test2(title, stun_cfg, server_flag, caller_cfg, |
| callee_cfg, &test_param); |
| } |
| |
| #define ROLE1 PJ_ICE_SESS_ROLE_CONTROLLED |
| #define ROLE2 PJ_ICE_SESS_ROLE_CONTROLLING |
| |
| int ice_test(void) |
| { |
| pj_pool_t *pool; |
| pj_stun_config stun_cfg; |
| unsigned i; |
| int rc; |
| struct sess_cfg_t { |
| const char *title; |
| unsigned server_flag; |
| struct test_cfg ua1; |
| struct test_cfg ua2; |
| } sess_cfg[] = |
| { |
| /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ |
| { |
| "hosts candidates only", |
| 0xFFFF, |
| {ROLE1, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }, |
| { |
| "host and srflxes", |
| 0xFFFF, |
| {ROLE1, 1, YES, YES, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, YES, YES, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }, |
| { |
| "host vs relay", |
| 0xFFFF, |
| {ROLE1, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }, |
| { |
| "relay vs host", |
| 0xFFFF, |
| {ROLE1, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, YES, NO, NO, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }, |
| { |
| "relay vs relay", |
| 0xFFFF, |
| {ROLE1, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, NO, NO, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }, |
| { |
| "all candidates", |
| 0xFFFF, |
| {ROLE1, 1, YES, YES, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, YES, YES, YES, NO, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }, |
| }; |
| |
| pool = pj_pool_create(mem, NULL, 512, 512, NULL); |
| rc = create_stun_config(pool, &stun_cfg); |
| if (rc != PJ_SUCCESS) { |
| pj_pool_release(pool); |
| return -7; |
| } |
| |
| /* Simple test first with host candidate */ |
| if (1) { |
| struct sess_cfg_t cfg = |
| { |
| "Basic with host candidates", |
| 0x0, |
| /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ |
| {ROLE1, 1, YES, NO, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, YES, NO, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }; |
| |
| rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| |
| cfg.ua1.comp_cnt = 2; |
| cfg.ua2.comp_cnt = 2; |
| rc = perform_test("Basic with host candidates, 2 components", |
| &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| } |
| |
| /* Simple test first with srflx candidate */ |
| if (1) { |
| struct sess_cfg_t cfg = |
| { |
| "Basic with srflx candidates", |
| 0xFFFF, |
| /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ |
| {ROLE1, 1, YES, YES, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, YES, YES, NO, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }; |
| |
| rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| |
| cfg.ua1.comp_cnt = 2; |
| cfg.ua2.comp_cnt = 2; |
| |
| rc = perform_test("Basic with srflx candidates, 2 components", |
| &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| } |
| |
| /* Simple test with relay candidate */ |
| if (1) { |
| struct sess_cfg_t cfg = |
| { |
| "Basic with relay candidates", |
| 0xFFFF, |
| /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ |
| {ROLE1, 1, NO, NO, YES, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, NO, NO, YES, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }; |
| |
| rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| |
| cfg.ua1.comp_cnt = 2; |
| cfg.ua2.comp_cnt = 2; |
| |
| rc = perform_test("Basic with relay candidates, 2 components", |
| &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| } |
| |
| /* Failure test with STUN resolution */ |
| if (1) { |
| struct sess_cfg_t cfg = |
| { |
| "STUN resolution failure", |
| 0x0, |
| /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ |
| {ROLE1, 2, NO, YES, NO, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}}, |
| {ROLE2, 2, NO, YES, NO, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}} |
| }; |
| |
| rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| |
| cfg.ua1.client_flag |= DEL_ON_ERR; |
| cfg.ua2.client_flag |= DEL_ON_ERR; |
| |
| rc = perform_test("STUN resolution failure with destroy on callback", |
| &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| } |
| |
| /* Failure test with TURN resolution */ |
| if (1) { |
| struct sess_cfg_t cfg = |
| { |
| "TURN allocation failure", |
| 0xFFFF, |
| /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ |
| {ROLE1, 2, NO, NO, YES, WRONG_TURN, 0, 0, 0, {PJ_STATUS_FROM_STUN_CODE(401), -1}}, |
| {ROLE2, 2, NO, NO, YES, WRONG_TURN, 0, 0, 0, {PJ_STATUS_FROM_STUN_CODE(401), -1}} |
| }; |
| |
| rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| |
| cfg.ua1.client_flag |= DEL_ON_ERR; |
| cfg.ua2.client_flag |= DEL_ON_ERR; |
| |
| rc = perform_test("TURN allocation failure with destroy on callback", |
| &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| } |
| |
| |
| /* STUN failure, testing TURN deallocation */ |
| if (1) { |
| struct sess_cfg_t cfg = |
| { |
| "STUN failure, testing TURN deallocation", |
| 0xFFFF & (~(CREATE_STUN_SERVER)), |
| /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ |
| {ROLE1, 1, YES, YES, YES, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}}, |
| {ROLE2, 1, YES, YES, YES, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}} |
| }; |
| |
| rc = perform_test(cfg.title, &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| |
| cfg.ua1.client_flag |= DEL_ON_ERR; |
| cfg.ua2.client_flag |= DEL_ON_ERR; |
| |
| rc = perform_test("STUN failure, testing TURN deallocation (cb)", |
| &stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2); |
| if (rc != 0) |
| goto on_return; |
| } |
| |
| rc = 0; |
| /* Iterate each test item */ |
| for (i=0; i<PJ_ARRAY_SIZE(sess_cfg); ++i) { |
| struct sess_cfg_t *cfg = &sess_cfg[i]; |
| unsigned delay[] = { 50, 2000 }; |
| unsigned d; |
| |
| PJ_LOG(3,(THIS_FILE, " %s", cfg->title)); |
| |
| /* For each test item, test with various answer delay */ |
| for (d=0; d<PJ_ARRAY_SIZE(delay); ++d) { |
| struct role_t { |
| pj_ice_sess_role ua1; |
| pj_ice_sess_role ua2; |
| } role[] = |
| { |
| { ROLE1, ROLE2}, |
| { ROLE2, ROLE1}, |
| { ROLE1, ROLE1}, |
| { ROLE2, ROLE2} |
| }; |
| unsigned j; |
| |
| cfg->ua1.answer_delay = delay[d]; |
| cfg->ua2.answer_delay = delay[d]; |
| |
| /* For each test item, test with role conflict scenarios */ |
| for (j=0; j<PJ_ARRAY_SIZE(role); ++j) { |
| unsigned k1; |
| |
| cfg->ua1.role = role[j].ua1; |
| cfg->ua2.role = role[j].ua2; |
| |
| /* For each test item, test with different number of components */ |
| for (k1=1; k1<=2; ++k1) { |
| unsigned k2; |
| |
| cfg->ua1.comp_cnt = k1; |
| |
| for (k2=1; k2<=2; ++k2) { |
| char title[120]; |
| |
| sprintf(title, |
| "%s/%s, %dms answer delay, %d vs %d components", |
| pj_ice_sess_role_name(role[j].ua1), |
| pj_ice_sess_role_name(role[j].ua2), |
| delay[d], k1, k2); |
| |
| cfg->ua2.comp_cnt = k2; |
| rc = perform_test(title, &stun_cfg, cfg->server_flag, |
| &cfg->ua1, &cfg->ua2); |
| if (rc != 0) |
| goto on_return; |
| } |
| } |
| } |
| } |
| } |
| |
| on_return: |
| destroy_stun_config(&stun_cfg); |
| pj_pool_release(pool); |
| return rc; |
| } |
| |
| int ice_one_conc_test(pj_stun_config *stun_cfg, int err_quit) |
| { |
| struct sess_cfg_t { |
| const char *title; |
| unsigned server_flag; |
| struct test_cfg ua1; |
| struct test_cfg ua2; |
| } cfg = |
| { |
| "Concurrency test", |
| 0xFFFF, |
| /* Role comp# host? stun? turn? flag? ans_del snd_del des_del */ |
| {ROLE1, 1, YES, YES, YES, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}}, |
| {ROLE2, 1, YES, YES, YES, 0, 0, 0, 0, {PJ_SUCCESS, PJ_SUCCESS}} |
| }; |
| struct sess_param test_param; |
| int rc; |
| |
| |
| /* test a: destroy as soon as nego starts */ |
| cfg.title = " ice test a: immediate destroy"; |
| pj_bzero(&test_param, sizeof(test_param)); |
| test_param.worker_cnt = 4; |
| test_param.worker_timeout = 1000; |
| test_param.destroy_after_create = PJ_TRUE; |
| |
| rc = perform_test2(cfg.title, stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2, &test_param); |
| if (rc != 0 && err_quit) |
| return rc; |
| |
| /* test b: destroy as soon as one is done */ |
| cfg.title = " ice test b: destroy after 1 success"; |
| test_param.destroy_after_create = PJ_FALSE; |
| test_param.destroy_after_one_done = PJ_TRUE; |
| |
| rc = perform_test2(cfg.title, stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2, &test_param); |
| if (rc != 0 && err_quit) |
| return rc; |
| |
| /* test c: normal */ |
| cfg.title = " ice test c: normal flow"; |
| pj_bzero(&test_param, sizeof(test_param)); |
| test_param.worker_cnt = 4; |
| test_param.worker_timeout = 1000; |
| |
| rc = perform_test2(cfg.title, stun_cfg, cfg.server_flag, |
| &cfg.ua1, &cfg.ua2, &test_param); |
| if (rc != 0 && err_quit) |
| return rc; |
| |
| return 0; |
| } |
| |
| int ice_conc_test(void) |
| { |
| const unsigned LOOP = 100; |
| pj_pool_t *pool; |
| pj_stun_config stun_cfg; |
| unsigned i; |
| int rc; |
| |
| pool = pj_pool_create(mem, NULL, 512, 512, NULL); |
| rc = create_stun_config(pool, &stun_cfg); |
| if (rc != PJ_SUCCESS) { |
| pj_pool_release(pool); |
| return -7; |
| } |
| |
| for (i = 0; i < LOOP; i++) { |
| PJ_LOG(3,(THIS_FILE, INDENT "Test %d of %d", i+1, LOOP)); |
| rc = ice_one_conc_test(&stun_cfg, PJ_TRUE); |
| if (rc) |
| break; |
| } |
| |
| /* Avoid compiler warning */ |
| goto on_return; |
| |
| on_return: |
| destroy_stun_config(&stun_cfg); |
| pj_pool_release(pool); |
| |
| return rc; |
| } |