BLOG | DOCUMENTATION | TRAC

Home --> Documentations --> PJNATH Reference

ice_demo, an interactive ICE endpoint

This sample demonstrates how to use ICE stream transport without using signaling protocol such as SIP. It provides interactive user interface to create and manage the ICE sessions as well as to exchange SDP with another ice_demo instance.

Features of the demo application:

  • supports host, STUN, and TURN candidates
  • disabling of host candidates
  • DNS SRV resolution for STUN and TURN servers
  • TCP connection to TURN server
  • Optional use of fingerprint for TURN
  • prints and parse SDP containing ICE infos
  • exchange SDP with copy/paste

This file is pjsip-apps/src/samples/icedemo.c

Screenshot on WinXP:

ice_demo.jpg

ice_demo on WinXP

00001 /* $Id: icedemo.c 4387 2013-02-27 10:16:08Z ming $ */
00002 /* 
00003  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
00004  *
00005  * This program is free software; you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License as published by
00007  * the Free Software Foundation; either version 2 of the License, or
00008  * (at your option) any later version.
00009  *
00010  * This program is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program; if not, write to the Free Software
00017  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
00018  */
00019 #include <stdio.h>
00020 #include <stdlib.h>
00021 #include <pjlib.h>
00022 #include <pjlib-util.h>
00023 #include <pjnath.h>
00024 
00025 
00026 #define THIS_FILE   "icedemo.c"
00027 
00028 /* For this demo app, configure longer STUN keep-alive time
00029  * so that it does't clutter the screen output.
00030  */
00031 #define KA_INTERVAL 300
00032 
00033 
00034 /* This is our global variables */
00035 static struct app_t
00036 {
00037     /* Command line options are stored here */
00038     struct options
00039     {
00040         unsigned    comp_cnt;
00041         pj_str_t    ns;
00042         int         max_host;
00043         pj_bool_t   regular;
00044         pj_str_t    stun_srv;
00045         pj_str_t    turn_srv;
00046         pj_bool_t   turn_tcp;
00047         pj_str_t    turn_username;
00048         pj_str_t    turn_password;
00049         pj_bool_t   turn_fingerprint;
00050         const char *log_file;
00051     } opt;
00052 
00053     /* Our global variables */
00054     pj_caching_pool      cp;
00055     pj_pool_t           *pool;
00056     pj_thread_t         *thread;
00057     pj_bool_t            thread_quit_flag;
00058     pj_ice_strans_cfg    ice_cfg;
00059     pj_ice_strans       *icest;
00060     FILE                *log_fhnd;
00061 
00062     /* Variables to store parsed remote ICE info */
00063     struct rem_info
00064     {
00065         char             ufrag[80];
00066         char             pwd[80];
00067         unsigned         comp_cnt;
00068         pj_sockaddr      def_addr[PJ_ICE_MAX_COMP];
00069         unsigned         cand_cnt;
00070         pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
00071     } rem;
00072 
00073 } icedemo;
00074 
00075 /* Utility to display error messages */
00076 static void icedemo_perror(const char *title, pj_status_t status)
00077 {
00078     char errmsg[PJ_ERR_MSG_SIZE];
00079 
00080     pj_strerror(status, errmsg, sizeof(errmsg));
00081     PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
00082 }
00083 
00084 /* Utility: display error message and exit application (usually
00085  * because of fatal error.
00086  */
00087 static void err_exit(const char *title, pj_status_t status)
00088 {
00089     if (status != PJ_SUCCESS) {
00090         icedemo_perror(title, status);
00091     }
00092     PJ_LOG(3,(THIS_FILE, "Shutting down.."));
00093 
00094     if (icedemo.icest)
00095         pj_ice_strans_destroy(icedemo.icest);
00096     
00097     pj_thread_sleep(500);
00098 
00099     icedemo.thread_quit_flag = PJ_TRUE;
00100     if (icedemo.thread) {
00101         pj_thread_join(icedemo.thread);
00102         pj_thread_destroy(icedemo.thread);
00103     }
00104 
00105     if (icedemo.ice_cfg.stun_cfg.ioqueue)
00106         pj_ioqueue_destroy(icedemo.ice_cfg.stun_cfg.ioqueue);
00107 
00108     if (icedemo.ice_cfg.stun_cfg.timer_heap)
00109         pj_timer_heap_destroy(icedemo.ice_cfg.stun_cfg.timer_heap);
00110 
00111     pj_caching_pool_destroy(&icedemo.cp);
00112 
00113     pj_shutdown();
00114 
00115     if (icedemo.log_fhnd) {
00116         fclose(icedemo.log_fhnd);
00117         icedemo.log_fhnd = NULL;
00118     }
00119 
00120     exit(status != PJ_SUCCESS);
00121 }
00122 
00123 #define CHECK(expr)     status=expr; \
00124                         if (status!=PJ_SUCCESS) { \
00125                             err_exit(#expr, status); \
00126                         }
00127 
00128 /*
00129  * This function checks for events from both timer and ioqueue (for
00130  * network events). It is invoked by the worker thread.
00131  */
00132 static pj_status_t handle_events(unsigned max_msec, unsigned *p_count)
00133 {
00134     enum { MAX_NET_EVENTS = 1 };
00135     pj_time_val max_timeout = {0, 0};
00136     pj_time_val timeout = { 0, 0};
00137     unsigned count = 0, net_event_count = 0;
00138     int c;
00139 
00140     max_timeout.msec = max_msec;
00141 
00142     /* Poll the timer to run it and also to retrieve the earliest entry. */
00143     timeout.sec = timeout.msec = 0;
00144     c = pj_timer_heap_poll( icedemo.ice_cfg.stun_cfg.timer_heap, &timeout );
00145     if (c > 0)
00146         count += c;
00147 
00148     /* timer_heap_poll should never ever returns negative value, or otherwise
00149      * ioqueue_poll() will block forever!
00150      */
00151     pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
00152     if (timeout.msec >= 1000) timeout.msec = 999;
00153 
00154     /* compare the value with the timeout to wait from timer, and use the 
00155      * minimum value. 
00156     */
00157     if (PJ_TIME_VAL_GT(timeout, max_timeout))
00158         timeout = max_timeout;
00159 
00160     /* Poll ioqueue. 
00161      * Repeat polling the ioqueue while we have immediate events, because
00162      * timer heap may process more than one events, so if we only process
00163      * one network events at a time (such as when IOCP backend is used),
00164      * the ioqueue may have trouble keeping up with the request rate.
00165      *
00166      * For example, for each send() request, one network event will be
00167      *   reported by ioqueue for the send() completion. If we don't poll
00168      *   the ioqueue often enough, the send() completion will not be
00169      *   reported in timely manner.
00170      */
00171     do {
00172         c = pj_ioqueue_poll( icedemo.ice_cfg.stun_cfg.ioqueue, &timeout);
00173         if (c < 0) {
00174             pj_status_t err = pj_get_netos_error();
00175             pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
00176             if (p_count)
00177                 *p_count = count;
00178             return err;
00179         } else if (c == 0) {
00180             break;
00181         } else {
00182             net_event_count += c;
00183             timeout.sec = timeout.msec = 0;
00184         }
00185     } while (c > 0 && net_event_count < MAX_NET_EVENTS);
00186 
00187     count += net_event_count;
00188     if (p_count)
00189         *p_count = count;
00190 
00191     return PJ_SUCCESS;
00192 
00193 }
00194 
00195 /*
00196  * This is the worker thread that polls event in the background.
00197  */
00198 static int icedemo_worker_thread(void *unused)
00199 {
00200     PJ_UNUSED_ARG(unused);
00201 
00202     while (!icedemo.thread_quit_flag) {
00203         handle_events(500, NULL);
00204     }
00205 
00206     return 0;
00207 }
00208 
00209 /*
00210  * This is the callback that is registered to the ICE stream transport to
00211  * receive notification about incoming data. By "data" it means application
00212  * data such as RTP/RTCP, and not packets that belong to ICE signaling (such
00213  * as STUN connectivity checks or TURN signaling).
00214  */
00215 static void cb_on_rx_data(pj_ice_strans *ice_st,
00216                           unsigned comp_id, 
00217                           void *pkt, pj_size_t size,
00218                           const pj_sockaddr_t *src_addr,
00219                           unsigned src_addr_len)
00220 {
00221     char ipstr[PJ_INET6_ADDRSTRLEN+10];
00222 
00223     PJ_UNUSED_ARG(ice_st);
00224     PJ_UNUSED_ARG(src_addr_len);
00225     PJ_UNUSED_ARG(pkt);
00226 
00227     // Don't do this! It will ruin the packet buffer in case TCP is used!
00228     //((char*)pkt)[size] = '\0';
00229 
00230     PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%.*s\"",
00231               comp_id, size,
00232               pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3),
00233               (unsigned)size,
00234               (char*)pkt));
00235 }
00236 
00237 /*
00238  * This is the callback that is registered to the ICE stream transport to
00239  * receive notification about ICE state progression.
00240  */
00241 static void cb_on_ice_complete(pj_ice_strans *ice_st, 
00242                                pj_ice_strans_op op,
00243                                pj_status_t status)
00244 {
00245     const char *opname = 
00246         (op==PJ_ICE_STRANS_OP_INIT? "initialization" :
00247             (op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));
00248 
00249     if (status == PJ_SUCCESS) {
00250         PJ_LOG(3,(THIS_FILE, "ICE %s successful", opname));
00251     } else {
00252         char errmsg[PJ_ERR_MSG_SIZE];
00253 
00254         pj_strerror(status, errmsg, sizeof(errmsg));
00255         PJ_LOG(1,(THIS_FILE, "ICE %s failed: %s", opname, errmsg));
00256         pj_ice_strans_destroy(ice_st);
00257         icedemo.icest = NULL;
00258     }
00259 }
00260 
00261 /* log callback to write to file */
00262 static void log_func(int level, const char *data, int len)
00263 {
00264     pj_log_write(level, data, len);
00265     if (icedemo.log_fhnd) {
00266         if (fwrite(data, len, 1, icedemo.log_fhnd) != 1)
00267             return;
00268     }
00269 }
00270 
00271 /*
00272  * This is the main application initialization function. It is called
00273  * once (and only once) during application initialization sequence by 
00274  * main().
00275  */
00276 static pj_status_t icedemo_init(void)
00277 {
00278     pj_status_t status;
00279 
00280     if (icedemo.opt.log_file) {
00281         icedemo.log_fhnd = fopen(icedemo.opt.log_file, "a");
00282         pj_log_set_log_func(&log_func);
00283     }
00284 
00285     /* Initialize the libraries before anything else */
00286     CHECK( pj_init() );
00287     CHECK( pjlib_util_init() );
00288     CHECK( pjnath_init() );
00289 
00290     /* Must create pool factory, where memory allocations come from */
00291     pj_caching_pool_init(&icedemo.cp, NULL, 0);
00292 
00293     /* Init our ICE settings with null values */
00294     pj_ice_strans_cfg_default(&icedemo.ice_cfg);
00295 
00296     icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory;
00297 
00298     /* Create application memory pool */
00299     icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo", 
00300                                   512, 512, NULL);
00301 
00302     /* Create timer heap for timer stuff */
00303     CHECK( pj_timer_heap_create(icedemo.pool, 100, 
00304                                 &icedemo.ice_cfg.stun_cfg.timer_heap) );
00305 
00306     /* and create ioqueue for network I/O stuff */
00307     CHECK( pj_ioqueue_create(icedemo.pool, 16, 
00308                              &icedemo.ice_cfg.stun_cfg.ioqueue) );
00309 
00310     /* something must poll the timer heap and ioqueue, 
00311      * unless we're on Symbian where the timer heap and ioqueue run
00312      * on themselves.
00313      */
00314     CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread,
00315                             NULL, 0, 0, &icedemo.thread) );
00316 
00317     icedemo.ice_cfg.af = pj_AF_INET();
00318 
00319     /* Create DNS resolver if nameserver is set */
00320     if (icedemo.opt.ns.slen) {
00321         CHECK( pj_dns_resolver_create(&icedemo.cp.factory, 
00322                                       "resolver", 
00323                                       0, 
00324                                       icedemo.ice_cfg.stun_cfg.timer_heap,
00325                                       icedemo.ice_cfg.stun_cfg.ioqueue, 
00326                                       &icedemo.ice_cfg.resolver) );
00327 
00328         CHECK( pj_dns_resolver_set_ns(icedemo.ice_cfg.resolver, 1, 
00329                                       &icedemo.opt.ns, NULL) );
00330     }
00331 
00332     /* -= Start initializing ICE stream transport config =- */
00333 
00334     /* Maximum number of host candidates */
00335     if (icedemo.opt.max_host != -1)
00336         icedemo.ice_cfg.stun.max_host_cands = icedemo.opt.max_host;
00337 
00338     /* Nomination strategy */
00339     if (icedemo.opt.regular)
00340         icedemo.ice_cfg.opt.aggressive = PJ_FALSE;
00341     else
00342         icedemo.ice_cfg.opt.aggressive = PJ_TRUE;
00343 
00344     /* Configure STUN/srflx candidate resolution */
00345     if (icedemo.opt.stun_srv.slen) {
00346         char *pos;
00347 
00348         /* Command line option may contain port number */
00349         if ((pos=pj_strchr(&icedemo.opt.stun_srv, ':')) != NULL) {
00350             icedemo.ice_cfg.stun.server.ptr = icedemo.opt.stun_srv.ptr;
00351             icedemo.ice_cfg.stun.server.slen = (pos - icedemo.opt.stun_srv.ptr);
00352 
00353             icedemo.ice_cfg.stun.port = (pj_uint16_t)atoi(pos+1);
00354         } else {
00355             icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv;
00356             icedemo.ice_cfg.stun.port = PJ_STUN_PORT;
00357         }
00358 
00359         /* For this demo app, configure longer STUN keep-alive time
00360          * so that it does't clutter the screen output.
00361          */
00362         icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL;
00363     }
00364 
00365     /* Configure TURN candidate */
00366     if (icedemo.opt.turn_srv.slen) {
00367         char *pos;
00368 
00369         /* Command line option may contain port number */
00370         if ((pos=pj_strchr(&icedemo.opt.turn_srv, ':')) != NULL) {
00371             icedemo.ice_cfg.turn.server.ptr = icedemo.opt.turn_srv.ptr;
00372             icedemo.ice_cfg.turn.server.slen = (pos - icedemo.opt.turn_srv.ptr);
00373 
00374             icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1);
00375         } else {
00376             icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv;
00377             icedemo.ice_cfg.turn.port = PJ_STUN_PORT;
00378         }
00379 
00380         /* TURN credential */
00381         icedemo.ice_cfg.turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
00382         icedemo.ice_cfg.turn.auth_cred.data.static_cred.username = icedemo.opt.turn_username;
00383         icedemo.ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
00384         icedemo.ice_cfg.turn.auth_cred.data.static_cred.data = icedemo.opt.turn_password;
00385 
00386         /* Connection type to TURN server */
00387         if (icedemo.opt.turn_tcp)
00388             icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_TCP;
00389         else
00390             icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP;
00391 
00392         /* For this demo app, configure longer keep-alive time
00393          * so that it does't clutter the screen output.
00394          */
00395         icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL;
00396     }
00397 
00398     /* -= That's it for now, initialization is complete =- */
00399     return PJ_SUCCESS;
00400 }
00401 
00402 
00403 /*
00404  * Create ICE stream transport instance, invoked from the menu.
00405  */
00406 static void icedemo_create_instance(void)
00407 {
00408     pj_ice_strans_cb icecb;
00409     pj_status_t status;
00410 
00411     if (icedemo.icest != NULL) {
00412         puts("ICE instance already created, destroy it first");
00413         return;
00414     }
00415 
00416     /* init the callback */
00417     pj_bzero(&icecb, sizeof(icecb));
00418     icecb.on_rx_data = cb_on_rx_data;
00419     icecb.on_ice_complete = cb_on_ice_complete;
00420 
00421     /* create the instance */
00422     status = pj_ice_strans_create("icedemo",                /* object name  */
00423                                 &icedemo.ice_cfg,           /* settings     */
00424                                 icedemo.opt.comp_cnt,       /* comp_cnt     */
00425                                 NULL,                       /* user data    */
00426                                 &icecb,                     /* callback     */
00427                                 &icedemo.icest)             /* instance ptr */
00428                                 ;
00429     if (status != PJ_SUCCESS)
00430         icedemo_perror("error creating ice", status);
00431     else
00432         PJ_LOG(3,(THIS_FILE, "ICE instance successfully created"));
00433 }
00434 
00435 /* Utility to nullify parsed remote info */
00436 static void reset_rem_info(void)
00437 {
00438     pj_bzero(&icedemo.rem, sizeof(icedemo.rem));
00439 }
00440 
00441 
00442 /*
00443  * Destroy ICE stream transport instance, invoked from the menu.
00444  */
00445 static void icedemo_destroy_instance(void)
00446 {
00447     if (icedemo.icest == NULL) {
00448         PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00449         return;
00450     }
00451 
00452     pj_ice_strans_destroy(icedemo.icest);
00453     icedemo.icest = NULL;
00454 
00455     reset_rem_info();
00456 
00457     PJ_LOG(3,(THIS_FILE, "ICE instance destroyed"));
00458 }
00459 
00460 
00461 /*
00462  * Create ICE session, invoked from the menu.
00463  */
00464 static void icedemo_init_session(unsigned rolechar)
00465 {
00466     pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ? 
00467                                 PJ_ICE_SESS_ROLE_CONTROLLING : 
00468                                 PJ_ICE_SESS_ROLE_CONTROLLED);
00469     pj_status_t status;
00470 
00471     if (icedemo.icest == NULL) {
00472         PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00473         return;
00474     }
00475 
00476     if (pj_ice_strans_has_sess(icedemo.icest)) {
00477         PJ_LOG(1,(THIS_FILE, "Error: Session already created"));
00478         return;
00479     }
00480 
00481     status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL);
00482     if (status != PJ_SUCCESS)
00483         icedemo_perror("error creating session", status);
00484     else
00485         PJ_LOG(3,(THIS_FILE, "ICE session created"));
00486 
00487     reset_rem_info();
00488 }
00489 
00490 
00491 /*
00492  * Stop/destroy ICE session, invoked from the menu.
00493  */
00494 static void icedemo_stop_session(void)
00495 {
00496     pj_status_t status;
00497 
00498     if (icedemo.icest == NULL) {
00499         PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00500         return;
00501     }
00502 
00503     if (!pj_ice_strans_has_sess(icedemo.icest)) {
00504         PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
00505         return;
00506     }
00507 
00508     status = pj_ice_strans_stop_ice(icedemo.icest);
00509     if (status != PJ_SUCCESS)
00510         icedemo_perror("error stopping session", status);
00511     else
00512         PJ_LOG(3,(THIS_FILE, "ICE session stopped"));
00513 
00514     reset_rem_info();
00515 }
00516 
00517 #define PRINT(fmt, arg0, arg1, arg2, arg3, arg4, arg5)      \
00518         printed = pj_ansi_snprintf(p, maxlen - (p-buffer),  \
00519                                    fmt, arg0, arg1, arg2, arg3, arg4, arg5); \
00520         if (printed <= 0) return -PJ_ETOOSMALL; \
00521         p += printed
00522 
00523 
00524 /* Utility to create a=candidate SDP attribute */
00525 static int print_cand(char buffer[], unsigned maxlen,
00526                       const pj_ice_sess_cand *cand)
00527 {
00528     char ipaddr[PJ_INET6_ADDRSTRLEN];
00529     char *p = buffer;
00530     int printed;
00531 
00532     PRINT("a=candidate:%.*s %u UDP %u %s %u typ ",
00533           (int)cand->foundation.slen,
00534           cand->foundation.ptr,
00535           (unsigned)cand->comp_id,
00536           cand->prio,
00537           pj_sockaddr_print(&cand->addr, ipaddr, 
00538                             sizeof(ipaddr), 0),
00539           (unsigned)pj_sockaddr_get_port(&cand->addr));
00540 
00541     PRINT("%s\n",
00542           pj_ice_get_cand_type_name(cand->type),
00543           0, 0, 0, 0, 0);
00544 
00545     if (p == buffer+maxlen)
00546         return -PJ_ETOOSMALL;
00547 
00548     *p = '\0';
00549 
00550     return p-buffer;
00551 }
00552 
00553 /* 
00554  * Encode ICE information in SDP.
00555  */
00556 static int encode_session(char buffer[], unsigned maxlen)
00557 {
00558     char *p = buffer;
00559     unsigned comp;
00560     int printed;
00561     pj_str_t local_ufrag, local_pwd;
00562     pj_status_t status;
00563 
00564     /* Write "dummy" SDP v=, o=, s=, and t= lines */
00565     PRINT("v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n", 
00566           0, 0, 0, 0, 0, 0);
00567 
00568     /* Get ufrag and pwd from current session */
00569     pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd,
00570                                 NULL, NULL);
00571 
00572     /* Write the a=ice-ufrag and a=ice-pwd attributes */
00573     PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n",
00574            (int)local_ufrag.slen,
00575            local_ufrag.ptr,
00576            (int)local_pwd.slen,
00577            local_pwd.ptr, 
00578            0, 0);
00579 
00580     /* Write each component */
00581     for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) {
00582         unsigned j, cand_cnt;
00583         pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND];
00584         char ipaddr[PJ_INET6_ADDRSTRLEN];
00585 
00586         /* Get default candidate for the component */
00587         status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]);
00588         if (status != PJ_SUCCESS)
00589             return -status;
00590 
00591         /* Write the default address */
00592         if (comp==0) {
00593             /* For component 1, default address is in m= and c= lines */
00594             PRINT("m=audio %d RTP/AVP 0\n"
00595                   "c=IN IP4 %s\n",
00596                   (int)pj_sockaddr_get_port(&cand[0].addr),
00597                   pj_sockaddr_print(&cand[0].addr, ipaddr,
00598                                     sizeof(ipaddr), 0),
00599                   0, 0, 0, 0);
00600         } else if (comp==1) {
00601             /* For component 2, default address is in a=rtcp line */
00602             PRINT("a=rtcp:%d IN IP4 %s\n",
00603                   (int)pj_sockaddr_get_port(&cand[0].addr),
00604                   pj_sockaddr_print(&cand[0].addr, ipaddr,
00605                                     sizeof(ipaddr), 0),
00606                   0, 0, 0, 0);
00607         } else {
00608             /* For other components, we'll just invent this.. */
00609             PRINT("a=Xice-defcand:%d IN IP4 %s\n",
00610                   (int)pj_sockaddr_get_port(&cand[0].addr),
00611                   pj_sockaddr_print(&cand[0].addr, ipaddr,
00612                                     sizeof(ipaddr), 0),
00613                   0, 0, 0, 0);
00614         }
00615 
00616         /* Enumerate all candidates for this component */
00617         cand_cnt = PJ_ARRAY_SIZE(cand);
00618         status = pj_ice_strans_enum_cands(icedemo.icest, comp+1,
00619                                           &cand_cnt, cand);
00620         if (status != PJ_SUCCESS)
00621             return -status;
00622 
00623         /* And encode the candidates as SDP */
00624         for (j=0; j<cand_cnt; ++j) {
00625             printed = print_cand(p, maxlen - (p-buffer), &cand[j]);
00626             if (printed < 0)
00627                 return -PJ_ETOOSMALL;
00628             p += printed;
00629         }
00630     }
00631 
00632     if (p == buffer+maxlen)
00633         return -PJ_ETOOSMALL;
00634 
00635     *p = '\0';
00636     return p - buffer;
00637 }
00638 
00639 
00640 /*
00641  * Show information contained in the ICE stream transport. This is
00642  * invoked from the menu.
00643  */
00644 static void icedemo_show_ice(void)
00645 {
00646     static char buffer[1000];
00647     int len;
00648 
00649     if (icedemo.icest == NULL) {
00650         PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00651         return;
00652     }
00653 
00654     puts("General info");
00655     puts("---------------");
00656     printf("Component count    : %d\n", icedemo.opt.comp_cnt);
00657     printf("Status             : ");
00658     if (pj_ice_strans_sess_is_complete(icedemo.icest))
00659         puts("negotiation complete");
00660     else if (pj_ice_strans_sess_is_running(icedemo.icest))
00661         puts("negotiation is in progress");
00662     else if (pj_ice_strans_has_sess(icedemo.icest))
00663         puts("session ready");
00664     else
00665         puts("session not created");
00666 
00667     if (!pj_ice_strans_has_sess(icedemo.icest)) {
00668         puts("Create the session first to see more info");
00669         return;
00670     }
00671 
00672     printf("Negotiated comp_cnt: %d\n", 
00673            pj_ice_strans_get_running_comp_cnt(icedemo.icest));
00674     printf("Role               : %s\n",
00675            pj_ice_strans_get_role(icedemo.icest)==PJ_ICE_SESS_ROLE_CONTROLLED ?
00676            "controlled" : "controlling");
00677 
00678     len = encode_session(buffer, sizeof(buffer));
00679     if (len < 0)
00680         err_exit("not enough buffer to show ICE status", -len);
00681 
00682     puts("");
00683     printf("Local SDP (paste this to remote host):\n"
00684            "--------------------------------------\n"
00685            "%s\n", buffer);
00686 
00687 
00688     puts("");
00689     puts("Remote info:\n"
00690          "----------------------");
00691     if (icedemo.rem.cand_cnt==0) {
00692         puts("No remote info yet");
00693     } else {
00694         unsigned i;
00695 
00696         printf("Remote ufrag       : %s\n", icedemo.rem.ufrag);
00697         printf("Remote password    : %s\n", icedemo.rem.pwd);
00698         printf("Remote cand. cnt.  : %d\n", icedemo.rem.cand_cnt);
00699 
00700         for (i=0; i<icedemo.rem.cand_cnt; ++i) {
00701             len = print_cand(buffer, sizeof(buffer), &icedemo.rem.cand[i]);
00702             if (len < 0)
00703                 err_exit("not enough buffer to show ICE status", -len);
00704 
00705             printf("  %s", buffer);
00706         }
00707     }
00708 }
00709 
00710 
00711 /*
00712  * Input and parse SDP from the remote (containing remote's ICE information) 
00713  * and save it to global variables.
00714  */
00715 static void icedemo_input_remote(void)
00716 {
00717     char linebuf[80];
00718     unsigned media_cnt = 0;
00719     unsigned comp0_port = 0;
00720     char     comp0_addr[80];
00721     pj_bool_t done = PJ_FALSE;
00722 
00723     puts("Paste SDP from remote host, end with empty line");
00724 
00725     reset_rem_info();
00726 
00727     comp0_addr[0] = '\0';
00728 
00729     while (!done) {
00730         int len;
00731         char *line;
00732 
00733         printf(">");
00734         if (stdout) fflush(stdout);
00735 
00736         if (fgets(linebuf, sizeof(linebuf), stdin)==NULL)
00737             break;
00738 
00739         len = strlen(linebuf);
00740         while (len && (linebuf[len-1] == '\r' || linebuf[len-1] == '\n'))
00741             linebuf[--len] = '\0';
00742 
00743         line = linebuf;
00744         while (len && pj_isspace(*line))
00745             ++line, --len;
00746 
00747         if (len==0)
00748             break;
00749 
00750         /* Ignore subsequent media descriptors */
00751         if (media_cnt > 1)
00752             continue;
00753 
00754         switch (line[0]) {
00755         case 'm':
00756             {
00757                 int cnt;
00758                 char media[32], portstr[32];
00759 
00760                 ++media_cnt;
00761                 if (media_cnt > 1) {
00762                     puts("Media line ignored");
00763                     break;
00764                 }
00765 
00766                 cnt = sscanf(line+2, "%s %s RTP/", media, portstr);
00767                 if (cnt != 2) {
00768                     PJ_LOG(1,(THIS_FILE, "Error parsing media line"));
00769                     goto on_error;
00770                 }
00771 
00772                 comp0_port = atoi(portstr);
00773                 
00774             }
00775             break;
00776         case 'c':
00777             {
00778                 int cnt;
00779                 char c[32], net[32], ip[80];
00780                 
00781                 cnt = sscanf(line+2, "%s %s %s", c, net, ip);
00782                 if (cnt != 3) {
00783                     PJ_LOG(1,(THIS_FILE, "Error parsing connection line"));
00784                     goto on_error;
00785                 }
00786 
00787                 strcpy(comp0_addr, ip);
00788             }
00789             break;
00790         case 'a':
00791             {
00792                 char *attr = strtok(line+2, ": \t\r\n");
00793                 if (strcmp(attr, "ice-ufrag")==0) {
00794                     strcpy(icedemo.rem.ufrag, attr+strlen(attr)+1);
00795                 } else if (strcmp(attr, "ice-pwd")==0) {
00796                     strcpy(icedemo.rem.pwd, attr+strlen(attr)+1);
00797                 } else if (strcmp(attr, "rtcp")==0) {
00798                     char *val = attr+strlen(attr)+1;
00799                     int af, cnt;
00800                     int port;
00801                     char net[32], ip[64];
00802                     pj_str_t tmp_addr;
00803                     pj_status_t status;
00804 
00805                     cnt = sscanf(val, "%d IN %s %s", &port, net, ip);
00806                     if (cnt != 3) {
00807                         PJ_LOG(1,(THIS_FILE, "Error parsing rtcp attribute"));
00808                         goto on_error;
00809                     }
00810 
00811                     if (strchr(ip, ':'))
00812                         af = pj_AF_INET6();
00813                     else
00814                         af = pj_AF_INET();
00815 
00816                     pj_sockaddr_init(af, &icedemo.rem.def_addr[1], NULL, 0);
00817                     tmp_addr = pj_str(ip);
00818                     status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[1],
00819                                                       &tmp_addr);
00820                     if (status != PJ_SUCCESS) {
00821                         PJ_LOG(1,(THIS_FILE, "Invalid IP address"));
00822                         goto on_error;
00823                     }
00824                     pj_sockaddr_set_port(&icedemo.rem.def_addr[1], (pj_uint16_t)port);
00825 
00826                 } else if (strcmp(attr, "candidate")==0) {
00827                     char *sdpcand = attr+strlen(attr)+1;
00828                     int af, cnt;
00829                     char foundation[32], transport[12], ipaddr[80], type[32];
00830                     pj_str_t tmpaddr;
00831                     int comp_id, prio, port;
00832                     pj_ice_sess_cand *cand;
00833                     pj_status_t status;
00834 
00835                     cnt = sscanf(sdpcand, "%s %d %s %d %s %d typ %s",
00836                                  foundation,
00837                                  &comp_id,
00838                                  transport,
00839                                  &prio,
00840                                  ipaddr,
00841                                  &port,
00842                                  type);
00843                     if (cnt != 7) {
00844                         PJ_LOG(1, (THIS_FILE, "error: Invalid ICE candidate line"));
00845                         goto on_error;
00846                     }
00847 
00848                     cand = &icedemo.rem.cand[icedemo.rem.cand_cnt];
00849                     pj_bzero(cand, sizeof(*cand));
00850                     
00851                     if (strcmp(type, "host")==0)
00852                         cand->type = PJ_ICE_CAND_TYPE_HOST;
00853                     else if (strcmp(type, "srflx")==0)
00854                         cand->type = PJ_ICE_CAND_TYPE_SRFLX;
00855                     else if (strcmp(type, "relay")==0)
00856                         cand->type = PJ_ICE_CAND_TYPE_RELAYED;
00857                     else {
00858                         PJ_LOG(1, (THIS_FILE, "Error: invalid candidate type '%s'", 
00859                                    type));
00860                         goto on_error;
00861                     }
00862 
00863                     cand->comp_id = (pj_uint8_t)comp_id;
00864                     pj_strdup2(icedemo.pool, &cand->foundation, foundation);
00865                     cand->prio = prio;
00866                     
00867                     if (strchr(ipaddr, ':'))
00868                         af = pj_AF_INET6();
00869                     else
00870                         af = pj_AF_INET();
00871 
00872                     tmpaddr = pj_str(ipaddr);
00873                     pj_sockaddr_init(af, &cand->addr, NULL, 0);
00874                     status = pj_sockaddr_set_str_addr(af, &cand->addr, &tmpaddr);
00875                     if (status != PJ_SUCCESS) {
00876                         PJ_LOG(1,(THIS_FILE, "Error: invalid IP address '%s'",
00877                                   ipaddr));
00878                         goto on_error;
00879                     }
00880 
00881                     pj_sockaddr_set_port(&cand->addr, (pj_uint16_t)port);
00882 
00883                     ++icedemo.rem.cand_cnt;
00884 
00885                     if (cand->comp_id > icedemo.rem.comp_cnt)
00886                         icedemo.rem.comp_cnt = cand->comp_id;
00887                 }
00888             }
00889             break;
00890         }
00891     }
00892 
00893     if (icedemo.rem.cand_cnt==0 ||
00894         icedemo.rem.ufrag[0]==0 ||
00895         icedemo.rem.pwd[0]==0 ||
00896         icedemo.rem.comp_cnt == 0)
00897     {
00898         PJ_LOG(1, (THIS_FILE, "Error: not enough info"));
00899         goto on_error;
00900     }
00901 
00902     if (comp0_port==0 || comp0_addr[0]=='\0') {
00903         PJ_LOG(1, (THIS_FILE, "Error: default address for component 0 not found"));
00904         goto on_error;
00905     } else {
00906         int af;
00907         pj_str_t tmp_addr;
00908         pj_status_t status;
00909 
00910         if (strchr(comp0_addr, ':'))
00911             af = pj_AF_INET6();
00912         else
00913             af = pj_AF_INET();
00914 
00915         pj_sockaddr_init(af, &icedemo.rem.def_addr[0], NULL, 0);
00916         tmp_addr = pj_str(comp0_addr);
00917         status = pj_sockaddr_set_str_addr(af, &icedemo.rem.def_addr[0],
00918                                           &tmp_addr);
00919         if (status != PJ_SUCCESS) {
00920             PJ_LOG(1,(THIS_FILE, "Invalid IP address in c= line"));
00921             goto on_error;
00922         }
00923         pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port);
00924     }
00925 
00926     PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added", 
00927                icedemo.rem.cand_cnt));
00928     return;
00929 
00930 on_error:
00931     reset_rem_info();
00932 }
00933 
00934 
00935 /*
00936  * Start ICE negotiation! This function is invoked from the menu.
00937  */
00938 static void icedemo_start_nego(void)
00939 {
00940     pj_str_t rufrag, rpwd;
00941     pj_status_t status;
00942 
00943     if (icedemo.icest == NULL) {
00944         PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00945         return;
00946     }
00947 
00948     if (!pj_ice_strans_has_sess(icedemo.icest)) {
00949         PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
00950         return;
00951     }
00952 
00953     if (icedemo.rem.cand_cnt == 0) {
00954         PJ_LOG(1,(THIS_FILE, "Error: No remote info, input remote info first"));
00955         return;
00956     }
00957 
00958     PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation.."));
00959 
00960     status = pj_ice_strans_start_ice(icedemo.icest, 
00961                                      pj_cstr(&rufrag, icedemo.rem.ufrag),
00962                                      pj_cstr(&rpwd, icedemo.rem.pwd),
00963                                      icedemo.rem.cand_cnt,
00964                                      icedemo.rem.cand);
00965     if (status != PJ_SUCCESS)
00966         icedemo_perror("Error starting ICE", status);
00967     else
00968         PJ_LOG(3,(THIS_FILE, "ICE negotiation started"));
00969 }
00970 
00971 
00972 /*
00973  * Send application data to remote agent.
00974  */
00975 static void icedemo_send_data(unsigned comp_id, const char *data)
00976 {
00977     pj_status_t status;
00978 
00979     if (icedemo.icest == NULL) {
00980         PJ_LOG(1,(THIS_FILE, "Error: No ICE instance, create it first"));
00981         return;
00982     }
00983 
00984     if (!pj_ice_strans_has_sess(icedemo.icest)) {
00985         PJ_LOG(1,(THIS_FILE, "Error: No ICE session, initialize first"));
00986         return;
00987     }
00988 
00989     /*
00990     if (!pj_ice_strans_sess_is_complete(icedemo.icest)) {
00991         PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress"));
00992         return;
00993     }
00994     */
00995 
00996     if (comp_id<1||comp_id>pj_ice_strans_get_running_comp_cnt(icedemo.icest)) {
00997         PJ_LOG(1,(THIS_FILE, "Error: invalid component ID"));
00998         return;
00999     }
01000 
01001     status = pj_ice_strans_sendto(icedemo.icest, comp_id, data, strlen(data),
01002                                   &icedemo.rem.def_addr[comp_id-1],
01003                                   pj_sockaddr_get_len(&icedemo.rem.def_addr[comp_id-1]));
01004     if (status != PJ_SUCCESS)
01005         icedemo_perror("Error sending data", status);
01006     else
01007         PJ_LOG(3,(THIS_FILE, "Data sent"));
01008 }
01009 
01010 
01011 /*
01012  * Display help for the menu.
01013  */
01014 static void icedemo_help_menu(void)
01015 {
01016     puts("");
01017     puts("-= Help on using ICE and this icedemo program =-");
01018     puts("");
01019     puts("This application demonstrates how to use ICE in pjnath without having\n"
01020          "to use the SIP protocol. To use this application, you will need to run\n"
01021          "two instances of this application, to simulate two ICE agents.\n");
01022 
01023     puts("Basic ICE flow:\n"
01024          " create instance [menu \"c\"]\n"
01025          " repeat these steps as wanted:\n"
01026          "   - init session as offerer or answerer [menu \"i\"]\n"
01027          "   - display our SDP [menu \"s\"]\n"
01028          "   - \"send\" our SDP from the \"show\" output above to remote, by\n"
01029          "     copy-pasting the SDP to the other icedemo application\n"
01030          "   - parse remote SDP, by pasting SDP generated by the other icedemo\n"
01031          "     instance [menu \"r\"]\n"
01032          "   - begin ICE negotiation in our end [menu \"b\"], and \n"
01033          "   - immediately begin ICE negotiation in the other icedemo instance\n"
01034          "   - ICE negotiation will run, and result will be printed to screen\n"
01035          "   - send application data to remote [menu \"x\"]\n"
01036          "   - end/stop ICE session [menu \"e\"]\n"
01037          " destroy instance [menu \"d\"]\n"
01038          "");
01039 
01040     puts("");
01041     puts("This concludes the help screen.");
01042     puts("");
01043 }
01044 
01045 
01046 /*
01047  * Display console menu
01048  */
01049 static void icedemo_print_menu(void)
01050 {
01051     puts("");
01052     puts("+----------------------------------------------------------------------+");
01053     puts("|                    M E N U                                           |");
01054     puts("+---+------------------------------------------------------------------+");
01055     puts("| c | create           Create the instance                             |");
01056     puts("| d | destroy          Destroy the instance                            |");
01057     puts("| i | init o|a         Initialize ICE session as offerer or answerer   |");
01058     puts("| e | stop             End/stop ICE session                            |");
01059     puts("| s | show             Display local ICE info                          |");
01060     puts("| r | remote           Input remote ICE info                           |");
01061     puts("| b | start            Begin ICE negotiation                           |");
01062     puts("| x | send <compid> .. Send data to remote                             |");
01063     puts("+---+------------------------------------------------------------------+");
01064     puts("| h |  help            * Help! *                                       |");
01065     puts("| q |  quit            Quit                                            |");
01066     puts("+----------------------------------------------------------------------+");
01067 }
01068 
01069 
01070 /*
01071  * Main console loop.
01072  */
01073 static void icedemo_console(void)
01074 {
01075     pj_bool_t app_quit = PJ_FALSE;
01076 
01077     while (!app_quit) {
01078         char input[80], *cmd;
01079         const char *SEP = " \t\r\n";
01080         int len;
01081 
01082         icedemo_print_menu();
01083 
01084         printf("Input: ");
01085         if (stdout) fflush(stdout);
01086 
01087         pj_bzero(input, sizeof(input));
01088         if (fgets(input, sizeof(input), stdin) == NULL)
01089             break;
01090 
01091         len = strlen(input);
01092         while (len && (input[len-1]=='\r' || input[len-1]=='\n'))
01093             input[--len] = '\0';
01094 
01095         cmd = strtok(input, SEP);
01096         if (!cmd)
01097             continue;
01098 
01099         if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) {
01100 
01101             icedemo_create_instance();
01102 
01103         } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) {
01104 
01105             icedemo_destroy_instance();
01106 
01107         } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) {
01108 
01109             char *role = strtok(NULL, SEP);
01110             if (role)
01111                 icedemo_init_session(*role);
01112             else
01113                 puts("error: Role required");
01114 
01115         } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) {
01116 
01117             icedemo_stop_session();
01118 
01119         } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) {
01120 
01121             icedemo_show_ice();
01122 
01123         } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) {
01124 
01125             icedemo_input_remote();
01126 
01127         } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) {
01128 
01129             icedemo_start_nego();
01130 
01131         } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) {
01132 
01133             char *comp = strtok(NULL, SEP);
01134 
01135             if (!comp) {
01136                 PJ_LOG(1,(THIS_FILE, "Error: component ID required"));
01137             } else {
01138                 char *data = comp + strlen(comp) + 1;
01139                 if (!data)
01140                     data = "";
01141                 icedemo_send_data(atoi(comp), data);
01142             }
01143 
01144         } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) {
01145 
01146             icedemo_help_menu();
01147 
01148         } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) {
01149 
01150             app_quit = PJ_TRUE;
01151 
01152         } else {
01153 
01154             printf("Invalid command '%s'\n", cmd);
01155 
01156         }
01157     }
01158 }
01159 
01160 
01161 /*
01162  * Display program usage.
01163  */
01164 static void icedemo_usage()
01165 {
01166     puts("Usage: icedemo [optons]");
01167     printf("icedemo v%s by pjsip.org\n", pj_get_version());
01168     puts("");
01169     puts("General options:");
01170     puts(" --comp-cnt, -c N          Component count (default=1)");
01171     puts(" --nameserver, -n IP       Configure nameserver to activate DNS SRV");
01172     puts("                           resolution");
01173     puts(" --max-host, -H N          Set max number of host candidates to N");
01174     puts(" --regular, -R             Use regular nomination (default aggressive)");
01175     puts(" --log-file, -L FILE       Save output to log FILE");
01176     puts(" --help, -h                Display this screen.");
01177     puts("");
01178     puts("STUN related options:");
01179     puts(" --stun-srv, -s HOSTDOM    Enable srflx candidate by resolving to STUN server.");
01180     puts("                           HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
01181     puts("                           name if DNS SRV resolution is used.");
01182     puts("");
01183     puts("TURN related options:");
01184     puts(" --turn-srv, -t HOSTDOM    Enable relayed candidate by using this TURN server.");
01185     puts("                           HOSTDOM may be a \"host_or_ip[:port]\" or a domain");
01186     puts("                           name if DNS SRV resolution is used.");
01187     puts(" --turn-tcp, -T            Use TCP to connect to TURN server");
01188     puts(" --turn-username, -u UID   Set TURN username of the credential to UID");
01189     puts(" --turn-password, -p PWD   Set password of the credential to WPWD");
01190     puts(" --turn-fingerprint, -F    Use fingerprint for outgoing TURN requests");
01191     puts("");
01192 }
01193 
01194 
01195 /*
01196  * And here's the main()
01197  */
01198 int main(int argc, char *argv[])
01199 {
01200     struct pj_getopt_option long_options[] = {
01201         { "comp-cnt",           1, 0, 'c'},
01202         { "nameserver",         1, 0, 'n'},
01203         { "max-host",           1, 0, 'H'},
01204         { "help",               0, 0, 'h'},
01205         { "stun-srv",           1, 0, 's'},
01206         { "turn-srv",           1, 0, 't'},
01207         { "turn-tcp",           0, 0, 'T'},
01208         { "turn-username",      1, 0, 'u'},
01209         { "turn-password",      1, 0, 'p'},
01210         { "turn-fingerprint",   0, 0, 'F'},
01211         { "regular",            0, 0, 'R'},
01212         { "log-file",           1, 0, 'L'},
01213     };
01214     int c, opt_id;
01215     pj_status_t status;
01216 
01217     icedemo.opt.comp_cnt = 1;
01218     icedemo.opt.max_host = -1;
01219 
01220     while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
01221         switch (c) {
01222         case 'c':
01223             icedemo.opt.comp_cnt = atoi(pj_optarg);
01224             if (icedemo.opt.comp_cnt < 1 || icedemo.opt.comp_cnt >= PJ_ICE_MAX_COMP) {
01225                 puts("Invalid component count value");
01226                 return 1;
01227             }
01228             break;
01229         case 'n':
01230             icedemo.opt.ns = pj_str(pj_optarg);
01231             break;
01232         case 'H':
01233             icedemo.opt.max_host = atoi(pj_optarg);
01234             break;
01235         case 'h':
01236             icedemo_usage();
01237             return 0;
01238         case 's':
01239             icedemo.opt.stun_srv = pj_str(pj_optarg);
01240             break;
01241         case 't':
01242             icedemo.opt.turn_srv = pj_str(pj_optarg);
01243             break;
01244         case 'T':
01245             icedemo.opt.turn_tcp = PJ_TRUE;
01246             break;
01247         case 'u':
01248             icedemo.opt.turn_username = pj_str(pj_optarg);
01249             break;
01250         case 'p':
01251             icedemo.opt.turn_password = pj_str(pj_optarg);
01252             break;
01253         case 'F':
01254             icedemo.opt.turn_fingerprint = PJ_TRUE;
01255             break;
01256         case 'R':
01257             icedemo.opt.regular = PJ_TRUE;
01258             break;
01259         case 'L':
01260             icedemo.opt.log_file = pj_optarg;
01261             break;
01262         default:
01263             printf("Argument \"%s\" is not valid. Use -h to see help",
01264                    argv[pj_optind]);
01265             return 1;
01266         }
01267     }
01268 
01269     status = icedemo_init();
01270     if (status != PJ_SUCCESS)
01271         return 1;
01272 
01273     icedemo_console();
01274 
01275     err_exit("Quitting..", PJ_SUCCESS);
01276     return 0;
01277 }

.

 


PJNATH - Open Source NAT traversal helper library supporting STUN, TURN, and ICE
Copyright (C) 2006-2009 Teluu Inc.