blob: 1f2a02081595397df0f106ad1a2c6a9ecc925467 [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;
Benny Prijono41903da2009-12-08 13:11:25 +000050 const char *log_file;
Benny Prijono00238772009-04-13 18:41:04 +000051 } opt;
52
Benny Prijono3ec27ba2009-04-15 13:38:40 +000053 /* Our global variables */
Benny Prijono00238772009-04-13 18:41:04 +000054 pj_caching_pool cp;
55 pj_pool_t *pool;
56 pj_thread_t *thread;
57 pj_bool_t thread_quit_flag;
58 pj_ice_strans_cfg ice_cfg;
59 pj_ice_strans *icest;
Benny Prijono41903da2009-12-08 13:11:25 +000060 FILE *log_fhnd;
Benny Prijono00238772009-04-13 18:41:04 +000061
Benny Prijono3ec27ba2009-04-15 13:38:40 +000062 /* Variables to store parsed remote ICE info */
Benny Prijono00238772009-04-13 18:41:04 +000063 struct rem_info
64 {
65 char ufrag[80];
66 char pwd[80];
67 unsigned comp_cnt;
68 pj_sockaddr def_addr[PJ_ICE_MAX_COMP];
69 unsigned cand_cnt;
70 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
71 } rem;
72
73} icedemo;
74
Benny Prijono3ec27ba2009-04-15 13:38:40 +000075/* Utility to display error messages */
Benny Prijono00238772009-04-13 18:41:04 +000076static void icedemo_perror(const char *title, pj_status_t status)
77{
78 char errmsg[PJ_ERR_MSG_SIZE];
79
80 pj_strerror(status, errmsg, sizeof(errmsg));
81 PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
82}
83
Benny Prijono3ec27ba2009-04-15 13:38:40 +000084/* Utility: display error message and exit application (usually
85 * because of fatal error.
86 */
Benny Prijono00238772009-04-13 18:41:04 +000087static void err_exit(const char *title, pj_status_t status)
88{
89 if (status != PJ_SUCCESS) {
90 icedemo_perror(title, status);
91 }
92 PJ_LOG(3,(THIS_FILE, "Shutting down.."));
93
94 if (icedemo.icest)
95 pj_ice_strans_destroy(icedemo.icest);
96
97 pj_thread_sleep(500);
98
99 icedemo.thread_quit_flag = PJ_TRUE;
100 if (icedemo.thread) {
101 pj_thread_join(icedemo.thread);
102 pj_thread_destroy(icedemo.thread);
103 }
104
105 if (icedemo.ice_cfg.stun_cfg.ioqueue)
106 pj_ioqueue_destroy(icedemo.ice_cfg.stun_cfg.ioqueue);
107
108 if (icedemo.ice_cfg.stun_cfg.timer_heap)
109 pj_timer_heap_destroy(icedemo.ice_cfg.stun_cfg.timer_heap);
110
111 pj_caching_pool_destroy(&icedemo.cp);
112
113 pj_shutdown();
Benny Prijono41903da2009-12-08 13:11:25 +0000114
115 if (icedemo.log_fhnd) {
116 fclose(icedemo.log_fhnd);
117 icedemo.log_fhnd = NULL;
118 }
119
Benny Prijono00238772009-04-13 18:41:04 +0000120 exit(status != PJ_SUCCESS);
121}
122
123#define CHECK(expr) status=expr; \
124 if (status!=PJ_SUCCESS) { \
125 err_exit(#expr, status); \
126 }
127
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000128/*
129 * This function checks for events from both timer and ioqueue (for
130 * network events). It is invoked by the worker thread.
131 */
132static pj_status_t handle_events(unsigned max_msec, unsigned *p_count)
Benny Prijono00238772009-04-13 18:41:04 +0000133{
134 enum { MAX_NET_EVENTS = 1 };
135 pj_time_val max_timeout = {0, 0};
136 pj_time_val timeout = { 0, 0};
137 unsigned count = 0, net_event_count = 0;
138 int c;
139
140 max_timeout.msec = max_msec;
141
142 /* Poll the timer to run it and also to retrieve the earliest entry. */
143 timeout.sec = timeout.msec = 0;
144 c = pj_timer_heap_poll( icedemo.ice_cfg.stun_cfg.timer_heap, &timeout );
145 if (c > 0)
146 count += c;
147
148 /* timer_heap_poll should never ever returns negative value, or otherwise
149 * ioqueue_poll() will block forever!
150 */
151 pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
152 if (timeout.msec >= 1000) timeout.msec = 999;
153
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000154 /* compare the value with the timeout to wait from timer, and use the
155 * minimum value.
156 */
Benny Prijono00238772009-04-13 18:41:04 +0000157 if (PJ_TIME_VAL_GT(timeout, max_timeout))
158 timeout = max_timeout;
159
160 /* Poll ioqueue.
161 * Repeat polling the ioqueue while we have immediate events, because
162 * timer heap may process more than one events, so if we only process
163 * one network events at a time (such as when IOCP backend is used),
164 * the ioqueue may have trouble keeping up with the request rate.
165 *
166 * For example, for each send() request, one network event will be
167 * reported by ioqueue for the send() completion. If we don't poll
168 * the ioqueue often enough, the send() completion will not be
169 * reported in timely manner.
170 */
171 do {
172 c = pj_ioqueue_poll( icedemo.ice_cfg.stun_cfg.ioqueue, &timeout);
173 if (c < 0) {
174 pj_status_t err = pj_get_netos_error();
175 pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
176 if (p_count)
177 *p_count = count;
178 return err;
179 } else if (c == 0) {
180 break;
181 } else {
182 net_event_count += c;
183 timeout.sec = timeout.msec = 0;
184 }
185 } while (c > 0 && net_event_count < MAX_NET_EVENTS);
186
187 count += net_event_count;
188 if (p_count)
189 *p_count = count;
190
191 return PJ_SUCCESS;
192
193}
194
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000195/*
196 * This is the worker thread that polls event in the background.
197 */
Benny Prijono00238772009-04-13 18:41:04 +0000198static int icedemo_worker_thread(void *unused)
199{
200 PJ_UNUSED_ARG(unused);
201
202 while (!icedemo.thread_quit_flag) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000203 handle_events(500, NULL);
Benny Prijono00238772009-04-13 18:41:04 +0000204 }
205
206 return 0;
207}
208
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000209/*
210 * This is the callback that is registered to the ICE stream transport to
211 * receive notification about incoming data. By "data" it means application
212 * data such as RTP/RTCP, and not packets that belong to ICE signaling (such
213 * as STUN connectivity checks or TURN signaling).
214 */
215static void cb_on_rx_data(pj_ice_strans *ice_st,
216 unsigned comp_id,
217 void *pkt, pj_size_t size,
218 const pj_sockaddr_t *src_addr,
219 unsigned src_addr_len)
Benny Prijono00238772009-04-13 18:41:04 +0000220{
221 char ipstr[PJ_INET6_ADDRSTRLEN+10];
222
223 PJ_UNUSED_ARG(ice_st);
224 PJ_UNUSED_ARG(src_addr_len);
225 PJ_UNUSED_ARG(pkt);
226
Benny Prijono41903da2009-12-08 13:11:25 +0000227 // Don't do this! It will ruin the packet buffer in case TCP is used!
228 //((char*)pkt)[size] = '\0';
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000229
Benny Prijono41903da2009-12-08 13:11:25 +0000230 PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%.*s\"",
Benny Prijono00238772009-04-13 18:41:04 +0000231 comp_id, size,
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000232 pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3),
Benny Prijono41903da2009-12-08 13:11:25 +0000233 (unsigned)size,
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000234 (char*)pkt));
Benny Prijono00238772009-04-13 18:41:04 +0000235}
236
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000237/*
238 * This is the callback that is registered to the ICE stream transport to
239 * receive notification about ICE state progression.
240 */
241static void cb_on_ice_complete(pj_ice_strans *ice_st,
242 pj_ice_strans_op op,
243 pj_status_t status)
Benny Prijono00238772009-04-13 18:41:04 +0000244{
245 const char *opname =
246 (op==PJ_ICE_STRANS_OP_INIT? "initialization" :
247 (op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));
248
Benny Prijono00238772009-04-13 18:41:04 +0000249 if (status == PJ_SUCCESS) {
250 PJ_LOG(3,(THIS_FILE, "ICE %s successful", opname));
251 } else {
252 char errmsg[PJ_ERR_MSG_SIZE];
253
254 pj_strerror(status, errmsg, sizeof(errmsg));
255 PJ_LOG(1,(THIS_FILE, "ICE %s failed: %s", opname, errmsg));
Benny Prijono41903da2009-12-08 13:11:25 +0000256 pj_ice_strans_destroy(ice_st);
257 icedemo.icest = NULL;
Benny Prijono00238772009-04-13 18:41:04 +0000258 }
259}
260
Benny Prijono41903da2009-12-08 13:11:25 +0000261/* log callback to write to file */
262static void log_func(int level, const char *data, int len)
263{
264 if (icedemo.log_fhnd)
265 fwrite(data, len, 1, icedemo.log_fhnd);
266 pj_log_write(level, data, len);
267}
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000268
269/*
270 * This is the main application initialization function. It is called
271 * once (and only once) during application initialization sequence by
272 * main().
273 */
Benny Prijono00238772009-04-13 18:41:04 +0000274static pj_status_t icedemo_init(void)
275{
276 pj_status_t status;
277
Benny Prijono41903da2009-12-08 13:11:25 +0000278 if (icedemo.opt.log_file) {
279 icedemo.log_fhnd = fopen(icedemo.opt.log_file, "a");
280 pj_log_set_log_func(&log_func);
281 }
282
Benny Prijono00238772009-04-13 18:41:04 +0000283 /* Initialize the libraries before anything else */
284 CHECK( pj_init() );
285 CHECK( pjlib_util_init() );
286 CHECK( pjnath_init() );
287
288 /* Must create pool factory, where memory allocations come from */
289 pj_caching_pool_init(&icedemo.cp, NULL, 0);
290
291 /* Init our ICE settings with null values */
292 pj_ice_strans_cfg_default(&icedemo.ice_cfg);
293
294 icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
295
296 /* Create application memory pool */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000297 icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo",
298 512, 512, NULL);
Benny Prijono00238772009-04-13 18:41:04 +0000299
300 /* Create timer heap for timer stuff */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000301 CHECK( pj_timer_heap_create(icedemo.pool, 100,
302 &icedemo.ice_cfg.stun_cfg.timer_heap) );
Benny Prijono00238772009-04-13 18:41:04 +0000303
304 /* and create ioqueue for network I/O stuff */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000305 CHECK( pj_ioqueue_create(icedemo.pool, 16,
306 &icedemo.ice_cfg.stun_cfg.ioqueue) );
Benny Prijono00238772009-04-13 18:41:04 +0000307
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000308 /* something must poll the timer heap and ioqueue,
309 * unless we're on Symbian where the timer heap and ioqueue run
310 * on themselves.
311 */
312 CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread,
313 NULL, 0, 0, &icedemo.thread) );
Benny Prijono00238772009-04-13 18:41:04 +0000314
315 icedemo.ice_cfg.af = pj_AF_INET();
316
317 /* Create DNS resolver if nameserver is set */
318 if (icedemo.opt.ns.slen) {
319 CHECK( pj_dns_resolver_create(&icedemo.cp.factory,
320 "resolver",
321 0,
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000322 icedemo.ice_cfg.stun_cfg.timer_heap,
Benny Prijono00238772009-04-13 18:41:04 +0000323 icedemo.ice_cfg.stun_cfg.ioqueue,
324 &icedemo.ice_cfg.resolver) );
325
326 CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1,
327 &icedemo.opt.ns, NULL) );
328 }
329
330 /* -= Start initializing ICE stream transport config =- */
331
Benny Prijono329d6382009-05-29 13:04:03 +0000332 /* Maximum number of host candidates */
333 if (icedemo.opt.max_host != -1)
334 icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host;
335
336 /* Nomination strategy */
337 if (icedemo.opt.regular)
338 icedemo.ice_cfg.opt.aggressive = PJ_FALSE;
339 else
340 icedemo.ice_cfg.opt.aggressive = PJ_TRUE;
Benny Prijono00238772009-04-13 18:41:04 +0000341
342 /* Configure STUN/srflx candidate resolution */
343 if (icedemo.opt.stun_srv.slen) {
344 char *pos;
345
346 /* Command line option may contain port number */
347 if ((pos=pj_strchr(&icedemo.opt.stun_srv, ':')) != NULL) {
348 icedemo.ice_cfg.stun.server.ptr = icedemo.opt.stun_srv.ptr;
349 icedemo.ice_cfg.stun.server.slen = (pos - icedemo.opt.stun_srv.ptr);
350
351 icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1);
352 } else {
353 icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv;
354 icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
355 }
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000356
357 /* For this demo app, configure longer STUN keep-alive time
358 * so that it does't clutter the screen output.
359 */
360 icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
Benny Prijono00238772009-04-13 18:41:04 +0000361 }
362
363 /* Configure TURN candidate */
364 if (icedemo.opt.turn_srv.slen) {
365 char *pos;
366
367 /* Command line option may contain port number */
368 if ((pos=pj_strchr(&icedemo.opt.turn_srv, ':')) != NULL) {
369 icedemo.ice_cfg.turn.server.ptr = icedemo.opt.turn_srv.ptr;
370 icedemo.ice_cfg.turn.server.slen = (pos - icedemo.opt.turn_srv.ptr);
371
372 icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1);
373 } else {
374 icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv;
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000375 icedemo.ice_cfg.turn.port = PJ_STUN_PORT;
Benny Prijono00238772009-04-13 18:41:04 +0000376 }
377
378 /* TURN credential */
379 icedemo.ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
380 icedemo.ice_cfg.turn.auth_cred.data.static_cred.username = icedemo.opt.turn_username;
381 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
382 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data = icedemo.opt.turn_password;
383
384 /* Connection type to TURN server */
385 if (icedemo.opt.turn_tcp)
386 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
387 else
388 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
389
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000390 /* For this demo app, configure longer keep-alive time
391 * so that it does't clutter the screen output.
Benny Prijono00238772009-04-13 18:41:04 +0000392 */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000393 icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
Benny Prijono00238772009-04-13 18:41:04 +0000394 }
395
396 /* -= That's it for now, initialization is complete =- */
397 return PJ_SUCCESS;
398}
399
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000400
401/*
402 * Create ICE stream transport instance, invoked from the menu.
403 */
Benny Prijono00238772009-04-13 18:41:04 +0000404static void icedemo_create_instance(void)
405{
406 pj_ice_strans_cb icecb;
407 pj_status_t status;
408
409 if (icedemo.icest != NULL) {
410 puts("ICE instance already created, destroy it first");
411 return;
412 }
413
414 /* init the callback */
415 pj_bzero(&icecb, sizeof(icecb));
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000416 icecb.on_rx_data = cb_on_rx_data;
417 icecb.on_ice_complete = cb_on_ice_complete;
Benny Prijono00238772009-04-13 18:41:04 +0000418
419 /* create the instance */
420 status = pj_ice_strans_create("icedemo", /* object name */
421 &icedemo.ice_cfg, /* settings */
422 icedemo.opt.comp_cnt, /* comp_cnt */
423 NULL, /* user data */
424 &icecb, /* callback */
425 &icedemo.icest) /* instance ptr */
426 ;
427 if (status != PJ_SUCCESS)
428 icedemo_perror("error creating ice", status);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000429 else
430 PJ_LOG(3,(THIS_FILE, "ICE instance successfully created"));
Benny Prijono00238772009-04-13 18:41:04 +0000431}
432
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000433/* Utility to nullify parsed remote info */
Benny Prijono00238772009-04-13 18:41:04 +0000434static void reset_rem_info(void)
435{
436 pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
437}
438
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000439
440/*
441 * Destroy ICE stream transport instance, invoked from the menu.
442 */
Benny Prijono00238772009-04-13 18:41:04 +0000443static void icedemo_destroy_instance(void)
444{
445 if (icedemo.icest == NULL) {
446 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
447 return;
448 }
449
450 pj_ice_strans_destroy(icedemo.icest);
451 icedemo.icest = NULL;
452
453 reset_rem_info();
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000454
455 PJ_LOG(3,(THIS_FILE, "ICE instance destroyed"));
Benny Prijono00238772009-04-13 18:41:04 +0000456}
457
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000458
459/*
460 * Create ICE session, invoked from the menu.
461 */
Benny Prijono00238772009-04-13 18:41:04 +0000462static void icedemo_init_session(unsigned rolechar)
463{
464 pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ?
465 PJ_ICE_SESS_ROLE_CONTROLLING :
466 PJ_ICE_SESS_ROLE_CONTROLLED);
467 pj_status_t status;
468
469 if (icedemo.icest == NULL) {
470 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
471 return;
472 }
473
474 if (pj_ice_strans_has_sess(icedemo.icest)) {
475 PJ_LOG(1,(THIS_FILE, "Error: Session already created"));
476 return;
477 }
478
479 status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL);
480 if (status != PJ_SUCCESS)
481 icedemo_perror("error creating session", status);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000482 else
483 PJ_LOG(3,(THIS_FILE, "ICE session created"));
Benny Prijono00238772009-04-13 18:41:04 +0000484
485 reset_rem_info();
486}
487
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000488
489/*
490 * Stop/destroy ICE session, invoked from the menu.
491 */
Benny Prijono00238772009-04-13 18:41:04 +0000492static void icedemo_stop_session(void)
493{
494 pj_status_t status;
495
496 if (icedemo.icest == NULL) {
497 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
498 return;
499 }
500
501 if (!pj_ice_strans_has_sess(icedemo.icest)) {
502 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
503 return;
504 }
505
506 status = pj_ice_strans_stop_ice(icedemo.icest);
507 if (status != PJ_SUCCESS)
508 icedemo_perror("error stopping session", status);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000509 else
510 PJ_LOG(3,(THIS_FILE, "ICE session stopped"));
Benny Prijono00238772009-04-13 18:41:04 +0000511
512 reset_rem_info();
513}
514
Benny Prijono00238772009-04-13 18:41:04 +0000515#define PRINT(fmt, arg0, arg1, arg2, arg3, arg4, arg5) \
516 printed = pj_ansi_snprintf(p, maxlen - (p-buffer), \
517 fmt, arg0, arg1, arg2, arg3, arg4, arg5); \
518 if (printed <= 0) return -PJ_ETOOSMALL; \
519 p += printed
520
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000521
522/* Utility to create a=candidate SDP attribute */
Benny Prijono00238772009-04-13 18:41:04 +0000523static int print_cand(char buffer[], unsigned maxlen,
524 const pj_ice_sess_cand *cand)
525{
526 char ipaddr[PJ_INET6_ADDRSTRLEN];
527 char *p = buffer;
528 int printed;
529
530 PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
531 (int)cand->foundation.slen,
532 cand->foundation.ptr,
533 (unsigned)cand->comp_id,
534 cand->prio,
535 pj_sockaddr_print(&cand->addr, ipaddr,
536 sizeof(ipaddr), 0),
537 (unsigned)pj_sockaddr_get_port(&cand->addr));
538
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000539 PRINT("%s\n",
540 pj_ice_get_cand_type_name(cand->type),
541 0, 0, 0, 0, 0);
Benny Prijono00238772009-04-13 18:41:04 +0000542
543 if (p == buffer+maxlen)
544 return -PJ_ETOOSMALL;
545
546 *p = '\0';
547
548 return p-buffer;
549}
550
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000551/*
552 * Encode ICE information in SDP.
553 */
Benny Prijono00238772009-04-13 18:41:04 +0000554static int encode_session(char buffer[], unsigned maxlen)
555{
556 char *p = buffer;
557 unsigned comp;
558 int printed;
559 pj_str_t local_ufrag, local_pwd;
560 pj_status_t status;
561
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000562 /* Write "dummy" SDP v=, o=, s=, and t= lines */
563 PRINT("v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n",
Benny Prijono00238772009-04-13 18:41:04 +0000564 0, 0, 0, 0, 0, 0);
565
566 /* Get ufrag and pwd from current session */
567 pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
568 NULL, NULL);
569
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000570 /* Write the a=ice-ufrag and a=ice-pwd attributes */
Benny Prijono00238772009-04-13 18:41:04 +0000571 PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n",
572 (int)local_ufrag.slen,
573 local_ufrag.ptr,
574 (int)local_pwd.slen,
575 local_pwd.ptr,
576 0, 0);
577
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000578 /* Write each component */
Benny Prijono00238772009-04-13 18:41:04 +0000579 for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) {
580 unsigned j, cand_cnt;
581 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
582 char ipaddr[PJ_INET6_ADDRSTRLEN];
583
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000584 /* Get default candidate for the component */
Benny Prijono00238772009-04-13 18:41:04 +0000585 status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
586 if (status != PJ_SUCCESS)
587 return -status;
588
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000589 /* Write the default address */
Benny Prijono00238772009-04-13 18:41:04 +0000590 if (comp==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000591 /* For component 1, default address is in m= and c= lines */
Benny Prijono00238772009-04-13 18:41:04 +0000592 PRINT("m=audio %d RTP/AVP 0\n"
593 "c=IN IP4 %s\n",
594 (int)pj_sockaddr_get_port(&cand[0].addr),
595 pj_sockaddr_print(&cand[0].addr, ipaddr,
596 sizeof(ipaddr), 0),
597 0, 0, 0, 0);
598 } else if (comp==1) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000599 /* For component 2, default address is in a=rtcp line */
Benny Prijono00238772009-04-13 18:41:04 +0000600 PRINT("a=rtcp:%d IN IP4 %s\n",
601 (int)pj_sockaddr_get_port(&cand[0].addr),
602 pj_sockaddr_print(&cand[0].addr, ipaddr,
603 sizeof(ipaddr), 0),
604 0, 0, 0, 0);
605 } else {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000606 /* For other components, we'll just invent this.. */
Benny Prijono00238772009-04-13 18:41:04 +0000607 PRINT("a=Xice-defcand:%d IN IP4 %s\n",
608 (int)pj_sockaddr_get_port(&cand[0].addr),
609 pj_sockaddr_print(&cand[0].addr, ipaddr,
610 sizeof(ipaddr), 0),
611 0, 0, 0, 0);
612 }
613
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000614 /* Enumerate all candidates for this component */
Benny Prijono00238772009-04-13 18:41:04 +0000615 status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
616 &cand_cnt, cand);
617 if (status != PJ_SUCCESS)
618 return -status;
619
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000620 /* And encode the candidates as SDP */
Benny Prijono00238772009-04-13 18:41:04 +0000621 for (j=0; j<cand_cnt; ++j) {
622 printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
623 if (printed < 0)
624 return -PJ_ETOOSMALL;
625 p += printed;
626 }
627 }
628
629 if (p == buffer+maxlen)
630 return -PJ_ETOOSMALL;
631
632 *p = '\0';
633 return p - buffer;
634}
635
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000636
637/*
638 * Show information contained in the ICE stream transport. This is
639 * invoked from the menu.
640 */
Benny Prijono00238772009-04-13 18:41:04 +0000641static void icedemo_show_ice(void)
642{
643 static char buffer[1000];
644 int len;
645
646 if (icedemo.icest == NULL) {
647 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
648 return;
649 }
650
651 puts("General info");
652 puts("---------------");
653 printf("Component count : %d\n", icedemo.opt.comp_cnt);
654 printf("Status : ");
655 if (pj_ice_strans_sess_is_complete(icedemo.icest))
656 puts("negotiation complete");
657 else if (pj_ice_strans_sess_is_running(icedemo.icest))
658 puts("negotiation is in progress");
659 else if (pj_ice_strans_has_sess(icedemo.icest))
660 puts("session ready");
661 else
662 puts("session not created");
663
664 if (!pj_ice_strans_has_sess(icedemo.icest)) {
665 puts("Create the session first to see more info");
666 return;
667 }
668
669 printf("Negotiated comp_cnt: %d\n",
670 pj_ice_strans_get_running_comp_cnt(icedemo.icest));
671 printf("Role : %s\n",
672 pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
673 "controlled" : "controlling");
674
675 len = encode_session(buffer, sizeof(buffer));
676 if (len < 0)
677 err_exit("not enough buffer to show ICE status", -len);
678
679 puts("");
680 printf("Local SDP (paste this to remote host):\n"
681 "--------------------------------------\n"
682 "%s\n", buffer);
683
684
685 puts("");
686 puts("Remote info:\n"
687 "----------------------");
688 if (icedemo.rem.cand_cnt==0) {
689 puts("No remote info yet");
690 } else {
691 unsigned i;
692
693 printf("Remote ufrag : %s\n", icedemo.rem.ufrag);
694 printf("Remote password : %s\n", icedemo.rem.pwd);
695 printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt);
696
697 for (i=0; i<icedemo.rem.cand_cnt; ++i) {
698 len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
699 if (len < 0)
700 err_exit("not enough buffer to show ICE status", -len);
701
702 printf(" %s", buffer);
703 }
704 }
705}
706
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000707
708/*
709 * Input and parse SDP from the remote (containing remote's ICE information)
710 * and save it to global variables.
711 */
Benny Prijono00238772009-04-13 18:41:04 +0000712static void icedemo_input_remote(void)
713{
714 char linebuf[80];
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000715 unsigned media_cnt = 0;
Benny Prijono00238772009-04-13 18:41:04 +0000716 unsigned comp0_port = 0;
717 char comp0_addr[80];
718 pj_bool_t done = PJ_FALSE;
719
720 puts("Paste SDP from remote host, end with empty line");
721
722 reset_rem_info();
723
724 comp0_addr[0] = '\0';
725
726 while (!done) {
727 int len;
728 char *line;
729
730 printf(">");
731 if (stdout) fflush(stdout);
732
733 if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
734 break;
735
736 len = strlen(linebuf);
737 while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
738 linebuf[--len] = '\0';
739
740 line = linebuf;
741 while (len && pj_isspace(*line))
742 ++line, --len;
743
744 if (len==0)
745 break;
746
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000747 /* Ignore subsequent media descriptors */
748 if (media_cnt > 1)
749 continue;
750
Benny Prijono00238772009-04-13 18:41:04 +0000751 switch (line[0]) {
752 case 'm':
753 {
754 int cnt;
755 char media[32], portstr[32];
756
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000757 ++media_cnt;
758 if (media_cnt > 1) {
759 puts("Media line ignored");
760 break;
761 }
762
Benny Prijono00238772009-04-13 18:41:04 +0000763 cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
764 if (cnt != 2) {
765 PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
766 goto on_error;
767 }
768
769 comp0_port = atoi(portstr);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000770
Benny Prijono00238772009-04-13 18:41:04 +0000771 }
772 break;
773 case 'c':
774 {
775 int cnt;
776 char c[32], net[32], ip[80];
777
778 cnt = sscanf(line+2, "%s %s %s", c, net, ip);
779 if (cnt != 3) {
780 PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
781 goto on_error;
782 }
783
784 strcpy(comp0_addr, ip);
785 }
786 break;
787 case 'a':
788 {
789 char *attr = strtok(line+2, ": \t\r\n");
790 if (strcmp(attr, "ice-ufrag")==0) {
791 strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
792 } else if (strcmp(attr, "ice-pwd")==0) {
793 strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
794 } else if (strcmp(attr, "rtcp")==0) {
795 char *val = attr+strlen(attr)+1;
796 int af, cnt;
797 int port;
798 char net[32], ip[64];
799 pj_str_t tmp_addr;
800 pj_status_t status;
801
802 cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
803 if (cnt != 3) {
804 PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
805 goto on_error;
806 }
807
808 if (strchr(ip, ':'))
809 af = pj_AF_INET6();
810 else
811 af = pj_AF_INET();
812
813 pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
814 tmp_addr = pj_str(ip);
815 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
816 &tmp_addr);
817 if (status != PJ_SUCCESS) {
818 PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
819 goto on_error;
820 }
821 pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
822
823 } else if (strcmp(attr, "candidate")==0) {
824 char *sdpcand = attr+strlen(attr)+1;
825 int af, cnt;
826 char foundation[32], transport[12], ipaddr[80], type[32];
827 pj_str_t tmpaddr;
828 int comp_id, prio, port;
829 pj_ice_sess_cand *cand;
830 pj_status_t status;
831
832 cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
833 foundation,
834 &comp_id,
835 transport,
836 &prio,
837 ipaddr,
838 &port,
839 type);
840 if (cnt != 7) {
841 PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
842 goto on_error;
843 }
844
845 cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
846 pj_bzero(cand, sizeof(*cand));
847
848 if (strcmp(type, "host")==0)
849 cand->type = PJ_ICE_CAND_TYPE_HOST;
850 else if (strcmp(type, "srflx")==0)
851 cand->type = PJ_ICE_CAND_TYPE_SRFLX;
852 else if (strcmp(type, "relay")==0)
853 cand->type = PJ_ICE_CAND_TYPE_RELAYED;
854 else {
855 PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
856 type));
857 goto on_error;
858 }
859
860 cand->comp_id = (pj_uint8_t)comp_id;
861 pj_strdup2(icedemo.pool, &cand->foundation, foundation);
862 cand->prio = prio;
863
864 if (strchr(ipaddr, ':'))
865 af = pj_AF_INET6();
866 else
867 af = pj_AF_INET();
868
869 tmpaddr = pj_str(ipaddr);
870 pj_sockaddr_init(af, &cand->addr, NULL, 0);
871 status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
872 if (status != PJ_SUCCESS) {
873 PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
874 ipaddr));
875 goto on_error;
876 }
877
878 pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
879
880 ++icedemo.rem.cand_cnt;
881
882 if (cand->comp_id > icedemo.rem.comp_cnt)
883 icedemo.rem.comp_cnt = cand->comp_id;
884 }
885 }
886 break;
887 }
888 }
889
890 if (icedemo.rem.cand_cnt==0 ||
891 icedemo.rem.ufrag[0]==0 ||
892 icedemo.rem.pwd[0]==0 ||
893 icedemo.rem.comp_cnt == 0)
894 {
895 PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
896 goto on_error;
897 }
898
899 if (comp0_port==0 || comp0_addr[0]=='\0') {
900 PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
901 goto on_error;
902 } else {
903 int af;
904 pj_str_t tmp_addr;
905 pj_status_t status;
906
907 if (strchr(comp0_addr, ':'))
908 af = pj_AF_INET6();
909 else
910 af = pj_AF_INET();
911
912 pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
913 tmp_addr = pj_str(comp0_addr);
914 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
915 &tmp_addr);
916 if (status != PJ_SUCCESS) {
917 PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
918 goto on_error;
919 }
920 pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
921 }
922
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000923 PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
924 icedemo.rem.cand_cnt));
Benny Prijono00238772009-04-13 18:41:04 +0000925 return;
926
927on_error:
928 reset_rem_info();
929}
930
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000931
932/*
933 * Start ICE negotiation! This function is invoked from the menu.
934 */
Benny Prijono00238772009-04-13 18:41:04 +0000935static void icedemo_start_nego(void)
936{
937 pj_str_t rufrag, rpwd;
938 pj_status_t status;
939
940 if (icedemo.icest == NULL) {
941 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
942 return;
943 }
944
945 if (!pj_ice_strans_has_sess(icedemo.icest)) {
946 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
947 return;
948 }
949
950 if (icedemo.rem.cand_cnt == 0) {
951 PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
952 return;
953 }
954
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000955 PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
956
Benny Prijono00238772009-04-13 18:41:04 +0000957 status = pj_ice_strans_start_ice(icedemo.icest,
958 pj_cstr(&rufrag, icedemo.rem.ufrag),
959 pj_cstr(&rpwd, icedemo.rem.pwd),
960 icedemo.rem.cand_cnt,
961 icedemo.rem.cand);
962 if (status != PJ_SUCCESS)
963 icedemo_perror("Error starting ICE", status);
964 else
965 PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
966}
967
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000968
969/*
970 * Send application data to remote agent.
971 */
Benny Prijono00238772009-04-13 18:41:04 +0000972static void icedemo_send_data(unsigned comp_id, const char *data)
973{
974 pj_status_t status;
975
976 if (icedemo.icest == NULL) {
977 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
978 return;
979 }
980
981 if (!pj_ice_strans_has_sess(icedemo.icest)) {
982 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
983 return;
984 }
985
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000986 /*
Benny Prijono00238772009-04-13 18:41:04 +0000987 if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
988 PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
989 return;
990 }
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000991 */
Benny Prijono00238772009-04-13 18:41:04 +0000992
Benny Prijono329d6382009-05-29 13:04:03 +0000993 if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
Benny Prijono00238772009-04-13 18:41:04 +0000994 PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
995 return;
996 }
997
998 status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
999 &icedemo.rem.def_addr[comp_id-1],
1000 pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
1001 if (status != PJ_SUCCESS)
1002 icedemo_perror("Error sending data", status);
1003 else
1004 PJ_LOG(3,(THIS_FILE, "Data sent"));
1005}
1006
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001007
1008/*
1009 * Display help for the menu.
1010 */
Benny Prijono00238772009-04-13 18:41:04 +00001011static void icedemo_help_menu(void)
1012{
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001013 puts("");
1014 puts("-= Help on using ICE and this icedemo program =-");
1015 puts("");
1016 puts("This application demonstrates how to use ICE in pjnath without having\n"
1017 "to use the SIP protocol. To use this application, you will need to run\n"
1018 "two instances of this application, to simulate two ICE agents.\n");
1019
1020 puts("Basic ICE flow:\n"
1021 " create instance [menu \"c\"]\n"
1022 " repeat these steps as wanted:\n"
1023 " - init session as offerer or answerer [menu \"i\"]\n"
1024 " - display our SDP [menu \"s\"]\n"
1025 " - \"send\" our SDP from the \"show\" output above to remote, by\n"
1026 " copy-pasting the SDP to the other icedemo application\n"
1027 " - parse remote SDP, by pasting SDP generated by the other icedemo\n"
1028 " instance [menu \"r\"]\n"
1029 " - begin ICE negotiation in our end [menu \"b\"]\n"
1030 " - begin ICE negotiation in the other icedemo instance\n"
1031 " - ICE negotiation will run, and result will be printed to screen\n"
1032 " - send application data to remote [menu \"x\"]\n"
1033 " - end/stop ICE session [menu \"e\"]\n"
1034 " destroy instance [menu \"d\"]\n"
1035 "");
1036
1037 puts("");
1038 puts("This concludes the help screen.");
1039 puts("");
Benny Prijono00238772009-04-13 18:41:04 +00001040}
1041
1042
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001043/*
1044 * Display console menu
1045 */
Benny Prijono00238772009-04-13 18:41:04 +00001046static void icedemo_print_menu(void)
1047{
1048 puts("");
1049 puts("+----------------------------------------------------------------------+");
1050 puts("| M E N U |");
1051 puts("+---+------------------------------------------------------------------+");
1052 puts("| c | create Create the instance |");
1053 puts("| d | destroy Destroy the instance |");
1054 puts("| i | init o|a Initialize ICE session as offerer or answerer |");
1055 puts("| e | stop End/stop ICE session |");
1056 puts("| s | show Display local ICE info |");
1057 puts("| r | remote Input remote ICE info |");
1058 puts("| b | start Begin ICE negotiation |");
1059 puts("| x | send <compid> .. Send data to remote |");
1060 puts("+---+------------------------------------------------------------------+");
1061 puts("| h | help * Help! * |");
1062 puts("| q | quit Quit |");
1063 puts("+----------------------------------------------------------------------+");
1064}
1065
1066
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001067/*
1068 * Main console loop.
1069 */
Benny Prijono00238772009-04-13 18:41:04 +00001070static void icedemo_console(void)
1071{
1072 pj_bool_t app_quit = PJ_FALSE;
1073
1074 while (!app_quit) {
1075 char input[80], *cmd;
1076 const char *SEP = " \t\r\n";
1077 int len;
1078
1079 icedemo_print_menu();
1080
1081 printf("Input: ");
1082 if (stdout) fflush(stdout);
1083
1084 pj_bzero(input, sizeof(input));
1085 if (fgets(input, sizeof(input), stdin) == NULL)
1086 break;
1087
1088 len = strlen(input);
1089 while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
1090 input[--len] = '\0';
1091
1092 cmd = strtok(input, SEP);
1093 if (!cmd)
1094 continue;
1095
1096 if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001097
Benny Prijono00238772009-04-13 18:41:04 +00001098 icedemo_create_instance();
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001099
Benny Prijono00238772009-04-13 18:41:04 +00001100 } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001101
Benny Prijono00238772009-04-13 18:41:04 +00001102 icedemo_destroy_instance();
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001103
Benny Prijono00238772009-04-13 18:41:04 +00001104 } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001105
Benny Prijono00238772009-04-13 18:41:04 +00001106 char *role = strtok(NULL, SEP);
1107 if (role)
1108 icedemo_init_session(*role);
1109 else
1110 puts("error: Role required");
Benny Prijono00238772009-04-13 18:41:04 +00001111
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001112 } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
1113
1114 icedemo_stop_session();
1115
1116 } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
1117
1118 icedemo_show_ice();
1119
1120 } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
1121
1122 icedemo_input_remote();
1123
1124 } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
1125
1126 icedemo_start_nego();
1127
1128 } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
1129
1130 char *comp = strtok(NULL, SEP);
1131
1132 if (!comp) {
Benny Prijono00238772009-04-13 18:41:04 +00001133 PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
1134 } else {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001135 char *data = comp + strlen(comp) + 1;
1136 if (!data)
1137 data = "";
1138 icedemo_send_data(atoi(comp), data);
Benny Prijono00238772009-04-13 18:41:04 +00001139 }
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001140
Benny Prijono00238772009-04-13 18:41:04 +00001141 } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001142
Benny Prijono00238772009-04-13 18:41:04 +00001143 icedemo_help_menu();
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001144
Benny Prijono00238772009-04-13 18:41:04 +00001145 } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001146
Benny Prijono00238772009-04-13 18:41:04 +00001147 app_quit = PJ_TRUE;
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001148
Benny Prijono00238772009-04-13 18:41:04 +00001149 } else {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001150
Benny Prijono00238772009-04-13 18:41:04 +00001151 printf("Invalid command '%s'\n", cmd);
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001152
Benny Prijono00238772009-04-13 18:41:04 +00001153 }
1154 }
1155}
1156
1157
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001158/*
1159 * Display program usage.
1160 */
Benny Prijono00238772009-04-13 18:41:04 +00001161static void icedemo_usage()
1162{
1163 puts("Usage: icedemo [optons]");
1164 printf("icedemo v%s by pjsip.org\n", pj_get_version());
1165 puts("");
1166 puts("General options:");
1167 puts(" --comp-cnt, -c N Component count (default=1)");
1168 puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
1169 puts(" resolution");
Benny Prijono329d6382009-05-29 13:04:03 +00001170 puts(" --max-host, -H N Set max number of host candidates to N");
1171 puts(" --regular, -R Use regular nomination (default aggressive)");
Benny Prijono41903da2009-12-08 13:11:25 +00001172 puts(" --log-file, -L FILE Save output to log FILE");
Benny Prijono00238772009-04-13 18:41:04 +00001173 puts(" --help, -h Display this screen.");
1174 puts("");
1175 puts("STUN related options:");
1176 puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server.");
1177 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1178 puts(" name if DNS SRV resolution is used.");
1179 puts("");
1180 puts("TURN related options:");
1181 puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN server.");
1182 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1183 puts(" name if DNS SRV resolution is used.");
1184 puts(" --turn-tcp, -T Use TCP to connect to TURN server");
1185 puts(" --turn-username, -u UID Set TURN username of the credential to UID");
1186 puts(" --turn-password, -p PWD Set password of the credential to WPWD");
1187 puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests");
1188 puts("");
1189}
1190
1191
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001192/*
1193 * And here's the main()
1194 */
Benny Prijono00238772009-04-13 18:41:04 +00001195int main(int argc, char *argv[])
1196{
1197 struct pj_getopt_option long_options[] = {
1198 { "comp-cnt", 1, 0, 'c'},
1199 { "nameserver", 1, 0, 'n'},
Benny Prijono329d6382009-05-29 13:04:03 +00001200 { "max-host", 1, 0, 'H'},
Benny Prijono00238772009-04-13 18:41:04 +00001201 { "help", 0, 0, 'h'},
1202 { "stun-srv", 1, 0, 's'},
1203 { "turn-srv", 1, 0, 't'},
1204 { "turn-tcp", 0, 0, 'T'},
1205 { "turn-username", 1, 0, 'u'},
1206 { "turn-password", 1, 0, 'p'},
Benny Prijono329d6382009-05-29 13:04:03 +00001207 { "turn-fingerprint", 0, 0, 'F'},
Benny Prijono41903da2009-12-08 13:11:25 +00001208 { "regular", 0, 0, 'R'},
1209 { "log-file", 1, 0, 'L'},
Benny Prijono00238772009-04-13 18:41:04 +00001210 };
1211 int c, opt_id;
1212 pj_status_t status;
1213
1214 icedemo.opt.comp_cnt = 1;
Benny Prijono329d6382009-05-29 13:04:03 +00001215 icedemo.opt.max_host = -1;
Benny Prijono00238772009-04-13 18:41:04 +00001216
Benny Prijono41903da2009-12-08 13:11:25 +00001217 while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
Benny Prijono00238772009-04-13 18:41:04 +00001218 switch (c) {
1219 case 'c':
1220 icedemo.opt.comp_cnt = atoi(pj_optarg);
1221 if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
1222 puts("Invalid component count value");
1223 return 1;
1224 }
1225 break;
1226 case 'n':
1227 icedemo.opt.ns = pj_str(pj_optarg);
1228 break;
1229 case 'H':
Benny Prijono329d6382009-05-29 13:04:03 +00001230 icedemo.opt.max_host = atoi(pj_optarg);
Benny Prijono00238772009-04-13 18:41:04 +00001231 break;
1232 case 'h':
1233 icedemo_usage();
1234 return 0;
1235 case 's':
1236 icedemo.opt.stun_srv = pj_str(pj_optarg);
1237 break;
1238 case 't':
1239 icedemo.opt.turn_srv = pj_str(pj_optarg);
1240 break;
1241 case 'T':
1242 icedemo.opt.turn_tcp = PJ_TRUE;
1243 break;
1244 case 'u':
1245 icedemo.opt.turn_username = pj_str(pj_optarg);
1246 break;
1247 case 'p':
1248 icedemo.opt.turn_password = pj_str(pj_optarg);
1249 break;
1250 case 'F':
1251 icedemo.opt.turn_fingerprint = PJ_TRUE;
1252 break;
Benny Prijono329d6382009-05-29 13:04:03 +00001253 case 'R':
1254 icedemo.opt.regular = PJ_TRUE;
1255 break;
Benny Prijono41903da2009-12-08 13:11:25 +00001256 case 'L':
1257 icedemo.opt.log_file = pj_optarg;
1258 break;
Benny Prijono00238772009-04-13 18:41:04 +00001259 default:
1260 printf("Argument \"%s\" is not valid. Use -h to see help",
1261 argv[pj_optind]);
1262 return 1;
1263 }
1264 }
1265
1266 status = icedemo_init();
1267 if (status != PJ_SUCCESS)
1268 return 1;
1269
1270 icedemo_console();
1271
1272 err_exit("Quitting..", PJ_SUCCESS);
1273 return 0;
1274}