/* $Id$ */ /* * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * 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 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 */ }; /* 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; /* Delay before sending data */ unsigned destroy_delay; /* Delay before destroy() */ struct test_result expected;/* Expected result */ }; /* 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 */ }; /* The test session */ struct test_sess { pj_pool_t *pool; pj_stun_config *stun_cfg; pj_dns_resolver *resolver; test_server *server; 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.no_host_cands = PJ_TRUE; } 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 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; 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) { 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; } 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; 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; icfg.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; iice, i+1); if (c1 == NULL) { PJ_LOG(3,("", 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,("", 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,("", INDENT "err: candidate pair does not match " "for component %d", i+1)); return start_err - 6; } } /* Extra components must not have valid pair */ for (; icfg.comp_cnt>i && pj_ice_strans_get_valid_pair(ept1->ice, i+1) != NULL) { PJ_LOG(3,("", 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,("", 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; \ } \ if (t.sec - t0.sec > (timeout)) break; \ } \ } 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) { pjlib_state pjlib_state; struct test_sess *sess; int rc; PJ_LOG(3,("", INDENT "%s", title)); capture_pjlib_state(stun_cfg, &pjlib_state); rc = create_sess(stun_cfg, server_flag, caller_cfg, callee_cfg, &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(30, ALL_READY, rc); if (!ALL_READY) { PJ_LOG(3,("", 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; } /* Wait until negotiation is complete on both endpoints */ #define ALL_DONE (sess->caller.result.nego_status!=PJ_EPENDING && \ sess->callee.result.nego_status!=PJ_EPENDING) WAIT_UNTIL(30, ALL_DONE, rc); if (!ALL_DONE) { PJ_LOG(3,("", 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 */ /* 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, 500, 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 0; } #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 = 4; cfg.ua2.comp_cnt = 4; rc = perform_test("Basic with host candidates, 4 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 = 4; cfg.ua2.comp_cnt = 4; rc = perform_test("Basic with srflx candidates, 4 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 = 4; cfg.ua2.comp_cnt = 4; rc = perform_test("Basic with relay candidates, 4 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, 4, NO, NO, YES, WRONG_TURN, 0, 0, 0, {PJ_STATUS_FROM_STUN_CODE(401), -1}}, {ROLE2, 4, 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, 2, YES, YES, YES, 0, 0, 0, 0, {PJNATH_ESTUNTIMEDOUT, -1}}, {ROLE2, 2, 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; ititle)); /* For each test item, test with various answer delay */ for (d=0; dua1.answer_delay = delay[d]; cfg->ua2.answer_delay = delay[d]; /* For each test item, test with role conflict scenarios */ for (j=0; jua1.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; }