blob: f3f7c6c3f2792f1e48c2ad9e07f2e44976e82963 [file] [log] [blame]
Benny Prijono00238772009-04-13 18:41:04 +00001/* $Id$ */
2/*
3 * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <stdio.h>
20#include <stdlib.h>
21#include <pjlib.h>
22#include <pjlib-util.h>
23#include <pjnath.h>
24
25
26#define THIS_FILE "icedemo.c"
27
Benny Prijono3ec27ba2009-04-15 13:38:40 +000028/* For this demo app, configure longer STUN keep-alive time
29 * so that it does't clutter the screen output.
30 */
31#define KA_INTERVAL 300
32
33
34/* This is our global variables */
Benny Prijono00238772009-04-13 18:41:04 +000035static struct app_t
36{
Benny Prijono3ec27ba2009-04-15 13:38:40 +000037 /* Command line options are stored here */
Benny Prijono00238772009-04-13 18:41:04 +000038 struct options
39 {
40 unsigned comp_cnt;
41 pj_str_t ns;
Benny Prijono329d6382009-05-29 13:04:03 +000042 int max_host;
43 pj_bool_t regular;
Benny Prijono00238772009-04-13 18:41:04 +000044 pj_str_t stun_srv;
45 pj_str_t turn_srv;
46 pj_bool_t turn_tcp;
47 pj_str_t turn_username;
48 pj_str_t turn_password;
49 pj_bool_t turn_fingerprint;
50 } opt;
51
Benny Prijono3ec27ba2009-04-15 13:38:40 +000052 /* Our global variables */
Benny Prijono00238772009-04-13 18:41:04 +000053 pj_caching_pool cp;
54 pj_pool_t *pool;
55 pj_thread_t *thread;
56 pj_bool_t thread_quit_flag;
57 pj_ice_strans_cfg ice_cfg;
58 pj_ice_strans *icest;
59
Benny Prijono3ec27ba2009-04-15 13:38:40 +000060 /* Variables to store parsed remote ICE info */
Benny Prijono00238772009-04-13 18:41:04 +000061 struct rem_info
62 {
63 char ufrag[80];
64 char pwd[80];
65 unsigned comp_cnt;
66 pj_sockaddr def_addr[PJ_ICE_MAX_COMP];
67 unsigned cand_cnt;
68 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
69 } rem;
70
71} icedemo;
72
Benny Prijono3ec27ba2009-04-15 13:38:40 +000073/* Utility to display error messages */
Benny Prijono00238772009-04-13 18:41:04 +000074static void icedemo_perror(const char *title, pj_status_t status)
75{
76 char errmsg[PJ_ERR_MSG_SIZE];
77
78 pj_strerror(status, errmsg, sizeof(errmsg));
79 PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
80}
81
Benny Prijono3ec27ba2009-04-15 13:38:40 +000082/* Utility: display error message and exit application (usually
83 * because of fatal error.
84 */
Benny Prijono00238772009-04-13 18:41:04 +000085static void err_exit(const char *title, pj_status_t status)
86{
87 if (status != PJ_SUCCESS) {
88 icedemo_perror(title, status);
89 }
90 PJ_LOG(3,(THIS_FILE, "Shutting down.."));
91
92 if (icedemo.icest)
93 pj_ice_strans_destroy(icedemo.icest);
94
95 pj_thread_sleep(500);
96
97 icedemo.thread_quit_flag = PJ_TRUE;
98 if (icedemo.thread) {
99 pj_thread_join(icedemo.thread);
100 pj_thread_destroy(icedemo.thread);
101 }
102
103 if (icedemo.ice_cfg.stun_cfg.ioqueue)
104 pj_ioqueue_destroy(icedemo.ice_cfg.stun_cfg.ioqueue);
105
106 if (icedemo.ice_cfg.stun_cfg.timer_heap)
107 pj_timer_heap_destroy(icedemo.ice_cfg.stun_cfg.timer_heap);
108
109 pj_caching_pool_destroy(&icedemo.cp);
110
111 pj_shutdown();
112 exit(status != PJ_SUCCESS);
113}
114
115#define CHECK(expr) status=expr; \
116 if (status!=PJ_SUCCESS) { \
117 err_exit(#expr, status); \
118 }
119
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000120/*
121 * This function checks for events from both timer and ioqueue (for
122 * network events). It is invoked by the worker thread.
123 */
124static pj_status_t handle_events(unsigned max_msec, unsigned *p_count)
Benny Prijono00238772009-04-13 18:41:04 +0000125{
126 enum { MAX_NET_EVENTS = 1 };
127 pj_time_val max_timeout = {0, 0};
128 pj_time_val timeout = { 0, 0};
129 unsigned count = 0, net_event_count = 0;
130 int c;
131
132 max_timeout.msec = max_msec;
133
134 /* Poll the timer to run it and also to retrieve the earliest entry. */
135 timeout.sec = timeout.msec = 0;
136 c = pj_timer_heap_poll( icedemo.ice_cfg.stun_cfg.timer_heap, &timeout );
137 if (c > 0)
138 count += c;
139
140 /* timer_heap_poll should never ever returns negative value, or otherwise
141 * ioqueue_poll() will block forever!
142 */
143 pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
144 if (timeout.msec >= 1000) timeout.msec = 999;
145
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000146 /* compare the value with the timeout to wait from timer, and use the
147 * minimum value.
148 */
Benny Prijono00238772009-04-13 18:41:04 +0000149 if (PJ_TIME_VAL_GT(timeout, max_timeout))
150 timeout = max_timeout;
151
152 /* Poll ioqueue.
153 * Repeat polling the ioqueue while we have immediate events, because
154 * timer heap may process more than one events, so if we only process
155 * one network events at a time (such as when IOCP backend is used),
156 * the ioqueue may have trouble keeping up with the request rate.
157 *
158 * For example, for each send() request, one network event will be
159 * reported by ioqueue for the send() completion. If we don't poll
160 * the ioqueue often enough, the send() completion will not be
161 * reported in timely manner.
162 */
163 do {
164 c = pj_ioqueue_poll( icedemo.ice_cfg.stun_cfg.ioqueue, &timeout);
165 if (c < 0) {
166 pj_status_t err = pj_get_netos_error();
167 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
168 if (p_count)
169 *p_count = count;
170 return err;
171 } else if (c == 0) {
172 break;
173 } else {
174 net_event_count += c;
175 timeout.sec = timeout.msec = 0;
176 }
177 } while (c > 0 && net_event_count < MAX_NET_EVENTS);
178
179 count += net_event_count;
180 if (p_count)
181 *p_count = count;
182
183 return PJ_SUCCESS;
184
185}
186
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000187/*
188 * This is the worker thread that polls event in the background.
189 */
Benny Prijono00238772009-04-13 18:41:04 +0000190static int icedemo_worker_thread(void *unused)
191{
192 PJ_UNUSED_ARG(unused);
193
194 while (!icedemo.thread_quit_flag) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000195 handle_events(500, NULL);
Benny Prijono00238772009-04-13 18:41:04 +0000196 }
197
198 return 0;
199}
200
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000201/*
202 * This is the callback that is registered to the ICE stream transport to
203 * receive notification about incoming data. By "data" it means application
204 * data such as RTP/RTCP, and not packets that belong to ICE signaling (such
205 * as STUN connectivity checks or TURN signaling).
206 */
207static void cb_on_rx_data(pj_ice_strans *ice_st,
208 unsigned comp_id,
209 void *pkt, pj_size_t size,
210 const pj_sockaddr_t *src_addr,
211 unsigned src_addr_len)
Benny Prijono00238772009-04-13 18:41:04 +0000212{
213 char ipstr[PJ_INET6_ADDRSTRLEN+10];
214
215 PJ_UNUSED_ARG(ice_st);
216 PJ_UNUSED_ARG(src_addr_len);
217 PJ_UNUSED_ARG(pkt);
218
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000219 ((char*)pkt)[size] = '\0';
220
221 PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%s\"",
Benny Prijono00238772009-04-13 18:41:04 +0000222 comp_id, size,
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000223 pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3),
224 (char*)pkt));
Benny Prijono00238772009-04-13 18:41:04 +0000225}
226
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000227/*
228 * This is the callback that is registered to the ICE stream transport to
229 * receive notification about ICE state progression.
230 */
231static void cb_on_ice_complete(pj_ice_strans *ice_st,
232 pj_ice_strans_op op,
233 pj_status_t status)
Benny Prijono00238772009-04-13 18:41:04 +0000234{
235 const char *opname =
236 (op==PJ_ICE_STRANS_OP_INIT? "initialization" :
237 (op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));
238
239 PJ_UNUSED_ARG(ice_st);
240
241 if (status == PJ_SUCCESS) {
242 PJ_LOG(3,(THIS_FILE, "ICE %s successful", opname));
243 } else {
244 char errmsg[PJ_ERR_MSG_SIZE];
245
246 pj_strerror(status, errmsg, sizeof(errmsg));
247 PJ_LOG(1,(THIS_FILE, "ICE %s failed: %s", opname, errmsg));
248 }
249}
250
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000251
252/*
253 * This is the main application initialization function. It is called
254 * once (and only once) during application initialization sequence by
255 * main().
256 */
Benny Prijono00238772009-04-13 18:41:04 +0000257static pj_status_t icedemo_init(void)
258{
259 pj_status_t status;
260
261 /* Initialize the libraries before anything else */
262 CHECK( pj_init() );
263 CHECK( pjlib_util_init() );
264 CHECK( pjnath_init() );
265
266 /* Must create pool factory, where memory allocations come from */
267 pj_caching_pool_init(&icedemo.cp, NULL, 0);
268
269 /* Init our ICE settings with null values */
270 pj_ice_strans_cfg_default(&icedemo.ice_cfg);
271
272 icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
273
274 /* Create application memory pool */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000275 icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo",
276 512, 512, NULL);
Benny Prijono00238772009-04-13 18:41:04 +0000277
278 /* Create timer heap for timer stuff */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000279 CHECK( pj_timer_heap_create(icedemo.pool, 100,
280 &icedemo.ice_cfg.stun_cfg.timer_heap) );
Benny Prijono00238772009-04-13 18:41:04 +0000281
282 /* and create ioqueue for network I/O stuff */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000283 CHECK( pj_ioqueue_create(icedemo.pool, 16,
284 &icedemo.ice_cfg.stun_cfg.ioqueue) );
Benny Prijono00238772009-04-13 18:41:04 +0000285
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000286 /* something must poll the timer heap and ioqueue,
287 * unless we're on Symbian where the timer heap and ioqueue run
288 * on themselves.
289 */
290 CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread,
291 NULL, 0, 0, &icedemo.thread) );
Benny Prijono00238772009-04-13 18:41:04 +0000292
293 icedemo.ice_cfg.af = pj_AF_INET();
294
295 /* Create DNS resolver if nameserver is set */
296 if (icedemo.opt.ns.slen) {
297 CHECK( pj_dns_resolver_create(&icedemo.cp.factory,
298 "resolver",
299 0,
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000300 icedemo.ice_cfg.stun_cfg.timer_heap,
Benny Prijono00238772009-04-13 18:41:04 +0000301 icedemo.ice_cfg.stun_cfg.ioqueue,
302 &icedemo.ice_cfg.resolver) );
303
304 CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1,
305 &icedemo.opt.ns, NULL) );
306 }
307
308 /* -= Start initializing ICE stream transport config =- */
309
Benny Prijono329d6382009-05-29 13:04:03 +0000310 /* Maximum number of host candidates */
311 if (icedemo.opt.max_host != -1)
312 icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host;
313
314 /* Nomination strategy */
315 if (icedemo.opt.regular)
316 icedemo.ice_cfg.opt.aggressive = PJ_FALSE;
317 else
318 icedemo.ice_cfg.opt.aggressive = PJ_TRUE;
Benny Prijono00238772009-04-13 18:41:04 +0000319
320 /* Configure STUN/srflx candidate resolution */
321 if (icedemo.opt.stun_srv.slen) {
322 char *pos;
323
324 /* Command line option may contain port number */
325 if ((pos=pj_strchr(&icedemo.opt.stun_srv, ':')) != NULL) {
326 icedemo.ice_cfg.stun.server.ptr = icedemo.opt.stun_srv.ptr;
327 icedemo.ice_cfg.stun.server.slen = (pos - icedemo.opt.stun_srv.ptr);
328
329 icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1);
330 } else {
331 icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv;
332 icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
333 }
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000334
335 /* For this demo app, configure longer STUN keep-alive time
336 * so that it does't clutter the screen output.
337 */
338 icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
Benny Prijono00238772009-04-13 18:41:04 +0000339 }
340
341 /* Configure TURN candidate */
342 if (icedemo.opt.turn_srv.slen) {
343 char *pos;
344
345 /* Command line option may contain port number */
346 if ((pos=pj_strchr(&icedemo.opt.turn_srv, ':')) != NULL) {
347 icedemo.ice_cfg.turn.server.ptr = icedemo.opt.turn_srv.ptr;
348 icedemo.ice_cfg.turn.server.slen = (pos - icedemo.opt.turn_srv.ptr);
349
350 icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1);
351 } else {
352 icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv;
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000353 icedemo.ice_cfg.turn.port = PJ_STUN_PORT;
Benny Prijono00238772009-04-13 18:41:04 +0000354 }
355
356 /* TURN credential */
357 icedemo.ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
358 icedemo.ice_cfg.turn.auth_cred.data.static_cred.username = icedemo.opt.turn_username;
359 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
360 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data = icedemo.opt.turn_password;
361
362 /* Connection type to TURN server */
363 if (icedemo.opt.turn_tcp)
364 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
365 else
366 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
367
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000368 /* For this demo app, configure longer keep-alive time
369 * so that it does't clutter the screen output.
Benny Prijono00238772009-04-13 18:41:04 +0000370 */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000371 icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
Benny Prijono00238772009-04-13 18:41:04 +0000372 }
373
374 /* -= That's it for now, initialization is complete =- */
375 return PJ_SUCCESS;
376}
377
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000378
379/*
380 * Create ICE stream transport instance, invoked from the menu.
381 */
Benny Prijono00238772009-04-13 18:41:04 +0000382static void icedemo_create_instance(void)
383{
384 pj_ice_strans_cb icecb;
385 pj_status_t status;
386
387 if (icedemo.icest != NULL) {
388 puts("ICE instance already created, destroy it first");
389 return;
390 }
391
392 /* init the callback */
393 pj_bzero(&icecb, sizeof(icecb));
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000394 icecb.on_rx_data = cb_on_rx_data;
395 icecb.on_ice_complete = cb_on_ice_complete;
Benny Prijono00238772009-04-13 18:41:04 +0000396
397 /* create the instance */
398 status = pj_ice_strans_create("icedemo", /* object name */
399 &icedemo.ice_cfg, /* settings */
400 icedemo.opt.comp_cnt, /* comp_cnt */
401 NULL, /* user data */
402 &icecb, /* callback */
403 &icedemo.icest) /* instance ptr */
404 ;
405 if (status != PJ_SUCCESS)
406 icedemo_perror("error creating ice", status);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000407 else
408 PJ_LOG(3,(THIS_FILE, "ICE instance successfully created"));
Benny Prijono00238772009-04-13 18:41:04 +0000409}
410
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000411/* Utility to nullify parsed remote info */
Benny Prijono00238772009-04-13 18:41:04 +0000412static void reset_rem_info(void)
413{
414 pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
415}
416
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000417
418/*
419 * Destroy ICE stream transport instance, invoked from the menu.
420 */
Benny Prijono00238772009-04-13 18:41:04 +0000421static void icedemo_destroy_instance(void)
422{
423 if (icedemo.icest == NULL) {
424 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
425 return;
426 }
427
428 pj_ice_strans_destroy(icedemo.icest);
429 icedemo.icest = NULL;
430
431 reset_rem_info();
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000432
433 PJ_LOG(3,(THIS_FILE, "ICE instance destroyed"));
Benny Prijono00238772009-04-13 18:41:04 +0000434}
435
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000436
437/*
438 * Create ICE session, invoked from the menu.
439 */
Benny Prijono00238772009-04-13 18:41:04 +0000440static void icedemo_init_session(unsigned rolechar)
441{
442 pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ?
443 PJ_ICE_SESS_ROLE_CONTROLLING :
444 PJ_ICE_SESS_ROLE_CONTROLLED);
445 pj_status_t status;
446
447 if (icedemo.icest == NULL) {
448 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
449 return;
450 }
451
452 if (pj_ice_strans_has_sess(icedemo.icest)) {
453 PJ_LOG(1,(THIS_FILE, "Error: Session already created"));
454 return;
455 }
456
457 status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL);
458 if (status != PJ_SUCCESS)
459 icedemo_perror("error creating session", status);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000460 else
461 PJ_LOG(3,(THIS_FILE, "ICE session created"));
Benny Prijono00238772009-04-13 18:41:04 +0000462
463 reset_rem_info();
464}
465
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000466
467/*
468 * Stop/destroy ICE session, invoked from the menu.
469 */
Benny Prijono00238772009-04-13 18:41:04 +0000470static void icedemo_stop_session(void)
471{
472 pj_status_t status;
473
474 if (icedemo.icest == NULL) {
475 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
476 return;
477 }
478
479 if (!pj_ice_strans_has_sess(icedemo.icest)) {
480 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
481 return;
482 }
483
484 status = pj_ice_strans_stop_ice(icedemo.icest);
485 if (status != PJ_SUCCESS)
486 icedemo_perror("error stopping session", status);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000487 else
488 PJ_LOG(3,(THIS_FILE, "ICE session stopped"));
Benny Prijono00238772009-04-13 18:41:04 +0000489
490 reset_rem_info();
491}
492
Benny Prijono00238772009-04-13 18:41:04 +0000493#define PRINT(fmt, arg0, arg1, arg2, arg3, arg4, arg5) \
494 printed = pj_ansi_snprintf(p, maxlen - (p-buffer), \
495 fmt, arg0, arg1, arg2, arg3, arg4, arg5); \
496 if (printed <= 0) return -PJ_ETOOSMALL; \
497 p += printed
498
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000499
500/* Utility to create a=candidate SDP attribute */
Benny Prijono00238772009-04-13 18:41:04 +0000501static int print_cand(char buffer[], unsigned maxlen,
502 const pj_ice_sess_cand *cand)
503{
504 char ipaddr[PJ_INET6_ADDRSTRLEN];
505 char *p = buffer;
506 int printed;
507
508 PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
509 (int)cand->foundation.slen,
510 cand->foundation.ptr,
511 (unsigned)cand->comp_id,
512 cand->prio,
513 pj_sockaddr_print(&cand->addr, ipaddr,
514 sizeof(ipaddr), 0),
515 (unsigned)pj_sockaddr_get_port(&cand->addr));
516
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000517 PRINT("%s\n",
518 pj_ice_get_cand_type_name(cand->type),
519 0, 0, 0, 0, 0);
Benny Prijono00238772009-04-13 18:41:04 +0000520
521 if (p == buffer+maxlen)
522 return -PJ_ETOOSMALL;
523
524 *p = '\0';
525
526 return p-buffer;
527}
528
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000529/*
530 * Encode ICE information in SDP.
531 */
Benny Prijono00238772009-04-13 18:41:04 +0000532static int encode_session(char buffer[], unsigned maxlen)
533{
534 char *p = buffer;
535 unsigned comp;
536 int printed;
537 pj_str_t local_ufrag, local_pwd;
538 pj_status_t status;
539
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000540 /* Write "dummy" SDP v=, o=, s=, and t= lines */
541 PRINT("v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n",
Benny Prijono00238772009-04-13 18:41:04 +0000542 0, 0, 0, 0, 0, 0);
543
544 /* Get ufrag and pwd from current session */
545 pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
546 NULL, NULL);
547
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000548 /* Write the a=ice-ufrag and a=ice-pwd attributes */
Benny Prijono00238772009-04-13 18:41:04 +0000549 PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n",
550 (int)local_ufrag.slen,
551 local_ufrag.ptr,
552 (int)local_pwd.slen,
553 local_pwd.ptr,
554 0, 0);
555
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000556 /* Write each component */
Benny Prijono00238772009-04-13 18:41:04 +0000557 for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) {
558 unsigned j, cand_cnt;
559 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
560 char ipaddr[PJ_INET6_ADDRSTRLEN];
561
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000562 /* Get default candidate for the component */
Benny Prijono00238772009-04-13 18:41:04 +0000563 status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
564 if (status != PJ_SUCCESS)
565 return -status;
566
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000567 /* Write the default address */
Benny Prijono00238772009-04-13 18:41:04 +0000568 if (comp==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000569 /* For component 1, default address is in m= and c= lines */
Benny Prijono00238772009-04-13 18:41:04 +0000570 PRINT("m=audio %d RTP/AVP 0\n"
571 "c=IN IP4 %s\n",
572 (int)pj_sockaddr_get_port(&cand[0].addr),
573 pj_sockaddr_print(&cand[0].addr, ipaddr,
574 sizeof(ipaddr), 0),
575 0, 0, 0, 0);
576 } else if (comp==1) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000577 /* For component 2, default address is in a=rtcp line */
Benny Prijono00238772009-04-13 18:41:04 +0000578 PRINT("a=rtcp:%d IN IP4 %s\n",
579 (int)pj_sockaddr_get_port(&cand[0].addr),
580 pj_sockaddr_print(&cand[0].addr, ipaddr,
581 sizeof(ipaddr), 0),
582 0, 0, 0, 0);
583 } else {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000584 /* For other components, we'll just invent this.. */
Benny Prijono00238772009-04-13 18:41:04 +0000585 PRINT("a=Xice-defcand:%d IN IP4 %s\n",
586 (int)pj_sockaddr_get_port(&cand[0].addr),
587 pj_sockaddr_print(&cand[0].addr, ipaddr,
588 sizeof(ipaddr), 0),
589 0, 0, 0, 0);
590 }
591
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000592 /* Enumerate all candidates for this component */
Benny Prijono00238772009-04-13 18:41:04 +0000593 status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
594 &cand_cnt, cand);
595 if (status != PJ_SUCCESS)
596 return -status;
597
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000598 /* And encode the candidates as SDP */
Benny Prijono00238772009-04-13 18:41:04 +0000599 for (j=0; j<cand_cnt; ++j) {
600 printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
601 if (printed < 0)
602 return -PJ_ETOOSMALL;
603 p += printed;
604 }
605 }
606
607 if (p == buffer+maxlen)
608 return -PJ_ETOOSMALL;
609
610 *p = '\0';
611 return p - buffer;
612}
613
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000614
615/*
616 * Show information contained in the ICE stream transport. This is
617 * invoked from the menu.
618 */
Benny Prijono00238772009-04-13 18:41:04 +0000619static void icedemo_show_ice(void)
620{
621 static char buffer[1000];
622 int len;
623
624 if (icedemo.icest == NULL) {
625 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
626 return;
627 }
628
629 puts("General info");
630 puts("---------------");
631 printf("Component count : %d\n", icedemo.opt.comp_cnt);
632 printf("Status : ");
633 if (pj_ice_strans_sess_is_complete(icedemo.icest))
634 puts("negotiation complete");
635 else if (pj_ice_strans_sess_is_running(icedemo.icest))
636 puts("negotiation is in progress");
637 else if (pj_ice_strans_has_sess(icedemo.icest))
638 puts("session ready");
639 else
640 puts("session not created");
641
642 if (!pj_ice_strans_has_sess(icedemo.icest)) {
643 puts("Create the session first to see more info");
644 return;
645 }
646
647 printf("Negotiated comp_cnt: %d\n",
648 pj_ice_strans_get_running_comp_cnt(icedemo.icest));
649 printf("Role : %s\n",
650 pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
651 "controlled" : "controlling");
652
653 len = encode_session(buffer, sizeof(buffer));
654 if (len < 0)
655 err_exit("not enough buffer to show ICE status", -len);
656
657 puts("");
658 printf("Local SDP (paste this to remote host):\n"
659 "--------------------------------------\n"
660 "%s\n", buffer);
661
662
663 puts("");
664 puts("Remote info:\n"
665 "----------------------");
666 if (icedemo.rem.cand_cnt==0) {
667 puts("No remote info yet");
668 } else {
669 unsigned i;
670
671 printf("Remote ufrag : %s\n", icedemo.rem.ufrag);
672 printf("Remote password : %s\n", icedemo.rem.pwd);
673 printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt);
674
675 for (i=0; i<icedemo.rem.cand_cnt; ++i) {
676 len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
677 if (len < 0)
678 err_exit("not enough buffer to show ICE status", -len);
679
680 printf(" %s", buffer);
681 }
682 }
683}
684
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000685
686/*
687 * Input and parse SDP from the remote (containing remote's ICE information)
688 * and save it to global variables.
689 */
Benny Prijono00238772009-04-13 18:41:04 +0000690static void icedemo_input_remote(void)
691{
692 char linebuf[80];
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000693 unsigned media_cnt = 0;
Benny Prijono00238772009-04-13 18:41:04 +0000694 unsigned comp0_port = 0;
695 char comp0_addr[80];
696 pj_bool_t done = PJ_FALSE;
697
698 puts("Paste SDP from remote host, end with empty line");
699
700 reset_rem_info();
701
702 comp0_addr[0] = '\0';
703
704 while (!done) {
705 int len;
706 char *line;
707
708 printf(">");
709 if (stdout) fflush(stdout);
710
711 if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
712 break;
713
714 len = strlen(linebuf);
715 while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
716 linebuf[--len] = '\0';
717
718 line = linebuf;
719 while (len && pj_isspace(*line))
720 ++line, --len;
721
722 if (len==0)
723 break;
724
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000725 /* Ignore subsequent media descriptors */
726 if (media_cnt > 1)
727 continue;
728
Benny Prijono00238772009-04-13 18:41:04 +0000729 switch (line[0]) {
730 case 'm':
731 {
732 int cnt;
733 char media[32], portstr[32];
734
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000735 ++media_cnt;
736 if (media_cnt > 1) {
737 puts("Media line ignored");
738 break;
739 }
740
Benny Prijono00238772009-04-13 18:41:04 +0000741 cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
742 if (cnt != 2) {
743 PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
744 goto on_error;
745 }
746
747 comp0_port = atoi(portstr);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000748
Benny Prijono00238772009-04-13 18:41:04 +0000749 }
750 break;
751 case 'c':
752 {
753 int cnt;
754 char c[32], net[32], ip[80];
755
756 cnt = sscanf(line+2, "%s %s %s", c, net, ip);
757 if (cnt != 3) {
758 PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
759 goto on_error;
760 }
761
762 strcpy(comp0_addr, ip);
763 }
764 break;
765 case 'a':
766 {
767 char *attr = strtok(line+2, ": \t\r\n");
768 if (strcmp(attr, "ice-ufrag")==0) {
769 strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
770 } else if (strcmp(attr, "ice-pwd")==0) {
771 strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
772 } else if (strcmp(attr, "rtcp")==0) {
773 char *val = attr+strlen(attr)+1;
774 int af, cnt;
775 int port;
776 char net[32], ip[64];
777 pj_str_t tmp_addr;
778 pj_status_t status;
779
780 cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
781 if (cnt != 3) {
782 PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
783 goto on_error;
784 }
785
786 if (strchr(ip, ':'))
787 af = pj_AF_INET6();
788 else
789 af = pj_AF_INET();
790
791 pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
792 tmp_addr = pj_str(ip);
793 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
794 &tmp_addr);
795 if (status != PJ_SUCCESS) {
796 PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
797 goto on_error;
798 }
799 pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
800
801 } else if (strcmp(attr, "candidate")==0) {
802 char *sdpcand = attr+strlen(attr)+1;
803 int af, cnt;
804 char foundation[32], transport[12], ipaddr[80], type[32];
805 pj_str_t tmpaddr;
806 int comp_id, prio, port;
807 pj_ice_sess_cand *cand;
808 pj_status_t status;
809
810 cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
811 foundation,
812 &comp_id,
813 transport,
814 &prio,
815 ipaddr,
816 &port,
817 type);
818 if (cnt != 7) {
819 PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
820 goto on_error;
821 }
822
823 cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
824 pj_bzero(cand, sizeof(*cand));
825
826 if (strcmp(type, "host")==0)
827 cand->type = PJ_ICE_CAND_TYPE_HOST;
828 else if (strcmp(type, "srflx")==0)
829 cand->type = PJ_ICE_CAND_TYPE_SRFLX;
830 else if (strcmp(type, "relay")==0)
831 cand->type = PJ_ICE_CAND_TYPE_RELAYED;
832 else {
833 PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
834 type));
835 goto on_error;
836 }
837
838 cand->comp_id = (pj_uint8_t)comp_id;
839 pj_strdup2(icedemo.pool, &cand->foundation, foundation);
840 cand->prio = prio;
841
842 if (strchr(ipaddr, ':'))
843 af = pj_AF_INET6();
844 else
845 af = pj_AF_INET();
846
847 tmpaddr = pj_str(ipaddr);
848 pj_sockaddr_init(af, &cand->addr, NULL, 0);
849 status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
850 if (status != PJ_SUCCESS) {
851 PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
852 ipaddr));
853 goto on_error;
854 }
855
856 pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
857
858 ++icedemo.rem.cand_cnt;
859
860 if (cand->comp_id > icedemo.rem.comp_cnt)
861 icedemo.rem.comp_cnt = cand->comp_id;
862 }
863 }
864 break;
865 }
866 }
867
868 if (icedemo.rem.cand_cnt==0 ||
869 icedemo.rem.ufrag[0]==0 ||
870 icedemo.rem.pwd[0]==0 ||
871 icedemo.rem.comp_cnt == 0)
872 {
873 PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
874 goto on_error;
875 }
876
877 if (comp0_port==0 || comp0_addr[0]=='\0') {
878 PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
879 goto on_error;
880 } else {
881 int af;
882 pj_str_t tmp_addr;
883 pj_status_t status;
884
885 if (strchr(comp0_addr, ':'))
886 af = pj_AF_INET6();
887 else
888 af = pj_AF_INET();
889
890 pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
891 tmp_addr = pj_str(comp0_addr);
892 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
893 &tmp_addr);
894 if (status != PJ_SUCCESS) {
895 PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
896 goto on_error;
897 }
898 pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
899 }
900
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000901 PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
902 icedemo.rem.cand_cnt));
Benny Prijono00238772009-04-13 18:41:04 +0000903 return;
904
905on_error:
906 reset_rem_info();
907}
908
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000909
910/*
911 * Start ICE negotiation! This function is invoked from the menu.
912 */
Benny Prijono00238772009-04-13 18:41:04 +0000913static void icedemo_start_nego(void)
914{
915 pj_str_t rufrag, rpwd;
916 pj_status_t status;
917
918 if (icedemo.icest == NULL) {
919 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
920 return;
921 }
922
923 if (!pj_ice_strans_has_sess(icedemo.icest)) {
924 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
925 return;
926 }
927
928 if (icedemo.rem.cand_cnt == 0) {
929 PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
930 return;
931 }
932
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000933 PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
934
Benny Prijono00238772009-04-13 18:41:04 +0000935 status = pj_ice_strans_start_ice(icedemo.icest,
936 pj_cstr(&rufrag, icedemo.rem.ufrag),
937 pj_cstr(&rpwd, icedemo.rem.pwd),
938 icedemo.rem.cand_cnt,
939 icedemo.rem.cand);
940 if (status != PJ_SUCCESS)
941 icedemo_perror("Error starting ICE", status);
942 else
943 PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
944}
945
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000946
947/*
948 * Send application data to remote agent.
949 */
Benny Prijono00238772009-04-13 18:41:04 +0000950static void icedemo_send_data(unsigned comp_id, const char *data)
951{
952 pj_status_t status;
953
954 if (icedemo.icest == NULL) {
955 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
956 return;
957 }
958
959 if (!pj_ice_strans_has_sess(icedemo.icest)) {
960 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
961 return;
962 }
963
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000964 /*
Benny Prijono00238772009-04-13 18:41:04 +0000965 if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
966 PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
967 return;
968 }
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000969 */
Benny Prijono00238772009-04-13 18:41:04 +0000970
Benny Prijono329d6382009-05-29 13:04:03 +0000971 if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
Benny Prijono00238772009-04-13 18:41:04 +0000972 PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
973 return;
974 }
975
976 status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
977 &icedemo.rem.def_addr[comp_id-1],
978 pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
979 if (status != PJ_SUCCESS)
980 icedemo_perror("Error sending data", status);
981 else
982 PJ_LOG(3,(THIS_FILE, "Data sent"));
983}
984
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000985
986/*
987 * Display help for the menu.
988 */
Benny Prijono00238772009-04-13 18:41:04 +0000989static void icedemo_help_menu(void)
990{
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000991 puts("");
992 puts("-= Help on using ICE and this icedemo program =-");
993 puts("");
994 puts("This application demonstrates how to use ICE in pjnath without having\n"
995 "to use the SIP protocol. To use this application, you will need to run\n"
996 "two instances of this application, to simulate two ICE agents.\n");
997
998 puts("Basic ICE flow:\n"
999 " create instance [menu \"c\"]\n"
1000 " repeat these steps as wanted:\n"
1001 " - init session as offerer or answerer [menu \"i\"]\n"
1002 " - display our SDP [menu \"s\"]\n"
1003 " - \"send\" our SDP from the \"show\" output above to remote, by\n"
1004 " copy-pasting the SDP to the other icedemo application\n"
1005 " - parse remote SDP, by pasting SDP generated by the other icedemo\n"
1006 " instance [menu \"r\"]\n"
1007 " - begin ICE negotiation in our end [menu \"b\"]\n"
1008 " - begin ICE negotiation in the other icedemo instance\n"
1009 " - ICE negotiation will run, and result will be printed to screen\n"
1010 " - send application data to remote [menu \"x\"]\n"
1011 " - end/stop ICE session [menu \"e\"]\n"
1012 " destroy instance [menu \"d\"]\n"
1013 "");
1014
1015 puts("");
1016 puts("This concludes the help screen.");
1017 puts("");
Benny Prijono00238772009-04-13 18:41:04 +00001018}
1019
1020
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001021/*
1022 * Display console menu
1023 */
Benny Prijono00238772009-04-13 18:41:04 +00001024static void icedemo_print_menu(void)
1025{
1026 puts("");
1027 puts("+----------------------------------------------------------------------+");
1028 puts("| M E N U |");
1029 puts("+---+------------------------------------------------------------------+");
1030 puts("| c | create Create the instance |");
1031 puts("| d | destroy Destroy the instance |");
1032 puts("| i | init o|a Initialize ICE session as offerer or answerer |");
1033 puts("| e | stop End/stop ICE session |");
1034 puts("| s | show Display local ICE info |");
1035 puts("| r | remote Input remote ICE info |");
1036 puts("| b | start Begin ICE negotiation |");
1037 puts("| x | send <compid> .. Send data to remote |");
1038 puts("+---+------------------------------------------------------------------+");
1039 puts("| h | help * Help! * |");
1040 puts("| q | quit Quit |");
1041 puts("+----------------------------------------------------------------------+");
1042}
1043
1044
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001045/*
1046 * Main console loop.
1047 */
Benny Prijono00238772009-04-13 18:41:04 +00001048static void icedemo_console(void)
1049{
1050 pj_bool_t app_quit = PJ_FALSE;
1051
1052 while (!app_quit) {
1053 char input[80], *cmd;
1054 const char *SEP = " \t\r\n";
1055 int len;
1056
1057 icedemo_print_menu();
1058
1059 printf("Input: ");
1060 if (stdout) fflush(stdout);
1061
1062 pj_bzero(input, sizeof(input));
1063 if (fgets(input, sizeof(input), stdin) == NULL)
1064 break;
1065
1066 len = strlen(input);
1067 while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
1068 input[--len] = '\0';
1069
1070 cmd = strtok(input, SEP);
1071 if (!cmd)
1072 continue;
1073
1074 if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001075
Benny Prijono00238772009-04-13 18:41:04 +00001076 icedemo_create_instance();
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001077
Benny Prijono00238772009-04-13 18:41:04 +00001078 } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001079
Benny Prijono00238772009-04-13 18:41:04 +00001080 icedemo_destroy_instance();
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001081
Benny Prijono00238772009-04-13 18:41:04 +00001082 } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001083
Benny Prijono00238772009-04-13 18:41:04 +00001084 char *role = strtok(NULL, SEP);
1085 if (role)
1086 icedemo_init_session(*role);
1087 else
1088 puts("error: Role required");
Benny Prijono00238772009-04-13 18:41:04 +00001089
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001090 } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
1091
1092 icedemo_stop_session();
1093
1094 } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
1095
1096 icedemo_show_ice();
1097
1098 } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
1099
1100 icedemo_input_remote();
1101
1102 } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
1103
1104 icedemo_start_nego();
1105
1106 } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
1107
1108 char *comp = strtok(NULL, SEP);
1109
1110 if (!comp) {
Benny Prijono00238772009-04-13 18:41:04 +00001111 PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
1112 } else {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001113 char *data = comp + strlen(comp) + 1;
1114 if (!data)
1115 data = "";
1116 icedemo_send_data(atoi(comp), data);
Benny Prijono00238772009-04-13 18:41:04 +00001117 }
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001118
Benny Prijono00238772009-04-13 18:41:04 +00001119 } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001120
Benny Prijono00238772009-04-13 18:41:04 +00001121 icedemo_help_menu();
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001122
Benny Prijono00238772009-04-13 18:41:04 +00001123 } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001124
Benny Prijono00238772009-04-13 18:41:04 +00001125 app_quit = PJ_TRUE;
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001126
Benny Prijono00238772009-04-13 18:41:04 +00001127 } else {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001128
Benny Prijono00238772009-04-13 18:41:04 +00001129 printf("Invalid command '%s'\n", cmd);
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001130
Benny Prijono00238772009-04-13 18:41:04 +00001131 }
1132 }
1133}
1134
1135
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001136/*
1137 * Display program usage.
1138 */
Benny Prijono00238772009-04-13 18:41:04 +00001139static void icedemo_usage()
1140{
1141 puts("Usage: icedemo [optons]");
1142 printf("icedemo v%s by pjsip.org\n", pj_get_version());
1143 puts("");
1144 puts("General options:");
1145 puts(" --comp-cnt, -c N Component count (default=1)");
1146 puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
1147 puts(" resolution");
Benny Prijono329d6382009-05-29 13:04:03 +00001148 puts(" --max-host, -H N Set max number of host candidates to N");
1149 puts(" --regular, -R Use regular nomination (default aggressive)");
Benny Prijono00238772009-04-13 18:41:04 +00001150 puts(" --help, -h Display this screen.");
1151 puts("");
1152 puts("STUN related options:");
1153 puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server.");
1154 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1155 puts(" name if DNS SRV resolution is used.");
1156 puts("");
1157 puts("TURN related options:");
1158 puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN server.");
1159 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1160 puts(" name if DNS SRV resolution is used.");
1161 puts(" --turn-tcp, -T Use TCP to connect to TURN server");
1162 puts(" --turn-username, -u UID Set TURN username of the credential to UID");
1163 puts(" --turn-password, -p PWD Set password of the credential to WPWD");
1164 puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests");
1165 puts("");
1166}
1167
1168
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001169/*
1170 * And here's the main()
1171 */
Benny Prijono00238772009-04-13 18:41:04 +00001172int main(int argc, char *argv[])
1173{
1174 struct pj_getopt_option long_options[] = {
1175 { "comp-cnt", 1, 0, 'c'},
1176 { "nameserver", 1, 0, 'n'},
Benny Prijono329d6382009-05-29 13:04:03 +00001177 { "max-host", 1, 0, 'H'},
Benny Prijono00238772009-04-13 18:41:04 +00001178 { "help", 0, 0, 'h'},
1179 { "stun-srv", 1, 0, 's'},
1180 { "turn-srv", 1, 0, 't'},
1181 { "turn-tcp", 0, 0, 'T'},
1182 { "turn-username", 1, 0, 'u'},
1183 { "turn-password", 1, 0, 'p'},
Benny Prijono329d6382009-05-29 13:04:03 +00001184 { "turn-fingerprint", 0, 0, 'F'},
1185 { "regular", 0, 0, 'R'}
Benny Prijono00238772009-04-13 18:41:04 +00001186 };
1187 int c, opt_id;
1188 pj_status_t status;
1189
1190 icedemo.opt.comp_cnt = 1;
Benny Prijono329d6382009-05-29 13:04:03 +00001191 icedemo.opt.max_host = -1;
Benny Prijono00238772009-04-13 18:41:04 +00001192
Benny Prijono329d6382009-05-29 13:04:03 +00001193 while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:hTFR", long_options, &opt_id))!=-1) {
Benny Prijono00238772009-04-13 18:41:04 +00001194 switch (c) {
1195 case 'c':
1196 icedemo.opt.comp_cnt = atoi(pj_optarg);
1197 if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
1198 puts("Invalid component count value");
1199 return 1;
1200 }
1201 break;
1202 case 'n':
1203 icedemo.opt.ns = pj_str(pj_optarg);
1204 break;
1205 case 'H':
Benny Prijono329d6382009-05-29 13:04:03 +00001206 icedemo.opt.max_host = atoi(pj_optarg);
Benny Prijono00238772009-04-13 18:41:04 +00001207 break;
1208 case 'h':
1209 icedemo_usage();
1210 return 0;
1211 case 's':
1212 icedemo.opt.stun_srv = pj_str(pj_optarg);
1213 break;
1214 case 't':
1215 icedemo.opt.turn_srv = pj_str(pj_optarg);
1216 break;
1217 case 'T':
1218 icedemo.opt.turn_tcp = PJ_TRUE;
1219 break;
1220 case 'u':
1221 icedemo.opt.turn_username = pj_str(pj_optarg);
1222 break;
1223 case 'p':
1224 icedemo.opt.turn_password = pj_str(pj_optarg);
1225 break;
1226 case 'F':
1227 icedemo.opt.turn_fingerprint = PJ_TRUE;
1228 break;
Benny Prijono329d6382009-05-29 13:04:03 +00001229 case 'R':
1230 icedemo.opt.regular = PJ_TRUE;
1231 break;
Benny Prijono00238772009-04-13 18:41:04 +00001232 default:
1233 printf("Argument \"%s\" is not valid. Use -h to see help",
1234 argv[pj_optind]);
1235 return 1;
1236 }
1237 }
1238
1239 status = icedemo_init();
1240 if (status != PJ_SUCCESS)
1241 return 1;
1242
1243 icedemo_console();
1244
1245 err_exit("Quitting..", PJ_SUCCESS);
1246 return 0;
1247}