summaryrefslogtreecommitdiff
path: root/pjsip-apps
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2009-04-15 13:38:40 +0000
committerBenny Prijono <bennylp@teluu.com>2009-04-15 13:38:40 +0000
commitfb257e0aaa5b9b078b57c252acdf69c1ba793513 (patch)
treeea94888b687fe79cf37120b2afcadb495db71d03 /pjsip-apps
parent6adc92ce701623103e8d79202b37e2a472e31a59 (diff)
More ticket #780: more work on icedemo sample application
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2600 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip-apps')
-rw-r--r--pjsip-apps/src/samples/icedemo.c281
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[] = {