From 1828ebd3406cbe2563fe3699599590bf30b69f54 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Mon, 13 Apr 2009 18:41:04 +0000 Subject: Ticket #780: initial work on an ICE demo app git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2592 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip-apps/src/samples/icedemo.c | 1054 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1054 insertions(+) create mode 100644 pjsip-apps/src/samples/icedemo.c (limited to 'pjsip-apps/src/samples') diff --git a/pjsip-apps/src/samples/icedemo.c b/pjsip-apps/src/samples/icedemo.c new file mode 100644 index 00000000..a8eb6e68 --- /dev/null +++ b/pjsip-apps/src/samples/icedemo.c @@ -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 +#include +#include +#include +#include + + +#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"); + 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 .. 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; +} -- cgit v1.2.3