diff options
-rw-r--r-- | pjsip-apps/src/samples/icedemo.c | 281 |
1 files changed, 230 insertions, 51 deletions
diff --git a/pjsip-apps/src/samples/icedemo.c b/pjsip-apps/src/samples/icedemo.c index a8eb6e68..1c13cf33 100644 --- a/pjsip-apps/src/samples/icedemo.c +++ b/pjsip-apps/src/samples/icedemo.c @@ -25,8 +25,16 @@ #define THIS_FILE "icedemo.c" +/* For this demo app, configure longer STUN keep-alive time + * so that it does't clutter the screen output. + */ +#define KA_INTERVAL 300 + + +/* This is our global variables */ static struct app_t { + /* Command line options are stored here */ struct options { unsigned comp_cnt; @@ -40,6 +48,7 @@ static struct app_t pj_bool_t turn_fingerprint; } opt; + /* Our global variables */ pj_caching_pool cp; pj_pool_t *pool; pj_thread_t *thread; @@ -47,6 +56,7 @@ static struct app_t pj_ice_strans_cfg ice_cfg; pj_ice_strans *icest; + /* Variables to store parsed remote ICE info */ struct rem_info { char ufrag[80]; @@ -59,6 +69,7 @@ static struct app_t } icedemo; +/* Utility to display error messages */ static void icedemo_perror(const char *title, pj_status_t status) { char errmsg[PJ_ERR_MSG_SIZE]; @@ -67,6 +78,9 @@ static void icedemo_perror(const char *title, pj_status_t status) PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg)); } +/* Utility: display error message and exit application (usually + * because of fatal error. + */ static void err_exit(const char *title, pj_status_t status) { if (status != PJ_SUCCESS) { @@ -102,8 +116,11 @@ static void err_exit(const char *title, pj_status_t status) err_exit(#expr, status); \ } -static pj_status_t icedemo_handle_events(unsigned max_msec, - unsigned *p_count) +/* + * This function checks for events from both timer and ioqueue (for + * network events). It is invoked by the worker thread. + */ +static pj_status_t handle_events(unsigned max_msec, unsigned *p_count) { enum { MAX_NET_EVENTS = 1 }; pj_time_val max_timeout = {0, 0}; @@ -125,7 +142,9 @@ static pj_status_t icedemo_handle_events(unsigned max_msec, pj_assert(timeout.sec >= 0 && timeout.msec >= 0); if (timeout.msec >= 1000) timeout.msec = 999; - /* compare the value with the timeout to wait from timer, and use the minimum value. */ + /* compare the value with the timeout to wait from timer, and use the + * minimum value. + */ if (PJ_TIME_VAL_GT(timeout, max_timeout)) timeout = max_timeout; @@ -164,22 +183,31 @@ static pj_status_t icedemo_handle_events(unsigned max_msec, } +/* + * This is the worker thread that polls event in the background. + */ static int icedemo_worker_thread(void *unused) { PJ_UNUSED_ARG(unused); while (!icedemo.thread_quit_flag) { - icedemo_handle_events(500, NULL); + handle_events(500, NULL); } return 0; } -static void icedemo_on_rx_data(pj_ice_strans *ice_st, - unsigned comp_id, - void *pkt, pj_size_t size, - const pj_sockaddr_t *src_addr, - unsigned src_addr_len) +/* + * This is the callback that is registered to the ICE stream transport to + * receive notification about incoming data. By "data" it means application + * data such as RTP/RTCP, and not packets that belong to ICE signaling (such + * as STUN connectivity checks or TURN signaling). + */ +static void cb_on_rx_data(pj_ice_strans *ice_st, + unsigned comp_id, + void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len) { char ipstr[PJ_INET6_ADDRSTRLEN+10]; @@ -187,14 +215,21 @@ static void icedemo_on_rx_data(pj_ice_strans *ice_st, PJ_UNUSED_ARG(src_addr_len); PJ_UNUSED_ARG(pkt); - PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s", + ((char*)pkt)[size] = '\0'; + + PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%s\"", comp_id, size, - pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3))); + pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3), + (char*)pkt)); } -static void icedemo_on_ice_complete(pj_ice_strans *ice_st, - pj_ice_strans_op op, - pj_status_t status) +/* + * This is the callback that is registered to the ICE stream transport to + * receive notification about ICE state progression. + */ +static void cb_on_ice_complete(pj_ice_strans *ice_st, + pj_ice_strans_op op, + pj_status_t status) { const char *opname = (op==PJ_ICE_STRANS_OP_INIT? "initialization" : @@ -212,6 +247,12 @@ static void icedemo_on_ice_complete(pj_ice_strans *ice_st, } } + +/* + * This is the main application initialization function. It is called + * once (and only once) during application initialization sequence by + * main(). + */ static pj_status_t icedemo_init(void) { pj_status_t status; @@ -230,16 +271,23 @@ static pj_status_t icedemo_init(void) icedemo.ice_cfg.stun_cfg.pf = &icedemo.cp.factory; /* Create application memory pool */ - icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo", 512, 512, NULL); + icedemo.pool = pj_pool_create(&icedemo.cp.factory, "icedemo", + 512, 512, NULL); /* Create timer heap for timer stuff */ - CHECK( pj_timer_heap_create(icedemo.pool, 100, &icedemo.ice_cfg.stun_cfg.timer_heap) ); + CHECK( pj_timer_heap_create(icedemo.pool, 100, + &icedemo.ice_cfg.stun_cfg.timer_heap) ); /* and create ioqueue for network I/O stuff */ - CHECK( pj_ioqueue_create(icedemo.pool, 16, &icedemo.ice_cfg.stun_cfg.ioqueue) ); + CHECK( pj_ioqueue_create(icedemo.pool, 16, + &icedemo.ice_cfg.stun_cfg.ioqueue) ); - /* something must poll the timer heap and ioqueue, unless we're on Symbian */ - CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread, NULL, 0, 0, &icedemo.thread) ); + /* something must poll the timer heap and ioqueue, + * unless we're on Symbian where the timer heap and ioqueue run + * on themselves. + */ + CHECK( pj_thread_create(icedemo.pool, "icedemo", &icedemo_worker_thread, + NULL, 0, 0, &icedemo.thread) ); icedemo.ice_cfg.af = pj_AF_INET(); @@ -248,7 +296,7 @@ static pj_status_t icedemo_init(void) CHECK( pj_dns_resolver_create(&icedemo.cp.factory, "resolver", 0, - icedemo.ice_cfg.stun_cfg.timer_heap, + icedemo.ice_cfg.stun_cfg.timer_heap, icedemo.ice_cfg.stun_cfg.ioqueue, &icedemo.ice_cfg.resolver) ); @@ -275,6 +323,11 @@ static pj_status_t icedemo_init(void) icedemo.ice_cfg.stun.server = icedemo.opt.stun_srv; icedemo.ice_cfg.stun.port = PJ_STUN_PORT; } + + /* For this demo app, configure longer STUN keep-alive time + * so that it does't clutter the screen output. + */ + icedemo.ice_cfg.stun.cfg.ka_interval = KA_INTERVAL; } /* Configure TURN candidate */ @@ -289,7 +342,7 @@ static pj_status_t icedemo_init(void) icedemo.ice_cfg.turn.port = (pj_uint16_t)atoi(pos+1); } else { icedemo.ice_cfg.turn.server = icedemo.opt.turn_srv; - icedemo.ice_cfg.stun.port = PJ_STUN_PORT; + icedemo.ice_cfg.turn.port = PJ_STUN_PORT; } /* TURN credential */ @@ -304,15 +357,20 @@ static pj_status_t icedemo_init(void) else icedemo.ice_cfg.turn.conn_type = PJ_TURN_TP_UDP; - /* You may further customize TURN settings, e.g.: - icedemo.ice_cfg.turn.alloc_param.lifetime = 300 + /* For this demo app, configure longer keep-alive time + * so that it does't clutter the screen output. */ + icedemo.ice_cfg.turn.alloc_param.ka_interval = KA_INTERVAL; } /* -= That's it for now, initialization is complete =- */ return PJ_SUCCESS; } + +/* + * Create ICE stream transport instance, invoked from the menu. + */ static void icedemo_create_instance(void) { pj_ice_strans_cb icecb; @@ -325,8 +383,8 @@ static void icedemo_create_instance(void) /* init the callback */ pj_bzero(&icecb, sizeof(icecb)); - icecb.on_rx_data = icedemo_on_rx_data; - icecb.on_ice_complete = icedemo_on_ice_complete; + icecb.on_rx_data = cb_on_rx_data; + icecb.on_ice_complete = cb_on_ice_complete; /* create the instance */ status = pj_ice_strans_create("icedemo", /* object name */ @@ -338,13 +396,20 @@ static void icedemo_create_instance(void) ; if (status != PJ_SUCCESS) icedemo_perror("error creating ice", status); + else + PJ_LOG(3,(THIS_FILE, "ICE instance successfully created")); } +/* Utility to nullify parsed remote info */ static void reset_rem_info(void) { pj_bzero(&icedemo.rem, sizeof(icedemo.rem)); } + +/* + * Destroy ICE stream transport instance, invoked from the menu. + */ static void icedemo_destroy_instance(void) { if (icedemo.icest == NULL) { @@ -356,8 +421,14 @@ static void icedemo_destroy_instance(void) icedemo.icest = NULL; reset_rem_info(); + + PJ_LOG(3,(THIS_FILE, "ICE instance destroyed")); } + +/* + * Create ICE session, invoked from the menu. + */ static void icedemo_init_session(unsigned rolechar) { pj_ice_sess_role role = (pj_tolower((pj_uint8_t)rolechar)=='o' ? @@ -378,10 +449,16 @@ static void icedemo_init_session(unsigned rolechar) status = pj_ice_strans_init_ice(icedemo.icest, role, NULL, NULL); if (status != PJ_SUCCESS) icedemo_perror("error creating session", status); + else + PJ_LOG(3,(THIS_FILE, "ICE session created")); reset_rem_info(); } + +/* + * Stop/destroy ICE session, invoked from the menu. + */ static void icedemo_stop_session(void) { pj_status_t status; @@ -399,17 +476,20 @@ static void icedemo_stop_session(void) status = pj_ice_strans_stop_ice(icedemo.icest); if (status != PJ_SUCCESS) icedemo_perror("error stopping session", status); + else + PJ_LOG(3,(THIS_FILE, "ICE session stopped")); reset_rem_info(); } - #define PRINT(fmt, arg0, arg1, arg2, arg3, arg4, arg5) \ printed = pj_ansi_snprintf(p, maxlen - (p-buffer), \ fmt, arg0, arg1, arg2, arg3, arg4, arg5); \ if (printed <= 0) return -PJ_ETOOSMALL; \ p += printed + +/* Utility to create a=candidate SDP attribute */ static int print_cand(char buffer[], unsigned maxlen, const pj_ice_sess_cand *cand) { @@ -426,24 +506,9 @@ static int print_cand(char buffer[], unsigned maxlen, sizeof(ipaddr), 0), (unsigned)pj_sockaddr_get_port(&cand->addr)); - switch (cand->type) { - case PJ_ICE_CAND_TYPE_HOST: - PRINT("host\n", 0, 0, 0, 0, 0, 0); - break; - case PJ_ICE_CAND_TYPE_SRFLX: - case PJ_ICE_CAND_TYPE_RELAYED: - case PJ_ICE_CAND_TYPE_PRFLX: - PRINT("%s raddr %s rport %d\n", - pj_ice_get_cand_type_name(cand->type), - pj_sockaddr_print(&cand->rel_addr, ipaddr, - sizeof(ipaddr), 0), - (int)pj_sockaddr_get_port(&cand->rel_addr), - 0, 0, 0); - break; - default: - pj_assert(!"Invalid candidate type"); - return -PJ_EBUG; - } + PRINT("%s\n", + pj_ice_get_cand_type_name(cand->type), + 0, 0, 0, 0, 0); if (p == buffer+maxlen) return -PJ_ETOOSMALL; @@ -453,7 +518,9 @@ static int print_cand(char buffer[], unsigned maxlen, return p-buffer; } -/* Encode ICE information in SDP */ +/* + * Encode ICE information in SDP. + */ static int encode_session(char buffer[], unsigned maxlen) { char *p = buffer; @@ -462,13 +529,15 @@ static int encode_session(char buffer[], unsigned maxlen) pj_str_t local_ufrag, local_pwd; pj_status_t status; - PRINT("v=0\no=- 3414953978 3414953978 IN IP4 127.0.0.1\ns=ice\nt=0 0\n", + /* Write "dummy" SDP v=, o=, s=, and t= lines */ + PRINT("v=0\no=- 3414953978 3414953978 IN IP4 localhost\ns=ice\nt=0 0\n", 0, 0, 0, 0, 0, 0); /* Get ufrag and pwd from current session */ pj_ice_strans_get_ufrag_pwd(icedemo.icest, &local_ufrag, &local_pwd, NULL, NULL); + /* Write the a=ice-ufrag and a=ice-pwd attributes */ PRINT("a=ice-ufrag:%.*s\na=ice-pwd:%.*s\n", (int)local_ufrag.slen, local_ufrag.ptr, @@ -476,16 +545,20 @@ static int encode_session(char buffer[], unsigned maxlen) local_pwd.ptr, 0, 0); + /* Write each component */ for (comp=0; comp<icedemo.opt.comp_cnt; ++comp) { unsigned j, cand_cnt; pj_ice_sess_cand cand[PJ_ICE_ST_MAX_CAND]; char ipaddr[PJ_INET6_ADDRSTRLEN]; + /* Get default candidate for the component */ status = pj_ice_strans_get_def_cand(icedemo.icest, comp+1, &cand[0]); if (status != PJ_SUCCESS) return -status; + /* Write the default address */ if (comp==0) { + /* For component 1, default address is in m= and c= lines */ PRINT("m=audio %d RTP/AVP 0\n" "c=IN IP4 %s\n", (int)pj_sockaddr_get_port(&cand[0].addr), @@ -493,12 +566,14 @@ static int encode_session(char buffer[], unsigned maxlen) sizeof(ipaddr), 0), 0, 0, 0, 0); } else if (comp==1) { + /* For component 2, default address is in a=rtcp line */ PRINT("a=rtcp:%d IN IP4 %s\n", (int)pj_sockaddr_get_port(&cand[0].addr), pj_sockaddr_print(&cand[0].addr, ipaddr, sizeof(ipaddr), 0), 0, 0, 0, 0); } else { + /* For other components, we'll just invent this.. */ PRINT("a=Xice-defcand:%d IN IP4 %s\n", (int)pj_sockaddr_get_port(&cand[0].addr), pj_sockaddr_print(&cand[0].addr, ipaddr, @@ -506,11 +581,13 @@ static int encode_session(char buffer[], unsigned maxlen) 0, 0, 0, 0); } + /* Enumerate all candidates for this component */ status = pj_ice_strans_enum_cands(icedemo.icest, comp+1, &cand_cnt, cand); if (status != PJ_SUCCESS) return -status; + /* And encode the candidates as SDP */ for (j=0; j<cand_cnt; ++j) { printed = print_cand(p, maxlen - (p-buffer), &cand[j]); if (printed < 0) @@ -526,6 +603,11 @@ static int encode_session(char buffer[], unsigned maxlen) return p - buffer; } + +/* + * Show information contained in the ICE stream transport. This is + * invoked from the menu. + */ static void icedemo_show_ice(void) { static char buffer[1000]; @@ -592,9 +674,15 @@ static void icedemo_show_ice(void) } } + +/* + * Input and parse SDP from the remote (containing remote's ICE information) + * and save it to global variables. + */ static void icedemo_input_remote(void) { char linebuf[80]; + unsigned media_cnt = 0; unsigned comp0_port = 0; char comp0_addr[80]; pj_bool_t done = PJ_FALSE; @@ -626,12 +714,22 @@ static void icedemo_input_remote(void) if (len==0) break; + /* Ignore subsequent media descriptors */ + if (media_cnt > 1) + continue; + switch (line[0]) { case 'm': { int cnt; char media[32], portstr[32]; + ++media_cnt; + if (media_cnt > 1) { + puts("Media line ignored"); + break; + } + cnt = sscanf(line+2, "%s %s RTP/", media, portstr); if (cnt != 2) { PJ_LOG(1,(THIS_FILE, "Error parsing media line")); @@ -639,6 +737,7 @@ static void icedemo_input_remote(void) } comp0_port = atoi(portstr); + } break; case 'c': @@ -791,13 +890,18 @@ static void icedemo_input_remote(void) pj_sockaddr_set_port(&icedemo.rem.def_addr[0], (pj_uint16_t)comp0_port); } - printf("Done, %d remote candidates added\n", icedemo.rem.cand_cnt); + PJ_LOG(3, (THIS_FILE, "Done, %d remote candidate(s) added", + icedemo.rem.cand_cnt)); return; on_error: reset_rem_info(); } + +/* + * Start ICE negotiation! This function is invoked from the menu. + */ static void icedemo_start_nego(void) { pj_str_t rufrag, rpwd; @@ -818,6 +922,8 @@ static void icedemo_start_nego(void) return; } + PJ_LOG(3,(THIS_FILE, "Starting ICE negotiation..")); + status = pj_ice_strans_start_ice(icedemo.icest, pj_cstr(&rufrag, icedemo.rem.ufrag), pj_cstr(&rpwd, icedemo.rem.pwd), @@ -829,6 +935,10 @@ static void icedemo_start_nego(void) PJ_LOG(3,(THIS_FILE, "ICE negotiation started")); } + +/* + * Send application data to remote agent. + */ static void icedemo_send_data(unsigned comp_id, const char *data) { pj_status_t status; @@ -843,10 +953,12 @@ static void icedemo_send_data(unsigned comp_id, const char *data) return; } + /* if (!pj_ice_strans_sess_is_complete(icedemo.icest)) { PJ_LOG(1,(THIS_FILE, "Error: ICE negotiation has not been started or is in progress")); return; } + */ if (comp_id > pj_ice_strans_get_running_comp_cnt(icedemo.icest)) { PJ_LOG(1,(THIS_FILE, "Error: invalid component ID")); @@ -862,11 +974,45 @@ static void icedemo_send_data(unsigned comp_id, const char *data) PJ_LOG(3,(THIS_FILE, "Data sent")); } + +/* + * Display help for the menu. + */ static void icedemo_help_menu(void) { + puts(""); + puts("-= Help on using ICE and this icedemo program =-"); + puts(""); + puts("This application demonstrates how to use ICE in pjnath without having\n" + "to use the SIP protocol. To use this application, you will need to run\n" + "two instances of this application, to simulate two ICE agents.\n"); + + puts("Basic ICE flow:\n" + " create instance [menu \"c\"]\n" + " repeat these steps as wanted:\n" + " - init session as offerer or answerer [menu \"i\"]\n" + " - display our SDP [menu \"s\"]\n" + " - \"send\" our SDP from the \"show\" output above to remote, by\n" + " copy-pasting the SDP to the other icedemo application\n" + " - parse remote SDP, by pasting SDP generated by the other icedemo\n" + " instance [menu \"r\"]\n" + " - begin ICE negotiation in our end [menu \"b\"]\n" + " - begin ICE negotiation in the other icedemo instance\n" + " - ICE negotiation will run, and result will be printed to screen\n" + " - send application data to remote [menu \"x\"]\n" + " - end/stop ICE session [menu \"e\"]\n" + " destroy instance [menu \"d\"]\n" + ""); + + puts(""); + puts("This concludes the help screen."); + puts(""); } +/* + * Display console menu + */ static void icedemo_print_menu(void) { puts(""); @@ -888,6 +1034,9 @@ static void icedemo_print_menu(void) } +/* + * Main console loop. + */ static void icedemo_console(void) { pj_bool_t app_quit = PJ_FALSE; @@ -915,43 +1064,70 @@ static void icedemo_console(void) continue; if (strcmp(cmd, "create")==0 || strcmp(cmd, "c")==0) { + icedemo_create_instance(); + } else if (strcmp(cmd, "destroy")==0 || strcmp(cmd, "d")==0) { + icedemo_destroy_instance(); + } else if (strcmp(cmd, "init")==0 || strcmp(cmd, "i")==0) { + char *role = strtok(NULL, SEP); if (role) icedemo_init_session(*role); else puts("error: Role required"); + } else if (strcmp(cmd, "stop")==0 || strcmp(cmd, "e")==0) { + icedemo_stop_session(); + } else if (strcmp(cmd, "show")==0 || strcmp(cmd, "s")==0) { + icedemo_show_ice(); + } else if (strcmp(cmd, "remote")==0 || strcmp(cmd, "r")==0) { + icedemo_input_remote(); + } else if (strcmp(cmd, "start")==0 || strcmp(cmd, "b")==0) { + icedemo_start_nego(); + } else if (strcmp(cmd, "send")==0 || strcmp(cmd, "x")==0) { - char *strcomp = strtok(NULL, SEP); - if (!strcmp) { + char *comp = strtok(NULL, SEP); + + if (!comp) { PJ_LOG(1,(THIS_FILE, "Error: component ID required")); } else { - char *data = strcomp + strlen(strcomp) + 1; - icedemo_send_data(atoi(strcomp), data); + char *data = comp + strlen(comp) + 1; + if (!data) + data = ""; + icedemo_send_data(atoi(comp), data); } + } else if (strcmp(cmd, "help")==0 || strcmp(cmd, "h")==0) { + icedemo_help_menu(); + } else if (strcmp(cmd, "quit")==0 || strcmp(cmd, "q")==0) { + app_quit = PJ_TRUE; + } else { + printf("Invalid command '%s'\n", cmd); + } } } +/* + * Display program usage. + */ static void icedemo_usage() { puts("Usage: icedemo [optons]"); @@ -981,6 +1157,9 @@ static void icedemo_usage() } +/* + * And here's the main() + */ int main(int argc, char *argv[]) { struct pj_getopt_option long_options[] = { |