blob: d5a1c9975bdf9f6e26c1a3fc5ba200e1d7a1754d [file] [log] [blame]
/* $Id$
*
*/
/*
* PJSIP - SIP Stack
* (C)2003-2005 Benny Prijono <bennylp@bulukucing.org>
*
* Author:
* Benny Prijono <bennylp@bulukucing.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/*
* THIS FILE IS INCLUDED BY main.c.
* IT WON'T COMPILE BY ITSELF.
*/
#include "getopt.h"
#include <stdio.h>
/*
* Display program usage
*/
static void usage()
{
puts("Usage:");
puts(" pjsua [options] [sip-url]");
puts("");
puts(" [sip-url] Default URL to invite.");
puts("");
puts("General options:");
puts(" --config-file=file Read the config/arguments from file.");
puts(" --log-file=fname Log to filename (default stderr)");
puts(" --log-level=N Set log max level to N (0(none) to 6(trace))");
puts(" --app-log-level=N Set log max level for stdout display to N");
puts(" --help Display this help screen");
puts(" --version Display version info");
puts("");
puts("Media options:");
puts(" --null-audio Use NULL audio device");
puts("");
puts("User Agent options:");
puts(" --auto-answer=sec Auto-answer all incoming calls after sec seconds.");
puts(" --auto-hangup=sec Auto-hangup all calls after sec seconds.");
puts("");
puts("SIP options:");
puts(" --local-port=port Set TCP/UDP port");
puts(" --id=url Set the URL of local ID (used in From header)");
puts(" --contact=url Override the Contact information");
puts(" --proxy=url Set the URL of proxy server");
puts(" --outbound=url Set the URL of outbound proxy server");
puts(" --registrar=url Set the URL of registrar server");
puts(" --reg-timeout=secs Set registration interval to secs (default 3600)");
puts("");
puts("Authentication options:");
puts(" --realm=string Set realm");
puts(" --username=string Set authentication username");
puts(" --password=string Set authentication password");
puts("");
puts("STUN options (all must be specified):");
puts(" --use-stun1=host[:port]");
puts(" --use-stun2=host[:port] Use STUN and set host name and port of STUN servers");
puts("");
puts("SIMPLE options (may be specified more than once):");
puts(" --add-buddy url Add the specified URL to the buddy list.");
puts(" --offer-x-ms-msg Offer \"x-ms-message\" in outgoing INVITE");
puts(" --no-presence Do not subscribe presence of buddies");
puts("");
fflush(stdout);
}
/* Display keystroke help. */
static void keystroke_help()
{
int i;
printf("Advertise status as: %s\n", (global.hide_status ? "Offline" : "Online"));
puts("");
puts("Buddy list:");
puts("-------------------------------------------------------------------------------");
for (i=0; i<global.buddy_cnt; ++i) {
printf(" %d\t%s <%s>\n", i+1, global.buddy[i].ptr,
(global.buddy_status[i]?"Online":"Offline"));
}
//printf("-------------------------------------\n");
puts("");
//puts("Commands:");
puts("+=============================================================================+");
puts("| Call Commands: | IM & Presence: | Misc: |");
puts("| | | |");
puts("| m Make new call | i Send IM | o Send OPTIONS |");
puts("| a Answer call | su Subscribe presence | d Dump status |");
puts("| h Hangup call | us Unsubscribe presence | d1 Dump detailed |");
puts("| ] Select next dialog | t Toggle Online status | |");
puts("| [ Select previous dialog | | |");
puts("+-----------------------------------------------------------------------------+");
puts("| q QUIT |");
puts("+=============================================================================+");
puts("");
fflush(stdout);
}
/*
* Verify that valid SIP url is given.
*/
static pj_status_t verify_sip_url(char *url)
{
pjsip_uri *p;
pj_pool_t *pool;
int len = (url ? strlen(url) : 0);
if (!len) return -1;
pool = pj_pool_create(global.pf, "check%p", 1024, 0, NULL);
if (!pool) return -1;
p = pjsip_parse_uri(pool, url, len, 0);
if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
p = NULL;
pj_pool_release(pool);
return p ? 0 : -1;
}
/*
* Read command arguments from config file.
*/
static int read_config_file(pj_pool_t *pool, const char *filename,
int *app_argc, char ***app_argv)
{
int i;
FILE *fhnd;
char line[200];
int argc = 0;
char **argv;
enum { MAX_ARGS = 64 };
/* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
argv[argc++] = *app_argv[0];
/* Open config file. */
fhnd = fopen(filename, "rt");
if (!fhnd) {
printf("Unable to open config file %s\n", filename);
return -1;
}
/* Scan tokens in the file. */
while (argc < MAX_ARGS && !feof(fhnd)) {
char *token, *p = line;
if (fgets(line, sizeof(line), fhnd) == NULL) break;
for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS;
token = strtok(NULL, " \t\r\n"))
{
int token_len;
if (!token) break;
if (*token == '#') break;
token_len = strlen(token);
if (!token_len)
continue;
argv[argc] = pj_pool_alloc(pool, token_len+1);
pj_memcpy(argv[argc], token, token_len+1);
++argc;
}
}
/* Copy arguments from command line */
for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
argv[argc++] = (*app_argv)[i];
if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
printf("Too many arguments specified in cmd line/config file\n");
fclose(fhnd);
return -1;
}
fclose(fhnd);
/* Assign the new command line back to the original command line. */
*app_argc = argc;
*app_argv = argv;
return 0;
}
/*
* Parse program arguments
*/
static int parse_args(pj_pool_t *pool, int argc, char *argv[])
{
int c;
int option_index;
enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
OPT_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR,
OPT_REG_TIMEOUT, OPT_ID, OPT_CONTACT,
OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
OPT_USE_STUN1, OPT_USE_STUN2,
OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
OPT_AUTO_ANSWER, OPT_AUTO_HANGUP};
struct option long_options[] = {
{ "config-file",1, 0, OPT_CONFIG_FILE},
{ "log-file", 1, 0, OPT_LOG_FILE},
{ "log-level", 1, 0, OPT_LOG_LEVEL},
{ "app-log-level",1,0,OPT_APP_LOG_LEVEL},
{ "help", 0, 0, OPT_HELP},
{ "version", 0, 0, OPT_VERSION},
{ "null-audio", 0, 0, OPT_NULL_AUDIO},
{ "local-port", 1, 0, OPT_LOCAL_PORT},
{ "proxy", 1, 0, OPT_PROXY},
{ "outbound", 1, 0, OPT_OUTBOUND_PROXY},
{ "registrar", 1, 0, OPT_REGISTRAR},
{ "reg-timeout",1, 0, OPT_REG_TIMEOUT},
{ "id", 1, 0, OPT_ID},
{ "contact", 1, 0, OPT_CONTACT},
{ "realm", 1, 0, OPT_REALM},
{ "username", 1, 0, OPT_USERNAME},
{ "password", 1, 0, OPT_PASSWORD},
{ "use-stun1", 1, 0, OPT_USE_STUN1},
{ "use-stun2", 1, 0, OPT_USE_STUN2},
{ "add-buddy", 1, 0, OPT_ADD_BUDDY},
{ "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
{ "no-presence", 0, 0, OPT_NO_PRESENCE},
{ "auto-answer",1, 0, OPT_AUTO_ANSWER},
{ "auto-hangup",1, 0, OPT_AUTO_HANGUP},
{ NULL, 0, 0, 0}
};
char *config_file = NULL;
/* Run getopt once to see if user specifies config file to read. */
while ((c=getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
switch (c) {
case 0:
config_file = optarg;
break;
}
if (config_file)
break;
}
if (config_file) {
if (read_config_file(pool, config_file, &argc, &argv) != 0)
return -1;
}
/* Reinitialize and re-run getopt again, possibly with new arguments
* read from config file.
*/
optind = 0;
while ((c=getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
char *err, *p;
switch (c) {
case OPT_LOG_FILE:
global.log_filename = optarg;
break;
case OPT_LOG_LEVEL:
c = strtoul(optarg, &err, 10);
if (*err) {
printf("Error: expecting integer value 0-6 for --log-level\n");
return -1;
}
pj_log_set_level( c );
break;
case OPT_APP_LOG_LEVEL:
global.app_log_level = strtoul(optarg, &err, 10);
if (*err) {
printf("Error: expecting integer value 0-6 for --app-log-level\n");
return -1;
}
break;
case OPT_HELP:
usage();
return -1;
case OPT_VERSION: /* version */
pj_dump_config();
return -1;
case OPT_NULL_AUDIO:
global.null_audio = 1;
break;
case OPT_LOCAL_PORT: /* local-port */
global.sip_port = strtoul(optarg, &err, 10);
if (*err) {
printf("Error: expecting integer value for --local-port\n");
return -1;
}
break;
case OPT_PROXY: /* proxy */
if (verify_sip_url(optarg) != 0) {
printf("Error: invalid SIP URL '%s' in proxy argument\n", optarg);
return -1;
}
global.proxy = pj_str(optarg);
break;
case OPT_OUTBOUND_PROXY: /* outbound proxy */
if (verify_sip_url(optarg) != 0) {
printf("Error: invalid SIP URL '%s' in outbound proxy argument\n", optarg);
return -1;
}
global.outbound_proxy = pj_str(optarg);
break;
case OPT_REGISTRAR: /* registrar */
if (verify_sip_url(optarg) != 0) {
printf("Error: invalid SIP URL '%s' in registrar argument\n", optarg);
return -1;
}
global.registrar_uri = pj_str(optarg);
break;
case OPT_REG_TIMEOUT: /* reg-timeout */
global.reg_timeout = strtoul(optarg, &err, 10);
if (*err) {
printf("Error: expecting integer value for --reg-timeout\n");
return -1;
}
break;
case OPT_ID: /* id */
if (verify_sip_url(optarg) != 0) {
printf("Error: invalid SIP URL '%s' in local id argument\n", optarg);
return -1;
}
global.local_uri = pj_str(optarg);
break;
case OPT_CONTACT: /* contact */
if (verify_sip_url(optarg) != 0) {
printf("Error: invalid SIP URL '%s' in contact argument\n", optarg);
return -1;
}
global.contact = pj_str(optarg);
break;
case OPT_USERNAME: /* Default authentication user */
if (!global.cred_count) global.cred_count = 1;
global.cred_info[0].username = pj_str(optarg);
break;
case OPT_REALM: /* Default authentication realm. */
if (!global.cred_count) global.cred_count = 1;
global.cred_info[0].realm = pj_str(optarg);
break;
case OPT_PASSWORD: /* authentication password */
if (!global.cred_count) global.cred_count = 1;
global.cred_info[0].data_type = 0;
global.cred_info[0].data = pj_str(optarg);
break;
case OPT_USE_STUN1: /* STUN server 1 */
p = pj_native_strchr(optarg, ':');
if (p) {
*p = '\0';
global.stun_srv1 = pj_str(optarg);
global.stun_port1 = strtoul(p+1, &err, 10);
if (*err || global.stun_port1==0) {
printf("Error: expecting port number with option --use-stun1\n");
return -1;
}
} else {
global.stun_port1 = 3478;
global.stun_srv1 = pj_str(optarg);
}
break;
case OPT_USE_STUN2: /* STUN server 2 */
p = pj_native_strchr(optarg, ':');
if (p) {
*p = '\0';
global.stun_srv2 = pj_str(optarg);
global.stun_port2 = strtoul(p+1, &err, 10);
if (*err || global.stun_port2==0) {
printf("Error: expecting port number with option --use-stun2\n");
return -1;
}
} else {
global.stun_port2 = 3478;
global.stun_srv2 = pj_str(optarg);
}
break;
case OPT_ADD_BUDDY: /* Add to buddy list. */
if (verify_sip_url(optarg) != 0) {
printf("Error: invalid URL '%s' in --add-buddy option\n", optarg);
return -1;
}
if (global.buddy_cnt == MAX_BUDDIES) {
printf("Error: too many buddies in buddy list.\n");
return -1;
}
global.buddy[global.buddy_cnt++] = pj_str(optarg);
break;
case OPT_OFFER_X_MS_MSG:
global.offer_x_ms_msg = 1;
break;
case OPT_NO_PRESENCE:
global.no_presence = 1;
break;
case OPT_AUTO_ANSWER:
global.auto_answer = strtoul(optarg, &err, 10);
if (*err) {
printf("Error: expecting integer value for --auto-answer option\n");
return -1;
}
break;
case OPT_AUTO_HANGUP:
global.auto_hangup = strtoul(optarg, &err, 10);
if (*err) {
printf("Error: expecting integer value for --auto-hangup option\n");
return -1;
}
break;
}
}
if (optind != argc) {
printf("Error: unknown options %s\n", argv[optind]);
return -1;
}
if (global.reg_timeout == 0)
global.reg_timeout = 3600;
return 0;
}
/* Print dialog. */
static void print_dialog(pjsip_dlg *dlg)
{
if (!dlg) {
puts("none");
return;
}
printf("%s: call-id=%.*s", dlg->obj_name,
(int)dlg->call_id->id.slen,
dlg->call_id->id.ptr);
printf(" (%s, %s)\n", pjsip_role_name(dlg->role),
pjsip_dlg_state_str(dlg->state));
}
/* Dump media statistic */
void dump_media_statistic(pjsip_dlg *dlg)
{
struct dialog_data *dlg_data = dlg->user_data;
pj_media_stream_stat stat[2];
const char *statname[2] = { "TX", "RX" };
int i;
pj_media_session_get_stat (dlg_data->msession, 0, &stat[0], &stat[1]);
printf("Media statistic:\n");
for (i=0; i<2; ++i) {
printf(" %s statistics:\n", statname[i]);
printf(" Pkt TX=%d RX=%d\n", stat[i].pkt_tx, stat[i].pkt_rx);
printf(" Octets TX=%d RX=%d\n", stat[i].oct_tx, stat[i].oct_rx);
printf(" Jitter %d ms\n", stat[i].jitter);
printf(" Pkt lost %d\n", stat[i].pkt_lost);
}
printf("\n");
}
/* Print all dialogs. */
static void print_all_dialogs()
{
pjsip_dlg *dlg = (pjsip_dlg *)global.user_agent->dlg_list.next;
puts("List all dialogs:");
while (dlg != (pjsip_dlg *) &global.user_agent->dlg_list) {
printf("%c", (dlg==global.cur_dlg ? '*' : ' '));
print_dialog(dlg);
dlg = dlg->next;
}
puts("");
}