blob: 7f17b92d2196247baf11702697c6f96f681776c2 [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{
Benny Prijono41903da2009-12-08 13:11:25 +0000264 pj_log_write(level, data, len);
Benny Prijono982e47e2010-01-04 14:20:22 +0000265 if (icedemo.log_fhnd) {
266 if (fwrite(data, len, 1, icedemo.log_fhnd) != 1)
267 return;
268 }
Benny Prijono41903da2009-12-08 13:11:25 +0000269}
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000270
271/*
272 * This is the main application initialization function. It is called
273 * once (and only once) during application initialization sequence by
274 * main().
275 */
Benny Prijono00238772009-04-13 18:41:04 +0000276static pj_status_t icedemo_init(void)
277{
278 pj_status_t status;
279
Benny Prijono41903da2009-12-08 13:11:25 +0000280 if (icedemo.opt.log_file) {
281 icedemo.log_fhnd = fopen(icedemo.opt.log_file, "a");
282 pj_log_set_log_func(&log_func);
283 }
284
Benny Prijono00238772009-04-13 18:41:04 +0000285 /* Initialize the libraries before anything else */
286 CHECK( pj_init() );
287 CHECK( pjlib_util_init() );
288 CHECK( pjnath_init() );
289
290 /* Must create pool factory, where memory allocations come from */
291 pj_caching_pool_init(&icedemo.cp, NULL, 0);
292
293 /* Init our ICE settings with null values */
294 pj_ice_strans_cfg_default(&icedemo.ice_cfg);
295
296 icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
297
298 /* Create application memory pool */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000299 icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo",
300 512, 512, NULL);
Benny Prijono00238772009-04-13 18:41:04 +0000301
302 /* Create timer heap for timer stuff */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000303 CHECK( pj_timer_heap_create(icedemo.pool, 100,
304 &icedemo.ice_cfg.stun_cfg.timer_heap) );
Benny Prijono00238772009-04-13 18:41:04 +0000305
306 /* and create ioqueue for network I/O stuff */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000307 CHECK( pj_ioqueue_create(icedemo.pool, 16,
308 &icedemo.ice_cfg.stun_cfg.ioqueue) );
Benny Prijono00238772009-04-13 18:41:04 +0000309
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000310 /* something must poll the timer heap and ioqueue,
311 * unless we're on Symbian where the timer heap and ioqueue run
312 * on themselves.
313 */
314 CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread,
315 NULL, 0, 0, &icedemo.thread) );
Benny Prijono00238772009-04-13 18:41:04 +0000316
317 icedemo.ice_cfg.af = pj_AF_INET();
318
319 /* Create DNS resolver if nameserver is set */
320 if (icedemo.opt.ns.slen) {
321 CHECK( pj_dns_resolver_create(&icedemo.cp.factory,
322 "resolver",
323 0,
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000324 icedemo.ice_cfg.stun_cfg.timer_heap,
Benny Prijono00238772009-04-13 18:41:04 +0000325 icedemo.ice_cfg.stun_cfg.ioqueue,
326 &icedemo.ice_cfg.resolver) );
327
328 CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1,
329 &icedemo.opt.ns, NULL) );
330 }
331
332 /* -= Start initializing ICE stream transport config =- */
333
Benny Prijono329d6382009-05-29 13:04:03 +0000334 /* Maximum number of host candidates */
335 if (icedemo.opt.max_host != -1)
336 icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host;
337
338 /* Nomination strategy */
339 if (icedemo.opt.regular)
340 icedemo.ice_cfg.opt.aggressive = PJ_FALSE;
341 else
342 icedemo.ice_cfg.opt.aggressive = PJ_TRUE;
Benny Prijono00238772009-04-13 18:41:04 +0000343
344 /* Configure STUN/srflx candidate resolution */
345 if (icedemo.opt.stun_srv.slen) {
346 char *pos;
347
348 /* Command line option may contain port number */
349 if ((pos=pj_strchr(&icedemo.opt.stun_srv, ':')) != NULL) {
350 icedemo.ice_cfg.stun.server.ptr = icedemo.opt.stun_srv.ptr;
351 icedemo.ice_cfg.stun.server.slen = (pos - icedemo.opt.stun_srv.ptr);
352
353 icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1);
354 } else {
355 icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv;
356 icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
357 }
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000358
359 /* For this demo app, configure longer STUN keep-alive time
360 * so that it does't clutter the screen output.
361 */
362 icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
Benny Prijono00238772009-04-13 18:41:04 +0000363 }
364
365 /* Configure TURN candidate */
366 if (icedemo.opt.turn_srv.slen) {
367 char *pos;
368
369 /* Command line option may contain port number */
370 if ((pos=pj_strchr(&icedemo.opt.turn_srv, ':')) != NULL) {
371 icedemo.ice_cfg.turn.server.ptr = icedemo.opt.turn_srv.ptr;
372 icedemo.ice_cfg.turn.server.slen = (pos - icedemo.opt.turn_srv.ptr);
373
374 icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1);
375 } else {
376 icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv;
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000377 icedemo.ice_cfg.turn.port = PJ_STUN_PORT;
Benny Prijono00238772009-04-13 18:41:04 +0000378 }
379
380 /* TURN credential */
381 icedemo.ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
382 icedemo.ice_cfg.turn.auth_cred.data.static_cred.username = icedemo.opt.turn_username;
383 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
384 icedemo.ice_cfg.turn.auth_cred.data.static_cred.data = icedemo.opt.turn_password;
385
386 /* Connection type to TURN server */
387 if (icedemo.opt.turn_tcp)
388 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
389 else
390 icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
391
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000392 /* For this demo app, configure longer keep-alive time
393 * so that it does't clutter the screen output.
Benny Prijono00238772009-04-13 18:41:04 +0000394 */
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000395 icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
Benny Prijono00238772009-04-13 18:41:04 +0000396 }
397
398 /* -= That's it for now, initialization is complete =- */
399 return PJ_SUCCESS;
400}
401
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000402
403/*
404 * Create ICE stream transport instance, invoked from the menu.
405 */
Benny Prijono00238772009-04-13 18:41:04 +0000406static void icedemo_create_instance(void)
407{
408 pj_ice_strans_cb icecb;
409 pj_status_t status;
410
411 if (icedemo.icest != NULL) {
412 puts("ICE instance already created, destroy it first");
413 return;
414 }
415
416 /* init the callback */
417 pj_bzero(&icecb, sizeof(icecb));
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000418 icecb.on_rx_data = cb_on_rx_data;
419 icecb.on_ice_complete = cb_on_ice_complete;
Benny Prijono00238772009-04-13 18:41:04 +0000420
421 /* create the instance */
422 status = pj_ice_strans_create("icedemo", /* object name */
423 &icedemo.ice_cfg, /* settings */
424 icedemo.opt.comp_cnt, /* comp_cnt */
425 NULL, /* user data */
426 &icecb, /* callback */
427 &icedemo.icest) /* instance ptr */
428 ;
429 if (status != PJ_SUCCESS)
430 icedemo_perror("error creating ice", status);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000431 else
432 PJ_LOG(3,(THIS_FILE, "ICE instance successfully created"));
Benny Prijono00238772009-04-13 18:41:04 +0000433}
434
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000435/* Utility to nullify parsed remote info */
Benny Prijono00238772009-04-13 18:41:04 +0000436static void reset_rem_info(void)
437{
438 pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
439}
440
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000441
442/*
443 * Destroy ICE stream transport instance, invoked from the menu.
444 */
Benny Prijono00238772009-04-13 18:41:04 +0000445static void icedemo_destroy_instance(void)
446{
447 if (icedemo.icest == NULL) {
448 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
449 return;
450 }
451
452 pj_ice_strans_destroy(icedemo.icest);
453 icedemo.icest = NULL;
454
455 reset_rem_info();
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000456
457 PJ_LOG(3,(THIS_FILE, "ICE instance destroyed"));
Benny Prijono00238772009-04-13 18:41:04 +0000458}
459
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000460
461/*
462 * Create ICE session, invoked from the menu.
463 */
Benny Prijono00238772009-04-13 18:41:04 +0000464static void icedemo_init_session(unsigned rolechar)
465{
466 pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ?
467 PJ_ICE_SESS_ROLE_CONTROLLING :
468 PJ_ICE_SESS_ROLE_CONTROLLED);
469 pj_status_t status;
470
471 if (icedemo.icest == NULL) {
472 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
473 return;
474 }
475
476 if (pj_ice_strans_has_sess(icedemo.icest)) {
477 PJ_LOG(1,(THIS_FILE, "Error: Session already created"));
478 return;
479 }
480
481 status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL);
482 if (status != PJ_SUCCESS)
483 icedemo_perror("error creating session", status);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000484 else
485 PJ_LOG(3,(THIS_FILE, "ICE session created"));
Benny Prijono00238772009-04-13 18:41:04 +0000486
487 reset_rem_info();
488}
489
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000490
491/*
492 * Stop/destroy ICE session, invoked from the menu.
493 */
Benny Prijono00238772009-04-13 18:41:04 +0000494static void icedemo_stop_session(void)
495{
496 pj_status_t status;
497
498 if (icedemo.icest == NULL) {
499 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
500 return;
501 }
502
503 if (!pj_ice_strans_has_sess(icedemo.icest)) {
504 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
505 return;
506 }
507
508 status = pj_ice_strans_stop_ice(icedemo.icest);
509 if (status != PJ_SUCCESS)
510 icedemo_perror("error stopping session", status);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000511 else
512 PJ_LOG(3,(THIS_FILE, "ICE session stopped"));
Benny Prijono00238772009-04-13 18:41:04 +0000513
514 reset_rem_info();
515}
516
Benny Prijono00238772009-04-13 18:41:04 +0000517#define PRINT(fmt, arg0, arg1, arg2, arg3, arg4, arg5) \
518 printed = pj_ansi_snprintf(p, maxlen - (p-buffer), \
519 fmt, arg0, arg1, arg2, arg3, arg4, arg5); \
520 if (printed <= 0) return -PJ_ETOOSMALL; \
521 p += printed
522
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000523
524/* Utility to create a=candidate SDP attribute */
Benny Prijono00238772009-04-13 18:41:04 +0000525static int print_cand(char buffer[], unsigned maxlen,
526 const pj_ice_sess_cand *cand)
527{
528 char ipaddr[PJ_INET6_ADDRSTRLEN];
529 char *p = buffer;
530 int printed;
531
532 PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
533 (int)cand->foundation.slen,
534 cand->foundation.ptr,
535 (unsigned)cand->comp_id,
536 cand->prio,
537 pj_sockaddr_print(&cand->addr, ipaddr,
538 sizeof(ipaddr), 0),
539 (unsigned)pj_sockaddr_get_port(&cand->addr));
540
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000541 PRINT("%s\n",
542 pj_ice_get_cand_type_name(cand->type),
543 0, 0, 0, 0, 0);
Benny Prijono00238772009-04-13 18:41:04 +0000544
545 if (p == buffer+maxlen)
546 return -PJ_ETOOSMALL;
547
548 *p = '\0';
549
550 return p-buffer;
551}
552
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000553/*
554 * Encode ICE information in SDP.
555 */
Benny Prijono00238772009-04-13 18:41:04 +0000556static int encode_session(char buffer[], unsigned maxlen)
557{
558 char *p = buffer;
559 unsigned comp;
560 int printed;
561 pj_str_t local_ufrag, local_pwd;
562 pj_status_t status;
563
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000564 /* Write "dummy" SDP v=, o=, s=, and t= lines */
565 PRINT("v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n",
Benny Prijono00238772009-04-13 18:41:04 +0000566 0, 0, 0, 0, 0, 0);
567
568 /* Get ufrag and pwd from current session */
569 pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
570 NULL, NULL);
571
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000572 /* Write the a=ice-ufrag and a=ice-pwd attributes */
Benny Prijono00238772009-04-13 18:41:04 +0000573 PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n",
574 (int)local_ufrag.slen,
575 local_ufrag.ptr,
576 (int)local_pwd.slen,
577 local_pwd.ptr,
578 0, 0);
579
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000580 /* Write each component */
Benny Prijono00238772009-04-13 18:41:04 +0000581 for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) {
582 unsigned j, cand_cnt;
583 pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
584 char ipaddr[PJ_INET6_ADDRSTRLEN];
585
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000586 /* Get default candidate for the component */
Benny Prijono00238772009-04-13 18:41:04 +0000587 status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
588 if (status != PJ_SUCCESS)
589 return -status;
590
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000591 /* Write the default address */
Benny Prijono00238772009-04-13 18:41:04 +0000592 if (comp==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000593 /* For component 1, default address is in m= and c= lines */
Benny Prijono00238772009-04-13 18:41:04 +0000594 PRINT("m=audio %d RTP/AVP 0\n"
595 "c=IN IP4 %s\n",
596 (int)pj_sockaddr_get_port(&cand[0].addr),
597 pj_sockaddr_print(&cand[0].addr, ipaddr,
598 sizeof(ipaddr), 0),
599 0, 0, 0, 0);
600 } else if (comp==1) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000601 /* For component 2, default address is in a=rtcp line */
Benny Prijono00238772009-04-13 18:41:04 +0000602 PRINT("a=rtcp:%d IN IP4 %s\n",
603 (int)pj_sockaddr_get_port(&cand[0].addr),
604 pj_sockaddr_print(&cand[0].addr, ipaddr,
605 sizeof(ipaddr), 0),
606 0, 0, 0, 0);
607 } else {
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000608 /* For other components, we'll just invent this.. */
Benny Prijono00238772009-04-13 18:41:04 +0000609 PRINT("a=Xice-defcand:%d IN IP4 %s\n",
610 (int)pj_sockaddr_get_port(&cand[0].addr),
611 pj_sockaddr_print(&cand[0].addr, ipaddr,
612 sizeof(ipaddr), 0),
613 0, 0, 0, 0);
614 }
615
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000616 /* Enumerate all candidates for this component */
Benny Prijono00238772009-04-13 18:41:04 +0000617 status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
618 &cand_cnt, cand);
619 if (status != PJ_SUCCESS)
620 return -status;
621
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000622 /* And encode the candidates as SDP */
Benny Prijono00238772009-04-13 18:41:04 +0000623 for (j=0; j<cand_cnt; ++j) {
624 printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
625 if (printed < 0)
626 return -PJ_ETOOSMALL;
627 p += printed;
628 }
629 }
630
631 if (p == buffer+maxlen)
632 return -PJ_ETOOSMALL;
633
634 *p = '\0';
635 return p - buffer;
636}
637
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000638
639/*
640 * Show information contained in the ICE stream transport. This is
641 * invoked from the menu.
642 */
Benny Prijono00238772009-04-13 18:41:04 +0000643static void icedemo_show_ice(void)
644{
645 static char buffer[1000];
646 int len;
647
648 if (icedemo.icest == NULL) {
649 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
650 return;
651 }
652
653 puts("General info");
654 puts("---------------");
655 printf("Component count : %d\n", icedemo.opt.comp_cnt);
656 printf("Status : ");
657 if (pj_ice_strans_sess_is_complete(icedemo.icest))
658 puts("negotiation complete");
659 else if (pj_ice_strans_sess_is_running(icedemo.icest))
660 puts("negotiation is in progress");
661 else if (pj_ice_strans_has_sess(icedemo.icest))
662 puts("session ready");
663 else
664 puts("session not created");
665
666 if (!pj_ice_strans_has_sess(icedemo.icest)) {
667 puts("Create the session first to see more info");
668 return;
669 }
670
671 printf("Negotiated comp_cnt: %d\n",
672 pj_ice_strans_get_running_comp_cnt(icedemo.icest));
673 printf("Role : %s\n",
674 pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
675 "controlled" : "controlling");
676
677 len = encode_session(buffer, sizeof(buffer));
678 if (len < 0)
679 err_exit("not enough buffer to show ICE status", -len);
680
681 puts("");
682 printf("Local SDP (paste this to remote host):\n"
683 "--------------------------------------\n"
684 "%s\n", buffer);
685
686
687 puts("");
688 puts("Remote info:\n"
689 "----------------------");
690 if (icedemo.rem.cand_cnt==0) {
691 puts("No remote info yet");
692 } else {
693 unsigned i;
694
695 printf("Remote ufrag : %s\n", icedemo.rem.ufrag);
696 printf("Remote password : %s\n", icedemo.rem.pwd);
697 printf("Remote cand. cnt. : %d\n", icedemo.rem.cand_cnt);
698
699 for (i=0; i<icedemo.rem.cand_cnt; ++i) {
700 len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
701 if (len < 0)
702 err_exit("not enough buffer to show ICE status", -len);
703
704 printf(" %s", buffer);
705 }
706 }
707}
708
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000709
710/*
711 * Input and parse SDP from the remote (containing remote's ICE information)
712 * and save it to global variables.
713 */
Benny Prijono00238772009-04-13 18:41:04 +0000714static void icedemo_input_remote(void)
715{
716 char linebuf[80];
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000717 unsigned media_cnt = 0;
Benny Prijono00238772009-04-13 18:41:04 +0000718 unsigned comp0_port = 0;
719 char comp0_addr[80];
720 pj_bool_t done = PJ_FALSE;
721
722 puts("Paste SDP from remote host, end with empty line");
723
724 reset_rem_info();
725
726 comp0_addr[0] = '\0';
727
728 while (!done) {
729 int len;
730 char *line;
731
732 printf(">");
733 if (stdout) fflush(stdout);
734
735 if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
736 break;
737
738 len = strlen(linebuf);
739 while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
740 linebuf[--len] = '\0';
741
742 line = linebuf;
743 while (len && pj_isspace(*line))
744 ++line, --len;
745
746 if (len==0)
747 break;
748
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000749 /* Ignore subsequent media descriptors */
750 if (media_cnt > 1)
751 continue;
752
Benny Prijono00238772009-04-13 18:41:04 +0000753 switch (line[0]) {
754 case 'm':
755 {
756 int cnt;
757 char media[32], portstr[32];
758
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000759 ++media_cnt;
760 if (media_cnt > 1) {
761 puts("Media line ignored");
762 break;
763 }
764
Benny Prijono00238772009-04-13 18:41:04 +0000765 cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
766 if (cnt != 2) {
767 PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
768 goto on_error;
769 }
770
771 comp0_port = atoi(portstr);
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000772
Benny Prijono00238772009-04-13 18:41:04 +0000773 }
774 break;
775 case 'c':
776 {
777 int cnt;
778 char c[32], net[32], ip[80];
779
780 cnt = sscanf(line+2, "%s %s %s", c, net, ip);
781 if (cnt != 3) {
782 PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
783 goto on_error;
784 }
785
786 strcpy(comp0_addr, ip);
787 }
788 break;
789 case 'a':
790 {
791 char *attr = strtok(line+2, ": \t\r\n");
792 if (strcmp(attr, "ice-ufrag")==0) {
793 strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
794 } else if (strcmp(attr, "ice-pwd")==0) {
795 strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
796 } else if (strcmp(attr, "rtcp")==0) {
797 char *val = attr+strlen(attr)+1;
798 int af, cnt;
799 int port;
800 char net[32], ip[64];
801 pj_str_t tmp_addr;
802 pj_status_t status;
803
804 cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
805 if (cnt != 3) {
806 PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
807 goto on_error;
808 }
809
810 if (strchr(ip, ':'))
811 af = pj_AF_INET6();
812 else
813 af = pj_AF_INET();
814
815 pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
816 tmp_addr = pj_str(ip);
817 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
818 &tmp_addr);
819 if (status != PJ_SUCCESS) {
820 PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
821 goto on_error;
822 }
823 pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
824
825 } else if (strcmp(attr, "candidate")==0) {
826 char *sdpcand = attr+strlen(attr)+1;
827 int af, cnt;
828 char foundation[32], transport[12], ipaddr[80], type[32];
829 pj_str_t tmpaddr;
830 int comp_id, prio, port;
831 pj_ice_sess_cand *cand;
832 pj_status_t status;
833
834 cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
835 foundation,
836 &comp_id,
837 transport,
838 &prio,
839 ipaddr,
840 &port,
841 type);
842 if (cnt != 7) {
843 PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
844 goto on_error;
845 }
846
847 cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
848 pj_bzero(cand, sizeof(*cand));
849
850 if (strcmp(type, "host")==0)
851 cand->type = PJ_ICE_CAND_TYPE_HOST;
852 else if (strcmp(type, "srflx")==0)
853 cand->type = PJ_ICE_CAND_TYPE_SRFLX;
854 else if (strcmp(type, "relay")==0)
855 cand->type = PJ_ICE_CAND_TYPE_RELAYED;
856 else {
857 PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'",
858 type));
859 goto on_error;
860 }
861
862 cand->comp_id = (pj_uint8_t)comp_id;
863 pj_strdup2(icedemo.pool, &cand->foundation, foundation);
864 cand->prio = prio;
865
866 if (strchr(ipaddr, ':'))
867 af = pj_AF_INET6();
868 else
869 af = pj_AF_INET();
870
871 tmpaddr = pj_str(ipaddr);
872 pj_sockaddr_init(af, &cand->addr, NULL, 0);
873 status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
874 if (status != PJ_SUCCESS) {
875 PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
876 ipaddr));
877 goto on_error;
878 }
879
880 pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
881
882 ++icedemo.rem.cand_cnt;
883
884 if (cand->comp_id > icedemo.rem.comp_cnt)
885 icedemo.rem.comp_cnt = cand->comp_id;
886 }
887 }
888 break;
889 }
890 }
891
892 if (icedemo.rem.cand_cnt==0 ||
893 icedemo.rem.ufrag[0]==0 ||
894 icedemo.rem.pwd[0]==0 ||
895 icedemo.rem.comp_cnt == 0)
896 {
897 PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
898 goto on_error;
899 }
900
901 if (comp0_port==0 || comp0_addr[0]=='\0') {
902 PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
903 goto on_error;
904 } else {
905 int af;
906 pj_str_t tmp_addr;
907 pj_status_t status;
908
909 if (strchr(comp0_addr, ':'))
910 af = pj_AF_INET6();
911 else
912 af = pj_AF_INET();
913
914 pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
915 tmp_addr = pj_str(comp0_addr);
916 status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
917 &tmp_addr);
918 if (status != PJ_SUCCESS) {
919 PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
920 goto on_error;
921 }
922 pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
923 }
924
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000925 PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added",
926 icedemo.rem.cand_cnt));
Benny Prijono00238772009-04-13 18:41:04 +0000927 return;
928
929on_error:
930 reset_rem_info();
931}
932
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000933
934/*
935 * Start ICE negotiation! This function is invoked from the menu.
936 */
Benny Prijono00238772009-04-13 18:41:04 +0000937static void icedemo_start_nego(void)
938{
939 pj_str_t rufrag, rpwd;
940 pj_status_t status;
941
942 if (icedemo.icest == NULL) {
943 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
944 return;
945 }
946
947 if (!pj_ice_strans_has_sess(icedemo.icest)) {
948 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
949 return;
950 }
951
952 if (icedemo.rem.cand_cnt == 0) {
953 PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
954 return;
955 }
956
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000957 PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
958
Benny Prijono00238772009-04-13 18:41:04 +0000959 status = pj_ice_strans_start_ice(icedemo.icest,
960 pj_cstr(&rufrag, icedemo.rem.ufrag),
961 pj_cstr(&rpwd, icedemo.rem.pwd),
962 icedemo.rem.cand_cnt,
963 icedemo.rem.cand);
964 if (status != PJ_SUCCESS)
965 icedemo_perror("Error starting ICE", status);
966 else
967 PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
968}
969
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000970
971/*
972 * Send application data to remote agent.
973 */
Benny Prijono00238772009-04-13 18:41:04 +0000974static void icedemo_send_data(unsigned comp_id, const char *data)
975{
976 pj_status_t status;
977
978 if (icedemo.icest == NULL) {
979 PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
980 return;
981 }
982
983 if (!pj_ice_strans_has_sess(icedemo.icest)) {
984 PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
985 return;
986 }
987
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000988 /*
Benny Prijono00238772009-04-13 18:41:04 +0000989 if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
990 PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
991 return;
992 }
Benny Prijono3ec27ba2009-04-15 13:38:40 +0000993 */
Benny Prijono00238772009-04-13 18:41:04 +0000994
Benny Prijono329d6382009-05-29 13:04:03 +0000995 if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
Benny Prijono00238772009-04-13 18:41:04 +0000996 PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
997 return;
998 }
999
1000 status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
1001 &icedemo.rem.def_addr[comp_id-1],
1002 pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
1003 if (status != PJ_SUCCESS)
1004 icedemo_perror("Error sending data", status);
1005 else
1006 PJ_LOG(3,(THIS_FILE, "Data sent"));
1007}
1008
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001009
1010/*
1011 * Display help for the menu.
1012 */
Benny Prijono00238772009-04-13 18:41:04 +00001013static void icedemo_help_menu(void)
1014{
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001015 puts("");
1016 puts("-= Help on using ICE and this icedemo program =-");
1017 puts("");
1018 puts("This application demonstrates how to use ICE in pjnath without having\n"
1019 "to use the SIP protocol. To use this application, you will need to run\n"
1020 "two instances of this application, to simulate two ICE agents.\n");
1021
1022 puts("Basic ICE flow:\n"
1023 " create instance [menu \"c\"]\n"
1024 " repeat these steps as wanted:\n"
1025 " - init session as offerer or answerer [menu \"i\"]\n"
1026 " - display our SDP [menu \"s\"]\n"
1027 " - \"send\" our SDP from the \"show\" output above to remote, by\n"
1028 " copy-pasting the SDP to the other icedemo application\n"
1029 " - parse remote SDP, by pasting SDP generated by the other icedemo\n"
1030 " instance [menu \"r\"]\n"
1031 " - begin ICE negotiation in our end [menu \"b\"]\n"
1032 " - begin ICE negotiation in the other icedemo instance\n"
1033 " - ICE negotiation will run, and result will be printed to screen\n"
1034 " - send application data to remote [menu \"x\"]\n"
1035 " - end/stop ICE session [menu \"e\"]\n"
1036 " destroy instance [menu \"d\"]\n"
1037 "");
1038
1039 puts("");
1040 puts("This concludes the help screen.");
1041 puts("");
Benny Prijono00238772009-04-13 18:41:04 +00001042}
1043
1044
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001045/*
1046 * Display console menu
1047 */
Benny Prijono00238772009-04-13 18:41:04 +00001048static void icedemo_print_menu(void)
1049{
1050 puts("");
1051 puts("+----------------------------------------------------------------------+");
1052 puts("| M E N U |");
1053 puts("+---+------------------------------------------------------------------+");
1054 puts("| c | create Create the instance |");
1055 puts("| d | destroy Destroy the instance |");
1056 puts("| i | init o|a Initialize ICE session as offerer or answerer |");
1057 puts("| e | stop End/stop ICE session |");
1058 puts("| s | show Display local ICE info |");
1059 puts("| r | remote Input remote ICE info |");
1060 puts("| b | start Begin ICE negotiation |");
1061 puts("| x | send <compid> .. Send data to remote |");
1062 puts("+---+------------------------------------------------------------------+");
1063 puts("| h | help * Help! * |");
1064 puts("| q | quit Quit |");
1065 puts("+----------------------------------------------------------------------+");
1066}
1067
1068
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001069/*
1070 * Main console loop.
1071 */
Benny Prijono00238772009-04-13 18:41:04 +00001072static void icedemo_console(void)
1073{
1074 pj_bool_t app_quit = PJ_FALSE;
1075
1076 while (!app_quit) {
1077 char input[80], *cmd;
1078 const char *SEP = " \t\r\n";
1079 int len;
1080
1081 icedemo_print_menu();
1082
1083 printf("Input: ");
1084 if (stdout) fflush(stdout);
1085
1086 pj_bzero(input, sizeof(input));
1087 if (fgets(input, sizeof(input), stdin) == NULL)
1088 break;
1089
1090 len = strlen(input);
1091 while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
1092 input[--len] = '\0';
1093
1094 cmd = strtok(input, SEP);
1095 if (!cmd)
1096 continue;
1097
1098 if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001099
Benny Prijono00238772009-04-13 18:41:04 +00001100 icedemo_create_instance();
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001101
Benny Prijono00238772009-04-13 18:41:04 +00001102 } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001103
Benny Prijono00238772009-04-13 18:41:04 +00001104 icedemo_destroy_instance();
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001105
Benny Prijono00238772009-04-13 18:41:04 +00001106 } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001107
Benny Prijono00238772009-04-13 18:41:04 +00001108 char *role = strtok(NULL, SEP);
1109 if (role)
1110 icedemo_init_session(*role);
1111 else
1112 puts("error: Role required");
Benny Prijono00238772009-04-13 18:41:04 +00001113
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001114 } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
1115
1116 icedemo_stop_session();
1117
1118 } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
1119
1120 icedemo_show_ice();
1121
1122 } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
1123
1124 icedemo_input_remote();
1125
1126 } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
1127
1128 icedemo_start_nego();
1129
1130 } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
1131
1132 char *comp = strtok(NULL, SEP);
1133
1134 if (!comp) {
Benny Prijono00238772009-04-13 18:41:04 +00001135 PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
1136 } else {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001137 char *data = comp + strlen(comp) + 1;
1138 if (!data)
1139 data = "";
1140 icedemo_send_data(atoi(comp), data);
Benny Prijono00238772009-04-13 18:41:04 +00001141 }
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001142
Benny Prijono00238772009-04-13 18:41:04 +00001143 } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001144
Benny Prijono00238772009-04-13 18:41:04 +00001145 icedemo_help_menu();
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001146
Benny Prijono00238772009-04-13 18:41:04 +00001147 } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001148
Benny Prijono00238772009-04-13 18:41:04 +00001149 app_quit = PJ_TRUE;
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001150
Benny Prijono00238772009-04-13 18:41:04 +00001151 } else {
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001152
Benny Prijono00238772009-04-13 18:41:04 +00001153 printf("Invalid command '%s'\n", cmd);
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001154
Benny Prijono00238772009-04-13 18:41:04 +00001155 }
1156 }
1157}
1158
1159
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001160/*
1161 * Display program usage.
1162 */
Benny Prijono00238772009-04-13 18:41:04 +00001163static void icedemo_usage()
1164{
1165 puts("Usage: icedemo [optons]");
1166 printf("icedemo v%s by pjsip.org\n", pj_get_version());
1167 puts("");
1168 puts("General options:");
1169 puts(" --comp-cnt, -c N Component count (default=1)");
1170 puts(" --nameserver, -n IP Configure nameserver to activate DNS SRV");
1171 puts(" resolution");
Benny Prijono329d6382009-05-29 13:04:03 +00001172 puts(" --max-host, -H N Set max number of host candidates to N");
1173 puts(" --regular, -R Use regular nomination (default aggressive)");
Benny Prijono41903da2009-12-08 13:11:25 +00001174 puts(" --log-file, -L FILE Save output to log FILE");
Benny Prijono00238772009-04-13 18:41:04 +00001175 puts(" --help, -h Display this screen.");
1176 puts("");
1177 puts("STUN related options:");
1178 puts(" --stun-srv, -s HOSTDOM Enable srflx candidate by resolving to STUN server.");
1179 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1180 puts(" name if DNS SRV resolution is used.");
1181 puts("");
1182 puts("TURN related options:");
1183 puts(" --turn-srv, -t HOSTDOM Enable relayed candidate by using this TURN server.");
1184 puts(" HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
1185 puts(" name if DNS SRV resolution is used.");
1186 puts(" --turn-tcp, -T Use TCP to connect to TURN server");
1187 puts(" --turn-username, -u UID Set TURN username of the credential to UID");
1188 puts(" --turn-password, -p PWD Set password of the credential to WPWD");
1189 puts(" --turn-fingerprint, -F Use fingerprint for outgoing TURN requests");
1190 puts("");
1191}
1192
1193
Benny Prijono3ec27ba2009-04-15 13:38:40 +00001194/*
1195 * And here's the main()
1196 */
Benny Prijono00238772009-04-13 18:41:04 +00001197int main(int argc, char *argv[])
1198{
1199 struct pj_getopt_option long_options[] = {
1200 { "comp-cnt", 1, 0, 'c'},
1201 { "nameserver", 1, 0, 'n'},
Benny Prijono329d6382009-05-29 13:04:03 +00001202 { "max-host", 1, 0, 'H'},
Benny Prijono00238772009-04-13 18:41:04 +00001203 { "help", 0, 0, 'h'},
1204 { "stun-srv", 1, 0, 's'},
1205 { "turn-srv", 1, 0, 't'},
1206 { "turn-tcp", 0, 0, 'T'},
1207 { "turn-username", 1, 0, 'u'},
1208 { "turn-password", 1, 0, 'p'},
Benny Prijono329d6382009-05-29 13:04:03 +00001209 { "turn-fingerprint", 0, 0, 'F'},
Benny Prijono41903da2009-12-08 13:11:25 +00001210 { "regular", 0, 0, 'R'},
1211 { "log-file", 1, 0, 'L'},
Benny Prijono00238772009-04-13 18:41:04 +00001212 };
1213 int c, opt_id;
1214 pj_status_t status;
1215
1216 icedemo.opt.comp_cnt = 1;
Benny Prijono329d6382009-05-29 13:04:03 +00001217 icedemo.opt.max_host = -1;
Benny Prijono00238772009-04-13 18:41:04 +00001218
Benny Prijono41903da2009-12-08 13:11:25 +00001219 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 +00001220 switch (c) {
1221 case 'c':
1222 icedemo.opt.comp_cnt = atoi(pj_optarg);
1223 if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
1224 puts("Invalid component count value");
1225 return 1;
1226 }
1227 break;
1228 case 'n':
1229 icedemo.opt.ns = pj_str(pj_optarg);
1230 break;
1231 case 'H':
Benny Prijono329d6382009-05-29 13:04:03 +00001232 icedemo.opt.max_host = atoi(pj_optarg);
Benny Prijono00238772009-04-13 18:41:04 +00001233 break;
1234 case 'h':
1235 icedemo_usage();
1236 return 0;
1237 case 's':
1238 icedemo.opt.stun_srv = pj_str(pj_optarg);
1239 break;
1240 case 't':
1241 icedemo.opt.turn_srv = pj_str(pj_optarg);
1242 break;
1243 case 'T':
1244 icedemo.opt.turn_tcp = PJ_TRUE;
1245 break;
1246 case 'u':
1247 icedemo.opt.turn_username = pj_str(pj_optarg);
1248 break;
1249 case 'p':
1250 icedemo.opt.turn_password = pj_str(pj_optarg);
1251 break;
1252 case 'F':
1253 icedemo.opt.turn_fingerprint = PJ_TRUE;
1254 break;
Benny Prijono329d6382009-05-29 13:04:03 +00001255 case 'R':
1256 icedemo.opt.regular = PJ_TRUE;
1257 break;
Benny Prijono41903da2009-12-08 13:11:25 +00001258 case 'L':
1259 icedemo.opt.log_file = pj_optarg;
1260 break;
Benny Prijono00238772009-04-13 18:41:04 +00001261 default:
1262 printf("Argument \"%s\" is not valid. Use -h to see help",
1263 argv[pj_optind]);
1264 return 1;
1265 }
1266 }
1267
1268 status = icedemo_init();
1269 if (status != PJ_SUCCESS)
1270 return 1;
1271
1272 icedemo_console();
1273
1274 err_exit("Quitting..", PJ_SUCCESS);
1275 return 0;
1276}