From 5263415f8300e09213e4dd3b684d3c16b8263f9f Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Tue, 13 Jun 2006 22:57:13 +0000 Subject: -- REWRITE OF PJSUA API -- git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@503 74dad513-b988-da41-8d7b-12977e46ad98 --- pjlib/src/pj/config.c | 2 +- pjsip-apps/build/activex-pjsua.dsp | 2 +- pjsip-apps/build/pjsua.dsp | 4 + pjsip-apps/src/pjsua/main.c | 58 +- pjsip-apps/src/pjsua/pjsua.c | 2143 +++++++++++++++++++++ pjsip/build/pjsua_lib.dsp | 16 +- pjsip/include/pjsua-lib/pjsua.h | 1974 +++++++++++++++---- pjsip/include/pjsua-lib/pjsua_console_app.h | 28 - pjsip/include/pjsua-lib/pjsua_internal.h | 359 ++++ pjsip/src/pjsua-lib/pjsua_acc.c | 776 ++++++++ pjsip/src/pjsua-lib/pjsua_call.c | 2765 ++++++++++++++++----------- pjsip/src/pjsua-lib/pjsua_console_app.c | 926 --------- pjsip/src/pjsua-lib/pjsua_core.c | 2139 +++++++-------------- pjsip/src/pjsua-lib/pjsua_im.c | 281 ++- pjsip/src/pjsua-lib/pjsua_imp.h | 259 --- pjsip/src/pjsua-lib/pjsua_media.c | 1044 ++++++++++ pjsip/src/pjsua-lib/pjsua_pres.c | 908 +++++---- pjsip/src/pjsua-lib/pjsua_reg.c | 281 --- pjsip/src/pjsua-lib/pjsua_settings.c | 1435 -------------- 19 files changed, 9080 insertions(+), 6320 deletions(-) create mode 100644 pjsip-apps/src/pjsua/pjsua.c delete mode 100644 pjsip/include/pjsua-lib/pjsua_console_app.h create mode 100644 pjsip/include/pjsua-lib/pjsua_internal.h create mode 100644 pjsip/src/pjsua-lib/pjsua_acc.c delete mode 100644 pjsip/src/pjsua-lib/pjsua_console_app.c delete mode 100644 pjsip/src/pjsua-lib/pjsua_imp.h create mode 100644 pjsip/src/pjsua-lib/pjsua_media.c delete mode 100644 pjsip/src/pjsua-lib/pjsua_reg.c delete mode 100644 pjsip/src/pjsua-lib/pjsua_settings.c diff --git a/pjlib/src/pj/config.c b/pjlib/src/pj/config.c index e7fa7be9..2a6aefd3 100644 --- a/pjlib/src/pj/config.c +++ b/pjlib/src/pj/config.c @@ -21,7 +21,7 @@ #include static const char *id = "config.c"; -const char *PJ_VERSION = "0.5.5.6"; +const char *PJ_VERSION = "0.5.6.0"; PJ_DEF(void) pj_dump_config(void) { diff --git a/pjsip-apps/build/activex-pjsua.dsp b/pjsip-apps/build/activex-pjsua.dsp index 6791a89c..529e8115 100644 --- a/pjsip-apps/build/activex-pjsua.dsp +++ b/pjsip-apps/build/activex-pjsua.dsp @@ -323,7 +323,7 @@ SOURCE="..\src\activex-pjsua\app.h" # End Source File # Begin Source File -SOURCE="..\src\activex-pjsua\pjsua.h" +SOURCE="..\src\activex-pjsua\pjsua-structs.h" # End Source File # Begin Source File diff --git a/pjsip-apps/build/pjsua.dsp b/pjsip-apps/build/pjsua.dsp index d555f841..8697fb69 100644 --- a/pjsip-apps/build/pjsua.dsp +++ b/pjsip-apps/build/pjsua.dsp @@ -92,6 +92,10 @@ LINK32=link.exe SOURCE=..\src\pjsua\main.c # End Source File +# Begin Source File + +SOURCE=..\src\pjsua\pjsua.c +# End Source File # End Group # Begin Group "Header Files" diff --git a/pjsip-apps/src/pjsua/main.c b/pjsip-apps/src/pjsua/main.c index b5774e73..7775687d 100644 --- a/pjsip-apps/src/pjsua/main.c +++ b/pjsip-apps/src/pjsua/main.c @@ -17,62 +17,24 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include -#include - #define THIS_FILE "main.c" -/***************************************************************************** - * main(): + +/* + * These are defined in pjsua.c. */ +pj_status_t app_init(int argc, char *argv[]); +pj_status_t app_main(void); +pj_status_t app_destroy(void); + int main(int argc, char *argv[]) { - pjsua_config cfg; - pj_str_t uri_to_call = { NULL, 0 }; - - /* Init default settings. */ - pjsua_default_config(&cfg); - - - /* Create PJLIB and memory pool */ - pjsua_create(); - - - /* Parse command line arguments: */ - if (pjsua_parse_args(argc, argv, &cfg, &uri_to_call) != PJ_SUCCESS) - return 1; - - - /* Init pjsua */ - if (pjsua_init(&cfg, &console_callback) != PJ_SUCCESS) + if (app_init(argc, argv) != PJ_SUCCESS) return 1; - - /* Start pjsua! */ - if (pjsua_start() != PJ_SUCCESS) { - pjsua_destroy(); - return 1; - } - - - /* Sleep for a while, let any messages get printed to console: */ - pj_thread_sleep(500); - - - /* Start UI console main loop: */ - pjsua_console_app_main(&uri_to_call); - - - /* Destroy pjsua: */ - pjsua_destroy(); - - /* This is for internal testing, to make sure that pjsua_destroy() - * can be called multiple times. - */ - pjsua_destroy(); - - - /* Exit... */ + app_main(); + app_destroy(); return 0; } diff --git a/pjsip-apps/src/pjsua/pjsua.c b/pjsip-apps/src/pjsua/pjsua.c new file mode 100644 index 00000000..666779eb --- /dev/null +++ b/pjsip-apps/src/pjsua/pjsua.c @@ -0,0 +1,2143 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include + + +#define THIS_FILE "pjsua.c" + + + +/* Pjsua application data */ +static struct app_config +{ + pjsua_config cfg; + pjsua_logging_config log_cfg; + pjsua_media_config media_cfg; + pjsua_transport_config udp_cfg; + pjsua_transport_config rtp_cfg; + + unsigned acc_cnt; + pjsua_acc_config acc_cfg[PJSUA_MAX_ACC]; + + unsigned buddy_cnt; + pjsua_buddy_config buddy_cfg[PJSUA_MAX_BUDDIES]; + + pj_pool_t *pool; + /* Compatibility with older pjsua */ + + unsigned codec_cnt; + pj_str_t codec_arg[32]; + unsigned clock_rate; + pj_bool_t null_audio; + pj_str_t wav_file; + pjsua_player_id wav_id; + pjsua_conf_port_id wav_port; + pj_bool_t auto_play; + pj_bool_t auto_loop; + unsigned ptime; + unsigned quality; + unsigned complexity; + unsigned auto_answer; + unsigned duration; +} app_config; + + +static pjsua_acc_id current_acc; +static pjsua_call_id current_call; +static pj_str_t uri_arg; + +/***************************************************************************** + * Configuration manipulation + */ + +/* Show usage */ +static void usage(void) +{ + puts ("Usage:"); + puts (" pjsua [options]"); + puts (""); + puts ("General options:"); + puts (" --help Display this help screen"); + puts (" --version Display version info"); + puts (""); + puts ("Logging 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)) (default=5)"); + puts (" --app-log-level=N Set log max level for stdout display (default=4)"); + puts (""); + puts ("SIP Account options:"); + puts (" --registrar=url Set the URL of registrar server"); + puts (" --id=url Set the URL of local ID (used in From header)"); + puts (" --contact=url Optionally override the Contact information"); + puts (" --proxy=url Optional URL of proxy server to visit"); + puts (" May be specified multiple times"); + puts (" --realm=string Set realm"); + puts (" --username=string Set authentication username"); + puts (" --password=string Set authentication password"); + puts (" --reg-timeout=SEC Optional registration interval (default 55)"); + puts (""); + puts ("SIP Account Control:"); + puts (" --next-account Add more account"); + puts (""); + puts ("Transport Options:"); + puts (" --local-port=port Set TCP/UDP port"); + puts (" --outbound=url Set the URL of global outbound proxy server"); + puts (" May be specified multiple times"); + puts (" --use-stun1=host[:port]"); + puts (" --use-stun2=host[:port] Resolve local IP with the specified STUN servers"); + puts (""); + puts ("Media Options:"); + puts (" --add-codec=name Manually add codec (default is to enable all)"); + puts (" --clock-rate=N Override sound device clock rate"); + puts (" --null-audio Use NULL audio device"); + puts (" --play-file=file Play WAV file in conference bridge"); + puts (" --auto-play Automatically play the file (to incoming calls only)"); + puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP"); + puts (" --rtp-port=N Base port to try for RTP (default=4000)"); + puts (" --complexity=N Specify encoding complexity (0-10, default=none(-1))"); + puts (" --quality=N Specify encoding quality (0-10, default=4)"); + puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)"); + puts (""); + puts ("Buddy List (can be more than one):"); + puts (" --add-buddy url Add the specified URL to the buddy list."); + puts (""); + puts ("User Agent options:"); + puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)"); + puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)"); + puts (" --duration=SEC Set maximum call duration (default:no limit)"); + puts (""); + fflush(stdout); +} + + +/* Set default config. */ +static void default_config(struct app_config *cfg) +{ + pjsua_config_default(&cfg->cfg); + pjsua_logging_config_default(&cfg->log_cfg); + pjsua_media_config_default(&cfg->media_cfg); + pjsua_transport_config_default(&cfg->udp_cfg); + cfg->udp_cfg.port = 5060; + pjsua_transport_config_default(&cfg->rtp_cfg); + cfg->rtp_cfg.port = 4000; + cfg->duration = (unsigned)-1; + cfg->wav_id = PJSUA_INVALID_ID; + cfg->wav_port = PJSUA_INVALID_ID; +} + + +/* + * 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) { + PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename)); + fflush(stdout); + 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))) { + PJ_LOG(1,(THIS_FILE, + "Too many arguments specified in cmd line/config file")); + fflush(stdout); + 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; + +} + +static int my_atoi(const char *cs) +{ + pj_str_t s; + return pj_strtoul(pj_cstr(&s, cs)); +} + + +/* Parse arguments. */ +static pj_status_t parse_args(int argc, char *argv[], + struct app_config *cfg, + pj_str_t *uri_to_call) +{ + 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, OPT_AUTO_PLAY, OPT_AUTO_LOOP, + OPT_AUTO_CONF, OPT_CLOCK_RATE, + OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC, + OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, + OPT_NEXT_ACCOUNT, OPT_MAX_CALLS, + OPT_DURATION, + }; + struct pj_getopt_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}, + { "clock-rate", 1, 0, OPT_CLOCK_RATE}, + { "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}, + { "auto-play", 0, 0, OPT_AUTO_PLAY}, + { "auto-loop", 0, 0, OPT_AUTO_LOOP}, + { "auto-conf", 0, 0, OPT_AUTO_CONF}, + { "play-file", 1, 0, OPT_PLAY_FILE}, + { "rtp-port", 1, 0, OPT_RTP_PORT}, + { "add-codec", 1, 0, OPT_ADD_CODEC}, + { "complexity", 1, 0, OPT_COMPLEXITY}, + { "quality", 1, 0, OPT_QUALITY}, + { "ptime", 1, 0, OPT_PTIME}, + { "next-account",0,0, OPT_NEXT_ACCOUNT}, + { "max-calls", 1, 0, OPT_MAX_CALLS}, + { "duration",1,0, OPT_DURATION}, + { NULL, 0, 0, 0} + }; + pj_status_t status; + pjsua_acc_config *cur_acc; + char *config_file = NULL; + unsigned i; + + /* Run pj_getopt once to see if user specifies config file to read. */ + while ((c=pj_getopt_long(argc, argv, "", long_options, + &option_index)) != -1) + { + switch (c) { + case OPT_CONFIG_FILE: + config_file = pj_optarg; + break; + } + if (config_file) + break; + } + + if (config_file) { + status = read_config_file(app_config.pool, config_file, &argc, &argv); + if (status != 0) + return status; + } + + cfg->acc_cnt = 0; + cur_acc = &cfg->acc_cfg[0]; + + + /* Reinitialize and re-run pj_getopt again, possibly with new arguments + * read from config file. + */ + pj_optind = 0; + while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) { + char *p; + pj_str_t tmp; + long lval; + + switch (c) { + + case OPT_LOG_FILE: + cfg->log_cfg.log_filename = pj_str(pj_optarg); + break; + + case OPT_LOG_LEVEL: + c = pj_strtoul(pj_cstr(&tmp, pj_optarg)); + if (c < 0 || c > 6) { + PJ_LOG(1,(THIS_FILE, + "Error: expecting integer value 0-6 " + "for --log-level")); + return PJ_EINVAL; + } + cfg->log_cfg.level = c; + pj_log_set_level( c ); + break; + + case OPT_APP_LOG_LEVEL: + cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg)); + if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) { + PJ_LOG(1,(THIS_FILE, + "Error: expecting integer value 0-6 " + "for --app-log-level")); + return PJ_EINVAL; + } + break; + + case OPT_HELP: + usage(); + return PJ_EINVAL; + + case OPT_VERSION: /* version */ + pj_dump_config(); + return PJ_EINVAL; + + case OPT_NULL_AUDIO: + cfg->null_audio = PJ_TRUE; + break; + + case OPT_CLOCK_RATE: + lval = pj_strtoul(pj_cstr(&tmp, pj_optarg)); + if (lval < 8000 || lval > 48000) { + PJ_LOG(1,(THIS_FILE, "Error: expecting value between " + "8000-48000 for clock rate")); + return PJ_EINVAL; + } + cfg->media_cfg.clock_rate = lval; + break; + + case OPT_LOCAL_PORT: /* local-port */ + lval = pj_strtoul(pj_cstr(&tmp, pj_optarg)); + if (lval < 1 || lval > 65535) { + PJ_LOG(1,(THIS_FILE, + "Error: expecting integer value for " + "--local-port")); + return PJ_EINVAL; + } + cfg->udp_cfg.port = (pj_uint16_t)lval; + break; + + case OPT_PROXY: /* proxy */ + if (pjsua_verify_sip_url(pj_optarg) != 0) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid SIP URL '%s' " + "in proxy argument", pj_optarg)); + return PJ_EINVAL; + } + cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg); + break; + + case OPT_OUTBOUND_PROXY: /* outbound proxy */ + if (pjsua_verify_sip_url(pj_optarg) != 0) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid SIP URL '%s' " + "in outbound proxy argument", pj_optarg)); + return PJ_EINVAL; + } + cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg); + break; + + case OPT_REGISTRAR: /* registrar */ + if (pjsua_verify_sip_url(pj_optarg) != 0) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid SIP URL '%s' in " + "registrar argument", pj_optarg)); + return PJ_EINVAL; + } + cur_acc->reg_uri = pj_str(pj_optarg); + break; + + case OPT_REG_TIMEOUT: /* reg-timeout */ + cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg)); + if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid value for --reg-timeout " + "(expecting 1-3600)")); + return PJ_EINVAL; + } + break; + + case OPT_ID: /* id */ + if (pjsua_verify_sip_url(pj_optarg) != 0) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid SIP URL '%s' " + "in local id argument", pj_optarg)); + return PJ_EINVAL; + } + cur_acc->id = pj_str(pj_optarg); + break; + + case OPT_CONTACT: /* contact */ + if (pjsua_verify_sip_url(pj_optarg) != 0) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid SIP URL '%s' " + "in contact argument", pj_optarg)); + return PJ_EINVAL; + } + cur_acc->contact = pj_str(pj_optarg); + break; + + case OPT_NEXT_ACCOUNT: /* Add more account. */ + cfg->acc_cnt++; + cur_acc = &cfg->acc_cfg[cfg->acc_cnt - 1]; + break; + + case OPT_USERNAME: /* Default authentication user */ + cur_acc->cred_count = 1; + cur_acc->cred_info[0].username = pj_str(pj_optarg); + break; + + case OPT_REALM: /* Default authentication realm. */ + cur_acc->cred_count = 1; + cur_acc->cred_info[0].realm = pj_str(pj_optarg); + break; + + case OPT_PASSWORD: /* authentication password */ + cur_acc->cred_count = 1; + cur_acc->cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; + cur_acc->cred_info[0].data = pj_str(pj_optarg); + break; + + case OPT_USE_STUN1: /* STUN server 1 */ + p = pj_ansi_strchr(pj_optarg, ':'); + if (p) { + *p = '\0'; + cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg); + cfg->udp_cfg.stun_config.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1)); + if (cfg->udp_cfg.stun_config.stun_port1 < 1 || cfg->udp_cfg.stun_config.stun_port1 > 65535) { + PJ_LOG(1,(THIS_FILE, + "Error: expecting port number with " + "option --use-stun1")); + return PJ_EINVAL; + } + } else { + cfg->udp_cfg.stun_config.stun_port1 = 3478; + cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg); + } + cfg->udp_cfg.use_stun = PJ_TRUE; + break; + + case OPT_USE_STUN2: /* STUN server 2 */ + p = pj_ansi_strchr(pj_optarg, ':'); + if (p) { + *p = '\0'; + cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg); + cfg->udp_cfg.stun_config.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1)); + if (cfg->udp_cfg.stun_config.stun_port2 < 1 || cfg->udp_cfg.stun_config.stun_port2 > 65535) { + PJ_LOG(1,(THIS_FILE, + "Error: expecting port number with " + "option --use-stun2")); + return PJ_EINVAL; + } + } else { + cfg->udp_cfg.stun_config.stun_port2 = 3478; + cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg); + } + break; + + case OPT_ADD_BUDDY: /* Add to buddy list. */ + if (pjsua_verify_sip_url(pj_optarg) != 0) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid URL '%s' in " + "--add-buddy option", pj_optarg)); + return -1; + } + if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) { + PJ_LOG(1,(THIS_FILE, + "Error: too many buddies in buddy list.")); + return -1; + } + cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg); + cfg->buddy_cnt++; + break; + + case OPT_AUTO_PLAY: + cfg->auto_play = 1; + break; + + case OPT_AUTO_LOOP: + cfg->auto_loop = 1; + break; + + case OPT_PLAY_FILE: + cfg->wav_file = pj_str(pj_optarg); + break; + + case OPT_RTP_PORT: + cfg->rtp_cfg.port = my_atoi(pj_optarg); + if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) { + PJ_LOG(1,(THIS_FILE, + "Error: rtp-port argument value " + "(expecting 1-65535")); + return -1; + } + break; + + case OPT_ADD_CODEC: + cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg); + break; + + case OPT_COMPLEXITY: + cfg->complexity = my_atoi(pj_optarg); + if (cfg->complexity < 0 || cfg->complexity > 10) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid --complexity (expecting 0-10")); + return -1; + } + break; + + case OPT_QUALITY: + cfg->quality = my_atoi(pj_optarg); + if (cfg->quality < 0 || cfg->quality > 10) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid --quality (expecting 0-10")); + return -1; + } + break; + + case OPT_PTIME: + cfg->ptime = my_atoi(pj_optarg); + if (cfg->ptime < 10 || cfg->ptime > 1000) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid --ptime option")); + return -1; + } + break; + + case OPT_AUTO_ANSWER: + cfg->auto_answer = my_atoi(pj_optarg); + if (cfg->auto_answer < 100 || cfg->auto_answer > 699) { + PJ_LOG(1,(THIS_FILE, + "Error: invalid code in --auto-answer " + "(expecting 100-699")); + return -1; + } + break; + + case OPT_MAX_CALLS: + cfg->cfg.max_calls = my_atoi(pj_optarg); + if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > 255) { + PJ_LOG(1,(THIS_FILE,"Too many calls for max-calls (1-255)")); + return -1; + } + break; + + case OPT_DURATION: + cfg->duration = my_atoi(pj_optarg); + break; + } + } + + if (pj_optind != argc) { + pj_str_t uri_arg; + + if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) { + PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind])); + return -1; + } + uri_arg = pj_str(argv[pj_optind]); + if (uri_to_call) + *uri_to_call = uri_arg; + pj_optind++; + + /* Add URI to call to buddy list if it's not already there */ + for (i=0; ibuddy_cnt; ++i) { + if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0) + break; + } + if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) { + cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg; + } + + } else { + if (uri_to_call) + uri_to_call->slen = 0; + } + + if (pj_optind != argc) { + PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind])); + return PJ_EINVAL; + } + + if (cfg->acc_cfg[0].id.slen && cfg->acc_cnt==0) + cfg->acc_cnt = 1; + + for (i=0; iacc_cnt; ++i) { + if (cfg->acc_cfg[i].cred_info[0].username.slen || + cfg->acc_cfg[i].cred_info[0].realm.slen) + { + cfg->acc_cfg[i].cred_count = 1; + cfg->acc_cfg[i].cred_info[0].scheme = pj_str("digest"); + } + } + + + return PJ_SUCCESS; +} + + +/* + * Save account settings + */ +static void write_account_settings(int acc_index, pj_str_t *result) +{ + unsigned i; + char line[128]; + pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index]; + + + pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index); + pj_strcat2(result, line); + + + /* Identity */ + if (acc_cfg->id.slen) { + pj_ansi_sprintf(line, "--id %.*s\n", + (int)acc_cfg->id.slen, + acc_cfg->id.ptr); + pj_strcat2(result, line); + } + + /* Registrar server */ + if (acc_cfg->reg_uri.slen) { + pj_ansi_sprintf(line, "--registrar %.*s\n", + (int)acc_cfg->reg_uri.slen, + acc_cfg->reg_uri.ptr); + pj_strcat2(result, line); + + pj_ansi_sprintf(line, "--reg-timeout %u\n", + acc_cfg->reg_timeout); + pj_strcat2(result, line); + } + + /* Contact */ + if (acc_cfg->contact.slen) { + pj_ansi_sprintf(line, "--contact %.*s\n", + (int)acc_cfg->contact.slen, + acc_cfg->contact.ptr); + pj_strcat2(result, line); + } + + /* Proxy */ + for (i=0; iproxy_cnt; ++i) { + pj_ansi_sprintf(line, "--proxy %.*s\n", + (int)acc_cfg->proxy[i].slen, + acc_cfg->proxy[i].ptr); + pj_strcat2(result, line); + } + + /* Credentials */ + for (i=0; icred_count; ++i) { + if (acc_cfg->cred_info[i].realm.slen) { + pj_ansi_sprintf(line, "--realm %.*s\n", + (int)acc_cfg->cred_info[i].realm.slen, + acc_cfg->cred_info[i].realm.ptr); + pj_strcat2(result, line); + } + + if (acc_cfg->cred_info[i].username.slen) { + pj_ansi_sprintf(line, "--username %.*s\n", + (int)acc_cfg->cred_info[i].username.slen, + acc_cfg->cred_info[i].username.ptr); + pj_strcat2(result, line); + } + + if (acc_cfg->cred_info[i].data.slen) { + pj_ansi_sprintf(line, "--password %.*s\n", + (int)acc_cfg->cred_info[i].data.slen, + acc_cfg->cred_info[i].data.ptr); + pj_strcat2(result, line); + } + } + +} + + +/* + * Write settings. + */ +static int write_settings(const struct app_config *config, + char *buf, pj_size_t max) +{ + unsigned acc_index; + unsigned i; + pj_str_t cfg; + char line[128]; + + PJ_UNUSED_ARG(max); + + cfg.ptr = buf; + cfg.slen = 0; + + /* Logging. */ + pj_strcat2(&cfg, "#\n# Logging options:\n#\n"); + pj_ansi_sprintf(line, "--log-level %d\n", + config->log_cfg.level); + pj_strcat2(&cfg, line); + + pj_ansi_sprintf(line, "--app-log-level %d\n", + config->log_cfg.console_level); + pj_strcat2(&cfg, line); + + if (config->log_cfg.log_filename.slen) { + pj_ansi_sprintf(line, "--log-file %.*s\n", + (int)config->log_cfg.log_filename.slen, + config->log_cfg.log_filename.ptr); + pj_strcat2(&cfg, line); + } + + + /* Save account settings. */ + for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) { + + write_account_settings(acc_index, &cfg); + + if (acc_index < config->acc_cnt-1) + pj_strcat2(&cfg, "--next-account\n"); + } + + + pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n"); + + /* Outbound proxy */ + for (i=0; icfg.outbound_proxy_cnt; ++i) { + pj_ansi_sprintf(line, "--outbound %.*s\n", + (int)config->cfg.outbound_proxy[i].slen, + config->cfg.outbound_proxy[i].ptr); + pj_strcat2(&cfg, line); + } + + + /* UDP Transport. */ + pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port); + pj_strcat2(&cfg, line); + + + /* STUN */ + if (config->udp_cfg.stun_config.stun_port1) { + pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n", + (int)config->udp_cfg.stun_config.stun_srv1.slen, + config->udp_cfg.stun_config.stun_srv1.ptr, + config->udp_cfg.stun_config.stun_port1); + pj_strcat2(&cfg, line); + } + + if (config->udp_cfg.stun_config.stun_port2) { + pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n", + (int)config->udp_cfg.stun_config.stun_srv2.slen, + config->udp_cfg.stun_config.stun_srv2.ptr, + config->udp_cfg.stun_config.stun_port2); + pj_strcat2(&cfg, line); + } + + + pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n"); + + + /* Media */ + if (config->null_audio) + pj_strcat2(&cfg, "--null-audio\n"); + if (config->auto_play) + pj_strcat2(&cfg, "--auto-play\n"); + if (config->auto_loop) + pj_strcat2(&cfg, "--auto-loop\n"); + if (config->wav_file.slen) { + pj_ansi_sprintf(line, "--play-file %s\n", + config->wav_file.ptr); + pj_strcat2(&cfg, line); + } + /* Media clock rate. */ + if (config->clock_rate) { + pj_ansi_sprintf(line, "--clock-rate %d\n", + config->clock_rate); + pj_strcat2(&cfg, line); + } + + + /* Encoding quality and complexity */ + if (config->quality > 0) { + pj_ansi_sprintf(line, "--quality %d\n", + config->quality); + pj_strcat2(&cfg, line); + } + if (config->complexity > 0) { + pj_ansi_sprintf(line, "--complexity %d\n", + config->complexity); + pj_strcat2(&cfg, line); + } + + /* ptime */ + if (config->ptime) { + pj_ansi_sprintf(line, "--ptime %d\n", + config->ptime); + pj_strcat2(&cfg, line); + } + + /* Start RTP port. */ + pj_ansi_sprintf(line, "--rtp-port %d\n", + config->rtp_cfg.port); + pj_strcat2(&cfg, line); + + /* Add codec. */ + for (i=0; icodec_cnt; ++i) { + pj_ansi_sprintf(line, "--add-codec %s\n", + config->codec_arg[i].ptr); + pj_strcat2(&cfg, line); + } + + pj_strcat2(&cfg, "\n#\n# User agent:\n#\n"); + + /* Auto-answer. */ + if (config->auto_answer != 0) { + pj_ansi_sprintf(line, "--auto-answer %d\n", + config->auto_answer); + pj_strcat2(&cfg, line); + } + + /* Max calls. */ + pj_ansi_sprintf(line, "--max-calls %d\n", + config->cfg.max_calls); + pj_strcat2(&cfg, line); + + /* Uas-duration. */ + if (config->duration != (unsigned)-1) { + pj_ansi_sprintf(line, "--duration %d\n", + config->duration); + pj_strcat2(&cfg, line); + } + + pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n"); + + /* Add buddies. */ + for (i=0; ibuddy_cnt; ++i) { + pj_ansi_sprintf(line, "--add-buddy %.*s\n", + (int)config->buddy_cfg[i].uri.slen, + config->buddy_cfg[i].uri.ptr); + pj_strcat2(&cfg, line); + } + + + *(cfg.ptr + cfg.slen) = '\0'; + return cfg.slen; +} + + +/* + * Dump application states. + */ +static void app_dump(pj_bool_t detail) +{ + unsigned old_decor; + char buf[1024]; + + PJ_LOG(3,(THIS_FILE, "Start dumping application states:")); + + old_decor = pj_log_get_decor(); + pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR)); + + if (detail) + pj_dump_config(); + + pjsip_endpt_dump(pjsua_get_pjsip_endpt(), detail); + pjmedia_endpt_dump(pjsua_get_pjmedia_endpt()); + pjsip_tsx_layer_dump(detail); + pjsip_ua_dump(detail); + + + /* Dump all invite sessions: */ + PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:")); + + if (pjsua_call_get_count() == 0) { + + PJ_LOG(3,(THIS_FILE, " - no sessions -")); + + } else { + unsigned i; + + for (i=0; i=0; --i) { + if (pjsua_call_is_active(i)) { + current_call = i; + return PJ_TRUE; + } + } + + for (i=max-1; i>current_call; --i) { + if (pjsua_call_is_active(i)) { + current_call = i; + return PJ_TRUE; + } + } + + current_call = PJSUA_INVALID_ID; + return PJ_FALSE; +} + + + +/* + * Handler when invite state has changed. + */ +static void on_call_state(pjsua_call_id call_id, pjsip_event *e) +{ + pjsua_call_info call_info; + + PJ_UNUSED_ARG(e); + + pjsua_call_get_info(call_id, &call_info); + + if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) { + + PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", + call_id, + call_info.last_status, + call_info.last_status_text.ptr)); + + if (call_id == current_call) { + find_next_call(); + } + + } else { + + PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", + call_id, + call_info.state_text.ptr)); + + if (current_call==PJSUA_INVALID_ID) + current_call = call_id; + + } +} + + +/** + * Handler when there is incoming call. + */ +static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, + pjsip_rx_data *rdata) +{ + pjsua_call_info call_info; + + PJ_UNUSED_ARG(acc_id); + PJ_UNUSED_ARG(rdata); + + pjsua_call_get_info(call_id, &call_info); + + if (app_config.auto_answer > 0) { + pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL); + } + + if (app_config.auto_answer < 200) { + PJ_LOG(3,(THIS_FILE, + "Incoming call for account %d!\n" + "From: %s\n" + "To: %s\n" + "Press a to answer or h to reject call", + acc_id, + call_info.remote_info.ptr, + call_info.local_info.ptr)); + } +} + + +/* + * Callback on media state changed event. + * The action may connect the call to sound device, to file, or + * to loop the call. + */ +static void on_call_media_state(pjsua_call_id call_id) +{ + pjsua_call_info call_info; + + pjsua_call_get_info(call_id, &call_info); + + if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) { + pj_bool_t connect_sound = PJ_TRUE; + + /* Loopback sound, if desired */ + if (app_config.auto_loop) { + pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot); + connect_sound = PJ_FALSE; + } + + /* Stream a file, if desired */ + if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) { + pjsua_conf_connect(app_config.wav_port, call_info.conf_slot); + connect_sound = PJ_FALSE; + } + + /* Otherwise connect to sound device */ + if (connect_sound) { + pjsua_conf_connect(call_info.conf_slot, 0); + pjsua_conf_connect(0, call_info.conf_slot); + } + + PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id)); + + } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) { + PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local", + call_id)); + } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) { + PJ_LOG(3,(THIS_FILE, + "Media for call %d is suspended (hold) by remote", + call_id)); + } else { + PJ_LOG(3,(THIS_FILE, + "Media for call %d is inactive", + call_id)); + } +} + + +/* + * Handler registration status has changed. + */ +static void on_reg_state(pjsua_acc_id acc_id) +{ + PJ_UNUSED_ARG(acc_id); + + // Log already written. +} + + +/* + * Handler on buddy state changed. + */ +static void on_buddy_state(pjsua_buddy_id buddy_id) +{ + pjsua_buddy_info info; + pjsua_buddy_get_info(buddy_id, &info); + + PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s", + (int)info.uri.slen, + info.uri.ptr, + (int)info.status_text.slen, + info.status_text.ptr)); +} + + +/** + * Incoming IM message (i.e. MESSAGE request)! + */ +static void on_pager(pjsua_call_id call_id, const pj_str_t *from, + const pj_str_t *to, const pj_str_t *contact, + const pj_str_t *mime_type, const pj_str_t *text) +{ + /* Note: call index may be -1 */ + PJ_UNUSED_ARG(call_id); + PJ_UNUSED_ARG(to); + PJ_UNUSED_ARG(contact); + PJ_UNUSED_ARG(mime_type); + + PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s", + (int)from->slen, from->ptr, + (int)text->slen, text->ptr)); +} + + +/** + * Received typing indication + */ +static void on_typing(pjsua_call_id call_id, const pj_str_t *from, + const pj_str_t *to, const pj_str_t *contact, + pj_bool_t is_typing) +{ + PJ_UNUSED_ARG(call_id); + PJ_UNUSED_ARG(to); + PJ_UNUSED_ARG(contact); + + PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s", + (int)from->slen, from->ptr, + (is_typing?"is typing..":"has stopped typing"))); +} + + +/* + * Print buddy list. + */ +static void print_buddy_list(void) +{ + pjsua_buddy_id ids[64]; + int i; + unsigned count = PJ_ARRAY_SIZE(ids); + + puts("Buddy list:"); + + pjsua_enum_buddies(ids, &count); + + if (count == 0) + puts(" -none-"); + else { + for (i=0; i<(int)count; ++i) { + pjsua_buddy_info info; + + if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS) + continue; + + printf(" [%2d] <%7s> %.*s\n", + ids[i]+1, info.status_text.ptr, + (int)info.uri.slen, + info.uri.ptr); + } + } + puts(""); +} + + +/* + * Print account status. + */ +static void print_acc_status(int acc_id) +{ + char buf[80]; + pjsua_acc_info info; + + pjsua_acc_get_info(acc_id, &info); + + if (!info.has_registration) { + pj_ansi_snprintf(buf, sizeof(buf), "%.*s", + (int)info.status_text.slen, + info.status_text.ptr); + + } else { + pj_ansi_snprintf(buf, sizeof(buf), + "%d/%.*s (expires=%d)", + info.status, + (int)info.status_text.slen, + info.status_text.ptr, + info.expires); + + } + + printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '), + acc_id, (int)info.acc_uri.slen, info.acc_uri.ptr, buf); + printf(" Online status: %s\n", + (info.online_status ? "Online" : "Invisible")); +} + + +/* + * Show a bit of help. + */ +static void keystroke_help(void) +{ + pjsua_acc_id acc_ids[16]; + unsigned count = PJ_ARRAY_SIZE(acc_ids); + int i; + + printf(">>>>\n"); + + pjsua_enum_accs(acc_ids, &count); + + printf("Account list:\n"); + for (i=0; i<(int)count; ++i) + print_acc_status(acc_ids[i]); + + print_buddy_list(); + + //puts("Commands:"); + puts("+=============================================================================+"); + puts("| Call Commands: | Buddy, IM & Presence: | Account: |"); + puts("| | | |"); + puts("| m Make new call | +b Add new buddy .| +a Add new accnt |"); + puts("| M Make multiple calls | -b Delete buddy | -a Delete accnt. |"); + puts("| a Answer call | !b Modify buddy | !a Modify accnt. |"); + puts("| h Hangup call (ha=all) | i Send IM | rr (Re-)register |"); + puts("| H Hold call | s Subscribe presence | ru Unregister |"); + puts("| v re-inVite (release hold) | u Unsubscribe presence | > Cycle next ac.|"); + puts("| ] Select next dialog | t ToGgle Online status | < Cycle prev ac.|"); + puts("| [ Select previous dialog +--------------------------+-------------------+"); + puts("| x Xfer call | Media Commands: | Status & Config: |"); + puts("| # Send DTMF string | | |"); + puts("| | cl List ports | d Dump status |"); + puts("| | cc Connect port | dd Dump detailed |"); + puts("| | cd Disconnect port | dc Dump config |"); + puts("| | | f Save config |"); + puts("+------------------------------+--------------------------+-------------------+"); + puts("| q QUIT |"); + puts("+=============================================================================+"); +} + + +/* + * Input simple string + */ +static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len) +{ + char *p; + + printf("%s (empty to cancel): ", title); fflush(stdout); + fgets(buf, len, stdin); + + /* Remove trailing newlines. */ + for (p=buf; ; ++p) { + if (*p=='\r' || *p=='\n') *p='\0'; + else if (!*p) break; + } + + if (!*buf) + return PJ_FALSE; + + return PJ_TRUE; +} + + +#define NO_NB -2 +struct input_result +{ + int nb_result; + char *uri_result; +}; + + +/* + * Input URL. + */ +static void ui_input_url(const char *title, char *buf, int len, + struct input_result *result) +{ + result->nb_result = NO_NB; + result->uri_result = NULL; + + print_buddy_list(); + + printf("Choices:\n" + " 0 For current dialog.\n" + " -1 All %d buddies in buddy list\n" + " [1 -%2d] Select from buddy list\n" + " URL An URL\n" + " Empty input (or 'q') to cancel\n" + , pjsua_get_buddy_count(), pjsua_get_buddy_count()); + printf("%s: ", title); + + fflush(stdout); + fgets(buf, len, stdin); + len = strlen(buf); + + /* Left trim */ + while (pj_isspace(*buf)) { + ++buf; + --len; + } + + /* Remove trailing newlines */ + while (len && (buf[len-1] == '\r' || buf[len-1] == '\n')) + buf[--len] = '\0'; + + if (len == 0 || buf[0]=='q') + return; + + if (pj_isdigit(*buf) || *buf=='-') { + + int i; + + if (*buf=='-') + i = 1; + else + i = 0; + + for (; inb_result = my_atoi(buf); + + if (result->nb_result >= 0 && + result->nb_result <= (int)pjsua_get_buddy_count()) + { + return; + } + if (result->nb_result == -1) + return; + + puts("Invalid input"); + result->nb_result = NO_NB; + return; + + } else { + pj_status_t status; + + if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Invalid URL", status); + return; + } + + result->uri_result = buf; + } +} + +/* + * List the ports in conference bridge + */ +static void conf_list(void) +{ + unsigned i, count; + pjsua_conf_port_id id[PJSUA_MAX_CALLS]; + + printf("Conference ports:\n"); + + count = PJ_ARRAY_SIZE(id); + pjsua_enum_conf_ports(id, &count); + + for (i=0; islen) { + pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, NULL); + } + + keystroke_help(); + + for (;;) { + + printf(">>> "); + fflush(stdout); + + fgets(menuin, sizeof(menuin), stdin); + + switch (menuin[0]) { + + case 'm': + /* Make call! : */ + printf("(You currently have %d calls)\n", + pjsua_call_get_count()); + + uri = NULL; + ui_input_url("Make call", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + + if (result.nb_result == -1 || result.nb_result == 0) { + puts("You can't do that with make call!"); + continue; + } else { + pjsua_buddy_info binfo; + pjsua_buddy_get_info(result.nb_result-1, &binfo); + uri = binfo.uri.ptr; + } + + } else if (result.uri_result) { + uri = result.uri_result; + } + + tmp = pj_str(uri); + pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL); + break; + + case 'M': + /* Make multiple calls! : */ + printf("(You currently have %d calls)\n", + pjsua_call_get_count()); + + if (!simple_input("Number of calls", menuin, sizeof(menuin))) + continue; + + count = my_atoi(menuin); + if (count < 1) + continue; + + ui_input_url("Make call", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + pjsua_buddy_info binfo; + if (result.nb_result == -1 || result.nb_result == 0) { + puts("You can't do that with make call!"); + continue; + } + pjsua_buddy_get_info(result.nb_result-1, &binfo); + uri = binfo.uri.ptr; + } else { + uri = result.uri_result; + } + + for (i=0; i= PJSIP_INV_STATE_CONNECTING) + { + puts("No pending incoming call"); + fflush(stdout); + continue; + + } else { + if (!simple_input("Answer with code (100-699)", buf, sizeof(buf))) + continue; + + if (my_atoi(buf) < 100) + continue; + + /* + * Must check again! + * Call may have been disconnected while we're waiting for + * keyboard input. + */ + if (current_call == -1) { + puts("Call has been disconnected"); + fflush(stdout); + continue; + } + + pjsua_call_answer(current_call, my_atoi(buf), NULL, NULL); + } + + break; + + + case 'h': + + if (current_call == -1) { + puts("No current call"); + fflush(stdout); + continue; + + } else if (menuin[1] == 'a') { + + /* Hangup all calls */ + pjsua_call_hangup_all(); + + } else { + + /* Hangup current calls */ + pjsua_call_hangup(current_call, 0, NULL, NULL); + } + break; + + case ']': + case '[': + /* + * Cycle next/prev dialog. + */ + if (menuin[0] == ']') { + find_next_call(); + + } else { + find_prev_call(); + } + + if (current_call != -1) { + + pjsua_call_get_info(current_call, &call_info); + PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s", + (int)call_info.remote_info.slen, + call_info.remote_info.ptr)); + + } else { + PJ_LOG(3,(THIS_FILE,"No current dialog")); + } + break; + + + case '>': + case '<': + if (!simple_input("Enter account ID to select", buf, sizeof(buf))) + break; + + i = my_atoi(buf); + if (pjsua_acc_is_valid(i)) { + current_acc = i; + PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i)); + } else { + PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i)); + } + break; + + + case '+': + if (menuin[1] == 'b') { + + pjsua_buddy_config buddy_cfg; + pjsua_buddy_id buddy_id; + pj_status_t status; + + if (!simple_input("Enter buddy's URI:", buf, sizeof(buf))) + break; + + if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) { + printf("Invalid SIP URI '%s'\n", buf); + break; + } + + pj_memset(&buddy_cfg, 0, sizeof(pjsua_buddy_config)); + + buddy_cfg.uri = pj_str(buf); + buddy_cfg.subscribe = PJ_TRUE; + + status = pjsua_buddy_add(&buddy_cfg, &buddy_id); + if (status == PJ_SUCCESS) { + printf("New buddy '%s' added at index %d\n", + buf, buddy_id+1); + } + + } else if (menuin[1] == 'a') { + + printf("Sorry, this command is not supported yet\n"); + + } else { + printf("Invalid input %s\n", menuin); + } + break; + + case '-': + if (menuin[1] == 'b') { + if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf))) + break; + + i = my_atoi(buf) - 1; + + if (!pjsua_buddy_is_valid(i)) { + printf("Invalid buddy id %d\n", i); + } else { + pjsua_buddy_del(i); + printf("Buddy %d deleted\n", i); + } + + } else if (menuin[1] == 'a') { + + if (!simple_input("Enter account ID to delete",buf,sizeof(buf))) + break; + + i = my_atoi(buf); + + if (!pjsua_acc_is_valid(i)) { + printf("Invalid account id %d\n", i); + } else { + pjsua_acc_del(i); + printf("Account %d deleted\n", i); + } + + } else { + printf("Invalid input %s\n", menuin); + } + break; + + case 'H': + /* + * Hold call. + */ + if (current_call != -1) { + + pjsua_call_set_hold(current_call, NULL); + + } else { + PJ_LOG(3,(THIS_FILE, "No current call")); + } + break; + + case 'v': + /* + * Send re-INVITE (to release hold, etc). + */ + if (current_call != -1) { + + pjsua_call_reinvite(current_call, PJ_TRUE, NULL); + + } else { + PJ_LOG(3,(THIS_FILE, "No current call")); + } + break; + + case 'x': + /* + * Transfer call. + */ + if (current_call == -1) { + + PJ_LOG(3,(THIS_FILE, "No current call")); + + } else { + int call = current_call; + + ui_input_url("Transfer to URL", buf, sizeof(buf), &result); + + /* Check if call is still there. */ + + if (call != current_call) { + puts("Call has been disconnected"); + continue; + } + + if (result.nb_result != NO_NB) { + if (result.nb_result == -1 || result.nb_result == 0) + puts("You can't do that with transfer call!"); + else { + pjsua_buddy_info binfo; + pjsua_buddy_get_info(result.nb_result-1, &binfo); + pjsua_call_xfer( current_call, &binfo.uri, NULL); + } + + } else if (result.uri_result) { + pj_str_t tmp; + tmp = pj_str(result.uri_result); + pjsua_call_xfer( current_call, &tmp, NULL); + } + } + break; + + case '#': + /* + * Send DTMF strings. + */ + if (current_call == -1) { + + PJ_LOG(3,(THIS_FILE, "No current call")); + + } else if (!pjsua_call_has_media(current_call)) { + + PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); + + } else { + pj_str_t digits; + int call = current_call; + pj_status_t status; + + if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, + sizeof(buf))) + { + break; + } + + if (call != current_call) { + puts("Call has been disconnected"); + continue; + } + + digits = pj_str(buf); + status = pjsua_call_dial_dtmf(current_call, &digits); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send DTMF", status); + } else { + puts("DTMF digits enqueued for transmission"); + } + } + break; + + case 's': + case 'u': + /* + * Subscribe/unsubscribe presence. + */ + ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + if (result.nb_result == -1) { + int i, count; + count = pjsua_get_buddy_count(); + for (i=0; ireg_timeout = PJSUA_REG_INTERVAL; +} + /** - * PJSUA settings. + * Account info. Application can query account info by calling + * #pjsua_acc_get_info(). */ -struct pjsua_config +typedef struct pjsua_acc_info { - /** SIP UDP signaling port. Set to zero to disable UDP signaling, - * which in this case application must manually add a transport - * to SIP endpoint. - * (default: 5060) + /** + * The account ID. */ - unsigned udp_port; + pjsua_acc_id id; - /** Optional hostname or IP address to publish as the host part of - * Contact header. This must be specified if UDP transport is - * disabled. - * (default: NULL) + /** + * Flag to indicate whether this is the default account. */ - pj_str_t sip_host; + pj_bool_t is_default; - /** Optional port number to publish in the port part of Contact header. - * This must be specified if UDP transport is disabled. - * (default: 0) + /** + * Account URI */ - unsigned sip_port; + pj_str_t acc_uri; - /** Start of RTP port. Set to zero to prevent pjsua from creating - * media transports, which in this case application must manually - * create media transport for each calls. - * (default: 4000) + /** + * Flag to tell whether this account has registration setting + * (reg_uri is not empty). */ - unsigned start_rtp_port; + pj_bool_t has_registration; /** - * Enable incoming and outgoing message logging (default: 1). + * An up to date expiration interval for account registration session. */ - pj_bool_t msg_logging; + int expires; - /** Maximum calls to support (default: 4) */ - unsigned max_calls; + /** + * Last registration status code. If status code is zero, the account + * is currently not registered. Any other value indicates the SIP + * status code of the registration. + */ + pjsip_status_code status; - /** Maximum slots in the conference bridge (default: 0/calculated - * as max_calls*2 + /** + * String describing the registration status. + */ + pj_str_t status_text; + + /** + * Presence online status for this account. + */ + pj_bool_t online_status; + + /** + * Buffer that is used internally to store the status text. */ - unsigned conf_ports; + char buf_[PJ_ERR_MSG_SIZE]; - /** Number of worker threads (value >=0, default: 1) */ - unsigned thread_cnt; +} pjsua_acc_info; - /** Separate ioqueue for media? (default: yes) */ - pj_bool_t media_has_ioqueue; - /** Number of worker thread for media (value >=0, default: 1) */ - unsigned media_thread_cnt; - /** First STUN server IP address. When STUN is configured, then the - * two STUN server settings must be fully set. - * (default: none) +/** + * STUN configuration. + */ +typedef struct pjsua_stun_config +{ + /** + * The first STUN server IP address or hostname. */ pj_str_t stun_srv1; - /** First STUN port number */ + /** + * Port number of the first STUN server. + * If zero, default STUN port will be used. + */ unsigned stun_port1; - - /** Second STUN server IP address */ + + /** + * Optional second STUN server IP address or hostname, for which the + * result of the mapping request will be compared to. If the value + * is empty, only one STUN server will be used. + */ pj_str_t stun_srv2; - /** Second STUN server port number */ + /** + * Port number of the second STUN server. + * If zero, default STUN port will be used. + */ unsigned stun_port2; - /** Sound player device ID (default: 0) */ - unsigned snd_player_id; +} pjsua_stun_config; - /** Sound capture device ID (default: 0) */ - unsigned snd_capture_id; - /** Internal clock rate (to be applied to sound devices and conference - * bridge, default is 0/follows the codec, or 44100 for MacOS). + +/** + * Call this function to initialize STUN config with default values. + * + * @param cfg The STUN config to be initialized. + */ +PJ_INLINE(void) pjsua_stun_config_default(pjsua_stun_config *cfg) +{ + pj_memset(cfg, 0, sizeof(*cfg)); +} + + +/** + * Transport configuration for creating UDP transports for both SIP + * and media. + */ +typedef struct pjsua_transport_config +{ + /** + * UDP port number to bind locally. This setting MUST be specified + * even when default port is desired. If the value is zero, the + * transport will be bound to any available port, and application + * can query the port by querying the transport info. */ - unsigned clock_rate; + unsigned port; - /** Do not use sound device (default: 0). */ - pj_bool_t null_audio; + /** + * Optional address where the socket should be bound. + */ + pj_in_addr ip_addr; - /** WAV file to load for auto_play (default: NULL) */ - pj_str_t wav_file; + /** + * Flag to indicate whether STUN should be used. + */ + pj_bool_t use_stun; - /** Auto play WAV file for calls? (default: no) */ - pj_bool_t auto_play; + /** + * STUN configuration, must be specified when STUN is used. + */ + pjsua_stun_config stun_config; + +} pjsua_transport_config; + + +/** + * Call this function to initialize UDP config with default values. + * + * @param cfg The UDP config to be initialized. + */ +PJ_INLINE(void) pjsua_transport_config_default(pjsua_transport_config *cfg) +{ + pj_memset(cfg, 0, sizeof(*cfg)); +} + + +/** + * Normalize STUN config. + */ +PJ_INLINE(void) pjsua_normalize_stun_config( pjsua_stun_config *cfg ) +{ + if (cfg->stun_srv1.slen) { + + if (cfg->stun_port1 == 0) + cfg->stun_port1 = 3478; + + if (cfg->stun_srv2.slen == 0) { + cfg->stun_srv2 = cfg->stun_srv1; + cfg->stun_port2 = cfg->stun_port1; + } else { + if (cfg->stun_port2 == 0) + cfg->stun_port2 = 3478; + } + + } else { + cfg->stun_port1 = 0; + cfg->stun_srv2.slen = 0; + cfg->stun_port2 = 0; + } +} - /** Auto loopback calls? (default: no) */ - pj_bool_t auto_loop; - /** Automatically put calls to conference? (default: no) */ - pj_bool_t auto_conf; +/** + * Duplicate transport config. + */ +PJ_INLINE(void) pjsua_transport_config_dup(pj_pool_t *pool, + pjsua_transport_config *dst, + const pjsua_transport_config *src) +{ + pj_memcpy(dst, src, sizeof(*src)); - /** Speex codec complexity? (default: 10) */ - unsigned complexity; + if (src->stun_config.stun_srv1.slen) { + pj_strdup_with_null(pool, &dst->stun_config.stun_srv1, + &src->stun_config.stun_srv1); + } - /** Speex codec quality? (default: 10) */ - unsigned quality; + if (src->stun_config.stun_srv2.slen) { + pj_strdup_with_null(pool, &dst->stun_config.stun_srv2, + &src->stun_config.stun_srv2); + } - /** Codec ptime? (default: 0 (follows the codec)) */ - unsigned ptime; + pjsua_normalize_stun_config(&dst->stun_config); +} - /** Number of additional codecs/"--add-codec" with pjsua (default: 0) */ - unsigned codec_cnt; - /** Additional codecs/"--add-codec" options */ - pj_str_t codec_arg[32]; - /** SIP status code to be automatically sent to incoming calls - * (default: 100). +/** + * Transport info. + */ +typedef struct pjsua_transport_info +{ + /** + * PJSUA transport identification. */ - unsigned auto_answer; + pjsua_transport_id id; - /** Periodic time to refresh call with re-INVITE (default: 0) + /** + * Transport type. */ - unsigned uas_refresh; + pjsip_transport_type_e type; - /** Maximum incoming call duration (default: 3600) */ - unsigned uas_duration; + /** + * Transport type name. + */ + pj_str_t type_name; - /** Outbound proxy (default: none) */ - pj_str_t outbound_proxy; + /** + * Transport string info/description. + */ + pj_str_t info; - /** Number of SIP accounts */ - unsigned acc_cnt; + /** + * Transport flag (see ##pjsip_transport_flags_e). + */ + unsigned flag; + + /** + * Local address length. + */ + unsigned addr_len; - /** SIP accounts configuration */ - pjsua_acc_config acc_config[32]; + /** + * Local/bound address. + */ + pj_sockaddr local_addr; - /** Logging verbosity (default: 5). */ - unsigned log_level; + /** + * Published address (or transport address name). + */ + pjsip_host_port local_name; - /** Logging to be displayed to stdout (default: 4) */ - unsigned app_log_level; + /** + * Current number of objects currently referencing this transport. + */ + unsigned usage_count; + + +} pjsua_transport_info; + + +/** + * Media configuration. + */ +typedef struct pjsua_media_config +{ + /** + * Clock rate to be applied to the conference bridge. + * If value is zero, default clock rate will be used (16KHz). + */ + unsigned clock_rate; + + /** + * Specify maximum number of media ports to be created in the + * conference bridge. Since all media terminate in the bridge + * (calls, file player, file recorder, etc), the value must be + * large enough to support all of them. However, the larger + * the value, the more computations are performed. + */ + unsigned max_media_ports; - /** Log decoration */ - unsigned log_decor; + /** + * Specify whether the media manager should manage its own + * ioqueue for the RTP/RTCP sockets. If yes, ioqueue will be created + * and at least one worker thread will be created too. If no, + * the RTP/RTCP sockets will share the same ioqueue as SIP sockets, + * and no worker thread is needed. + * + * Normally application would say yes here, unless it wants to + * run everything from a single thread. + */ + pj_bool_t has_ioqueue; + + /** + * Specify the number of worker threads to handle incoming RTP + * packets. A value of one is recommended for most applications. + */ + unsigned thread_cnt; + + +} pjsua_media_config; + + +/** + * Use this function to initialize media config. + * + * @param cfg The media config to be initialized. + */ +PJ_INLINE(void) pjsua_media_config_default(pjsua_media_config *cfg) +{ + pj_memset(cfg, 0, sizeof(*cfg)); + + cfg->clock_rate = 16000; + cfg->max_media_ports = 32; + cfg->has_ioqueue = PJ_TRUE; + cfg->thread_cnt = 1; +} + + + +/** + * Logging configuration. + */ +typedef struct pjsua_logging_config +{ + /** + * Log incoming and outgoing SIP message? Yes! + */ + pj_bool_t msg_logging; + + /** + * Input verbosity level. Value 5 is reasonable. + */ + unsigned level; - /** Optional log filename (default: NULL) */ + /** + * Verbosity level for console. Value 4 is reasonable. + */ + unsigned console_level; + + /** + * Log decoration. + */ + unsigned decor; + + /** + * Optional log filename. + */ pj_str_t log_filename; - /** Number of buddies in address book (default: 0) */ - unsigned buddy_cnt; + /** + * Optional callback function to be called to write log to + * application specific device. This function will be called for + * log messages on input verbosity level. + */ + void (*cb)(int level, const char *data, pj_size_t len); + + +} pjsua_logging_config; + + +/** + * Use this function to initialize logging config. + * + * @param cfg The logging config to be initialized. + */ +PJ_INLINE(void) pjsua_logging_config_default(pjsua_logging_config *cfg) +{ + pj_memset(cfg, 0, sizeof(*cfg)); + + cfg->msg_logging = PJ_TRUE; + cfg->level = 5; + cfg->console_level = 4; + cfg->decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | + PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE; +} + +/** + * Use this function to duplicate logging config. + * + * @param pool Pool to use. + * @param dst Destination config. + * @param src Source config. + */ +PJ_INLINE(void) pjsua_logging_config_dup(pj_pool_t *pool, + pjsua_logging_config *dst, + const pjsua_logging_config *src) +{ + pj_memcpy(dst, src, sizeof(*src)); + pj_strdup_with_null(pool, &dst->log_filename, &src->log_filename); +} + + +/** + * Buddy configuration. + */ +typedef struct pjsua_buddy_config +{ + /** + * Buddy URL or name address. + */ + pj_str_t uri; - /** Buddies URI */ - pj_str_t buddy_uri[256]; -}; + /** + * Specify whether presence subscription should start immediately. + */ + pj_bool_t subscribe; + +} pjsua_buddy_config; /** - * @see pjsua_config + * Buddy's online status. */ -typedef struct pjsua_config pjsua_config; +typedef enum pjsua_buddy_status +{ + /** + * Online status is unknown (possibly because no presence subscription + * has been established). + */ + PJSUA_BUDDY_STATUS_UNKNOWN, + + /** + * Buddy is known to be offline. + */ + PJSUA_BUDDY_STATUS_ONLINE, + + /** + * Buddy is offline. + */ + PJSUA_BUDDY_STATUS_OFFLINE, + +} pjsua_buddy_status; +/** + * Buddy info. + */ +typedef struct pjsua_buddy_info +{ + /** + * The buddy ID. + */ + pjsua_buddy_id id; + + /** + * The full URI of the buddy, as specified in the configuration. + */ + pj_str_t uri; + + /** + * Buddy's Contact, only available when presence subscription has + * been established to the buddy. + */ + pj_str_t contact; + + /** + * Buddy's online status. + */ + pjsua_buddy_status status; + + /** + * Text to describe buddy's online status. + */ + pj_str_t status_text; + + /** + * Flag to indicate that we should monitor the presence information for + * this buddy (normally yes, unless explicitly disabled). + */ + pj_bool_t monitor_pres; + + /** + * Internal buffer. + */ + char buf_[256]; + +} pjsua_buddy_info; + + +/** + * Codec config. + */ +typedef struct pjsua_codec_info +{ + /** + * Codec unique identification. + */ + pj_str_t codec_id; + + /** + * Codec priority (integer 0-255). + */ + pj_uint8_t priority; + + /** + * Internal buffer. + */ + char buf_[32]; + +} pjsua_codec_info; + + /** * Application callbacks. */ -struct pjsua_callback +typedef struct pjsua_callback { /** * Notify application when invite state has changed. * Application may then query the call info to get the * detail call states. */ - void (*on_call_state)(int call_index, pjsip_event *e); + void (*on_call_state)(pjsua_call_id call_id, pjsip_event *e); /** * Notify application on incoming call. */ - void (*on_incoming_call)(pjsua_acc_id acc_id, int call_index, + void (*on_incoming_call)(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata); + /** + * Notify application when media state in the call has changed. + * Normal application would need to implement this callback, e.g. + * to connect the call's media to sound device. + */ + void (*on_call_media_state)(pjsua_call_id call_id); + /** * Notify application on call being transfered. * Application can decide to accept/reject transfer request @@ -293,7 +726,7 @@ struct pjsua_callback * is not defined, the default behavior is to accept the * transfer. */ - void (*on_call_transfered)(int call_index, + void (*on_call_transfered)(pjsua_call_id call_id, const pj_str_t *dst, pjsip_status_code *code); @@ -312,289 +745,801 @@ struct pjsua_callback /** * Notify application on incoming pager (i.e. MESSAGE request). - * Argument call_index will be -1 if MESSAGE request is not related to an + * Argument call_id will be -1 if MESSAGE request is not related to an * existing call. */ - void (*on_pager)(int call_index, const pj_str_t *from, - const pj_str_t *to, const pj_str_t *txt); + void (*on_pager)(pjsua_call_id call_id, const pj_str_t *from, + const pj_str_t *to, const pj_str_t *contact, + const pj_str_t *mime_type, const pj_str_t *body); + + /** + * Notify application about the delivery status of outgoing pager + * request. + * + * @param call_id Containts the ID of the call where the IM was + * sent, or PJSUA_INVALID_ID if the IM was sent + * outside call context. + * @param to Destination URI. + * @param body Message body. + * @param user_data Arbitrary data that was specified when sending + * IM message. + * @param status Delivery status. + * @param reason Delivery status reason. + */ + void (*on_pager_status)(pjsua_call_id call_id, + const pj_str_t *to, + const pj_str_t *body, + void *user_data, + pjsip_status_code status, + const pj_str_t *reason); /** * Notify application about typing indication. */ - void (*on_typing)(int call_index, const pj_str_t *from, - const pj_str_t *to, pj_bool_t is_typing); + void (*on_typing)(pjsua_call_id call_id, const pj_str_t *from, + const pj_str_t *to, const pj_str_t *contact, + pj_bool_t is_typing); + +} pjsua_callback; + + -}; /** - * @see pjsua_callback + * PJSUA settings. */ -typedef struct pjsua_callback pjsua_callback; +typedef struct pjsua_config +{ + + /** + * Maximum calls to support (default: 4) + */ + unsigned max_calls; + + /** + * Number of worker threads. Normally application will want to have at + * least one worker thread, unless when it wants to poll the library + * periodically, which in this case the worker thread can be set to + * zero. + */ + unsigned thread_cnt; + + /** + * Number of outbound proxies in the array. + */ + unsigned outbound_proxy_cnt; + + /** + * Specify the URL of outbound proxies to visit for all outgoing requests. + * The outbound proxies will be used for all accounts, and it will + * be used to build the route set for outgoing requests. The final + * route set for outgoing requests will consists of the outbound proxies + * and the proxy configured in the account. + */ + pj_str_t outbound_proxy[4]; + + /** + * Number of credentials in the credential array. + */ + unsigned cred_count; + + /** + * Array of credentials. These credentials will be used by all accounts, + * and can be used to authenticate against outbound proxies. + */ + pjsip_cred_info cred_info[PJSUA_ACC_MAX_PROXIES]; + + /** + * Application callback. + */ + pjsua_callback cb; + +} pjsua_config; + + +/** + * Use this function to initialize pjsua config. + * + * @param cfg pjsua config to be initialized. + */ +PJ_INLINE(void) pjsua_config_default(pjsua_config *cfg) +{ + pj_memset(cfg, 0, sizeof(*cfg)); + + cfg->max_calls = 4; + cfg->thread_cnt = 1; +} + + +/** + * Duplicate credential. + */ +PJ_INLINE(void) pjsip_cred_dup( pj_pool_t *pool, + pjsip_cred_info *dst, + const pjsip_cred_info *src) +{ + pj_strdup_with_null(pool, &dst->realm, &src->realm); + pj_strdup_with_null(pool, &dst->scheme, &src->scheme); + pj_strdup_with_null(pool, &dst->username, &src->username); + pj_strdup_with_null(pool, &dst->data, &src->data); + +} + + +/** + * Duplicate pjsua_config. + */ +PJ_INLINE(void) pjsua_config_dup(pj_pool_t *pool, + pjsua_config *dst, + const pjsua_config *src) +{ + unsigned i; + + pj_memcpy(dst, src, sizeof(*src)); + + for (i=0; ioutbound_proxy_cnt; ++i) { + pj_strdup_with_null(pool, &dst->outbound_proxy[i], + &src->outbound_proxy[i]); + } + + for (i=0; icred_count; ++i) { + pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]); + } +} + + +/** + * Call media status. + */ +typedef enum pjsua_call_media_status +{ + PJSUA_CALL_MEDIA_NONE, + PJSUA_CALL_MEDIA_ACTIVE, + PJSUA_CALL_MEDIA_LOCAL_HOLD, + PJSUA_CALL_MEDIA_REMOTE_HOLD, +} pjsua_call_media_status; /** * Call info. */ -struct pjsua_call_info +typedef struct pjsua_call_info { - unsigned index; - pj_bool_t active; + /** Call identification. */ + pjsua_call_id id; + + /** Initial call role (UAC == caller) */ pjsip_role_e role; + + /** Local URI */ pj_str_t local_info; + + /** Local Contact */ + pj_str_t local_contact; + + /** Remote URI */ pj_str_t remote_info; + + /** Remote contact */ + pj_str_t remote_contact; + + /** Dialog Call-ID string. */ + pj_str_t call_id; + + /** Call state */ pjsip_inv_state state; + + /** Text describing the state */ pj_str_t state_text; + + /** Last status code heard, which can be used as cause code */ pjsip_status_code last_status; + + /** The reason phrase describing the status. */ pj_str_t last_status_text; + + /** Call media status. */ + pjsua_call_media_status media_status; + + /** Media direction */ + pjmedia_dir media_dir; + + /** The conference port number for the call */ + pjsua_conf_port_id conf_slot; + + /** Up-to-date call connected duration (zero when call is not + * established) + */ pj_time_val connect_duration; + + /** Total call duration, including set-up time */ pj_time_val total_duration; - pj_bool_t has_media; - pjsua_conf_port_id conf_slot; -}; -typedef struct pjsua_call_info pjsua_call_info; + /** Internal */ + struct { + char local_info[128]; + char local_contact[128]; + char remote_info[128]; + char remote_contact[128]; + char call_id[128]; + char last_status_text[128]; + } buf_; +} pjsua_call_info; -enum pjsua_buddy_status -{ - PJSUA_BUDDY_STATUS_UNKNOWN, - PJSUA_BUDDY_STATUS_ONLINE, - PJSUA_BUDDY_STATUS_OFFLINE, -}; -typedef enum pjsua_buddy_status pjsua_buddy_status; /** - * Buddy info. + * Conference port info. */ -struct pjsua_buddy_info +typedef struct pjsua_conf_port_info { - pjsua_buddy_id index; - pj_bool_t is_valid; + /** Conference port number. */ + pjsua_conf_port_id slot_id; + + /** Port name. */ pj_str_t name; - pj_str_t display_name; - pj_str_t host; - unsigned port; - pj_str_t uri; - pjsua_buddy_status status; - pj_str_t status_text; - pj_bool_t monitor; -}; -typedef struct pjsua_buddy_info pjsua_buddy_info; + /** Clock rate. */ + unsigned clock_rate; + + /** Number of channels. */ + unsigned channel_count; + + /** Samples per frame */ + unsigned samples_per_frame; + + /** Bits per sample */ + unsigned bits_per_sample; + + /** Number of listeners in the array. */ + unsigned listener_cnt; + + /** Array of listeners (in other words, ports where this port is + * transmitting to. + */ + pjsua_conf_port_id listeners[PJSUA_MAX_CONF_PORTS]; + +} pjsua_conf_port_info; /** - * Account info. + * This structure holds information about custom media transport to + * be registered to pjsua. */ -struct pjsua_acc_info +typedef struct pjsua_media_transport { - pjsua_acc_id index; - pj_str_t acc_id; - pj_bool_t has_registration; - int expires; - pjsip_status_code status; - pj_str_t status_text; - pj_bool_t online_status; - char buf[PJ_ERR_MSG_SIZE]; -}; + /** + * Media socket information containing the address information + * of the RTP and RTCP socket. + */ + pjmedia_sock_info skinfo; + + /** + * The media transport instance. + */ + pjmedia_transport *transport; -typedef struct pjsua_acc_info pjsua_acc_info; +} pjsua_media_transport; + + +/** + * This structure describes additional information to be sent with + * outgoing SIP message. + */ +typedef struct pjsua_msg_data +{ + /** + * Additional message headers as linked list. + */ + pjsip_hdr hdr_list; + + /** + * MIME type of optional message body. + */ + pj_str_t content_type; + + /** + * Optional message body. + */ + pj_str_t msg_body; + +} pjsua_msg_data; + + +/** + * Initialize message data. + * + * @param msg_data Message data to be initialized. + */ +PJ_INLINE(void) pjsua_msg_data_init(pjsua_msg_data *msg_data) +{ + pj_memset(msg_data, 0, sizeof(*msg_data)); + pj_list_init(&msg_data->hdr_list); +} + + +/***************************************************************************** + * PJSUA Core API + */ + + +/** + * Instantiate pjsua application. Application must call this function before + * calling any other functions, to make sure that the underlying libraries + * are properly initialized. Once this function has returned success, + * application must call pjsua_destroy() before quitting. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_create(void); + + +/** + * Initialize pjsua with the specified settings. All the settings are + * optional, and the default values will be used when the config is not + * specified. + * + * @param ua_cfg User agent configuration. + * @param log_cfg Optional logging configuration. + * @param media_cfg Optional media configuration. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *ua_cfg, + const pjsua_logging_config *log_cfg, + const pjsua_media_config *media_cfg); + + +/** + * Application is recommended to call this function after all initialization + * is done, so that the library can do additional checking set up + * additional + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_start(void); + + +/** + * Destroy pjsua. This function must be called once PJSUA is created. To + * make it easier for application, application may call this function + * several times with no danger. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_destroy(void); + + +/** + * Poll pjsua for events, and if necessary block the caller thread for + * the specified maximum interval (in miliseconds). + * + * @param msec_timeout Maximum time to wait, in miliseconds. + * + * @return The number of events that have been handled during the + * poll. Negative value indicates error, and application + * can retrieve the error as (err = -return_value). + */ +PJ_DECL(int) pjsua_handle_events(unsigned msec_timeout); + + +/** + * Create memory pool. + * + * @param name Optional pool name. + * @param size Initial size of the pool. + * @param increment Increment size. + * + * @return The pool, or NULL when there's no memory. + */ +PJ_DECL(pj_pool_t*) pjsua_pool_create(const char *name, pj_size_t init_size, + pj_size_t increment); + + +/** + * Application can call this function at any time (after pjsua_create(), of + * course) to change logging settings. + * + * @param c Logging configuration. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_reconfigure_logging(const pjsua_logging_config *c); /** - * Conference port info. + * Internal function to get SIP endpoint instance of pjsua, which is + * needed for example to register module, create transports, etc. + * Probably is only valid after #pjsua_init() is called. + * + * @return SIP endpoint instance. */ -struct pjsua_conf_port_info -{ - pjsua_conf_port_id slot_id; - pj_str_t name; - unsigned clock_rate; - unsigned channel_count; - unsigned samples_per_frame; - unsigned bits_per_sample; - unsigned listener_cnt; - pjsua_conf_port_id listeners[256]; -}; - +PJ_DECL(pjsip_endpoint*) pjsua_get_pjsip_endpt(void); -typedef struct pjsua_conf_port_info pjsua_conf_port_info; +/** + * Internal function to get media endpoint instance. + * Only valid after #pjsua_init() is called. + * + * @return Media endpoint instance. + */ +PJ_DECL(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void); /***************************************************************************** - * PJSUA API (defined in pjsua_core.c). + * PJSUA SIP Transport API. */ /** - * Initialize pjsua settings with default parameters. + * Create SIP transport. + * + * @param type Transport type. + * @param cfg Transport configuration. + * @param p_id Optional pointer to receive transport ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(void) pjsua_default_config(pjsua_config *cfg); - +PJ_DECL(pj_status_t) pjsua_transport_create(pjsip_transport_type_e type, + const pjsua_transport_config *cfg, + pjsua_transport_id *p_id); /** - * Validate configuration. + * Register transport that has been created by application. + * + * @param tp Transport instance. + * @param p_id Optional pointer to receive transport ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_test_config(const pjsua_config *cfg, - char *errmsg, - int len); +PJ_DECL(pj_status_t) pjsua_transport_register(pjsip_transport *tp, + pjsua_transport_id *p_id); /** - * Instantiate pjsua application. This initializes pjlib/pjlib-util, and - * creates memory pool factory to be used by application. + * Enumerate all transports currently created in the system. + * + * @param id Array to receive transport ids. + * @param count In input, specifies the maximum number of elements. + * On return, it contains the actual number of elements. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_create(void); +PJ_DECL(pj_status_t) pjsua_enum_transports( pjsua_transport_id id[], + unsigned *count ); /** - * Initialize pjsua application with the specified settings. + * Get information about transports. * - * This will initialize all libraries, create endpoint instance, and register - * pjsip modules. + * @param id Transport ID. + * @param info Pointer to receive transport info. * - * Application may register module after calling this function. + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, - const pjsua_callback *cb); +PJ_DECL(pj_status_t) pjsua_transport_get_info(pjsua_transport_id id, + pjsua_transport_info *info); /** - * Start pjsua stack. Application calls this after pjsua settings has been - * configured. + * Disable a transport or re-enable it. By default transport is always + * enabled after it is created. Disabling a transport does not necessarily + * close the socket, it will only discard incoming messages and prevent + * the transport from being used to send outgoing messages. + * + * @param id Transport ID. + * @param enabled Non-zero to enable, zero to disable. * - * This will start the transport, worker threads (if any), and registration - * process, if registration is configured. + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_start(void); +PJ_DECL(pj_status_t) pjsua_transport_set_enable(pjsua_transport_id id, + pj_bool_t enabled); -/** - * Destroy pjsua. - */ -PJ_DECL(pj_status_t) pjsua_destroy(void); /** - * Poll pjsua. + * Close the transport. If transport is forcefully closed, it will be + * immediately closed, and any pending transactions that are using the + * transport may not terminate properly. Otherwise, the system will wait + * until all transactions are closed while preventing new users from + * using the transport, and will close the transport when it is safe to + * do so. + * + * @param id Transport ID. + * @param force Non-zero to immediately close the transport. This + * is not recommended! + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(int) pjsua_handle_events(unsigned msec_timeout); +PJ_DECL(pj_status_t) pjsua_transport_close( pjsua_transport_id id, + pj_bool_t force ); -/** - * Get SIP endpoint instance. - * Only valid after pjsua_init(). +/***************************************************************************** + * PJSUA Media Transport. */ -PJ_DECL(pjsip_endpoint*) pjsua_get_pjsip_endpt(void); /** - * Get media endpoint instance. - * Only valid after pjsua_init(). + * Create UDP media transports for all the calls. This function creates + * one UDP media transport for each call. + * + * @param cfg Media transport configuration. The "port" field in the + * configuration is used as the start port to bind the + * sockets. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void); +PJ_DECL(pj_status_t) +pjsua_media_transports_create(const pjsua_transport_config *cfg); + /** - * Replace media transport. + * Register custom media transports to be used by calls. There must + * enough media transports for all calls. + * + * @param tp The media transport array. + * @param count Number of elements in the array. This number MUST + * match the number of maximum calls configured when + * pjsua is created. + * @param auto_delete Flag to indicate whether the transports should be + * destroyed when pjsua is shutdown. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_set_call_media_transport(unsigned call_index, - const pjmedia_sock_info *i, - pjmedia_transport *tp); +PJ_DECL(pj_status_t) +pjsua_media_transports_attach( pjsua_media_transport tp[], + unsigned count, + pj_bool_t auto_delete); + /***************************************************************************** - * PJSUA Call API (defined in pjsua_call.c). + * PJSUA Call API. */ /** * Get maximum number of calls configured in pjsua. + * + * @return Maximum number of calls configured. */ PJ_DECL(unsigned) pjsua_call_get_max_count(void); /** - * Get current number of active calls. + * Get number of currently active calls. + * + * @return Number of currently active calls. */ PJ_DECL(unsigned) pjsua_call_get_count(void); +/** + * Enumerate all active calls. + * + * @param ids Array of account IDs to be initialized. + * @param count In input, specifies the maximum number of elements. + * On return, it contains the actual number of elements. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_enum_calls(pjsua_call_id ids[], + unsigned *count); + + +/** + * Make outgoing call to the specified URI using the specified account. + * + * @param acc_id The account to be used. + * @param target URI to be put in the request URI. + * @param dst_uri URI to be put in the To header (normally is the same + * as the target URI). + * @param options Options (must be zero at the moment). + * @param user_data Arbitrary user data to be attached to the call, and + * can be retrieved later. + * @param msg_data Optional headers etc to be added to outgoing INVITE + * request, or NULL if no custom header is desired. + * @param p_call_id Pointer to receive call identification. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, + const pj_str_t *dst_uri, + unsigned options, + void *user_data, + const pjsua_msg_data *msg_data, + pjsua_call_id *p_call_id); + + /** * Check if the specified call has active INVITE session and the INVITE * session has not been disconnected. + * + * @param call_id Call identification. + * + * @return Non-zero if call is active. */ -PJ_DECL(pj_bool_t) pjsua_call_is_active(unsigned call_index); +PJ_DECL(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id); /** - * Check if call has a media session. + * Check if call has an active media session. + * + * @param call_id Call identification. + * + * @return Non-zero if yes. */ -PJ_DECL(pj_bool_t) pjsua_call_has_media(unsigned call_index); +PJ_DECL(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id); /** - * Get call info. + * Get the conference port identification associated with the call. + * + * @param call_id Call identification. + * + * @return Conference port ID, or PJSUA_INVALID_ID when the + * media has not been established or is not active. + */ +PJ_DECL(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id); + +/** + * Obtain detail information about the specified call. + * + * @param call_id Call identification. + * @param info Call info to be initialized. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_call_get_info(unsigned call_index, +PJ_DECL(pj_status_t) pjsua_call_get_info(pjsua_call_id call_id, pjsua_call_info *info); /** - * Duplicate call info. + * Attach application specific data to the call. + * + * @param call_id Call identification. + * @param user_data Arbitrary data to be attached to the call. + * + * @return The user data. */ -PJ_DECL(void) pjsua_call_info_dup(pj_pool_t *pool, - pjsua_call_info *dst_info, - const pjsua_call_info *src_info); +PJ_DECL(pj_status_t) pjsua_call_set_user_data(pjsua_call_id call_id, + void *user_data); /** - * Make outgoing call. + * Get user data attached to the call. + * + * @param call_id Call identification. + * + * @return The user data. */ -PJ_DECL(pj_status_t) pjsua_call_make_call(unsigned acc_id, - const pj_str_t *dst_uri, - int *p_call_index); +PJ_DECL(void*) pjsua_call_get_user_data(pjsua_call_id call_id); /** - * Answer call. + * Send response to incoming INVITE request. + * + * @param call_id Incoming call identification. + * @param code Status code, (100-699). + * @param reason Optional reason phrase. If NULL, default text + * will be used. + * @param msg_data Optional list of headers etc to be added to outgoing + * response message. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_call_answer(int call_index, int code); +PJ_DECL(pj_status_t) pjsua_call_answer(pjsua_call_id call_id, + unsigned code, + const pj_str_t *reason, + const pjsua_msg_data *msg_data); /** - * Hangup call. + * Hangup call by using method that is appropriate according to the + * call state. + * + * @param call_id Call identification. + * @param code Optional status code to be sent when we're rejecting + * incoming call. If the value is zero, "603/Decline" + * will be sent. + * @param reason Optional reason phrase to be sent when we're rejecting + * incoming call. If NULL, default text will be used. + * @param msg_data Optional list of headers etc to be added to outgoing + * request/response message. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(void) pjsua_call_hangup(int call_index); +PJ_DECL(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id, + unsigned code, + const pj_str_t *reason, + const pjsua_msg_data *msg_data); /** - * Put call on-hold. + * Put the specified call on hold. + * + * @param call_id Call identification. + * @param msg_data Optional message components to be sent with + * the request. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_call_set_hold(int call_index); +PJ_DECL(pj_status_t) pjsua_call_set_hold(pjsua_call_id call_id, + const pjsua_msg_data *msg_data); /** * Send re-INVITE (to release hold). + * + * @param call_id Call identification. + * @param unhold If this argument is non-zero and the call is locally + * held, this will release the local hold. + * @param msg_data Optional message components to be sent with + * the request. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_call_reinvite(int call_index); +PJ_DECL(pj_status_t) pjsua_call_reinvite(pjsua_call_id call_id, + pj_bool_t unhold, + const pjsua_msg_data *msg_data); /** - * Transfer call. + * Initiate call transfer to the specified address. + * + * @param call_id Call identification. + * @param dest Address of new target to be contacted. + * @param msg_data Optional message components to be sent with + * the request. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest); +PJ_DECL(pj_status_t) pjsua_call_xfer(pjsua_call_id call_id, + const pj_str_t *dest, + const pjsua_msg_data *msg_data); /** - * Dial DTMF. + * Send DTMF digits to remote using RFC 2833 payload formats. + * + * @param call_id Call identification. + * @param digits DTMF digits to be sent. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_call_dial_dtmf(unsigned call_index, +PJ_DECL(pj_status_t) pjsua_call_dial_dtmf(pjsua_call_id call_id, const pj_str_t *digits); - /** * Send instant messaging inside INVITE session. + * + * @param call_id Call identification. + * @param mime_type Optional MIME type. If NULL, then "text/plain" is + * assumed. + * @param content The message content. + * @param msg_data Optional list of headers etc to be included in outgoing + * request. The body descriptor in the msg_data is + * ignored. + * @param user_data Optional user data, which will be given back when + * the IM callback is called. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_call_send_im(int call_index, const pj_str_t *text); +PJ_DECL(pj_status_t) pjsua_call_send_im( pjsua_call_id call_id, + const pj_str_t *mime_type, + const pj_str_t *content, + const pjsua_msg_data *msg_data, + void *user_data); /** * Send IM typing indication inside INVITE session. + * + * @param call_id Call identification. + * @param is_typing Non-zero to indicate to remote that local person is + * currently typing an IM. + * @param msg_data Optional list of headers etc to be included in outgoing + * request. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_call_send_typing_ind(int call_index, - pj_bool_t is_typing); +PJ_DECL(pj_status_t) pjsua_call_send_typing_ind(pjsua_call_id call_id, + pj_bool_t is_typing, + const pjsua_msg_data*msg_data); /** * Terminate all calls. @@ -604,82 +1549,191 @@ PJ_DECL(void) pjsua_call_hangup_all(void); /** * Dump call and media statistics to string. + * + * @param call_id Call identification. + * @param with_media Non-zero to include media information too. + * @param buffer Buffer where the statistics are to be written to. + * @param maxlen Maximum length of buffer. + * @param indent Spaces for left indentation. + * + * @return PJ_SUCCESS on success. */ -PJ_DECL(void) pjsua_call_dump(int call_index, int with_media, - char *buffer, unsigned maxlen, - const char *indent); +PJ_DECL(pj_status_t) pjsua_call_dump(pjsua_call_id call_id, + pj_bool_t with_media, + char *buffer, + unsigned maxlen, + const char *indent); /***************************************************************************** - * PJSUA Account and Client Registration API (defined in pjsua_reg.c). + * PJSUA Account and Client Registration API. */ /** - * Get number of accounts. + * Get number of current accounts. + * + * @return Current number of accounts. */ -PJ_DECL(unsigned) pjsua_get_acc_count(void); +PJ_DECL(unsigned) pjsua_acc_get_count(void); + /** - * Get account info. + * Check if the specified account ID is valid. + * + * @param acc_id Account ID to check. + * + * @return Non-zero if account ID is valid. */ -PJ_DECL(pj_status_t) pjsua_acc_get_info(pjsua_acc_id acc_id, - pjsua_acc_info *info); +PJ_DECL(pj_bool_t) pjsua_acc_is_valid(pjsua_acc_id acc_id); /** - * Enum accounts id. + * Add a new account to pjsua. PJSUA must have been initialized (with + * #pjsua_init()) before calling this function. + * + * @param cfg Account configuration. + * @param is_default If non-zero, this account will be set as the default + * account. The default account will be used when sending + * outgoing requests (e.g. making call) when no account is + * specified, and when receiving incoming requests when the + * request does not match any accounts. It is recommended + * that default account is set to local/LAN account. + * @param p_acc_id Pointer to receive account ID of the new account. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_acc_enum_id( pjsua_acc_id ids[], - unsigned *count ); +PJ_DECL(pj_status_t) pjsua_acc_add(const pjsua_acc_config *cfg, + pj_bool_t is_default, + pjsua_acc_id *p_acc_id); /** - * Enum accounts info. + * Add a local account. A local account is used to identify local endpoint + * instead of a specific user, and for this reason, a transport ID is needed + * to obtain the local address information. + * + * @param tid Transport ID to generate account address. + * @param is_default If non-zero, this account will be set as the default + * account. The default account will be used when sending + * outgoing requests (e.g. making call) when no account is + * specified, and when receiving incoming requests when the + * request does not match any accounts. It is recommended + * that default account is set to local/LAN account. + * @param p_acc_id Pointer to receive account ID of the new account. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[], - unsigned *count ); +PJ_DECL(pj_status_t) pjsua_acc_add_local(pjsua_transport_id tid, + pj_bool_t is_default, + pjsua_acc_id *p_acc_id); + +/** + * Delete account. + * + * @param acc_id Id of the account to be deleted. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_id); /** - * Find account for outgoing request. + * Modify account information. + * + * @param acc_id Id of the account to be modified. + * @param cfg New account configuration. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *url); +PJ_DECL(pj_status_t) pjsua_acc_modify(pjsua_acc_id acc_id, + const pjsua_acc_config *cfg); + /** - * Find account for incoming request. + * Modify account's presence status to be advertised to remote/presence + * subscribers. + * + * @param acc_id The account ID. + * @param is_online True of false. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata); +PJ_DECL(pj_status_t) pjsua_acc_set_online_status(pjsua_acc_id acc_id, + pj_bool_t is_online); + /** - * Add a new account. - * This function should be called after pjsua_init(). - * Application should call pjsua_acc_set_registration() to start - * registration for this account. + * Update registration or perform unregistration. + * + * @param acc_id The account ID. + * @param renew If renew argument is zero, this will start + * unregistration process. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_acc_add(const pjsua_acc_config *cfg, - pjsua_acc_id *acc_id); +PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_id, + pj_bool_t renew); + /** - * Delete account. + * Get account information. + * + * @param acc_id Account identification. + * @param info Pointer to receive account information. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_id); +PJ_DECL(pj_status_t) pjsua_acc_get_info(pjsua_acc_id acc_id, + pjsua_acc_info *info); /** - * Set account's presence status. + * Enum accounts all account ids. + * + * @param ids Array of account IDs to be initialized. + * @param count In input, specifies the maximum number of elements. + * On return, it contains the actual number of elements. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_acc_set_online_status(pjsua_acc_id acc_id, - pj_bool_t is_online); +PJ_DECL(pj_status_t) pjsua_enum_accs(pjsua_acc_id ids[], + unsigned *count ); + + +/** + * Enum accounts info. + * + * @param info Array of account infos to be initialized. + * @param count In input, specifies the maximum number of elements. + * On return, it contains the actual number of elements. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[], + unsigned *count ); /** - * Update registration or perform unregistration. If renew argument is zero, - * this will start unregistration process. + * This is an internal function to find the most appropriate account to + * used to reach to the specified URL. + * + * @param url The remote URL to reach. + * + * @return Account id. */ -PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_id, - pj_bool_t renew); +PJ_DECL(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *url); + +/** + * This is an internal function to find the most appropriate account to be + * used to handle incoming calls. + * + * @param rdata The incoming request message. + * + * @return Account id. + */ +PJ_DECL(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata); @@ -688,41 +1742,88 @@ PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_id, */ /** - * Get buddy count. + * Get total number of buddies. + * + * @return Number of buddies. */ PJ_DECL(unsigned) pjsua_get_buddy_count(void); /** - * Get buddy info. + * Check if buddy ID is valid. + * + * @param buddy_id Buddy ID to check. + * + * @return Non-zero if buddy ID is valid. + */ +PJ_DECL(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id); + + +/** + * Enum buddy IDs. + * + * @param ids Array of ids to be initialized. + * @param count On input, specifies max elements in the array. + * On return, it contains actual number of elements + * that have been initialized. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_enum_buddies(pjsua_buddy_id ids[], + unsigned *count); + +/** + * Get detailed buddy info. + * + * @param buddy_id The buddy identification. + * @param info Pointer to receive information about buddy. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_buddy_get_info(pjsua_buddy_id buddy_index, +PJ_DECL(pj_status_t) pjsua_buddy_get_info(pjsua_buddy_id buddy_id, pjsua_buddy_info *info); /** * Add new buddy. + * + * @param cfg Buddy configuration. + * @param p_buddy_id Pointer to receive buddy ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_buddy_add(const pj_str_t *uri, - pjsua_buddy_id *buddy_index); +PJ_DECL(pj_status_t) pjsua_buddy_add(const pjsua_buddy_config *cfg, + pjsua_buddy_id *p_buddy_id); /** * Delete buddy. + * + * @param buddy_id Buddy identification. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_index); +PJ_DECL(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id); /** * Enable/disable buddy's presence monitoring. + * + * @param buddy_id Buddy identification. + * @param subscribe Specify non-zero to activate presence subscription to + * the specified buddy. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_buddy_subscribe_pres(pjsua_buddy_id buddy_index, - pj_bool_t monitor); +PJ_DECL(pj_status_t) pjsua_buddy_subscribe_pres(pjsua_buddy_id buddy_id, + pj_bool_t subscribe); /** - * Dump presence subscriptions. + * Dump presence subscriptions to log file. + * + * @param verbose Yes or no. */ -PJ_DECL(void) pjsua_pres_dump(pj_bool_t detail); +PJ_DECL(void) pjsua_pres_dump(pj_bool_t verbose); /***************************************************************************** @@ -737,176 +1838,355 @@ extern const pjsip_method pjsip_message_method; /** - * Send IM outside dialog. + * Send instant messaging outside dialog, using the specified account for + * route set and authentication. + * + * @param acc_id Account ID to be used to send the request. + * @param to Remote URI. + * @param mime_type Optional MIME type. If NULL, then "text/plain" is + * assumed. + * @param content The message content. + * @param msg_data Optional list of headers etc to be included in outgoing + * request. The body descriptor in the msg_data is + * ignored. + * @param user_data Optional user data, which will be given back when + * the IM callback is called. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_im_send(int acc_id, const pj_str_t *dst_uri, - const pj_str_t *text); +PJ_DECL(pj_status_t) pjsua_im_send(pjsua_acc_id acc_id, + const pj_str_t *to, + const pj_str_t *mime_type, + const pj_str_t *content, + const pjsua_msg_data *msg_data, + void *user_data); /** * Send typing indication outside dialog. + * + * @param acc_id Account ID to be used to send the request. + * @param to Remote URI. + * @param is_typing If non-zero, it tells remote person that local person + * is currently composing an IM. + * @param msg_data Optional list of headers etc to be added to outgoing + * request. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_im_typing(int acc_id, const pj_str_t *dst_uri, - pj_bool_t is_typing); +PJ_DECL(pj_status_t) pjsua_im_typing(pjsua_acc_id acc_id, + const pj_str_t *to, + pj_bool_t is_typing, + const pjsua_msg_data *msg_data); /***************************************************************************** - * Media. + * Conference bridge manipulation. */ /** * Get maxinum number of conference ports. + * + * @return Maximum number of ports in the conference bridge. + */ +PJ_DECL(unsigned) pjsua_conf_get_max_ports(void); + + +/** + * Get current number of active ports in the bridge. + * + * @return The number. */ -PJ_DECL(unsigned) pjsua_conf_max_ports(void); +PJ_DECL(unsigned) pjsua_conf_get_active_ports(void); /** - * Enum all conference ports. + * Enumerate all conference ports. + * + * @param id Array of conference port ID to be initialized. + * @param count On input, specifies max elements in the array. + * On return, it contains actual number of elements + * that have been initialized. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_conf_enum_port_ids(pjsua_conf_port_id id[], - unsigned *count); +PJ_DECL(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[], + unsigned *count); /** * Get information about the specified conference port + * + * @param id Port identification. + * @param info Pointer to store the port info. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id, pjsua_conf_port_info *info); /** - * Connect conference port. + * Establish unidirectional media flow from souce to sink. One source + * may transmit to multiple destinations/sink. And if multiple + * sources are transmitting to the same sink, the media will be mixed + * together. Source and sink may refer to the same ID, effectively + * looping the media. + * + * If bidirectional media flow is desired, application needs to call + * this function twice, with the second one having the arguments + * reversed. + * + * @param source Port ID of the source media/transmitter. + * @param sink Port ID of the destination media/received. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_conf_connect(pjsua_conf_port_id src_port, - pjsua_conf_port_id dst_port); +PJ_DECL(pj_status_t) pjsua_conf_connect(pjsua_conf_port_id source, + pjsua_conf_port_id sink); /** - * Connect conference port connection. + * Disconnect media flow from the source to destination port. + * + * @param source Port ID of the source media/transmitter. + * @param sink Port ID of the destination media/received. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_conf_disconnect(pjsua_conf_port_id src_port, - pjsua_conf_port_id dst_port); +PJ_DECL(pj_status_t) pjsua_conf_disconnect(pjsua_conf_port_id source, + pjsua_conf_port_id sink); + +/***************************************************************************** + * File player. + */ /** - * Create a file player. + * Create a file player, and automatically connect this player to + * the conference bridge. + * + * @param filename The filename to be played. Currently only + * WAV files are supported. + * @param options Options (currently zero). + * @param user_data Arbitrary user data to be associated with the player. + * @param p_id Pointer to receive player ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsua_player_create(const pj_str_t *filename, - pjsua_player_id *id); + unsigned options, + void *user_data, + pjsua_player_id *p_id); /** - * Get conference port associated with player. + * Get conference port ID associated with player. + * + * @param id The file player ID. + * + * @return Conference port ID associated with this player. */ PJ_DECL(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id); /** * Set playback position. + * + * @param id The file player ID. + * @param samples The playback position, in samples. Application can + * specify zero to re-start the playback. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsua_player_set_pos(pjsua_player_id id, pj_uint32_t samples); /** - * Destroy player. + * Close the file, remove the player from the bridge, and free + * resources associated with the file player. + * + * @param id The file player ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsua_player_destroy(pjsua_player_id id); +/***************************************************************************** + * File recorder. + */ /** - * Create a file recorder. + * Create a file recorder, and automatically connect this recorder to + * the conference bridge. + * + * @param filename Output file name. + * @param file_format Specify the file format (currently only WAV is + * supported, so the value MUST be zero). + * @param encoding Specify the encoding to be applied to the file. + * Currently only 16bit raw PCM is supported, so + * the value must be NULL. + * @param max_size Maximum file size. Specify -1 to remove size + * limitation. + * @param options Optional options. + * @param user_data Arbitrary user data which will be given in the + * callback once the recording complete. + * @param p_id Pointer to receive the recorder instance. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsua_recorder_create(const pj_str_t *filename, - pjsua_recorder_id *id); + unsigned file_format, + const pj_str_t *encoding, + pj_ssize_t max_size, + unsigned options, + void *user_data, + pjsua_recorder_id *p_id); /** * Get conference port associated with recorder. + * + * @param id The recorder ID. + * + * @return Conference port ID associated with this recorder. */ PJ_DECL(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id); /** - * Destroy recorder (will complete recording). + * Destroy recorder (this will complete recording). + * + * @param id The recorder ID. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id); +/***************************************************************************** + * Sound devices. + */ + /** * Enum sound devices. + * + * @param info Array of info to be initialized. + * @param count On input, specifies max elements in the array. + * On return, it contains actual number of elements + * that have been initialized. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_enum_snd_devices(unsigned *count, - pjmedia_snd_dev_info info[]); +PJ_DECL(pj_status_t) pjsua_enum_snd_devs(pjmedia_snd_dev_info info[], + unsigned *count); /** - * Select or change sound device. - * This will only change the device ID in configuration (not changing - * the current device). + * Select or change sound device. Application may call this function at + * any time to replace current sound device. + * + * @param capture_dev Device ID of the capture device. + * @param playback_dev Device ID of the playback device. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_set_snd_dev(int snd_capture_id, - int snd_player_id); +PJ_DECL(pj_status_t) pjsua_set_snd_dev(int capture_dev, + int playback_dev); -/***************************************************************************** - * Utilities. +/** + * Set pjsua to use null sound device. The null sound device only provides + * the timing needed by the conference bridge, and will not interract with + * any hardware. * + * @return PJ_SUCCESS on success, or the appropriate error code. */ +PJ_DECL(pj_status_t) pjsua_set_null_snd_dev(void); -/** String to describe invite session states */ -extern const char *pjsua_inv_state_names[]; -/** - * Parse arguments (pjsua_opt.c). +/***************************************************************************** + * Codecs. */ -PJ_DECL(pj_status_t) pjsua_parse_args(int argc, char *argv[], - pjsua_config *cfg, - pj_str_t *uri_to_call); /** - * Load settings from a file. + * Enum all supported codecs in the system. + * + * @param id Array of ID to be initialized. + * @param count On input, specifies max elements in the array. + * On return, it contains actual number of elements + * that have been initialized. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_load_settings(const char *filename, - pjsua_config *cfg, - pj_str_t *uri_to_call); +PJ_DECL(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[], + unsigned *count ); + /** - * Get pjsua running config. + * Change codec priority. + * + * @param id Codec ID. + * @param priority Codec priority, 0-255, where zero means to disable + * the codec. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(void) pjsua_get_config(pj_pool_t *pool, - pjsua_config *config); +PJ_DECL(pj_status_t) pjsua_codec_set_priority( const pj_str_t *id, + pj_uint8_t priority ); /** - * Dump settings. - * If cfg is NULL, it will dump current settings. + * Get codec parameters. + * + * @param id Codec ID. + * @param param Structure to receive codec parameters. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(int) pjsua_dump_settings(const pjsua_config *cfg, - char *buf, pj_size_t max); +PJ_DECL(pj_status_t) pjsua_codec_get_param( const pj_str_t *id, + pjmedia_codec_param *param ); + /** - * Save settings to a file. + * Set codec parameters. + * + * @param id Codec ID. + * @param param Codec parameter to set. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ -PJ_DECL(pj_status_t) pjsua_save_settings(const char *filename, - const pjsua_config *cfg); +PJ_DECL(pj_status_t) pjsua_codec_set_param( const pj_str_t *id, + const pjmedia_codec_param *param); + + + +/***************************************************************************** + * Utilities. + * + */ + /* * Verify that valid SIP url is given. - * @return PJ_SUCCESS if valid. + * + * @param c_url The URL, as NULL terminated string. + * + * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjsua_verify_sip_url(const char *c_url); -/* - * Dump application states. - */ -PJ_DECL(void) pjsua_dump(pj_bool_t detail); /** * Display error message for the specified error code. + * + * @param sender The log sender field. + * @param title Message title for the error. + * @param status Status code. */ PJ_DECL(void) pjsua_perror(const char *sender, const char *title, pj_status_t status); diff --git a/pjsip/include/pjsua-lib/pjsua_console_app.h b/pjsip/include/pjsua-lib/pjsua_console_app.h deleted file mode 100644 index 8be93714..00000000 --- a/pjsip/include/pjsua-lib/pjsua_console_app.h +++ /dev/null @@ -1,28 +0,0 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#ifndef __PJSUA_CONSOLE_APP_H__ -#define __PJSUA_CONSOLE_APP_H__ - - -void pjsua_console_app_main(const pj_str_t *uri_to_call); - -extern pjsip_module pjsua_console_app_msg_logger; -extern pjsua_callback console_callback; - -#endif /* __PJSUA_CONSOLE_APP_H__ */ diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h new file mode 100644 index 00000000..8b061c5b --- /dev/null +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -0,0 +1,359 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJSUA_INTERNAL_H__ +#define __PJSUA_INTERNAL_H__ + +/** + * This is the private header used by pjsua library implementation. + * Applications should not include this file. + */ + +PJ_BEGIN_DECL + +/** + * Structure to be attached to invite dialog. + * Given a dialog "dlg", application can retrieve this structure + * by accessing dlg->mod_data[pjsua.mod.id]. + */ +typedef struct pjsua_call +{ + unsigned index; /**< Index in pjsua array. */ + pjsip_inv_session *inv; /**< The invite session. */ + void *user_data; /**< User/application data. */ + pjsip_status_code last_code; /**< Last status code seen. */ + pj_str_t last_text; /**< Last status text seen. */ + pj_time_val start_time;/**< First INVITE sent/received. */ + pj_time_val res_time; /**< First response sent/received. */ + pj_time_val conn_time; /**< Connected/confirmed time. */ + pj_time_val dis_time; /**< Disconnect time. */ + pjsua_acc_id acc_id; /**< Account index being used. */ + pjsua_call_media_status media_st;/**< Media state. */ + pjmedia_dir media_dir; /**< Media direction. */ + pjmedia_session *session; /**< The media session. */ + int conf_slot; /**< Slot # in conference bridge. */ + pjsip_evsub *xfer_sub; /**< Xfer server subscription, if this + call was triggered by xfer. */ + pjmedia_sock_info skinfo; /**< Preallocated media sockets. */ + pjmedia_transport *med_tp; /**< Media transport. */ + pj_timer_entry refresh_tm;/**< Timer to send re-INVITE. */ + pj_timer_entry hangup_tm; /**< Timer to hangup call. */ + + char last_text_buf_[128]; /**< Buffer for last_text. */ + +} pjsua_call; + + +/** + * Server presence subscription list head. + */ +typedef struct pjsua_srv_pres +{ + PJ_DECL_LIST_MEMBER(struct pjsua_srv_pres); + pjsip_evsub *sub; + char *remote; +} pjsua_srv_pres; + + +/** + * Account + */ +typedef struct pjsua_acc +{ + pjsua_acc_config cfg; /**< Account configuration. */ + pj_bool_t valid; /**< Is this account valid? */ + + int index; /**< Index in accounts array. */ + pj_str_t user_part; /**< User part of local URI. */ + pj_str_t host_part; /**< Host part of local URI. */ + pj_str_t real_contact; /**< Real contact address. */ + + pjsip_regc *regc; /**< Client registration session. */ + pj_timer_entry reg_timer; /**< Registration timer. */ + pj_status_t reg_last_err; /**< Last registration error. */ + int reg_last_code; /**< Last status last register. */ + + pjsip_route_hdr route_set; /**< Complete route set inc. outbnd.*/ + + unsigned cred_cnt; /**< Number of credentials. */ + pjsip_cred_info cred[PJSUA_ACC_MAX_PROXIES]; /**< Complete creds. */ + + pj_bool_t online_status; /**< Our online status. */ + pjsua_srv_pres pres_srv_list; /**< Server subscription list. */ + +} pjsua_acc; + + +/** + *Transport. + */ +typedef struct transport_data +{ + int index; + pjsip_transport *tp; +} transport_data; + + +/** + * Buddy data. + */ +typedef struct pjsua_buddy +{ + unsigned index; /**< Buddy index. */ + pj_str_t uri; /**< Buddy URI. */ + pj_str_t contact; /**< Contact learned from subscrp. */ + pj_str_t name; /**< Buddy name. */ + pj_str_t display; /**< Buddy display name. */ + pj_str_t host; /**< Buddy host. */ + unsigned port; /**< Buddy port. */ + pj_bool_t monitor; /**< Should we monitor? */ + pjsip_evsub *sub; /**< Buddy presence subscription */ + pjsip_pres_status status; /**< Buddy presence status. */ + +} pjsua_buddy; + + +/** + * File player/recorder data. + */ +typedef struct pjsua_file_data +{ + pjmedia_port *port; + unsigned slot; +} pjsua_file_data; + + +/** + * Additional parameters for conference bridge. + */ +typedef struct pjsua_conf_setting +{ + unsigned channel_count; + unsigned samples_per_frame; + unsigned bits_per_sample; +} pjsua_conf_setting; + + +/** + * Global pjsua application data. + */ +struct pjsua_data +{ + + /* Control: */ + pj_caching_pool cp; /**< Global pool factory. */ + pj_pool_t *pool; /**< pjsua's private pool. */ + pj_mutex_t *mutex; /**< Mutex protection for this data */ + + /* Logging: */ + pjsua_logging_config log_cfg; /**< Current logging config. */ + pj_oshandle_t log_file; /**acc_id = src->acc_id; + dst->call_id = src->call_id; + pj_strdup_with_null(pool, &dst->to, &src->to); + dst->user_data = src->user_data; + pj_strdup_with_null(pool, &dst->body, &src->body); + + return dst; +} + + +#define PJSUA_LOCK() pj_mutex_lock(pjsua_var.mutex); +#define PJSUA_UNLOCK() pj_mutex_unlock(pjsua_var.mutex); + + + +/** + * Handle incoming invite request. + */ +pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata); + +/** + * Init presence. + */ +pj_status_t pjsua_pres_init(); + +/* + * Start presence subsystem. + */ +pj_status_t pjsua_pres_start(void); + +/** + * Refresh presence subscriptions + */ +void pjsua_pres_refresh(void); + +/* + * Shutdown presence. + */ +void pjsua_pres_shutdown(void); + +/** + * Terminate server subscription for the account + */ +void pjsua_pres_delete_acc(int acc_id); + +/** + * Init IM module handler to handle incoming MESSAGE outside dialog. + */ +pj_status_t pjsua_im_init(void); + +/** + * Init call subsystem. + */ +pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg); + +/** + * Start call subsystem. + */ +pj_status_t pjsua_call_subsys_start(void); + +/** + * Init media subsystems. + */ +pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg); + +/** + * Start pjsua media subsystem. + */ +pj_status_t pjsua_media_subsys_start(void); + +/** + * Destroy pjsua media subsystem. + */ +pj_status_t pjsua_media_subsys_destroy(void); + +/** + * Private: check if we can accept the message. + * If not, then p_accept header will be filled with a valid + * Accept header. + */ +pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata, + pjsip_accept_hdr **p_accept_hdr); + +/** + * Private: process pager message. + * This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing(). + */ +void pjsua_im_process_pager(int call_id, const pj_str_t *from, + const pj_str_t *to, pjsip_rx_data *rdata); + + +/** + * Create Accept header for MESSAGE. + */ +pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool); + +/* + * Add additional headers etc in msg_data specified by application + * when sending requests. + */ +void pjsua_process_msg_data(pjsip_tx_data *tdata, + const pjsua_msg_data *msg_data); + + +/* + * Add route_set to outgoing requests + */ +void pjsua_set_msg_route_set( pjsip_tx_data *tdata, + const pjsip_route_hdr *route_set ); + + +/* + * Simple version of MIME type parsing (it doesn't support parameters) + */ +void pjsua_parse_media_type( pj_pool_t *pool, + const pj_str_t *mime, + pjsip_media_type *media_type); + + +PJ_END_DECL + +#endif /* __PJSUA_INTERNAL_H__ */ + diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c new file mode 100644 index 00000000..b16f5fdf --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -0,0 +1,776 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + + +#define THIS_FILE "pjsua_acc.c" + + +/* + * Get number of current accounts. + */ +PJ_DEF(unsigned) pjsua_acc_get_count(void) +{ + return pjsua_var.acc_cnt; +} + + +/* + * Check if the specified account ID is valid. + */ +PJ_DEF(pj_bool_t) pjsua_acc_is_valid(pjsua_acc_id acc_id) +{ + return acc_id>=0 && acc_idid, &src->id); + pj_strdup_with_null(pool, &dst->reg_uri, &src->reg_uri); + pj_strdup_with_null(pool, &dst->contact, &src->contact); + + dst->proxy_cnt = src->proxy_cnt; + for (i=0; iproxy_cnt; ++i) + pj_strdup_with_null(pool, &dst->proxy[i], &src->proxy[i]); + + dst->reg_timeout = src->reg_timeout; + dst->cred_count = src->cred_count; + + for (i=0; icred_count; ++i) { + pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]); + } +} + + +/* + * Update account's real contact address. + */ +static void update_acc_contact(unsigned acc_id, + unsigned tp_id) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsip_transport *tp = pjsua_var.tpdata[tp_id].tp; + char uri[80]; + + /* Transport must be valid */ + pj_assert(tp != NULL); + + /* Build URI for the account */ + pj_ansi_sprintf(uri, "", + (int)tp->local_name.host.slen, + tp->local_name.host.ptr, + tp->local_name.port, + pjsip_transport_get_type_name(tp->key.type)); + + + pj_strdup2(pjsua_var.pool, &acc->real_contact, uri); +} + + +/* + * Initialize a new account (after configuration is set). + */ +static pj_status_t initialize_acc(unsigned acc_id) +{ + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + unsigned i; + + /* Need to parse local_uri to get the elements: */ + + uri = pjsip_parse_uri(pjsua_var.pool, acc_cfg->id.ptr, + acc_cfg->id.slen, 0); + if (uri == NULL) { + pjsua_perror(THIS_FILE, "Invalid local URI", + PJSIP_EINVALIDURI); + return PJSIP_EINVALIDURI; + } + + /* Local URI MUST be a SIP or SIPS: */ + + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri)) + { + pjsua_perror(THIS_FILE, "Invalid local URI", + PJSIP_EINVALIDSCHEME); + return PJSIP_EINVALIDSCHEME; + } + + + /* Get the SIP URI object: */ + + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri); + + /* Save the user and domain part. These will be used when finding an + * account for incoming requests. + */ + acc->user_part = sip_uri->user; + acc->host_part = sip_uri->host; + + + /* Create Contact header if not present. */ + if (acc_cfg->contact.slen == 0) { + acc_cfg->contact = acc_cfg->id; + } + + PJ_TODO(attach_account_to_transport); + if (pjsua_var.tpdata[0].tp) + update_acc_contact(acc_id, 0); + + /* Build account route-set from outbound proxies and route set from + * account configuration. + */ + pj_list_init(&acc->route_set); + + for (i=0; iroute_set, r); + } + + for (i=0; iproxy_cnt; ++i) { + pj_str_t hname = { "Route", 5}; + pjsip_route_hdr *r; + pj_str_t tmp; + + pj_strdup_with_null(pjsua_var.pool, &tmp, &acc_cfg->proxy[i]); + r = pjsip_parse_hdr(pjsua_var.pool, &hname, tmp.ptr, tmp.slen, NULL); + if (r == NULL) { + pjsua_perror(THIS_FILE, "Invalid URI in account route set", + PJ_EINVAL); + return PJ_EINVAL; + } + pj_list_push_back(&acc->route_set, r); + } + + + /* Concatenate credentials from account config and global config */ + acc->cred_cnt = 0; + for (i=0; icred_count; ++i) { + acc->cred[acc->cred_cnt++] = acc_cfg->cred_info[i]; + } + for (i=0; icred_cnt < PJ_ARRAY_SIZE(acc->cred); ++i) + { + acc->cred[acc->cred_cnt++] = pjsua_var.ua_cfg.cred_info[i]; + } + + /* Init presence subscription */ + pj_list_init(&acc->pres_srv_list); + + /* Mark account as valid */ + pjsua_var.acc[acc_id].valid = PJ_TRUE; + + + return PJ_SUCCESS; +} + + +/* + * Add a new account to pjsua. + */ +PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg, + pj_bool_t is_default, + pjsua_acc_id *p_acc_id) +{ + unsigned id; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_var.acc_cnt < PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_ETOOMANY); + + /* Must have a transport */ + PJ_ASSERT_RETURN(pjsua_var.tpdata[0].tp != NULL, PJ_EINVALIDOP); + + PJSUA_LOCK(); + + /* Find empty account id. */ + for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.acc); ++id) { + if (pjsua_var.acc[id].valid == PJ_FALSE) + break; + } + + /* Expect to find a slot */ + PJ_ASSERT_ON_FAIL( id < PJ_ARRAY_SIZE(pjsua_var.acc), + {PJSUA_UNLOCK(); return PJ_EBUG;}); + + /* Copy config */ + copy_acc_config(pjsua_var.pool, &pjsua_var.acc[id].cfg, cfg); + + /* Normalize registration timeout */ + if (pjsua_var.acc[id].cfg.reg_uri.slen && + pjsua_var.acc[id].cfg.reg_timeout == 0) + { + pjsua_var.acc[id].cfg.reg_timeout = PJSUA_REG_INTERVAL; + } + + status = initialize_acc(id); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error adding account", status); + PJSUA_UNLOCK(); + return status; + } + + if (is_default) + pjsua_var.default_acc = id; + + if (p_acc_id) + *p_acc_id = id; + + pjsua_var.acc_cnt++; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Account %.*s added with id %d", + (int)cfg->id.slen, cfg->id.ptr, id)); + + /* If accounts has registration enabled, start registration */ + if (pjsua_var.acc[id].cfg.reg_uri.slen) + pjsua_acc_set_registration(id, PJ_TRUE); + + + return PJ_SUCCESS; +} + + +/* + * Add local account + */ +PJ_DEF(pj_status_t) pjsua_acc_add_local( pjsua_transport_id tid, + pj_bool_t is_default, + pjsua_acc_id *p_acc_id) +{ + pjsua_acc_config cfg; + pjsip_transport *tp; + char uri[62]; + + /* Transport must be valid */ + tp = pjsua_var.tpdata[tid].tp; + PJ_ASSERT_RETURN(tp != NULL, PJ_EINVAL); + + pjsua_acc_config_default(&cfg); + + /* Build URI for the account */ + pj_ansi_sprintf(uri, "", + (int)tp->local_name.host.slen, + tp->local_name.host.ptr, + tp->local_name.port); + + cfg.id = pj_str(uri); + + return pjsua_acc_add(&cfg, is_default, p_acc_id); +} + + +/* + * Delete account. + */ +PJ_DEF(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_id) +{ + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJSUA_LOCK(); + + /* Delete registration */ + if (pjsua_var.acc[acc_id].regc != NULL) + pjsua_acc_set_registration(acc_id, PJ_FALSE); + + /* Delete server presence subscription */ + pjsua_pres_delete_acc(acc_id); + + /* Invalidate */ + pjsua_var.acc[acc_id].valid = PJ_FALSE; + + PJ_TODO(may_need_to_scan_calls); + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Account id %d deleted", acc_id)); + + return PJ_SUCCESS; +} + + +/* + * Modify account information. + */ +PJ_DEF(pj_status_t) pjsua_acc_modify( pjsua_acc_id acc_id, + const pjsua_acc_config *cfg) +{ + PJ_TODO(pjsua_acc_modify); + return PJ_EINVALIDOP; +} + + +/* + * Modify account's presence status to be advertised to remote/presence + * subscribers. + */ +PJ_DEF(pj_status_t) pjsua_acc_set_online_status( pjsua_acc_id acc_id, + pj_bool_t is_online) +{ + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + pjsua_var.acc[acc_id].online_status = is_online; + pjsua_pres_refresh(); + return PJ_SUCCESS; +} + + +/* + * This callback is called by pjsip_regc when outgoing register + * request has completed. + */ +static void regc_cb(struct pjsip_regc_cbparam *param) +{ + + pjsua_acc *acc = param->token; + + PJSUA_LOCK(); + + /* + * Print registration status. + */ + if (param->status!=PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "SIP registration error", + param->status); + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + + } else if (param->code < 0 || param->code >= 300) { + PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%.*s)", + param->code, + (int)param->reason.slen, param->reason.ptr)); + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + + } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { + + if (param->expiration < 1) { + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + PJ_LOG(3,(THIS_FILE, "%s: unregistration success", + pjsua_var.acc[acc->index].cfg.id.ptr)); + } else { + PJ_LOG(3, (THIS_FILE, + "%s: registration success, status=%d (%.*s), " + "will re-register in %d seconds", + pjsua_var.acc[acc->index].cfg.id.ptr, + param->code, + (int)param->reason.slen, param->reason.ptr, + param->expiration)); + } + + } else { + PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); + } + + acc->reg_last_err = param->status; + acc->reg_last_code = param->code; + + if (pjsua_var.ua_cfg.cb.on_reg_state) + (*pjsua_var.ua_cfg.cb.on_reg_state)(acc->index); + + PJSUA_UNLOCK(); +} + + +/* + * Initialize client registration. + */ +static pj_status_t pjsua_regc_init(int acc_id) +{ + pjsua_acc *acc; + pj_status_t status; + + acc = &pjsua_var.acc[acc_id]; + + if (acc->cfg.reg_uri.slen == 0) { + PJ_LOG(3,(THIS_FILE, "Registrar URI is not specified")); + return PJ_SUCCESS; + } + + /* initialize SIP registration if registrar is configured */ + + status = pjsip_regc_create( pjsua_var.endpt, + acc, ®c_cb, &acc->regc); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create client registration", + status); + return status; + } + + status = pjsip_regc_init( acc->regc, + &acc->cfg.reg_uri, + &acc->cfg.id, + &acc->cfg.id, + 1, &acc->real_contact, + acc->cfg.reg_timeout); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Client registration initialization error", + status); + return status; + } + + /* Set credentials + */ + if (acc->cred_cnt) { + pjsip_regc_set_credentials( acc->regc, acc->cred_cnt, acc->cred); + } + + /* Set route-set + */ + if (!pj_list_empty(&acc->route_set)) { + pjsip_regc_set_route_set( acc->regc, &acc->route_set ); + } + + return PJ_SUCCESS; +} + + +/* + * Update registration or perform unregistration. + */ +PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id, + pj_bool_t renew) +{ + pj_status_t status = 0; + pjsip_tx_data *tdata = 0; + + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJSUA_LOCK(); + + if (renew) { + if (pjsua_var.acc[acc_id].regc == NULL) { + // Need route set. + status = pjsua_regc_init(acc_id); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create registration", + status); + goto on_return; + } + } + if (!pjsua_var.acc[acc_id].regc) { + status = PJ_EINVALIDOP; + goto on_return; + } + + status = pjsip_regc_register(pjsua_var.acc[acc_id].regc, 1, + &tdata); + + } else { + if (pjsua_var.acc[acc_id].regc == NULL) { + PJ_LOG(3,(THIS_FILE, "Currently not registered")); + status = PJ_EINVALIDOP; + goto on_return; + } + status = pjsip_regc_unregister(pjsua_var.acc[acc_id].regc, &tdata); + } + + if (status == PJ_SUCCESS) + status = pjsip_regc_send( pjsua_var.acc[acc_id].regc, tdata ); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create/send REGISTER", + status); + } else { + PJ_LOG(3,(THIS_FILE, "%s sent", + (renew? "Registration" : "Unregistration"))); + } + +on_return: + PJSUA_UNLOCK(); + return status; +} + + +/* + * Get account information. + */ +PJ_DEF(pj_status_t) pjsua_acc_get_info( pjsua_acc_id acc_id, + pjsua_acc_info *info) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + + PJ_ASSERT_RETURN(info != NULL, PJ_EINVAL); + + pj_memset(info, 0, sizeof(pjsua_acc_info)); + + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJSUA_LOCK(); + + if (pjsua_var.acc[acc_id].valid == PJ_FALSE) { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } + + info->id = acc_id; + info->is_default = (pjsua_var.default_acc == acc_id); + info->acc_uri = acc_cfg->id; + info->has_registration = (acc->cfg.reg_uri.slen > 0); + info->online_status = acc->online_status; + + if (acc->reg_last_err) { + info->status = acc->reg_last_err; + pj_strerror(acc->reg_last_err, info->buf_, sizeof(info->buf_)); + info->status_text = pj_str(info->buf_); + } else if (acc->reg_last_code) { + if (acc->regc) { + info->status = acc->reg_last_code; + info->status_text = *pjsip_get_status_text(acc->reg_last_code); + } else { + info->status = 0; + info->status_text = pj_str("not registered"); + } + } else if (acc->cfg.reg_uri.slen) { + info->status = 100; + info->status_text = pj_str("In Progress"); + } else { + info->status = 0; + info->status_text = pj_str("does not register"); + } + + if (acc->regc) { + pjsip_regc_info regc_info; + pjsip_regc_get_info(acc->regc, ®c_info); + info->expires = regc_info.next_reg; + } else { + info->expires = -1; + } + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; + +} + + +/* + * Enum accounts all account ids. + */ +PJ_DEF(pj_status_t) pjsua_enum_accs(pjsua_acc_id ids[], + unsigned *count ) +{ + unsigned i, c; + + PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL); + + PJSUA_LOCK(); + + for (i=0, c=0; c<*count && ihost)==0) + break; + } + + if (acc_id == PJ_ARRAY_SIZE(pjsua_var.acc)) { + /* Just use default account */ + acc_id = pjsua_var.default_acc; + } + +on_return: + PJSUA_UNLOCK(); + + return acc_id; +} + + +/* + * This is an internal function to find the most appropriate account to be + * used to handle incoming calls. + */ +PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata) +{ + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + unsigned acc_id; + + uri = rdata->msg_info.to->uri; + + /* Just return default account if To URI is not SIP: */ + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri)) + { + return pjsua_var.default_acc; + } + + + PJSUA_LOCK(); + + sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); + + /* Find account which has matching username and domain. */ + for (acc_id=0; acc_id < pjsua_var.acc_cnt; ++acc_id) { + + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + + if (pj_stricmp(&acc->user_part, &sip_uri->user)==0 && + pj_stricmp(&acc->host_part, &sip_uri->host)==0) + { + /* Match ! */ + PJSUA_UNLOCK(); + return acc_id; + } + } + + /* No matching, try match domain part only. */ + for (acc_id=0; acc_id < pjsua_var.acc_cnt; ++acc_id) { + + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + + if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) { + /* Match ! */ + PJSUA_UNLOCK(); + return acc_id; + } + } + + /* Still no match, use default account */ + PJSUA_UNLOCK(); + return pjsua_var.default_acc; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 507e3c93..92c1f92e 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -17,283 +17,235 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include -#include -#include "pjsua_imp.h" +#include -/* - * pjsua_call.c - * - * Call (INVITE) related stuffs. - */ -#define THIS_FILE "pjsua_call.c" +#define THIS_FILE "pjsua_call.c" -#define REFRESH_CALL_TIMER 0x63 -#define HANGUP_CALL_TIMER 0x64 +/* This callback receives notification from invite session when the + * session state has changed. + */ +static void pjsua_call_on_state_changed(pjsip_inv_session *inv, + pjsip_event *e); -/* Proto */ -static void schedule_call_timer( pjsua_call *call, pj_timer_entry *e, - int timer_type, int duration ); +/* This callback is called by invite session framework when UAC session + * has forked. + */ +static void pjsua_call_on_forked( pjsip_inv_session *inv, + pjsip_event *e); /* - * Timer callback when UAS needs to send re-INVITE to see if remote - * is still there. + * Callback to be called when SDP offer/answer negotiation has just completed + * in the session. This function will start/update media if negotiation + * has succeeded. */ -static void call_on_timer(pj_timer_heap_t *ht, pj_timer_entry *e) -{ - pjsua_call *call = e->user_data; +static void pjsua_call_on_media_update(pjsip_inv_session *inv, + pj_status_t status); + +/* + * Called when session received new offer. + */ +static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer); - PJ_UNUSED_ARG(ht); +/* + * This callback is called when transaction state has changed in INVITE + * session. We use this to trap: + * - incoming REFER request. + * - incoming MESSAGE request. + */ +static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_event *e); - if (e->id == REFRESH_CALL_TIMER) { - /* If call is still not connected, hangup. */ - if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { - PJ_LOG(3,(THIS_FILE, "Refresh call timer is called when " - "invite is still not confirmed. Call %d will " - "disconnect.", call->index)); - pjsua_call_hangup(call->index); - } else { - PJ_LOG(3,(THIS_FILE, "Refreshing call %d", call->index)); - schedule_call_timer(call,e,REFRESH_CALL_TIMER, - pjsua.config.uas_refresh); - pjsua_call_reinvite(call->index); - } +/* Destroy the call's media */ +static pj_status_t call_destroy_media(int call_id); - } else if (e->id == HANGUP_CALL_TIMER) { - PJ_LOG(3,(THIS_FILE, "Call %d duration exceeded, disconnecting call", - call->index)); - pjsua_call_hangup(call->index); +/* Create inactive SDP for call hold. */ +static pj_status_t create_inactive_sdp(pjsua_call *call, + pjmedia_sdp_session **p_answer); - } -} /* - * Schedule call timer. + * Reset call descriptor. */ -static void schedule_call_timer( pjsua_call *call, pj_timer_entry *e, - int timer_type, int duration ) +static void reset_call(pjsua_call_id id) { - pj_time_val timeout; - - if (duration == 0) { - /* Cancel timer. */ - if (e->id != 0) { - pjsip_endpt_cancel_timer(pjsua.endpt, e); - e->id = 0; - } - - } else { - /* Schedule timer. */ - timeout.sec = duration; - timeout.msec = 0; - - e->cb = &call_on_timer; - e->id = timer_type; - e->user_data = call; - - pjsip_endpt_schedule_timer( pjsua.endpt, e, &timeout); - } + pjsua_call *call = &pjsua_var.calls[id]; + + call->index = id; + call->inv = NULL; + call->user_data = NULL; + call->session = NULL; + call->xfer_sub = NULL; + call->last_code = 0; + call->conf_slot = PJSUA_INVALID_ID; + call->last_text.ptr = call->last_text_buf_; + call->last_text.slen = 0; } /* - * Destroy the call's media + * Init call subsystem. */ -static pj_status_t call_destroy_media(int call_index) +pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg) { - pjsua_call *call = &pjsua.calls[call_index]; - - if (call->conf_slot > 0) { - pjmedia_conf_remove_port(pjsua.mconf, call->conf_slot); - call->conf_slot = 0; - } + pjsip_inv_callback inv_cb; + unsigned i; + pj_status_t status; - if (call->session) { - /* Destroy session (this will also close RTP/RTCP sockets). */ - pjmedia_session_destroy(call->session); - call->session = NULL; + /* Init calls array. */ + for (i=0; istate != PJSIP_INV_STATE_DISCONNECTED; + return pjsua_var.ua_cfg.max_calls; } -/** - * Check if call has a media session. + +/* + * Get number of currently active calls. */ -PJ_DEF(pj_bool_t) pjsua_call_has_media(unsigned call_index) +PJ_DEF(unsigned) pjsua_call_get_count(void) { - PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, PJ_EINVAL); - return pjsua.calls[call_index].session != NULL; + return pjsua_var.call_cnt; } -/** - * Get call info. +/* + * Enum calls. */ -PJ_DEF(pj_status_t) pjsua_call_get_info( unsigned call_index, - pjsua_call_info *info) +PJ_DEF(pj_status_t) pjsua_enum_calls( pjsua_call_id ids[], + unsigned *count) { - pjsua_call *call; - - PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, - PJ_EINVAL); - - pj_memset(info, 0, sizeof(pjsua_call_info)); - - call = &pjsua.calls[call_index]; - info->active = pjsua_call_is_active(call_index); - - if (call->inv == NULL) - return PJ_SUCCESS; - - info->index = call_index; - info->role = call->inv->role; - info->local_info = call->inv->dlg->local.info_str; - info->remote_info = call->inv->dlg->remote.info_str; - info->state = call->inv->state; - info->state_text = pj_str((char*)pjsip_inv_state_name(info->state)); - - if (info->state >= PJSIP_INV_STATE_DISCONNECTED) { - - info->total_duration = call->dis_time; - PJ_TIME_VAL_SUB(info->total_duration, call->start_time); - - if (call->conn_time.sec) { - info->connect_duration = call->dis_time; - PJ_TIME_VAL_SUB(info->total_duration, call->conn_time); - } - - } else if (info->state == PJSIP_INV_STATE_CONFIRMED) { + unsigned i, c; - pj_gettimeofday(&info->total_duration); - PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL); - pj_gettimeofday(&info->connect_duration); - PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time); + PJSUA_LOCK(); - } else { - pj_gettimeofday(&info->total_duration); - PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + for (i=0, c=0; c<*count && ilast_status = call->last_code; - info->last_status_text = *pjsip_get_status_text(info->last_status); + *count = c; - info->has_media = (call->session != NULL); - info->conf_slot = call->conf_slot; + PJSUA_UNLOCK(); return PJ_SUCCESS; } -/** - * Duplicate call info. - */ -PJ_DEF(void) pjsua_call_info_dup( pj_pool_t *pool, - pjsua_call_info *dst_info, - const pjsua_call_info *src_info) -{ - PJ_ASSERT_ON_FAIL(pool && dst_info && src_info, return); - - pj_memcpy(dst_info, src_info, sizeof(pjsua_call_info)); - - pj_strdup(pool, &dst_info->local_info, &src_info->local_info); - pj_strdup(pool, &dst_info->remote_info, &src_info->remote_info); - - /* state_text and cause_text belong to pjsip, so don't need to be - * duplicated because they'll always be available. - */ -} - - -/** - * Make outgoing call. +/* + * Make outgoing call to the specified URI using the specified account. */ -PJ_DEF(pj_status_t) pjsua_call_make_call(unsigned acc_index, - const pj_str_t *dest_uri, - int *p_call_index) +PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id, + const pj_str_t *dest_uri, + unsigned options, + void *user_data, + const pjsua_msg_data *msg_data, + pjsua_call_id *p_call_id) { pjsip_dialog *dlg = NULL; pjmedia_sdp_session *offer; pjsip_inv_session *inv = NULL; - unsigned call_index; + pjsua_acc *acc; + pjsua_call *call; + unsigned call_id; pjsip_tx_data *tdata; pj_status_t status; - PJ_ASSERT_RETURN(acc_index==0 || acc_index < pjsua.config.acc_cnt, + /* Check that account is valid */ + PJ_ASSERT_RETURN(acc_id>=0 || acc_idvalid) { + pjsua_perror(THIS_FILE, "Unable to make call because account " + "is not valid", PJ_EINVALIDOP); + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } /* Find free call slot. */ - for (call_index=0; call_indexstart_time); /* Reset first response time */ - pjsua.calls[call_index].res_time.sec = 0; + call->res_time.sec = 0; /* Create outgoing dialog: */ status = pjsip_dlg_create_uac( pjsip_ua_instance(), - &pjsua.config.acc_config[acc_index].id, - &pjsua.config.acc_config[acc_index].contact, - dest_uri, dest_uri, - &dlg); + &acc->cfg.id, &acc->cfg.contact, + dest_uri, dest_uri, &dlg); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Dialog creation failed", status); + PJSUA_UNLOCK(); return status; } /* Get media capability from media endpoint: */ - status = pjmedia_endpt_create_sdp( pjsua.med_endpt, dlg->pool, 1, - &pjsua.calls[call_index].skinfo, - &offer); + status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, dlg->pool, 1, + &call->skinfo, &offer); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status); goto on_error; @@ -310,24 +262,23 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(unsigned acc_index, /* Create and associate our data in the session. */ - pjsua.calls[call_index].inv = inv; + call->inv = inv; - dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; - inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + dlg->mod_data[pjsua_var.mod.id] = call; + inv->mod_data[pjsua_var.mod.id] = call; + /* Attach user data */ + call->user_data = user_data; /* Set dialog Route-Set: */ - - if (!pj_list_empty(&pjsua.acc[acc_index].route_set)) - pjsip_dlg_set_route_set(dlg, &pjsua.acc[acc_index].route_set); + if (!pj_list_empty(&acc->route_set)) + pjsip_dlg_set_route_set(dlg, &acc->route_set); /* Set credentials: */ - if (pjsua.config.acc_config[acc_index].cred_count) { - pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index]; + if (acc->cred_cnt) { pjsip_auth_clt_set_credentials( &dlg->auth_sess, - acc_cfg->cred_count, - acc_cfg->cred_info); + acc->cred_cnt, acc->cred); } @@ -341,6 +292,10 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(unsigned acc_index, } + /* Add additional headers etc */ + + pjsua_process_msg_data( tdata, msg_data); + /* Send initial INVITE: */ status = pjsip_inv_send_msg(inv, tdata); @@ -356,13 +311,14 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(unsigned acc_index, goto on_error; } - /* Done. */ - ++pjsua.call_cnt; + ++pjsua_var.call_cnt; + + if (p_call_id) + *p_call_id = call_id; - if (p_call_index) - *p_call_index = call_index; + PJSUA_UNLOCK(); return PJ_SUCCESS; @@ -374,46 +330,18 @@ on_error: pjsip_dlg_terminate(dlg); } - if (call_index != -1) { - pjsua.calls[call_index].inv = NULL; - } - return status; -} - - -/** - * Answer call. - */ -PJ_DEF(pj_status_t) pjsua_call_answer(int call_index, int code) -{ - pjsip_tx_data *tdata; - pj_status_t status; - - PJ_ASSERT_RETURN( call_index >= 0 && - call_index < (int)pjsua.config.max_calls, - PJ_EINVAL); - - if (pjsua.calls[call_index].inv == NULL) { - PJ_LOG(3,(THIS_FILE, "Call %d already disconnected")); - return PJSIP_ESESSIONTERMINATED; + if (call_id != -1) { + reset_call(call_id); } - status = pjsip_inv_answer(pjsua.calls[call_index].inv, - code, NULL, NULL, &tdata); - if (status == PJ_SUCCESS) - status = pjsip_inv_send_msg(pjsua.calls[call_index].inv, - tdata); - - if (status != PJ_SUCCESS) - pjsua_perror(THIS_FILE, "Unable to create/send response", - status); - + PJSUA_UNLOCK(); return status; } /** * Handle incoming INVITE request. + * Called by pjsua_core.c */ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) { @@ -423,8 +351,9 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsip_tx_data *response = NULL; unsigned options = 0; pjsip_inv_session *inv = NULL; - int acc_index; - unsigned call_index; + int acc_id; + pjsua_call *call; + int call_id = -1; pjmedia_sdp_session *answer; pj_status_t status; @@ -441,7 +370,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) /* Verify that we can handle the request. */ status = pjsip_inv_verify_request(rdata, &options, NULL, NULL, - pjsua.endpt, &response); + pjsua_var.endpt, &response); if (status != PJ_SUCCESS) { /* @@ -452,13 +381,13 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsip_response_addr res_addr; pjsip_get_response_addr(response->pool, rdata, &res_addr); - pjsip_endpt_send_response(pjsua.endpt, &res_addr, response, + pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response, NULL, NULL); } else { /* Respond with 500 (Internal Server Error) */ - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, NULL, NULL); } @@ -471,101 +400,88 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) */ /* Find free call slot. */ - for (call_index=0; call_index < pjsua.config.max_calls; ++call_index) { - if (pjsua.calls[call_index].inv == NULL) + for (call_id=0; call_id<(int)pjsua_var.ua_cfg.max_calls; ++call_id) { + if (pjsua_var.calls[call_id].inv == NULL) break; } - if (call_index == PJSUA_MAX_CALLS) { - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, + if (call_id == (int)pjsua_var.ua_cfg.max_calls) { + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, PJSIP_SC_BUSY_HERE, NULL, NULL, NULL); return PJ_TRUE; } - /* Mark call start time. */ - pj_gettimeofday(&pjsua.calls[call_index].start_time); + /* Clear call descriptor */ + reset_call(call_id); - /* Reset first response time */ - pjsua.calls[call_index].res_time.sec = 0; + call = &pjsua_var.calls[call_id]; - /* Get media capability from media endpoint: */ + /* Mark call start time. */ + pj_gettimeofday(&call->start_time); - status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool, 1, - &pjsua.calls[call_index].skinfo, - &answer ); + /* Get media capability from media endpoint: */ + status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, + rdata->tp_info.pool, 1, + &call->skinfo, &answer ); if (status != PJ_SUCCESS) { - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, NULL, NULL); - return PJ_TRUE; } - /* TODO: - * + /* * Get which account is most likely to be associated with this incoming * call. We need the account to find which contact URI to put for * the call. */ - acc_index = 0; + acc_id = pjsua_acc_find_for_incoming(rdata); /* Create dialog: */ - status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, - &pjsua.config.acc_config[acc_index].contact, + &pjsua_var.acc[acc_id].cfg.contact, &dlg); if (status != PJ_SUCCESS) { - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, NULL, NULL); return PJ_TRUE; } + /* Set credentials */ + if (pjsua_var.acc[acc_id].cred_cnt) { + pjsip_auth_clt_set_credentials(&dlg->auth_sess, + pjsua_var.acc[acc_id].cred_cnt, + pjsua_var.acc[acc_id].cred); + } /* Create invite session: */ - status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv); if (status != PJ_SUCCESS) { - pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL); pjsip_dlg_terminate(dlg); return PJ_TRUE; } - /* Create and attach pjsua data to the dialog: */ + /* Create and attach pjsua_var data to the dialog: */ + call->inv = inv; - pjsua.calls[call_index].inv = inv; - - dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; - inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + dlg->mod_data[pjsua_var.mod.id] = call; + inv->mod_data[pjsua_var.mod.id] = call; /* Must answer with some response to initial INVITE. * If auto-answer flag is set, send 200 straight away, otherwise send 100. */ - status = pjsip_inv_initial_answer(inv, rdata, - (pjsua.config.auto_answer ? - pjsua.config.auto_answer : 100), - NULL, NULL, &response); + 100, NULL, NULL, &response); if (status != PJ_SUCCESS) { - - int st_code; - pjsua_perror(THIS_FILE, "Unable to send answer to incoming INVITE", status); - /* If failed to send 2xx response, there's a good chance that it is - * because SDP negotiation has failed. - */ - if (pjsua.config.auto_answer/100 == 2) - st_code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE; - else - st_code = 500; - - pjsip_dlg_respond(dlg, rdata, st_code, NULL, NULL, NULL); - pjsip_inv_terminate(inv, st_code, PJ_FALSE); + pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL); + pjsip_inv_terminate(inv, 500, PJ_FALSE); return PJ_TRUE; } else { @@ -574,1121 +490,1772 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsua_perror(THIS_FILE, "Unable to send 100 response", status); } - if (pjsua.config.auto_answer < 200) { - PJ_LOG(3,(THIS_FILE, - "\nIncoming call!!\n" - "From: %.*s\n" - "To: %.*s\n" - "(press 'a' to answer, 'h' to decline)", - (int)dlg->remote.info_str.slen, - dlg->remote.info_str.ptr, - (int)dlg->local.info_str.slen, - dlg->local.info_str.ptr)); - } else { - PJ_LOG(3,(THIS_FILE, - "Call From:%.*s To:%.*s was answered with %d (%s)", - (int)dlg->remote.info_str.slen, - dlg->remote.info_str.ptr, - (int)dlg->local.info_str.slen, - dlg->local.info_str.ptr, - pjsua.config.auto_answer, - pjsip_get_status_text(pjsua.config.auto_answer)->ptr )); - } - - ++pjsua.call_cnt; - - /* Schedule timer to refresh. */ - if (pjsua.config.uas_refresh > 0) { - schedule_call_timer( &pjsua.calls[call_index], - &pjsua.calls[call_index].refresh_tm, - REFRESH_CALL_TIMER, - pjsua.config.uas_refresh); - } + ++pjsua_var.call_cnt; - /* Schedule timer to hangup call. */ - if (pjsua.config.uas_duration > 0) { - schedule_call_timer( &pjsua.calls[call_index], - &pjsua.calls[call_index].hangup_tm, - HANGUP_CALL_TIMER, - pjsua.config.uas_duration); - } /* Notify application */ - if (pjsua.cb.on_incoming_call) - pjsua.cb.on_incoming_call(acc_index, call_index, rdata); - + if (pjsua_var.ua_cfg.cb.on_incoming_call) + pjsua_var.ua_cfg.cb.on_incoming_call(acc_id, call_id, rdata); /* This INVITE request has been handled. */ return PJ_TRUE; } + /* - * This callback receives notification from invite session when the - * session state has changed. + * Check if the specified call has active INVITE session and the INVITE + * session has not been disconnected. */ -static void pjsua_call_on_state_changed(pjsip_inv_session *inv, - pjsip_event *e) +PJ_DEF(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id) { - pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id]; + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + return pjsua_var.calls[call_id].inv != NULL && + pjsua_var.calls[call_id].inv->state != PJSIP_INV_STATE_DISCONNECTED; +} - if (!call) - return; - /* Get call times */ - switch (inv->state) { - case PJSIP_INV_STATE_EARLY: - case PJSIP_INV_STATE_CONNECTING: - if (call->res_time.sec == 0) - pj_gettimeofday(&call->res_time); - call->last_code = e->body.tsx_state.tsx->status_code; - break; - case PJSIP_INV_STATE_CONFIRMED: - pj_gettimeofday(&call->conn_time); - break; - case PJSIP_INV_STATE_DISCONNECTED: - pj_gettimeofday(&call->dis_time); - if (e->body.tsx_state.tsx->status_code > call->last_code) { - call->last_code = e->body.tsx_state.tsx->status_code; - } - break; - default: - call->last_code = e->body.tsx_state.tsx->status_code; - break; - } +/* + * Check if call has an active media session. + */ +PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id) +{ + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + return pjsua_var.calls[call_id].session != NULL; +} - /* If this is an outgoing INVITE that was created because of - * REFER/transfer, send NOTIFY to transferer. - */ - if (call->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) { - int st_code = -1; - pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE; - - switch (call->inv->state) { - case PJSIP_INV_STATE_NULL: - case PJSIP_INV_STATE_CALLING: - /* Do nothing */ - break; +/* + * Get the conference port identification associated with the call. + */ +PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id) +{ + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + return pjsua_var.calls[call_id].conf_slot; +} - case PJSIP_INV_STATE_EARLY: - case PJSIP_INV_STATE_CONNECTING: - st_code = e->body.tsx_state.tsx->status_code; - ev_state = PJSIP_EVSUB_STATE_ACTIVE; - break; - case PJSIP_INV_STATE_CONFIRMED: - /* When state is confirmed, send the final 200/OK and terminate - * subscription. - */ - st_code = e->body.tsx_state.tsx->status_code; - ev_state = PJSIP_EVSUB_STATE_TERMINATED; - break; +/* + * Obtain detail information about the specified call. + */ +PJ_DEF(pj_status_t) pjsua_call_get_info( pjsua_call_id call_id, + pjsua_call_info *info) +{ + pjsua_call *call; - case PJSIP_INV_STATE_DISCONNECTED: - st_code = e->body.tsx_state.tsx->status_code; - ev_state = PJSIP_EVSUB_STATE_TERMINATED; - break; + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); - case PJSIP_INV_STATE_INCOMING: - /* Nothing to do. Just to keep gcc from complaining about - * unused enums. - */ - break; - } + pj_memset(info, 0, sizeof(*info)); - if (st_code != -1) { - pjsip_tx_data *tdata; - pj_status_t status; + PJSUA_LOCK(); - status = pjsip_xfer_notify( call->xfer_sub, - ev_state, st_code, - NULL, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status); - } else { - status = pjsip_xfer_send_request(call->xfer_sub, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status); - } - } - } + call = &pjsua_var.calls[call_id]; + + if (call->inv == NULL) { + PJSUA_UNLOCK(); + return PJ_SUCCESS; } + pjsip_dlg_inc_lock(call->inv->dlg); - if (pjsua.cb.on_call_state) - (*pjsua.cb.on_call_state)(call->index, e); - /* call->inv may be NULL now */ + /* id and role */ + info->id = call_id; + info->role = call->inv->role; - /* Destroy media session when invite session is disconnected. */ - if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + /* local info */ + info->local_info.ptr = info->buf_.local_info; + pj_strncpy(&info->local_info, &call->inv->dlg->local.info_str, + sizeof(info->buf_.local_info)); + + /* local contact */ + info->local_contact.ptr = info->buf_.local_contact; + info->local_contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, + call->inv->dlg->local.contact->uri, + info->local_contact.ptr, + sizeof(info->buf_.local_contact)); + + /* remote info */ + info->remote_info.ptr = info->buf_.remote_info; + pj_strncpy(&info->remote_info, &call->inv->dlg->remote.info_str, + sizeof(info->buf_.remote_info)); + + /* remote contact */ + if (call->inv->dlg->remote.contact) { + int len; + info->remote_contact.ptr = info->buf_.remote_contact; + len = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, + call->inv->dlg->remote.contact->uri, + info->remote_contact.ptr, + sizeof(info->buf_.remote_contact)); + if (len < 0) len = 0; + info->remote_contact.slen = len; + } else { + info->remote_contact.slen = 0; + } - pj_assert(call != NULL); + /* call id */ + info->call_id.ptr = info->buf_.call_id; + pj_strncpy(&info->call_id, &call->inv->dlg->call_id->id, + sizeof(info->buf_.call_id)); - if (call) - call_destroy_media(call->index); + /* state, state_text */ + info->state = call->inv->state; + info->state_text = pj_str((char*)pjsip_inv_state_name(info->state)); - /* Remove timers. */ - schedule_call_timer(call, &call->refresh_tm, REFRESH_CALL_TIMER, 0); - schedule_call_timer(call, &call->hangup_tm, HANGUP_CALL_TIMER, 0); + /* If call is disconnected, set the last_status from the cause code */ + if (call->inv->state >= PJSIP_INV_STATE_DISCONNECTED) { + /* last_status, last_status_text */ + info->last_status = call->inv->cause; - /* Free call */ - call->inv = NULL; - --pjsua.call_cnt; + info->last_status_text.ptr = info->buf_.last_status_text; + pj_strncpy(&info->last_status_text, &call->inv->cause_text, + sizeof(info->buf_.last_status_text)); + } else { + /* last_status, last_status_text */ + info->last_status = call->last_code; + + info->last_status_text.ptr = info->buf_.last_status_text; + pj_strncpy(&info->last_status_text, &call->last_text, + sizeof(info->buf_.last_status_text)); + } + + /* media status and dir */ + info->media_status = call->media_st; + info->media_dir = call->media_dir; + + + /* conference slot number */ + info->conf_slot = call->conf_slot; + + /* calculate duration */ + if (info->state >= PJSIP_INV_STATE_DISCONNECTED) { + + info->total_duration = call->dis_time; + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + + if (call->conn_time.sec) { + info->connect_duration = call->dis_time; + PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time); + } + + } else if (info->state == PJSIP_INV_STATE_CONFIRMED) { + + pj_gettimeofday(&info->total_duration); + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + + pj_gettimeofday(&info->connect_duration); + PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time); + + } else { + pj_gettimeofday(&info->total_duration); + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); } + + pjsip_dlg_dec_lock(call->inv->dlg); + PJSUA_UNLOCK(); + + return PJ_SUCCESS; } /* - * Callback called by event framework when the xfer subscription state - * has changed. + * Attach application specific data to the call. */ -static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +PJ_DEF(pj_status_t) pjsua_call_set_user_data( pjsua_call_id call_id, + void *user_data) { - - PJ_UNUSED_ARG(event); + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + pjsua_var.calls[call_id].user_data = user_data; - /* - * We're only interested when subscription is terminated, to - * clear the xfer_sub member of the inv_data. - */ - if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { - pjsua_call *call; + return PJ_SUCCESS; +} - call = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); - if (!call) - return; - pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); - call->xfer_sub = NULL; +/* + * Get user data attached to the call. + */ +PJ_DEF(void*) pjsua_call_get_user_data(pjsua_call_id call_id) +{ + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + NULL); + return pjsua_var.calls[call_id].user_data; +} - PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated")); + +/* + * Send response to incoming INVITE request. + */ +PJ_DEF(pj_status_t) pjsua_call_answer( pjsua_call_id call_id, + unsigned code, + const pj_str_t *reason, + const pjsua_msg_data *msg_data) +{ + pjsua_call *call; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (call->inv == NULL) { + PJ_LOG(3,(THIS_FILE, "Call %d already disconnected", call_id)); + PJSUA_UNLOCK(); + return PJSIP_ESESSIONTERMINATED; + } + + /* Create response message */ + status = pjsip_inv_answer(call->inv, code, reason, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating response", + status); + PJSUA_UNLOCK(); + return status; } + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Send the message */ + status = pjsip_inv_send_msg(call->inv, tdata); + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error sending response", + status); + + PJSUA_UNLOCK(); + + return status; } /* - * Follow transfer (REFER) request. + * Hangup call by using method that is appropriate according to the + * call state. */ -static void on_call_transfered( pjsip_inv_session *inv, - pjsip_rx_data *rdata ) +PJ_DEF(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id, + unsigned code, + const pj_str_t *reason, + const pjsua_msg_data *msg_data) { + pjsua_call *call; pj_status_t status; pjsip_tx_data *tdata; - pjsua_call *existing_call; - int new_call; - const pj_str_t str_refer_to = { "Refer-To", 8}; - pjsip_generic_string_hdr *refer_to; - char *uri; - pj_str_t tmp; - struct pjsip_evsub_user xfer_cb; - pjsip_status_code code; - pjsip_evsub *sub; - existing_call = inv->dlg->mod_data[pjsua.mod.id]; - /* Find the Refer-To header */ - refer_to = (pjsip_generic_string_hdr*) - pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL); + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); - if (refer_to == NULL) { - /* Invalid Request. - * No Refer-To header! - */ - PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!")); - pjsip_dlg_respond( inv->dlg, rdata, 400, NULL, NULL, NULL); - return; + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + PJSUA_UNLOCK(); + return PJ_EINVAL; } - /* Notify callback */ - code = PJSIP_SC_OK; - if (pjsua.cb.on_call_transfered) - (*pjsua.cb.on_call_transfered)(existing_call->index, - &refer_to->hvalue, &code); + if (code==0) { + if (call->inv->state == PJSIP_INV_STATE_CONFIRMED) + code = PJSIP_SC_OK; + else if (call->inv->role == PJSIP_ROLE_UAS) + code = PJSIP_SC_DECLINE; + else + code = PJSIP_SC_REQUEST_TERMINATED; + } - if (code < 200) - code = 200; - if (code >= 300) { - /* Application rejects call transfer request */ - pjsip_dlg_respond( inv->dlg, rdata, code, NULL, NULL, NULL); - return; + status = pjsip_inv_end_session(call->inv, code, reason, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Failed to create end session message", + status); + PJSUA_UNLOCK(); + return status; } - PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s", - (int)inv->dlg->remote.info_str.slen, - inv->dlg->remote.info_str.ptr, - (int)refer_to->hvalue.slen, - refer_to->hvalue.ptr)); + /* pjsip_inv_end_session may return PJ_SUCCESS with NULL + * as p_tdata when INVITE transaction has not been answered + * with any provisional responses. + */ + if (tdata == NULL) { + PJSUA_UNLOCK(); + return PJ_SUCCESS; + } - /* Init callback */ - pj_memset(&xfer_cb, 0, sizeof(xfer_cb)); - xfer_cb.on_evsub_state = &xfer_on_evsub_state; + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); - /* Create transferee event subscription */ - status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub); + /* Send the message */ + status = pjsip_inv_send_msg(call->inv, tdata); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create xfer uas", status); - pjsip_dlg_respond( inv->dlg, rdata, 500, NULL, NULL, NULL); - return; + pjsua_perror(THIS_FILE, + "Failed to send end session message", + status); + PJSUA_UNLOCK(); + return status; } - /* Accept the REFER request, send 200 (OK). */ - pjsip_xfer_accept(sub, rdata, code, NULL); + PJSUA_UNLOCK(); - /* Create initial NOTIFY request */ - status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, - 100, NULL, &tdata); + return PJ_SUCCESS; +} + + +/* + * Put the specified call on hold. + */ +PJ_DEF(pj_status_t) pjsua_call_set_hold(pjsua_call_id call_id, + const pjsua_msg_data *msg_data) +{ + pjmedia_sdp_session *sdp; + pjsua_call *call; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + PJSUA_UNLOCK(); + return PJSIP_ESESSIONTERMINATED; + } + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed")); + PJSUA_UNLOCK(); + return PJSIP_ESESSIONSTATE; + } + + status = create_inactive_sdp(call, &sdp); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status); - return; + PJSUA_UNLOCK(); + return status; } - /* Send initial NOTIFY request */ - status = pjsip_xfer_send_request( sub, tdata); + /* Create re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status); - return; + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + PJSUA_UNLOCK(); + return status; } - /* We're cheating here. - * We need to get a null terminated string from a pj_str_t. - * So grab the pointer from the hvalue and NULL terminate it, knowing - * that the NULL position will be occupied by a newline. - */ - uri = refer_to->hvalue.ptr; - uri[refer_to->hvalue.slen] = '\0'; + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); - /* Now make the outgoing call. */ - tmp = pj_str(uri); - status = pjsua_call_make_call(existing_call->acc_index, &tmp, &new_call); + /* Send the request */ + status = pjsip_inv_send_msg( call->inv, tdata); if (status != PJ_SUCCESS) { - - /* Notify xferer about the error */ - status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, - 500, NULL, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", - status); - return; - } - status = pjsip_xfer_send_request(sub, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", - status); - return; - } - return; + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + PJSUA_UNLOCK(); + return status; } - /* Put the server subscription in inv_data. - * Subsequent state changed in pjsua_inv_on_state_changed() will be - * reported back to the server subscription. - */ - pjsua.calls[new_call].xfer_sub = sub; + PJSUA_UNLOCK(); - /* Put the invite_data in the subscription. */ - pjsip_evsub_set_mod_data(sub, pjsua.mod.id, &pjsua.calls[new_call]); + return PJ_SUCCESS; } /* - * This callback is called when transaction state has changed in INVITE - * session. We use this to trap: - * - incoming REFER request. - * - incoming MESSAGE request. + * Send re-INVITE (to release hold). */ -static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, - pjsip_transaction *tsx, - pjsip_event *e) +PJ_DEF(pj_status_t) pjsua_call_reinvite( pjsua_call_id call_id, + pj_bool_t unhold, + const pjsua_msg_data *msg_data) { - pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id]; + pjmedia_sdp_session *sdp; + pjsip_tx_data *tdata; + pjsua_call *call; + pj_status_t status; - if (tsx->role==PJSIP_ROLE_UAS && - tsx->state==PJSIP_TSX_STATE_TRYING && - pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0) - { - /* - * Incoming REFER request. - */ - on_call_transfered(call->inv, e->body.tsx_state.src.rdata); - } - else if (tsx->role==PJSIP_ROLE_UAS && - tsx->state==PJSIP_TSX_STATE_TRYING && - pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0) - { - /* - * Incoming MESSAGE request! - */ - pjsip_rx_data *rdata; - pjsip_msg *msg; - pjsip_accept_hdr *accept_hdr; - pj_status_t status; + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); - rdata = e->body.tsx_state.src.rdata; - msg = rdata->msg_info.msg; + PJSUA_LOCK(); - /* Request MUST have message body, with Content-Type equal to - * "text/plain". - */ - if (pjsua_im_accept_pager(rdata, &accept_hdr) == PJ_FALSE) { + call = &pjsua_var.calls[call_id]; - pjsip_hdr hdr_list; + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + PJSUA_UNLOCK(); + return PJSIP_ESESSIONTERMINATED; + } - pj_list_init(&hdr_list); - pj_list_push_back(&hdr_list, accept_hdr); - pjsip_dlg_respond( inv->dlg, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, - NULL, &hdr_list, NULL ); - return; - } + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); + PJSUA_UNLOCK(); + return PJSIP_ESESSIONSTATE; + } - /* Respond with 200 first, so that remote doesn't retransmit in case - * the UI takes too long to process the message. - */ - status = pjsip_dlg_respond( inv->dlg, rdata, 200, NULL, NULL, NULL); + /* Create SDP */ + status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, call->inv->pool, + 1, &call->skinfo, &sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", + status); + PJSUA_UNLOCK(); + return status; + } - /* Process MESSAGE request */ - pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str, - &inv->dlg->local.info_str, rdata); + /* Create re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + PJSUA_UNLOCK(); + return status; } -} + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + /* Send the request */ + status = pjsip_inv_send_msg( call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + PJSUA_UNLOCK(); + return status; + } -/* - * This callback is called by invite session framework when UAC session - * has forked. - */ -static void pjsua_call_on_forked( pjsip_inv_session *inv, - pjsip_event *e) -{ - PJ_UNUSED_ARG(inv); - PJ_UNUSED_ARG(e); + PJSUA_UNLOCK(); - PJ_TODO(HANDLE_FORKED_DIALOG); + return PJ_SUCCESS; } /* - * Create inactive SDP for call hold. + * Initiate call transfer to the specified address. */ -static pj_status_t create_inactive_sdp(pjsua_call *call, - pjmedia_sdp_session **p_answer) +PJ_DEF(pj_status_t) pjsua_call_xfer( pjsua_call_id call_id, + const pj_str_t *dest, + const pjsua_msg_data *msg_data) { + pjsip_evsub *sub; + pjsip_tx_data *tdata; + pjsua_call *call; pj_status_t status; - pjmedia_sdp_conn *conn; - pjmedia_sdp_attr *attr; - pjmedia_sdp_session *sdp; - /* Create new offer */ - status = pjmedia_endpt_create_sdp(pjsua.med_endpt, pjsua.pool, 1, - &call->skinfo, &sdp); + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + PJSUA_UNLOCK(); + return PJSIP_ESESSIONTERMINATED; + } + + /* Create xfer client subscription. + * We're not interested in knowing the transfer result, so we + * put NULL as the callback. + */ + status = pjsip_xfer_create_uac(call->inv->dlg, NULL, &sub); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + pjsua_perror(THIS_FILE, "Unable to create xfer", status); + PJSUA_UNLOCK(); return status; } - /* Get SDP media connection line */ - conn = sdp->media[0]->conn; - if (!conn) - conn = sdp->conn; + /* + * Create REFER request. + */ + status = pjsip_xfer_initiate(sub, dest, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create REFER request", status); + PJSUA_UNLOCK(); + return status; + } - /* Modify address */ - conn->addr = pj_str("0.0.0.0"); + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); - /* Remove existing directions attributes */ - pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendrecv"); - pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendonly"); - pjmedia_sdp_media_remove_all_attr(sdp->media[0], "recvonly"); - pjmedia_sdp_media_remove_all_attr(sdp->media[0], "inactive"); + /* Send. */ + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send REFER request", status); + PJSUA_UNLOCK(); + return status; + } - /* Add inactive attribute */ - attr = pjmedia_sdp_attr_create(pjsua.pool, "inactive", NULL); - pjmedia_sdp_media_add_attr(sdp->media[0], attr); + /* For simplicity (that's what this program is intended to be!), + * leave the original invite session as it is. More advanced application + * may want to hold the INVITE, or terminate the invite, or whatever. + */ - *p_answer = sdp; + PJSUA_UNLOCK(); + + return PJ_SUCCESS; - return status; } + /* - * Called when session received new offer. + * Send DTMF digits to remote using RFC 2833 payload formats. */ -static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, - const pjmedia_sdp_session *offer) +PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id, + const pj_str_t *digits) { pjsua_call *call; - pjmedia_sdp_conn *conn; - pjmedia_sdp_session *answer; - pj_bool_t is_remote_active; pj_status_t status; - call = inv->dlg->mod_data[pjsua.mod.id]; + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; - /* - * See if remote is offering active media (i.e. not on-hold) - */ - is_remote_active = PJ_TRUE; + if (!call->session) { + PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } - conn = offer->media[0]->conn; - if (!conn) - conn = offer->conn; + status = pjmedia_session_dial_dtmf( call->session, 0, digits); - if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 || - pj_strcmp2(&conn->addr, "0")==0) - { - is_remote_active = PJ_FALSE; + PJSUA_UNLOCK(); - } - else if (pjmedia_sdp_media_find_attr2(offer->media[0], "inactive", NULL)) - { - is_remote_active = PJ_FALSE; + return status; +} + + +/** + * Send instant messaging inside INVITE session. + */ +PJ_DEF(pj_status_t) pjsua_call_send_im( pjsua_call_id call_id, + const pj_str_t *mime_type, + const pj_str_t *content, + const pjsua_msg_data *msg_data, + void *user_data) +{ + pjsua_call *call; + const pj_str_t mime_text_plain = pj_str("text/plain"); + pjsip_media_type ctype; + pjsua_im_data *im_data; + pjsip_tx_data *tdata; + pj_status_t status; + + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + PJSUA_UNLOCK(); + return PJSIP_ESESSIONTERMINATED; } - PJ_LOG(4,(THIS_FILE, "Received SDP offer, remote media is %s", - (is_remote_active ? "active" : "inactive"))); + /* Lock dialog. */ + pjsip_dlg_inc_lock(call->inv->dlg); - /* Supply candidate answer */ - if (is_remote_active) { - status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1, - &call->skinfo, &answer); - } else { - status = create_inactive_sdp( call, &answer ); + /* Set default media type if none is specified */ + if (mime_type == NULL) { + mime_type = &mime_text_plain; } + /* Create request message. */ + status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method, + -1, &tdata); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create local SDP", status); - return; + pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status); + goto on_return; } - status = pjsip_inv_set_sdp_answer(call->inv, answer); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to set answer", status); - return; + /* Add accept header. */ + pjsip_msg_add_hdr( tdata->msg, + (pjsip_hdr*)pjsua_im_create_accept(tdata->pool)); + + /* Parse MIME type */ + pjsua_parse_media_type(tdata->pool, mime_type, &ctype); + + /* Create "text/plain" message body. */ + tdata->msg->body = pjsip_msg_body_create( tdata->pool, &ctype.type, + &ctype.subtype, content); + if (tdata->msg->body == NULL) { + pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM); + pjsip_tx_data_dec_ref(tdata); + goto on_return; } -} + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); -#if 0 -/* Disconnect call */ -static void call_disconnect(pjsip_inv_session *inv, - int st_code) -{ - pjsip_tx_data *tdata; - pj_status_t status; + /* Create IM data and attach to the request. */ + im_data = pj_pool_zalloc(tdata->pool, sizeof(*im_data)); + im_data->acc_id = call->acc_id; + im_data->call_id = call_id; + im_data->to = call->inv->dlg->remote.info_str; + pj_strdup_with_null(tdata->pool, &im_data->body, content); + im_data->user_data = user_data; - status = pjsip_inv_end_session(inv, st_code, NULL, &tdata); - if (status == PJ_SUCCESS) - status = pjsip_inv_send_msg(inv, tdata); + /* Send the request. */ + status = pjsip_dlg_send_request( call->inv->dlg, tdata, + pjsua_var.mod.id, im_data); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to disconnect call", status); + pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status); + goto on_return; } + +on_return: + pjsip_dlg_dec_lock(call->inv->dlg); + PJSUA_UNLOCK(); + return status; } -#endif + /* - * Callback to be called when SDP offer/answer negotiation has just completed - * in the session. This function will start/update media if negotiation - * has succeeded. + * Send IM typing indication inside INVITE session. */ -static void pjsua_call_on_media_update(pjsip_inv_session *inv, - pj_status_t status) +PJ_DEF(pj_status_t) pjsua_call_send_typing_ind( pjsua_call_id call_id, + pj_bool_t is_typing, + const pjsua_msg_data*msg_data) { pjsua_call *call; - pjmedia_session_info sess_info; - const pjmedia_sdp_session *local_sdp; - const pjmedia_sdp_session *remote_sdp; - pjmedia_port *media_port; - pj_str_t port_name; - char tmp[PJSIP_MAX_URL_SIZE]; + pjsip_tx_data *tdata; + pj_status_t status; - call = inv->dlg->mod_data[pjsua.mod.id]; - if (status != PJ_SUCCESS) { + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); - pjsua_perror(THIS_FILE, "SDP negotiation has failed", status); + PJSUA_LOCK(); - /* Disconnect call if we're not in the middle of initializing an - * UAS dialog and if this is not a re-INVITE - */ - if (inv->state != PJSIP_INV_STATE_NULL && - inv->state != PJSIP_INV_STATE_CONFIRMED) - { - //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); - } - return; + call = &pjsua_var.calls[call_id]; + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + PJSUA_UNLOCK(); + return PJSIP_ESESSIONTERMINATED; } - /* Destroy existing media session, if any. */ - - if (call) - call_destroy_media(call->index); - - /* Get local and remote SDP */ - - status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); + /* Lock dialog. */ + pjsip_dlg_inc_lock(call->inv->dlg); + + /* Create request message. */ + status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method, + -1, &tdata); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Unable to retrieve currently active local SDP", - status); - //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); - return; + pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status); + goto on_return; } + /* Create "application/im-iscomposing+xml" msg body. */ + tdata->msg->body = pjsip_iscomposing_create_body(tdata->pool, is_typing, + NULL, NULL, -1); + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); - status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); + /* Send the request. */ + status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Unable to retrieve currently active remote SDP", - status); - //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); - return; + pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status); + goto on_return; } - if (pjsua.config.null_audio) - return; +on_return: + pjsip_dlg_dec_lock(call->inv->dlg); + PJSUA_UNLOCK(); + return status; +} - /* Create media session info based on SDP parameters. - * We only support one stream per session at the moment - */ - status = pjmedia_session_info_from_sdp( call->inv->dlg->pool, - pjsua.med_endpt, - 1,&sess_info, - local_sdp, remote_sdp); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create media session", - status); - //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); - return; - } - /* Override ptime, if this option is specified. */ - if (pjsua.config.ptime) { - sess_info.stream_info[0].param->setting.frm_per_pkt = (pj_uint8_t) - (pjsua.config.ptime / - sess_info.stream_info[0].param->info.frm_ptime); - if (sess_info.stream_info[0].param->setting.frm_per_pkt==0) - sess_info.stream_info[0].param->setting.frm_per_pkt = 1; - } +/* + * Terminate all calls. + */ +PJ_DEF(void) pjsua_call_hangup_all(void) +{ + unsigned i; - /* Optionally, application may modify other stream settings here - * (such as jitter buffer parameters, codec ptime, etc.) - */ + PJSUA_LOCK(); - /* Create session based on session info. */ - status = pjmedia_session_create( pjsua.med_endpt, &sess_info, - &call->med_tp, - call, &call->session ); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create media session", - status); - //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); - return; + for (i=0; isession, 0, &media_port); + PJSUA_UNLOCK(); +} - /* - * Add the call to conference bridge. - */ - port_name.ptr = tmp; - port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, - call->inv->dlg->remote.info->uri, - tmp, sizeof(tmp)); - if (port_name.slen < 1) { - port_name = pj_str("call"); - } - status = pjmedia_conf_add_port( pjsua.mconf, call->inv->pool, - media_port, - &port_name, - &call->conf_slot); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create conference slot", - status); - call_destroy_media(call->index); - //call_disconnect(inv, PJSIP_SC_INTERNAL_SERVER_ERROR); - return; +static const char *good_number(char *buf, pj_int32_t val) +{ + if (val < 1000) { + pj_ansi_sprintf(buf, "%d", val); + } else if (val < 1000000) { + pj_ansi_sprintf(buf, "%d.%dK", + val / 1000, + (val % 1000) / 100); + } else { + pj_ansi_sprintf(buf, "%d.%02dM", + val / 1000000, + (val % 1000000) / 10000); } - /* If auto-play is configured, connect the call to the file player - * port - */ - if (pjsua.config.auto_play && pjsua.config.wav_file.slen && - call->inv->role == PJSIP_ROLE_UAS) - { + return buf; +} - pjmedia_conf_connect_port( pjsua.mconf, pjsua.player[0].slot, - call->conf_slot, 0); - } - if (pjsua.config.auto_loop && call->inv->role == PJSIP_ROLE_UAS) { +/* Dump media session */ +static void dump_media_session(const char *indent, + char *buf, unsigned maxlen, + pjmedia_session *session) +{ + unsigned i; + char *p = buf, *end = buf+maxlen; + int len; + pjmedia_session_info info; + + pjmedia_session_get_info(session, &info); + + for (i=0; iconf_slot, - call->conf_slot, 0); + + len = pj_ansi_snprintf(buf, end-p, + "%s #%d %.*s @%dKHz, %s, peer=%s:%d", + indent, i, + info.stream_info[i].fmt.encoding_name.slen, + info.stream_info[i].fmt.encoding_name.ptr, + info.stream_info[i].fmt.clock_rate / 1000, + dir, + rem_addr, rem_port); + if (len < 1 || len > end-p) { + *p = '\0'; + return; + } - } - if (pjsua.config.auto_conf) { - unsigned i; + p += len; + *p++ = '\n'; + *p = '\0'; + + if (stat.rx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, stat.rx.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } - pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot, 0); - pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0, 0); + len = pj_ansi_snprintf(p, end-p, + "%s RX pt=%d, stat last update: %s\n" + "%s total %spkt %sB (%sB +IP hdr)\n" + "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n" + "%s (msec) min avg max last\n" + "%s loss period: %7.3f %7.3f %7.3f %7.3f\n" + "%s jitter : %7.3f %7.3f %7.3f %7.3f%s", + indent, info.stream_info[i].fmt.pt, + last_update, + indent, + good_number(packets, stat.rx.pkt), + good_number(bytes, stat.rx.bytes), + good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32), + indent, + stat.rx.loss, + stat.rx.loss * 100.0 / stat.rx.pkt, + stat.rx.dup, + stat.rx.dup * 100.0 / stat.rx.pkt, + stat.rx.reorder, + stat.rx.reorder * 100.0 / stat.rx.pkt, + indent, indent, + stat.rx.loss_period.min / 1000.0, + stat.rx.loss_period.avg / 1000.0, + stat.rx.loss_period.max / 1000.0, + stat.rx.loss_period.last / 1000.0, + indent, + stat.rx.jitter.min / 1000.0, + stat.rx.jitter.avg / 1000.0, + stat.rx.jitter.max / 1000.0, + stat.rx.jitter.last / 1000.0, + "" + ); + + if (len < 1 || len > end-p) { + *p = '\0'; + return; + } - for (i=0; i < pjsua.config.max_calls; ++i) { + p += len; + *p++ = '\n'; + *p = '\0'; + + if (stat.tx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, stat.tx.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } - if (!pjsua.calls[i].session) - continue; + len = pj_ansi_snprintf(p, end-p, + "%s TX pt=%d, ptime=%dms, stat last update: %s\n" + "%s total %spkt %sB (%sB +IP hdr)\n" + "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n" + "%s (msec) min avg max last\n" + "%s loss period: %7.3f %7.3f %7.3f %7.3f\n" + "%s jitter : %7.3f %7.3f %7.3f %7.3f%s", + indent, + info.stream_info[i].tx_pt, + info.stream_info[i].param->info.frm_ptime * + info.stream_info[i].param->setting.frm_per_pkt, + last_update, + + indent, + good_number(packets, stat.tx.pkt), + good_number(bytes, stat.tx.bytes), + good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32), + + indent, + stat.tx.loss, + stat.tx.loss * 100.0 / stat.tx.pkt, + stat.tx.dup, + stat.tx.dup * 100.0 / stat.tx.pkt, + stat.tx.reorder, + stat.tx.reorder * 100.0 / stat.tx.pkt, + + indent, indent, + stat.tx.loss_period.min / 1000.0, + stat.tx.loss_period.avg / 1000.0, + stat.tx.loss_period.max / 1000.0, + stat.tx.loss_period.last / 1000.0, + indent, + stat.tx.jitter.min / 1000.0, + stat.tx.jitter.avg / 1000.0, + stat.tx.jitter.max / 1000.0, + stat.tx.jitter.last / 1000.0, + "" + ); + + if (len < 1 || len > end-p) { + *p = '\0'; + return; + } - pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, - pjsua.calls[i].conf_slot, 0); - pjmedia_conf_connect_port( pjsua.mconf, pjsua.calls[i].conf_slot, - call->conf_slot, 0); + p += len; + *p++ = '\n'; + *p = '\0'; + + len = pj_ansi_snprintf(p, end-p, + "%s RTT msec : %7.3f %7.3f %7.3f %7.3f", + indent, + stat.rtt.min / 1000.0, + stat.rtt.avg / 1000.0, + stat.rtt.max / 1000.0, + stat.rtt.last / 1000.0 + ); + if (len < 1 || len > end-p) { + *p = '\0'; + return; } - } - - /* Normal operation: if no auto_xx is given, connect new call to - * the sound device port (port zero) in the main conference bridge. - */ - if (pjsua.config.auto_play == 0 && pjsua.config.auto_loop == 0 && - pjsua.config.auto_conf == 0) - { - pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot, 0); - pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0, 0); + p += len; + *p++ = '\n'; + *p = '\0'; } +} - /* Done. */ - { - struct pjmedia_session_info sess_info; - char info[80]; - int info_len = 0; - unsigned i; +/* Print call info */ +static void print_call(const char *title, + int call_id, + char *buf, pj_size_t size) +{ + int len; + pjsip_inv_session *inv = pjsua_var.calls[call_id].inv; + pjsip_dialog *dlg = inv->dlg; + char userinfo[128]; - pjmedia_session_get_info(call->session, &sess_info); - for (i=0; idir) { - case PJMEDIA_DIR_NONE: - dir = "inactive"; - break; - case PJMEDIA_DIR_ENCODING: - dir = "sendonly"; - break; - case PJMEDIA_DIR_DECODING: - dir = "recvonly"; - break; - case PJMEDIA_DIR_ENCODING_DECODING: - dir = "sendrecv"; - break; - default: - dir = "unknown"; - break; - } - len = pj_ansi_sprintf( info+info_len, - ", stream #%d: %.*s (%s)", i, - (int)strm_info->fmt.encoding_name.slen, - strm_info->fmt.encoding_name.ptr, - dir); - if (len > 0) - info_len += len; - } - PJ_LOG(3,(THIS_FILE,"Media started%s", info)); - } + len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); + if (len < 1) + pj_ansi_strcpy(userinfo, "<--uri too long-->"); + else + userinfo[len] = '\0'; + + len = pj_ansi_snprintf(buf, size, "%s[%s] %s", + title, + pjsip_inv_state_name(inv->state), + userinfo); + if (len < 1 || len >= (int)size) { + pj_ansi_strcpy(buf, "<--uri too long-->"); + len = 18; + } else + buf[len] = '\0'; } /* - * Hangup call. + * Dump call and media statistics to string. */ -PJ_DEF(void) pjsua_call_hangup(int call_index) +PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id, + pj_bool_t with_media, + char *buffer, + unsigned maxlen, + const char *indent) { pjsua_call *call; - int code; - pj_status_t status; - pjsip_tx_data *tdata; - - - call = &pjsua.calls[call_index]; + pj_time_val duration, res_delay, con_delay; + char tmp[128]; + char *p, *end; + int len; - if (!call->inv) { - PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); - return; - } + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); - if (call->inv->state == PJSIP_INV_STATE_CONFIRMED) - code = PJSIP_SC_OK; - else if (call->inv->role == PJSIP_ROLE_UAS) - code = PJSIP_SC_DECLINE; - else - code = PJSIP_SC_REQUEST_TERMINATED; + PJSUA_LOCK(); - status = pjsip_inv_end_session(call->inv, code, NULL, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Failed to create end session message", - status); - return; - } + call = &pjsua_var.calls[call_id]; - /* pjsip_inv_end_session may return PJ_SUCCESS with NULL - * as p_tdata when INVITE transaction has not been answered - * with any provisional responses. - */ - if (tdata == NULL) - return; + *buffer = '\0'; + p = buffer; + end = buffer + maxlen; + len = 0; - status = pjsip_inv_send_msg(call->inv, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Failed to send end session message", - status); - return; + if (call->inv == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; } -} - -/* - * Put call on-Hold. - */ -PJ_DEF(pj_status_t) pjsua_call_set_hold(int call_index) -{ - pjmedia_sdp_session *sdp; - pjsua_call *call; - pjsip_tx_data *tdata; - pj_status_t status; - - call = &pjsua.calls[call_index]; + print_call(indent, call_id, tmp, sizeof(tmp)); - if (!call->inv) { - PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); - return PJSIP_ESESSIONTERMINATED; + len = pj_ansi_strlen(tmp); + pj_ansi_strcpy(buffer, tmp); + + p += len; + *p++ = '\r'; + *p++ = '\n'; + + /* Calculate call duration */ + if (call->inv->state >= PJSIP_INV_STATE_CONFIRMED) { + pj_gettimeofday(&duration); + PJ_TIME_VAL_SUB(duration, call->conn_time); + con_delay = call->conn_time; + PJ_TIME_VAL_SUB(con_delay, call->start_time); + } else { + duration.sec = duration.msec = 0; + con_delay.sec = con_delay.msec = 0; } - if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { - PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed")); - return PJSIP_ESESSIONSTATE; + /* Calculate first response delay */ + if (call->inv->state >= PJSIP_INV_STATE_EARLY) { + res_delay = call->res_time; + PJ_TIME_VAL_SUB(res_delay, call->start_time); + } else { + res_delay.sec = res_delay.msec = 0; + } + + /* Print duration */ + len = pj_ansi_snprintf(p, end-p, + "%s Call time: %02dh:%02dm:%02ds, " + "1st res in %d ms, conn in %dms", + indent, + (duration.sec / 3600), + ((duration.sec % 3600)/60), + (duration.sec % 60), + PJ_TIME_VAL_MSEC(res_delay), + PJ_TIME_VAL_MSEC(con_delay)); + + if (len > 0 && len < end-p) { + p += len; + *p++ = '\n'; + *p = '\0'; } - status = create_inactive_sdp(call, &sdp); - if (status != PJ_SUCCESS) - return status; - - /* Send re-INVITE with new offer */ - status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); - return status; - } + /* Dump session statistics */ + if (with_media && call->session) + dump_media_session(indent, p, end-p, call->session); - status = pjsip_inv_send_msg( call->inv, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); - return status; - } + PJSUA_UNLOCK(); return PJ_SUCCESS; } /* - * re-INVITE. + * Destroy the call's media */ -PJ_DEF(pj_status_t) pjsua_call_reinvite(int call_index) +static pj_status_t call_destroy_media(int call_id) { - pjmedia_sdp_session *sdp; - pjsip_tx_data *tdata; - pjsua_call *call; - pj_status_t status; - - call = &pjsua.calls[call_index]; + pjsua_call *call = &pjsua_var.calls[call_id]; - if (!call->inv) { - PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); - return PJSIP_ESESSIONTERMINATED; + if (call->conf_slot != PJSUA_INVALID_ID) { + pjmedia_conf_remove_port(pjsua_var.mconf, call->conf_slot); + call->conf_slot = PJSUA_INVALID_ID; } + if (call->session) { + /* Destroy session (this will also close RTP/RTCP sockets). */ + pjmedia_session_destroy(call->session); + call->session = NULL; - if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { - PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); - return PJSIP_ESESSIONSTATE; - } - - /* Create SDP */ - status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1, - &call->skinfo, &sdp); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", - status); - return status; - } + PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed", + call_id)); - /* Send re-INVITE with new offer */ - status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); - return status; } - status = pjsip_inv_send_msg( call->inv, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); - return status; - } + call->media_st = PJSUA_CALL_MEDIA_NONE; return PJ_SUCCESS; } /* - * Transfer call. + * This callback receives notification from invite session when the + * session state has changed. */ -PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest) +static void pjsua_call_on_state_changed(pjsip_inv_session *inv, + pjsip_event *e) { - pjsip_evsub *sub; - pjsip_tx_data *tdata; pjsua_call *call; - pj_status_t status; - - call = &pjsua.calls[call_index]; + PJSUA_LOCK(); - if (!call->inv) { - PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); - return PJSIP_ESESSIONTERMINATED; + call = inv->dlg->mod_data[pjsua_var.mod.id]; + + if (!call) { + PJSUA_UNLOCK(); + return; } - - /* Create xfer client subscription. - * We're not interested in knowing the transfer result, so we - * put NULL as the callback. + + + /* Get call times */ + switch (inv->state) { + case PJSIP_INV_STATE_EARLY: + case PJSIP_INV_STATE_CONNECTING: + if (call->res_time.sec == 0) + pj_gettimeofday(&call->res_time); + call->last_code = e->body.tsx_state.tsx->status_code; + pj_strncpy(&call->last_text, + &e->body.tsx_state.tsx->status_text, + sizeof(call->last_text_buf_)); + break; + case PJSIP_INV_STATE_CONFIRMED: + pj_gettimeofday(&call->conn_time); + break; + case PJSIP_INV_STATE_DISCONNECTED: + pj_gettimeofday(&call->dis_time); + if (e->body.tsx_state.tsx->status_code > call->last_code) { + call->last_code = e->body.tsx_state.tsx->status_code; + pj_strncpy(&call->last_text, + &e->body.tsx_state.tsx->status_text, + sizeof(call->last_text_buf_)); + } + break; + default: + call->last_code = e->body.tsx_state.tsx->status_code; + pj_strncpy(&call->last_text, + &e->body.tsx_state.tsx->status_text, + sizeof(call->last_text_buf_)); + break; + } + + /* If this is an outgoing INVITE that was created because of + * REFER/transfer, send NOTIFY to transferer. */ - status = pjsip_xfer_create_uac(call->inv->dlg, NULL, &sub); + if (call->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) { + int st_code = -1; + pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE; + + + switch (call->inv->state) { + case PJSIP_INV_STATE_NULL: + case PJSIP_INV_STATE_CALLING: + /* Do nothing */ + break; + + case PJSIP_INV_STATE_EARLY: + case PJSIP_INV_STATE_CONNECTING: + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_ACTIVE; + break; + + case PJSIP_INV_STATE_CONFIRMED: + /* When state is confirmed, send the final 200/OK and terminate + * subscription. + */ + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + break; + + case PJSIP_INV_STATE_DISCONNECTED: + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + break; + + case PJSIP_INV_STATE_INCOMING: + /* Nothing to do. Just to keep gcc from complaining about + * unused enums. + */ + break; + } + + if (st_code != -1) { + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_xfer_notify( call->xfer_sub, + ev_state, st_code, + NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status); + } else { + status = pjsip_xfer_send_request(call->xfer_sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status); + } + } + } + } + + + if (pjsua_var.ua_cfg.cb.on_call_state) + (*pjsua_var.ua_cfg.cb.on_call_state)(call->index, e); + + /* call->inv may be NULL now */ + + /* Destroy media session when invite session is disconnected. */ + if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + + pj_assert(call != NULL); + + if (call) + call_destroy_media(call->index); + + /* Free call */ + call->inv = NULL; + --pjsua_var.call_cnt; + } + + PJSUA_UNLOCK(); +} + +/* + * This callback is called by invite session framework when UAC session + * has forked. + */ +static void pjsua_call_on_forked( pjsip_inv_session *inv, + pjsip_event *e) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(e); + + PJ_TODO(HANDLE_FORKED_DIALOG); +} + + +/* + * Callback to be called when SDP offer/answer negotiation has just completed + * in the session. This function will start/update media if negotiation + * has succeeded. + */ +static void pjsua_call_on_media_update(pjsip_inv_session *inv, + pj_status_t status) +{ + int prev_media_st = 0; + pjsua_call *call; + pjmedia_session_info sess_info; + const pjmedia_sdp_session *local_sdp; + const pjmedia_sdp_session *remote_sdp; + pjmedia_port *media_port; + pj_str_t port_name; + char tmp[PJSIP_MAX_URL_SIZE]; + + PJSUA_LOCK(); + + call = inv->dlg->mod_data[pjsua_var.mod.id]; + if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create xfer", status); - return status; + + pjsua_perror(THIS_FILE, "SDP negotiation has failed", status); + + /* Disconnect call if we're not in the middle of initializing an + * UAS dialog and if this is not a re-INVITE + */ + if (inv->state != PJSIP_INV_STATE_NULL && + inv->state != PJSIP_INV_STATE_CONFIRMED) + { + //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + } + + PJSUA_UNLOCK(); + return; } - /* - * Create REFER request. - */ - status = pjsip_xfer_initiate(sub, dest, &tdata); + /* Destroy existing media session, if any. */ + + if (call) { + prev_media_st = call->media_st; + call_destroy_media(call->index); + } + + /* Get local and remote SDP */ + + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create REFER request", status); - return status; + pjsua_perror(THIS_FILE, + "Unable to retrieve currently active local SDP", + status); + //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + PJSUA_UNLOCK(); + return; } - /* Send. */ - status = pjsip_xfer_send_request(sub, tdata); + + status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send REFER request", status); + pjsua_perror(THIS_FILE, + "Unable to retrieve currently active remote SDP", + status); + //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + PJSUA_UNLOCK(); + return; + } + + /* Create media session info based on SDP parameters. + * We only support one stream per session at the moment + */ + status = pjmedia_session_info_from_sdp( call->inv->dlg->pool, + pjsua_var.med_endpt, + 1,&sess_info, + local_sdp, remote_sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create media session", + status); + //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + PJSUA_UNLOCK(); + return; + } + + /* Check if media is put on-hold */ + if (sess_info.stream_cnt == 0 || + sess_info.stream_info[0].dir == PJMEDIA_DIR_NONE) + { + + /* Determine who puts the call on-hold */ + if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) { + if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) { + /* It was local who offer hold */ + call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD; + } else { + call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD; + } + } + + call->media_dir = PJMEDIA_DIR_NONE; + + } else { + + /* Override ptime, if this option is specified. */ + PJ_TODO(set_codec_ptime_in_call); + + + /* Optionally, application may modify other stream settings here + * (such as jitter buffer parameters, codec ptime, etc.) + */ + + /* Create session based on session info. */ + status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info, + &call->med_tp, + call, &call->session ); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create media session", + status); + //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + PJSUA_UNLOCK(); + return; + } + + + /* Get the port interface of the first stream in the session. + * We need the port interface to add to the conference bridge. + */ + pjmedia_session_get_port(call->session, 0, &media_port); + + + /* + * Add the call to conference bridge. + */ + port_name.ptr = tmp; + port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + call->inv->dlg->remote.info->uri, + tmp, sizeof(tmp)); + if (port_name.slen < 1) { + port_name = pj_str("call"); + } + status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool, + media_port, + &port_name, + (unsigned*)&call->conf_slot); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create conference slot", + status); + call_destroy_media(call->index); + //call_disconnect(inv, PJSIP_SC_INTERNAL_SERVER_ERROR); + PJSUA_UNLOCK(); + return; + } + + /* Call's media state is active */ + call->media_st = PJSUA_CALL_MEDIA_ACTIVE; + call->media_dir = sess_info.stream_info[0].dir; + } + + /* Print info. */ + { + char info[80]; + int info_len = 0; + unsigned i; + + for (i=0; idir) { + case PJMEDIA_DIR_NONE: + dir = "inactive"; + break; + case PJMEDIA_DIR_ENCODING: + dir = "sendonly"; + break; + case PJMEDIA_DIR_DECODING: + dir = "recvonly"; + break; + case PJMEDIA_DIR_ENCODING_DECODING: + dir = "sendrecv"; + break; + default: + dir = "unknown"; + break; + } + len = pj_ansi_sprintf( info+info_len, + ", stream #%d: %.*s (%s)", i, + (int)strm_info->fmt.encoding_name.slen, + strm_info->fmt.encoding_name.ptr, + dir); + if (len > 0) + info_len += len; + } + PJ_LOG(4,(THIS_FILE,"Media updates%s", info)); + } + + /* Call application callback, if any */ + if (pjsua_var.ua_cfg.cb.on_call_media_state) + pjsua_var.ua_cfg.cb.on_call_media_state(call->index); + + + PJSUA_UNLOCK(); +} + + +/* + * Create inactive SDP for call hold. + */ +static pj_status_t create_inactive_sdp(pjsua_call *call, + pjmedia_sdp_session **p_answer) +{ + pj_status_t status; + pjmedia_sdp_conn *conn; + pjmedia_sdp_attr *attr; + pjmedia_sdp_session *sdp; + + /* Create new offer */ + status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pjsua_var.pool, 1, + &call->skinfo, &sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); return status; } - /* For simplicity (that's what this program is intended to be!), - * leave the original invite session as it is. More advanced application - * may want to hold the INVITE, or terminate the invite, or whatever. + /* Get SDP media connection line */ + conn = sdp->media[0]->conn; + if (!conn) + conn = sdp->conn; + + /* Modify address */ + conn->addr = pj_str("0.0.0.0"); + + /* Remove existing directions attributes */ + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendrecv"); + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendonly"); + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "recvonly"); + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "inactive"); + + /* Add inactive attribute */ + attr = pjmedia_sdp_attr_create(pjsua_var.pool, "inactive", NULL); + pjmedia_sdp_media_add_attr(sdp->media[0], attr); + + *p_answer = sdp; + + return status; +} + + +/* + * Called when session received new offer. + */ +static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer) +{ + const char *remote_state; + pjsua_call *call; + pjmedia_sdp_conn *conn; + pjmedia_sdp_session *answer; + pj_bool_t is_remote_active; + pj_status_t status; + + PJSUA_LOCK(); + + call = inv->dlg->mod_data[pjsua_var.mod.id]; + + /* + * See if remote is offering active media (i.e. not on-hold) */ + is_remote_active = PJ_TRUE; - return PJ_SUCCESS; + conn = offer->media[0]->conn; + if (!conn) + conn = offer->conn; + + if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 || + pj_strcmp2(&conn->addr, "0")==0) + { + is_remote_active = PJ_FALSE; + + } + else if (pjmedia_sdp_media_find_attr2(offer->media[0], "inactive", NULL)) + { + is_remote_active = PJ_FALSE; + } + + remote_state = (is_remote_active ? "active" : "inactive"); + + /* Supply candidate answer */ + if (call->media_st == PJSUA_CALL_MEDIA_LOCAL_HOLD || !is_remote_active) { + PJ_LOG(4,(THIS_FILE, + "Call %d: RX new media offer, creating inactive SDP " + "(media in offer is %s)", call->index, remote_state)); + status = create_inactive_sdp( call, &answer ); + } else { + PJ_LOG(4,(THIS_FILE, "Call %d: received updated media offer", + call->index)); + status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, + call->inv->pool, 1, + &call->skinfo, &answer); + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + PJSUA_UNLOCK(); + return; + } + + status = pjsip_inv_set_sdp_answer(call->inv, answer); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to set answer", status); + PJSUA_UNLOCK(); + return; + } + + PJSUA_UNLOCK(); } -/** - * Dial DTMF. +/* + * Callback called by event framework when the xfer subscription state + * has changed. */ -PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( unsigned call_index, - const pj_str_t *digits) +static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) { - pjsua_call *call = &pjsua.calls[call_index]; + + PJ_UNUSED_ARG(event); + + /* + * We're only interested when subscription is terminated, to + * clear the xfer_sub member of the inv_data. + */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsua_call *call; - PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, PJ_EINVAL); + call = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!call) + return; - if (!call->session) { - PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); - return -1; - } + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + call->xfer_sub = NULL; - return pjmedia_session_dial_dtmf( call->session, 0, digits); + PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated")); + } } -/** - * Send instant messaging inside INVITE session. +/* + * Follow transfer (REFER) request. */ -PJ_DEF(pj_status_t) pjsua_call_send_im(int call_index, const pj_str_t *str) +static void on_call_transfered( pjsip_inv_session *inv, + pjsip_rx_data *rdata ) { - pjsua_call *call; - const pj_str_t mime_text = pj_str("text"); - const pj_str_t mime_plain = pj_str("plain"); - pjsip_tx_data *tdata; pj_status_t status; + pjsip_tx_data *tdata; + pjsua_call *existing_call; + int new_call; + const pj_str_t str_refer_to = { "Refer-To", 8}; + pjsip_generic_string_hdr *refer_to; + char *uri; + pj_str_t tmp; + struct pjsip_evsub_user xfer_cb; + pjsip_status_code code; + pjsip_evsub *sub; - call = &pjsua.calls[call_index]; + existing_call = inv->dlg->mod_data[pjsua_var.mod.id]; - if (!call->inv) { - PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); - return PJSIP_ESESSIONTERMINATED; - } + /* Find the Refer-To header */ + refer_to = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL); - /* Lock dialog. */ - pjsip_dlg_inc_lock(call->inv->dlg); - - /* Create request message. */ - status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method, - -1, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status); - goto on_return; + if (refer_to == NULL) { + /* Invalid Request. + * No Refer-To header! + */ + PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!")); + pjsip_dlg_respond( inv->dlg, rdata, 400, NULL, NULL, NULL); + return; } - /* Add accept header. */ - pjsip_msg_add_hdr( tdata->msg, - (pjsip_hdr*)pjsua_im_create_accept(tdata->pool)); - - /* Create "text/plain" message body. */ - tdata->msg->body = pjsip_msg_body_create( tdata->pool, &mime_text, - &mime_plain, str); - if (tdata->msg->body == NULL) { - pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM); - pjsip_tx_data_dec_ref(tdata); - goto on_return; - } + /* Notify callback */ + code = PJSIP_SC_OK; + if (pjsua_var.ua_cfg.cb.on_call_transfered) + (*pjsua_var.ua_cfg.cb.on_call_transfered)(existing_call->index, + &refer_to->hvalue, &code); - /* Send the request. */ - status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status); - goto on_return; + if (code < 200) + code = 200; + if (code >= 300) { + /* Application rejects call transfer request */ + pjsip_dlg_respond( inv->dlg, rdata, code, NULL, NULL, NULL); + return; } -on_return: - pjsip_dlg_dec_lock(call->inv->dlg); - return status; -} + PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s", + (int)inv->dlg->remote.info_str.slen, + inv->dlg->remote.info_str.ptr, + (int)refer_to->hvalue.slen, + refer_to->hvalue.ptr)); + /* Init callback */ + pj_memset(&xfer_cb, 0, sizeof(xfer_cb)); + xfer_cb.on_evsub_state = &xfer_on_evsub_state; -/** - * Send IM typing indication inside INVITE session. - */ -PJ_DEF(pj_status_t) pjsua_call_send_typing_ind(int call_index, - pj_bool_t is_typing) -{ - pjsua_call *call; - pjsip_tx_data *tdata; - pj_status_t status; + /* Create transferee event subscription */ + status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create xfer uas", status); + pjsip_dlg_respond( inv->dlg, rdata, 500, NULL, NULL, NULL); + return; + } - call = &pjsua.calls[call_index]; + /* Accept the REFER request, send 200 (OK). */ + pjsip_xfer_accept(sub, rdata, code, NULL); - if (!call->inv) { - PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); - return PJSIP_ESESSIONTERMINATED; + /* Create initial NOTIFY request */ + status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, + 100, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status); + return; } - /* Lock dialog. */ - pjsip_dlg_inc_lock(call->inv->dlg); - - /* Create request message. */ - status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method, - -1, &tdata); + /* Send initial NOTIFY request */ + status = pjsip_xfer_send_request( sub, tdata); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status); - goto on_return; + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status); + return; } - /* Create "application/im-iscomposing+xml" msg body. */ - tdata->msg->body = pjsip_iscomposing_create_body(tdata->pool, is_typing, - NULL, NULL, -1); + /* We're cheating here. + * We need to get a null terminated string from a pj_str_t. + * So grab the pointer from the hvalue and NULL terminate it, knowing + * that the NULL position will be occupied by a newline. + */ + uri = refer_to->hvalue.ptr; + uri[refer_to->hvalue.slen] = '\0'; - /* Send the request. */ - status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL); + /* Now make the outgoing call. */ + tmp = pj_str(uri); + status = pjsua_call_make_call(existing_call->acc_id, &tmp, 0, + existing_call->user_data, NULL, + &new_call); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status); - goto on_return; + + /* Notify xferer about the error */ + status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + 500, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", + status); + return; + } + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", + status); + return; + } + return; } -on_return: - pjsip_dlg_dec_lock(call->inv->dlg); - return status; + /* Put the server subscription in inv_data. + * Subsequent state changed in pjsua_inv_on_state_changed() will be + * reported back to the server subscription. + */ + pjsua_var.calls[new_call].xfer_sub = sub; + + /* Put the invite_data in the subscription. */ + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, + &pjsua_var.calls[new_call]); } + /* - * Terminate all calls. + * This callback is called when transaction state has changed in INVITE + * session. We use this to trap: + * - incoming REFER request. + * - incoming MESSAGE request. */ -PJ_DEF(void) pjsua_call_hangup_all(void) +static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_event *e) { - unsigned i; + pjsua_call *call = inv->dlg->mod_data[pjsua_var.mod.id]; - for (i=0; irole==PJSIP_ROLE_UAS && + tsx->state==PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0) + { + /* + * Incoming REFER request. + */ + on_call_transfered(call->inv, e->body.tsx_state.src.rdata); - call = &pjsua.calls[i]; + } + else if (tsx->role==PJSIP_ROLE_UAS && + tsx->state==PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0) + { + /* + * Incoming MESSAGE request! + */ + pjsip_rx_data *rdata; + pjsip_msg *msg; + pjsip_accept_hdr *accept_hdr; + pj_status_t status; - if (call->inv->state == PJSIP_INV_STATE_CONFIRMED) { - st_code = 200; - } else { - st_code = PJSIP_SC_GONE; - } + rdata = e->body.tsx_state.src.rdata; + msg = rdata->msg_info.msg; - if (pjsip_inv_end_session(call->inv, st_code, NULL, &tdata)==0) { - if (tdata) - pjsip_inv_send_msg(call->inv, tdata); - } - } -} + /* Request MUST have message body, with Content-Type equal to + * "text/plain". + */ + if (pjsua_im_accept_pager(rdata, &accept_hdr) == PJ_FALSE) { + pjsip_hdr hdr_list; -pj_status_t pjsua_call_init(void) -{ - /* Initialize invite session callback. */ - pjsip_inv_callback inv_cb; - pj_status_t status; + pj_list_init(&hdr_list); + pj_list_push_back(&hdr_list, accept_hdr); - pj_memset(&inv_cb, 0, sizeof(inv_cb)); - inv_cb.on_state_changed = &pjsua_call_on_state_changed; - inv_cb.on_new_session = &pjsua_call_on_forked; - inv_cb.on_media_update = &pjsua_call_on_media_update; - inv_cb.on_rx_offer = &pjsua_call_on_rx_offer; - inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed; + pjsip_dlg_respond( inv->dlg, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, + NULL, &hdr_list, NULL ); + PJSUA_UNLOCK(); + return; + } + /* Respond with 200 first, so that remote doesn't retransmit in case + * the UI takes too long to process the message. + */ + status = pjsip_dlg_respond( inv->dlg, rdata, 200, NULL, NULL, NULL); - /* Initialize invite session module: */ - status = pjsip_inv_usage_init(pjsua.endpt, &inv_cb); - - return status; -} + /* Process MESSAGE request */ + pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str, + &inv->dlg->local.info_str, rdata); -/** - * Replace media transport. - */ -PJ_DEF(pj_status_t) pjsua_set_call_media_transport( unsigned call_index, - const pjmedia_sock_info *i, - pjmedia_transport *tp) -{ - pjsua_call *call = &pjsua.calls[call_index]; + } + else if (tsx->role == PJSIP_ROLE_UAC && + pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0) + { + /* Handle outgoing pager status */ + if (tsx->status_code >= 200) { + pjsua_im_data *im_data; + + im_data = tsx->mod_data[pjsua_var.mod.id]; + /* im_data can be NULL if this is typing indication */ + + if (im_data && pjsua_var.ua_cfg.cb.on_pager_status) { + pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id, + &im_data->to, + &im_data->body, + im_data->user_data, + tsx->status_code, + &tsx->status_text); + } + } + } - if (i) - pj_memcpy(&call->skinfo, i, sizeof(pjmedia_sock_info)); - - if (call->med_tp) - (*call->med_tp->op->destroy)(call->med_tp); - call->med_tp = tp; - return PJ_SUCCESS; + PJSUA_UNLOCK(); } diff --git a/pjsip/src/pjsua-lib/pjsua_console_app.c b/pjsip/src/pjsua-lib/pjsua_console_app.c deleted file mode 100644 index a54dd689..00000000 --- a/pjsip/src/pjsua-lib/pjsua_console_app.c +++ /dev/null @@ -1,926 +0,0 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include -#include /* atoi */ -#include - -#define THIS_FILE "main.c" - -/* Current dialog */ -static int current_acc; -static int current_call = -1; - - -/* - * Find next call. - */ -static pj_bool_t find_next_call(void) -{ - int i, max; - - max = pjsua_call_get_max_count(); - for (i=current_call+1; i=0; --i) { - if (pjsua_call_is_active(i)) { - current_call = i; - return PJ_TRUE; - } - } - - for (i=max-1; i>current_call; --i) { - if (pjsua_call_is_active(i)) { - current_call = i; - return PJ_TRUE; - } - } - - current_call = -1; - return PJ_FALSE; -} - - - -/* - * Notify UI when invite state has changed. - */ -static void console_on_call_state(int call_index, pjsip_event *e) -{ - pjsua_call_info call_info; - - PJ_UNUSED_ARG(e); - - pjsua_call_get_info(call_index, &call_info); - - if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) { - - PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", - call_index, - call_info.last_status, - call_info.last_status_text.ptr)); - - if ((int)call_index == current_call) { - find_next_call(); - } - - } else { - - PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", - call_index, - call_info.state_text.ptr)); - - if (current_call==-1) - current_call = call_index; - - } -} - -/** - * Notify UI when registration status has changed. - */ -static void console_on_reg_state(int acc_index) -{ - PJ_UNUSED_ARG(acc_index); - - // Log already written. -} - - -/** - * Notify UI on buddy state changed. - */ -static void console_on_buddy_state(int buddy_index) -{ - pjsua_buddy_info info; - pjsua_buddy_get_info(buddy_index, &info); - - PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s", - (int)info.uri.slen, - info.uri.ptr, - (int)info.status_text.slen, - info.status_text.ptr)); -} - - -/** - * Incoming IM message (i.e. MESSAGE request)! - */ -static void console_on_pager(int call_index, const pj_str_t *from, - const pj_str_t *to, const pj_str_t *text) -{ - /* Note: call index may be -1 */ - PJ_UNUSED_ARG(call_index); - PJ_UNUSED_ARG(to); - - PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s", - (int)from->slen, from->ptr, - (int)text->slen, text->ptr)); -} - - -/** - * Typing indication - */ -static void console_on_typing(int call_index, const pj_str_t *from, - const pj_str_t *to, pj_bool_t is_typing) -{ - PJ_UNUSED_ARG(call_index); - PJ_UNUSED_ARG(to); - - PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s", - (int)from->slen, from->ptr, - (is_typing?"is typing..":"has stopped typing"))); -} - - -/* - * Print buddy list. - */ -static void print_buddy_list(void) -{ - int i, count; - - puts("Buddy list:"); - - count = pjsua_get_buddy_count(); - - if (count == 0) - puts(" -none-"); - else { - for (i=0; i %.*s\n", - i+1, info.status_text.ptr, - (int)info.uri.slen, - info.uri.ptr); - } - } - puts(""); -} - - -/* - * Print account status. - */ -static void print_acc_status(int acc_index) -{ - char buf[80]; - pjsua_acc_info info; - - pjsua_acc_get_info(acc_index, &info); - - if (!info.has_registration) { - pj_ansi_strcpy(buf, " -not registered to server-"); - - } else { - pj_ansi_snprintf(buf, sizeof(buf), - "%.*s (%.*s;expires=%d)", - (int)info.status_text.slen, - info.status_text.ptr, - (int)info.acc_id.slen, - info.acc_id.ptr, - info.expires); - - } - - printf("[%2d] Registration status: %s\n", acc_index, buf); - printf(" Online status: %s\n", - (info.online_status ? "Online" : "Invisible")); -} - -/* - * Show a bit of help. - */ -static void keystroke_help(void) -{ - int i; - - printf(">>>>\n"); - - for (i=0; i<(int)pjsua_get_acc_count(); ++i) - print_acc_status(i); - - print_buddy_list(); - - //puts("Commands:"); - puts("+=============================================================================+"); - puts("| Call Commands: | IM & Presence: | Misc: |"); - puts("| | | |"); - puts("| m Make new call | i Send IM | o Send OPTIONS |"); - puts("| M Make multiple calls | s Subscribe presence | rr (Re-)register |"); - puts("| a Answer call | u Unsubscribe presence | ru Unregister |"); - puts("| h Hangup call (ha=all) | t ToGgle Online status | |"); - puts("| H Hold call | | |"); - puts("| v re-inVite (release hold) +--------------------------+-------------------+"); - puts("| ] Select next dialog | Conference Command | |"); - puts("| [ Select previous dialog | cl List ports | d Dump status |"); - puts("| x Xfer call | cc Connect port | dd Dump detailed |"); - puts("| # Send DTMF string | cd Disconnect port | dc Dump config |"); - puts("+------------------------------+--------------------------+-------------------+"); - puts("| q QUIT |"); - puts("+=============================================================================+"); -} - - -/* - * Input simple string - */ -static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len) -{ - char *p; - - printf("%s (empty to cancel): ", title); fflush(stdout); - fgets(buf, len, stdin); - - /* Remove trailing newlines. */ - for (p=buf; ; ++p) { - if (*p=='\r' || *p=='\n') *p='\0'; - else if (!*p) break; - } - - if (!*buf) - return PJ_FALSE; - - return PJ_TRUE; -} - - -#define NO_NB -2 -struct input_result -{ - int nb_result; - char *uri_result; -}; - - -/* - * Input URL. - */ -static void ui_input_url(const char *title, char *buf, int len, - struct input_result *result) -{ - result->nb_result = NO_NB; - result->uri_result = NULL; - - print_buddy_list(); - - printf("Choices:\n" - " 0 For current dialog.\n" - " -1 All %d buddies in buddy list\n" - " [1 -%2d] Select from buddy list\n" - " URL An URL\n" - " Empty input (or 'q') to cancel\n" - , pjsua_get_buddy_count(), pjsua_get_buddy_count()); - printf("%s: ", title); - - fflush(stdout); - fgets(buf, len, stdin); - len = strlen(buf); - - /* Left trim */ - while (pj_isspace(*buf)) { - ++buf; - --len; - } - - /* Remove trailing newlines */ - while (len && (buf[len-1] == '\r' || buf[len-1] == '\n')) - buf[--len] = '\0'; - - if (len == 0 || buf[0]=='q') - return; - - if (pj_isdigit(*buf) || *buf=='-') { - - int i; - - if (*buf=='-') - i = 1; - else - i = 0; - - for (; inb_result = atoi(buf); - - if (result->nb_result >= 0 && - result->nb_result <= (int)pjsua_get_buddy_count()) - { - return; - } - if (result->nb_result == -1) - return; - - puts("Invalid input"); - result->nb_result = NO_NB; - return; - - } else { - pj_status_t status; - - if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Invalid URL", status); - return; - } - - result->uri_result = buf; - } -} - -static void conf_list(void) -{ - unsigned i, count; - pjsua_conf_port_id id[PJSUA_MAX_CALLS]; - - printf("Conference ports:\n"); - - count = PJ_ARRAY_SIZE(id); - pjsua_conf_enum_port_ids(id, &count); - - for (i=0; islen) { - pjsua_call_make_call( current_acc, uri_to_call, NULL); - } - - keystroke_help(); - - for (;;) { - - printf(">>> "); - fflush(stdout); - - fgets(menuin, sizeof(menuin), stdin); - - switch (menuin[0]) { - - case 'm': - /* Make call! : */ - printf("(You currently have %d calls)\n", - pjsua_call_get_count()); - - uri = NULL; - ui_input_url("Make call", buf, sizeof(buf), &result); - if (result.nb_result != NO_NB) { - - if (result.nb_result == -1 || result.nb_result == 0) { - puts("You can't do that with make call!"); - continue; - } else { - pjsua_buddy_info binfo; - pjsua_buddy_get_info(result.nb_result-1, &binfo); - uri = binfo.uri.ptr; - } - - } else if (result.uri_result) { - uri = result.uri_result; - } - - tmp = pj_str(uri); - pjsua_call_make_call( current_acc, &tmp, NULL); - break; - - case 'M': - /* Make multiple calls! : */ - printf("(You currently have %d calls)\n", - pjsua_call_get_count()); - - if (!simple_input("Number of calls", menuin, sizeof(menuin))) - continue; - - count = atoi(menuin); - if (count < 1) - continue; - - ui_input_url("Make call", buf, sizeof(buf), &result); - if (result.nb_result != NO_NB) { - pjsua_buddy_info binfo; - if (result.nb_result == -1 || result.nb_result == 0) { - puts("You can't do that with make call!"); - continue; - } - pjsua_buddy_get_info(result.nb_result-1, &binfo); - uri = binfo.uri.ptr; - } else { - uri = result.uri_result; - } - - for (i=0; i= PJSIP_INV_STATE_CONNECTING) - { - puts("No pending incoming call"); - fflush(stdout); - continue; - - } else { - if (!simple_input("Answer with code (100-699)", buf, sizeof(buf))) - continue; - - if (atoi(buf) < 100) - continue; - - /* - * Must check again! - * Call may have been disconnected while we're waiting for - * keyboard input. - */ - if (current_call == -1) { - puts("Call has been disconnected"); - fflush(stdout); - continue; - } - - pjsua_call_answer(current_call, atoi(buf)); - } - - break; - - - case 'h': - - if (current_call == -1) { - puts("No current call"); - fflush(stdout); - continue; - - } else if (menuin[1] == 'a') { - - /* Hangup all calls */ - pjsua_call_hangup_all(); - - } else { - - /* Hangup current calls */ - pjsua_call_hangup(current_call); - } - break; - - case ']': - case '[': - /* - * Cycle next/prev dialog. - */ - if (menuin[0] == ']') { - find_next_call(); - - } else { - find_prev_call(); - } - - if (current_call != -1) { - - pjsua_call_get_info(current_call, &call_info); - PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s", - (int)call_info.remote_info.slen, - call_info.remote_info.ptr)); - - } else { - PJ_LOG(3,(THIS_FILE,"No current dialog")); - } - break; - - case 'H': - /* - * Hold call. - */ - if (current_call != -1) { - - pjsua_call_set_hold(current_call); - - } else { - PJ_LOG(3,(THIS_FILE, "No current call")); - } - break; - - case 'v': - /* - * Send re-INVITE (to release hold, etc). - */ - if (current_call != -1) { - - pjsua_call_reinvite(current_call); - - } else { - PJ_LOG(3,(THIS_FILE, "No current call")); - } - break; - - case 'x': - /* - * Transfer call. - */ - if (current_call == -1) { - - PJ_LOG(3,(THIS_FILE, "No current call")); - - } else { - int call = current_call; - - ui_input_url("Transfer to URL", buf, sizeof(buf), &result); - - /* Check if call is still there. */ - - if (call != current_call) { - puts("Call has been disconnected"); - continue; - } - - if (result.nb_result != NO_NB) { - if (result.nb_result == -1 || result.nb_result == 0) - puts("You can't do that with transfer call!"); - else { - pjsua_buddy_info binfo; - pjsua_buddy_get_info(result.nb_result-1, &binfo); - pjsua_call_xfer( current_call, - &binfo.uri); - } - - } else if (result.uri_result) { - pj_str_t tmp; - tmp = pj_str(result.uri_result); - pjsua_call_xfer( current_call, &tmp); - } - } - break; - - case '#': - /* - * Send DTMF strings. - */ - if (current_call == -1) { - - PJ_LOG(3,(THIS_FILE, "No current call")); - - } else if (!pjsua_call_has_media(current_call)) { - - PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); - - } else { - pj_str_t digits; - int call = current_call; - pj_status_t status; - - if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, - sizeof(buf))) - { - break; - } - - if (call != current_call) { - puts("Call has been disconnected"); - continue; - } - - digits = pj_str(buf); - status = pjsua_call_dial_dtmf(current_call, &digits); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send DTMF", status); - } else { - puts("DTMF digits enqueued for transmission"); - } - } - break; - - case 's': - case 'u': - /* - * Subscribe/unsubscribe presence. - */ - ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result); - if (result.nb_result != NO_NB) { - if (result.nb_result == -1) { - int i, count; - count = pjsua_get_buddy_count(); - for (i=0; i -#include "pjsua_imp.h" +#include -/* - * pjsua_core.c - * - * Core application functionalities. - */ #define THIS_FILE "pjsua_core.c" -/* - * Global variable. - */ -struct pjsua pjsua; +/* PJSUA application instance. */ +struct pjsua_data pjsua_var; -/* - * Default local URI, if none is specified in cmd-line - */ -#define PJSUA_LOCAL_URI "" +/* Display error */ +PJ_DEF(void) pjsua_perror( const char *sender, const char *title, + pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(3,(sender, "%s: %s [status=%d]", title, errmsg, status)); +} -/* - * Init default application parameters. - */ -PJ_DEF(void) pjsua_default_config(pjsua_config *cfg) +static void init_data() { unsigned i; - pj_memset(cfg, 0, sizeof(pjsua_config)); - - cfg->thread_cnt = 1; - cfg->media_has_ioqueue = 1; - cfg->media_thread_cnt = 1; - cfg->udp_port = 5060; - cfg->start_rtp_port = 4000; - cfg->msg_logging = PJ_TRUE; - cfg->max_calls = 4; - cfg->conf_ports = 0; - -#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 - pjsua.clock_rate = 44100; -#endif - - cfg->complexity = 10; - cfg->quality = 10; + for (i=0; iauto_answer = 100; - cfg->uas_duration = 3600; - - /* Default logging settings: */ - cfg->log_level = 5; - cfg->app_log_level = 4; - cfg->log_decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | - PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE; - - - /* Also init logging settings in pjsua.config, because log - * may be written before pjsua_init() is called. - */ - pjsua.config.log_level = 5; - pjsua.config.app_log_level = 4; - - - /* Init accounts: */ - for (i=0; iacc_config[i].reg_timeout = 55; - } - + for (i=0; iudp_port == 0) { - if (cfg->sip_host.slen==0 || cfg->sip_port==0) { - strncpy_with_null(errmsg, - "sip_host and sip_port must be specified", - len); - return -1; - } - } - - if (cfg->max_calls < 1) { - strncpy_with_null(errmsg, - "max_calls needs to be at least 1", - len); - return -1; - } - - /* STUN */ - if (cfg->stun_srv1.slen || cfg->stun_port1 || cfg->stun_port2 || - cfg->stun_srv2.slen) - { - if (cfg->stun_port1 == 0) { - strncpy_with_null(errmsg, "stun_port1 required", len); - return -1; - } - if (cfg->stun_srv1.slen == 0) { - strncpy_with_null(errmsg, "stun_srv1 required", len); - return -1; - } - if (cfg->stun_port2 == 0) { - strncpy_with_null(errmsg, "stun_port2 required", len); - return -1; - } - if (cfg->stun_srv2.slen == 0) { - strncpy_with_null(errmsg, "stun_srv2 required", len); - return -1; - } - } - - /* Verify accounts */ - for (i=0; iacc_cnt; ++i) { - const pjsua_acc_config *acc_cfg = &cfg->acc_config[i]; - unsigned j; - - if (acc_cfg->id.slen == 0) { - strncpy_with_null(errmsg, "missing account ID", len); - return -1; - } - - if (acc_cfg->id.slen == 0) { - strncpy_with_null(errmsg, "missing registrar URI", len); - return -1; - } - - if (acc_cfg->reg_timeout == 0) { - strncpy_with_null(errmsg, "missing registration timeout", len); - return -1; - } - - - for (j=0; jcred_count; ++j) { - - if (acc_cfg->cred_info[j].scheme.slen == 0) { - strncpy_with_null(errmsg, "missing auth scheme in account", - len); - return -1; - } - - if (acc_cfg->cred_info[j].realm.slen == 0) { - strncpy_with_null(errmsg, "missing realm in account", len); - return -1; - } + PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n" + "%s\n" + "--end msg--", + rdata->msg_info.len, + pjsip_rx_data_get_info(rdata), + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + rdata->msg_info.msg_buf)); + + /* Always return false, otherwise messages will not get processed! */ + return PJ_FALSE; +} - if (acc_cfg->cred_info[j].username.slen == 0) { - strncpy_with_null(errmsg, "missing username in account", len); - return -1; - } +/* Notification on outgoing messages */ +static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata) +{ + + /* Important note: + * tp_info field is only valid after outgoing messages has passed + * transport layer. So don't try to access tp_info when the module + * has lower priority than transport layer. + */ - } - } + PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n" + "%s\n" + "--end msg--", + (tdata->buf.cur - tdata->buf.start), + pjsip_tx_data_get_info(tdata), + tdata->tp_info.dst_name, + tdata->tp_info.dst_port, + tdata->buf.start)); + /* Always return success, otherwise message will not get sent! */ return PJ_SUCCESS; } +/* The module instance. */ +static pjsip_module pjsua_msg_logger = +{ + NULL, NULL, /* prev, next. */ + { "mod-pjsua-log", 13 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &logging_on_rx_msg, /* on_rx_request() */ + &logging_on_rx_msg, /* on_rx_response() */ + &logging_on_tx_msg, /* on_tx_request. */ + &logging_on_tx_msg, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + +/***************************************************************************** + * These two functions are the main callbacks registered to PJSIP stack + * to receive SIP request and response messages that are outside any + * dialogs and any transactions. + */ /* * Handler for receiving incoming requests. @@ -201,13 +141,18 @@ PJ_DEF(pj_status_t) pjsua_test_config( const pjsua_config *cfg, */ static pj_bool_t mod_pjsua_on_rx_request(pjsip_rx_data *rdata) { + pj_bool_t processed = PJ_FALSE; + + PJSUA_LOCK(); if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) { - return pjsua_call_on_incoming(rdata); + processed = pjsua_call_on_incoming(rdata); } - return PJ_FALSE; + PJSUA_UNLOCK(); + + return processed; } @@ -229,799 +174,279 @@ static pj_bool_t mod_pjsua_on_rx_response(pjsip_rx_data *rdata) } -static int PJ_THREAD_FUNC pjsua_poll(void *arg) -{ - pj_status_t last_err = 0; - - PJ_UNUSED_ARG(arg); - - do { - pj_time_val timeout = { 0, 10 }; - pj_status_t status; - - status = pjsip_endpt_handle_events (pjsua.endpt, &timeout); - if (status != PJ_SUCCESS && status != last_err) { - last_err = status; - pjsua_perror(THIS_FILE, "handle_events() returned error", status); - } - } while (!pjsua.quit_flag); - - return 0; -} - -/** - * Poll pjsua. +/***************************************************************************** + * Logging. */ -PJ_DECL(int) pjsua_handle_events(unsigned msec_timeout) + +/* Log callback */ +static void log_writer(int level, const char *buffer, int len) { - unsigned count = 0; - pj_time_val tv; - pj_status_t status; + /* Write to stdout, file, and application callback. */ - tv.sec = 0; - tv.msec = msec_timeout; - pj_time_val_normalize(&tv); + if (level <= (int)pjsua_var.log_cfg.console_level) + pj_log_write(level, buffer, len); - status = pjsip_endpt_handle_events2(pjsua.endpt, &tv, &count); - if (status != PJ_SUCCESS) - return -status; + if (pjsua_var.log_file) { + pj_ssize_t size = len; + pj_file_write(pjsua_var.log_file, buffer, &size); + } - return count; + if (pjsua_var.log_cfg.cb) + (*pjsua_var.log_cfg.cb)(level, buffer, len); } -#define pjsua_has_stun() (pjsua.config.stun_port1 && \ - pjsua.config.stun_port2) - - /* - * Create and initialize SIP socket (and possibly resolve public - * address via STUN, depending on config). + * Application can call this function at any time (after pjsua_create(), of + * course) to change logging settings. */ -static pj_status_t create_sip_udp_sock(int port, - pj_sock_t *p_sock, - pj_sockaddr_in *p_pub_addr) +PJ_DEF(pj_status_t) pjsua_reconfigure_logging(const pjsua_logging_config *cfg) { - pj_sock_t sock; pj_status_t status; - status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "socket() error", status); - return status; - } + /* Save config. */ + pjsua_logging_config_dup(pjsua_var.pool, &pjsua_var.log_cfg, cfg); - status = pj_sock_bind_in(sock, 0, (pj_uint16_t)port); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "bind() error", status); - pj_sock_close(sock); - return status; - } + /* Redirect log function to ours */ + pj_log_set_log_func( &log_writer ); - if (pjsua_has_stun()) { - status = pj_stun_get_mapped_addr(&pjsua.cp.factory, 1, &sock, - &pjsua.config.stun_srv1, - pjsua.config.stun_port1, - &pjsua.config.stun_srv2, - pjsua.config.stun_port2, - p_pub_addr); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "STUN resolve error", status); - pj_sock_close(sock); - return status; - } + /* Close existing file, if any */ + if (pjsua_var.log_file) { + pj_file_close(pjsua_var.log_file); + pjsua_var.log_file = NULL; + } - } else { + /* If output log file is desired, create the file: */ + if (pjsua_var.log_cfg.log_filename.slen) { - const pj_str_t *hostname = pj_gethostname(); - struct pj_hostent he; + status = pj_file_open(pjsua_var.pool, + pjsua_var.log_cfg.log_filename.ptr, + PJ_O_WRONLY, + &pjsua_var.log_file); - status = pj_gethostbyname(hostname, &he); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to resolve local host", status); - pj_sock_close(sock); + pjsua_perror(THIS_FILE, "Error creating log file", status); return status; } + } - pj_memset(p_pub_addr, 0, sizeof(pj_sockaddr_in)); - p_pub_addr->sin_family = PJ_AF_INET; - p_pub_addr->sin_port = pj_htons((pj_uint16_t)port); - p_pub_addr->sin_addr = *(pj_in_addr*)he.h_addr; + /* Unregister msg logging if it's previously registered */ + if (pjsua_msg_logger.id >= 0) { + pjsip_endpt_unregister_module(pjsua_var.endpt, &pjsua_msg_logger); + pjsua_msg_logger.id = -1; } - *p_sock = sock; + /* Enable SIP message logging */ + if (pjsua_var.log_cfg.msg_logging) + pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_msg_logger); + + return PJ_SUCCESS; } -/* - * Create RTP and RTCP socket pair, and possibly resolve their public - * address via STUN. +/***************************************************************************** + * PJSUA Base API. */ -static pj_status_t create_rtp_rtcp_sock(pjmedia_sock_info *skinfo) -{ - enum { - RTP_RETRY = 100 - }; - int i; - static pj_uint16_t rtp_port; - pj_sockaddr_in mapped_addr[2]; - pj_status_t status = PJ_SUCCESS; - pj_sock_t sock[2]; - - if (rtp_port == 0) - rtp_port = (pj_uint16_t)pjsua.config.start_rtp_port; - - for (i=0; i<2; ++i) - sock[i] = PJ_INVALID_SOCKET; - - - /* Loop retry to bind RTP and RTCP sockets. */ - for (i=0; irtp_sock = sock[0]; - pj_memcpy(&skinfo->rtp_addr_name, - &mapped_addr[0], sizeof(pj_sockaddr_in)); - - skinfo->rtcp_sock = sock[1]; - pj_memcpy(&skinfo->rtcp_addr_name, - &mapped_addr[1], sizeof(pj_sockaddr_in)); - - PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d", - pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr), - pj_ntohs(skinfo->rtp_addr_name.sin_port))); - PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d", - pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr), - pj_ntohs(skinfo->rtcp_addr_name.sin_port))); - - rtp_port += 2; - return PJ_SUCCESS; - -on_error: - for (i=0; i<2; ++i) { - if (sock[i] != PJ_INVALID_SOCKET) - pj_sock_close(sock[i]); - } - return status; + return 0; } - -/** - * Create pjsua application. - * This initializes pjlib/pjlib-util, and creates memory pool factory to - * be used by application. +/* + * Instantiate pjsua application. */ PJ_DEF(pj_status_t) pjsua_create(void) { pj_status_t status; - /* Init PJLIB: */ + /* Init pjsua data */ + init_data(); + + /* Set default logging settings */ + pjsua_logging_config_default(&pjsua_var.log_cfg); + /* Init PJLIB: */ status = pj_init(); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "pj_init() error", status); - return status; - } + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - /* Init PJLIB-UTIL: */ + /* Init PJLIB-UTIL: */ status = pjlib_util_init(); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "pjlib_util_init() error", status); - return status; - } + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - /* Init memory pool: */ /* Init caching pool. */ - pj_caching_pool_init(&pjsua.cp, &pj_pool_factory_default_policy, 0); + pj_caching_pool_init(&pjsua_var.cp, &pj_pool_factory_default_policy, 0); /* Create memory pool for application. */ - pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL); - - /* Must create endpoint to initialize SIP parser. */ - /* Create global endpoint: */ + pjsua_var.pool = pjsua_pool_create("pjsua", 4000, 4000); + + PJ_ASSERT_RETURN(pjsua_var.pool, PJ_ENOMEM); - status = pjsip_endpt_create(&pjsua.cp.factory, - pj_gethostname()->ptr, - &pjsua.endpt); + /* Create mutex */ + status = pj_mutex_create_recursive(pjsua_var.pool, "pjsua", + &pjsua_var.mutex); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create SIP endpoint", status); + pjsua_perror(THIS_FILE, "Unable to create mutex", status); return status; } - /* Must create media endpoint too */ - status = pjmedia_endpt_create(&pjsua.cp.factory, - pjsua.config.media_has_ioqueue? NULL : - pjsip_endpt_get_ioqueue(pjsua.endpt), - pjsua.config.media_thread_cnt, - &pjsua.med_endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Media stack initialization has returned error", - status); - return status; - } + /* Must create SIP endpoint to initialize SIP parser. The parser + * is needed for example when application needs to call pjsua_verify_url(). + */ + status = pjsip_endpt_create(&pjsua_var.cp.factory, + pj_gethostname()->ptr, + &pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); return PJ_SUCCESS; } - /* - * Init media. + * Initialize pjsua with the specified settings. All the settings are + * optional, and the default values will be used when the config is not + * specified. */ -static pj_status_t init_media(void) +PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, + const pjsua_logging_config *log_cfg, + const pjsua_media_config *media_cfg) { - int i; - unsigned options; - pj_str_t codec_id; + pjsua_config default_cfg; + pjsua_media_config default_media_cfg; pj_status_t status; - /* Register all codecs */ -#if PJMEDIA_HAS_SPEEX_CODEC - /* Register speex. */ - status = pjmedia_codec_speex_init(pjsua.med_endpt, - PJMEDIA_SPEEX_NO_UWB, - pjsua.config.quality, - pjsua.config.complexity ); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing Speex codec", - status); - return status; - } - - /* Set "speex/16000/1" to have highest priority */ - codec_id = pj_str("speex/16000/1"); - pjmedia_codec_mgr_set_codec_priority( - pjmedia_endpt_get_codec_mgr(pjsua.med_endpt), - &codec_id, - PJMEDIA_CODEC_PRIO_HIGHEST); -#endif /* PJMEDIA_HAS_SPEEX_CODEC */ + /* Create default configurations when the config is not supplied */ -#if PJMEDIA_HAS_GSM_CODEC - /* Register GSM */ - status = pjmedia_codec_gsm_init(pjsua.med_endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing GSM codec", - status); - return status; + if (ua_cfg == NULL) { + pjsua_config_default(&default_cfg); + ua_cfg = &default_cfg; } -#endif /* PJMEDIA_HAS_GSM_CODEC */ -#if PJMEDIA_HAS_G711_CODEC - /* Register PCMA and PCMU */ - status = pjmedia_codec_g711_init(pjsua.med_endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing G711 codec", - status); - return status; + if (media_cfg == NULL) { + pjsua_media_config_default(&default_media_cfg); + media_cfg = &default_media_cfg; } -#endif /* PJMEDIA_HAS_G711_CODEC */ -#if PJMEDIA_HAS_L16_CODEC - /* Register L16 family codecs, but disable all */ - status = pjmedia_codec_l16_init(pjsua.med_endpt, 0); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing L16 codecs", - status); - return status; + /* Initialize logging first so that info/errors can be captured */ + if (log_cfg) { + status = pjsua_reconfigure_logging(log_cfg); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); } - /* Disable ALL L16 codecs */ - codec_id = pj_str("L16"); - pjmedia_codec_mgr_set_codec_priority( - pjmedia_endpt_get_codec_mgr(pjsua.med_endpt), - &codec_id, - PJMEDIA_CODEC_PRIO_DISABLED); + /* Init SIP UA: */ -#endif /* PJMEDIA_HAS_L16_CODEC */ + /* Initialize transaction layer: */ + status = pjsip_tsx_layer_init_module(pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - /* Enable those codecs that user put with "--add-codec", and move - * the priority to top - */ - for (i=0; i<(int)pjsua.config.codec_cnt; ++i) { - pjmedia_codec_mgr_set_codec_priority( - pjmedia_endpt_get_codec_mgr(pjsua.med_endpt), - &pjsua.config.codec_arg[i], - PJMEDIA_CODEC_PRIO_HIGHEST); - } + /* Initialize UA layer module: */ + status = pjsip_ua_init_module( pjsua_var.endpt, NULL ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - /* Init options for conference bridge. */ - options = PJMEDIA_CONF_NO_DEVICE; + /* Initialize and register PJSUA application module. */ + { + const pjsip_module mod_initializer = + { + NULL, NULL, /* prev, next. */ + { "mod-pjsua", 9 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &mod_pjsua_on_rx_request, /* on_rx_request() */ + &mod_pjsua_on_rx_response, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + }; - /* Calculate maximum number of ports, if it's not specified */ - if (pjsua.config.conf_ports == 0) { - pjsua.config.conf_ports = 3 * pjsua.config.max_calls; - } + pjsua_var.mod = mod_initializer; - /* Init conference bridge. */ - pjsua.clock_rate = pjsua.config.clock_rate ? pjsua.config.clock_rate : 16000; - pjsua.samples_per_frame = pjsua.clock_rate * 10 / 1000; - status = pjmedia_conf_create(pjsua.pool, - pjsua.config.conf_ports, - pjsua.clock_rate, - 1, /* mono */ - pjsua.samples_per_frame, - 16, - options, - &pjsua.mconf); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Media stack initialization has returned error", - status); - return status; + status = pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_var.mod); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); } - if (pjsua.config.null_audio == PJ_FALSE) { - pjmedia_port *conf_port; + - /* Create sound device port */ - status = pjmedia_snd_port_create(pjsua.pool, - pjsua.config.snd_capture_id, - pjsua.config.snd_player_id, - pjsua.clock_rate, 1 /* mono */, - pjsua.samples_per_frame, 16, - 0, &pjsua.snd_port); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create sound device", status); - return status; - } + /* Initialize PJSUA call subsystem: */ + status = pjsua_call_subsys_init(ua_cfg); + if (status != PJ_SUCCESS) + goto on_error; - /* Get the port interface of the conference bridge */ - conf_port = pjmedia_conf_get_master_port(pjsua.mconf); - /* Connect conference port interface to sound port */ - pjmedia_snd_port_connect( pjsua.snd_port, conf_port); + /* Initialize PJSUA media subsystem */ + status = pjsua_media_subsys_init(media_cfg); + if (status != PJ_SUCCESS) + goto on_error; - } else { - pjmedia_port *null_port, *conf_port; - /* Create NULL port */ - status = pjmedia_null_port_create(pjsua.pool, pjsua.clock_rate, - 1, pjsua.samples_per_frame, 16, - &null_port); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create NULL port", status); - return status; - } + /* Init core SIMPLE module : */ + status = pjsip_evsub_init_module(pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - /* Get the port interface of the conference bridge */ - conf_port = pjmedia_conf_get_master_port(pjsua.mconf); - - /* Create master port to control conference bridge's clock */ - status = pjmedia_master_port_create(pjsua.pool, null_port, conf_port, - 0, &pjsua.master_port); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create master port", status); - return status; - } - } - - /* Create WAV file player if required: */ - - if (pjsua.config.wav_file.slen) { - - status = pjsua_player_create(&pjsua.config.wav_file, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create file player", - status); - return status; - } - } - - - return PJ_SUCCESS; -} - - -/* - * Copy account configuration. - */ -static void copy_acc_config(pj_pool_t *pool, - pjsua_acc_config *dst_acc, - const pjsua_acc_config *src_acc) -{ - unsigned j; - - pj_memcpy(dst_acc, src_acc, sizeof(pjsua_acc_config)); - - pj_strdup_with_null(pool, &dst_acc->id, &src_acc->id); - pj_strdup_with_null(pool, &dst_acc->reg_uri, &src_acc->reg_uri); - pj_strdup_with_null(pool, &dst_acc->contact, &src_acc->contact); - pj_strdup_with_null(pool, &dst_acc->proxy, &src_acc->proxy); - - for (j=0; jcred_count; ++j) { - pj_strdup_with_null(pool, &dst_acc->cred_info[j].realm, - &src_acc->cred_info[j].realm); - pj_strdup_with_null(pool, &dst_acc->cred_info[j].scheme, - &src_acc->cred_info[j].scheme); - pj_strdup_with_null(pool, &dst_acc->cred_info[j].username, - &src_acc->cred_info[j].username); - pj_strdup_with_null(pool, &dst_acc->cred_info[j].data, - &src_acc->cred_info[j].data); - } -} - - -/* - * Copy configuration. - */ -void pjsua_copy_config( pj_pool_t *pool, pjsua_config *dst, - const pjsua_config *src) -{ - unsigned i; - - /* Plain memcpy */ - pj_memcpy(dst, src, sizeof(pjsua_config)); - - /* Duplicate strings */ - pj_strdup_with_null(pool, &dst->sip_host, &src->sip_host); - pj_strdup_with_null(pool, &dst->stun_srv1, &src->stun_srv1); - pj_strdup_with_null(pool, &dst->stun_srv2, &src->stun_srv2); - pj_strdup_with_null(pool, &dst->wav_file, &src->wav_file); - - for (i=0; icodec_cnt; ++i) { - pj_strdup_with_null(pool, &dst->codec_arg[i], &src->codec_arg[i]); - } - - pj_strdup_with_null(pool, &dst->outbound_proxy, &src->outbound_proxy); - //pj_strdup_with_null(pool, &dst->uri_to_call, &src->uri_to_call); - - for (i=0; iacc_cnt; ++i) { - pjsua_acc_config *dst_acc = &dst->acc_config[i]; - const pjsua_acc_config *src_acc = &src->acc_config[i]; - copy_acc_config(pool, dst_acc, src_acc); - } - - pj_strdup_with_null(pool, &dst->log_filename, &src->log_filename); - - for (i=0; ibuddy_cnt; ++i) { - pj_strdup_with_null(pool, &dst->buddy_uri[i], &src->buddy_uri[i]); - } -} - - -/***************************************************************************** - * Console application custom logging: - */ - - -static void log_writer(int level, const char *buffer, int len) -{ - /* Write to both stdout and file. */ - - if (level <= (int)pjsua.config.app_log_level) - pj_log_write(level, buffer, len); - - if (pjsua.log_file) { - fwrite(buffer, len, 1, pjsua.log_file); - fflush(pjsua.log_file); - } -} - - -static pj_status_t logging_init() -{ - /* Redirect log function to ours */ - - pj_log_set_log_func( &log_writer ); - - /* If output log file is desired, create the file: */ - - if (pjsua.config.log_filename.slen) { - pjsua.log_file = fopen(pjsua.config.log_filename.ptr, "wt"); - if (pjsua.log_file == NULL) { - PJ_LOG(1,(THIS_FILE, "Unable to open log file %s", - pjsua.config.log_filename.ptr)); - return -1; - } - } - - /* Enable SIP message logging */ - if (pjsua.config.msg_logging) - pjsip_endpt_register_module(pjsua.endpt, &pjsua_msg_logger); - - return PJ_SUCCESS; -} - - -static void logging_shutdown(void) -{ - /* Close logging file, if any: */ - - if (pjsua.log_file) { - fclose(pjsua.log_file); - pjsua.log_file = NULL; - } -} - - -/* - * Initialize pjsua application. - * This will initialize all libraries, create endpoint instance, and register - * pjsip modules. - */ -PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, - const pjsua_callback *cb) -{ - char errmsg[80]; - unsigned i; - pj_status_t status; - - - /* Init accounts: */ - for (i=0; isip_host.slen == 0 || cfg->sip_port == 0) { - PJ_LOG(1,(THIS_FILE, - "Error: sip_host and sip_port must be specified")); - status = PJ_EINVAL; - goto on_error; - } - - pjsua.sip_sock = PJ_INVALID_SOCKET; - } - - - /* Init media endpoint */ - status = init_media(); + /* Init out-of-dialog MESSAGE request handler. */ + status = pjsua_im_init(); if (status != PJ_SUCCESS) goto on_error; + /* Start worker thread if needed. */ + if (pjsua_var.ua_cfg.thread_cnt) { + unsigned i; - /* Init RTP sockets, only when UDP transport is enabled */ - for (i=0; pjsua.config.start_rtp_port && iop->destroy(pjsua.calls[i].med_tp); - } - goto on_error; - } - status = pjmedia_transport_udp_attach(pjsua.med_endpt, NULL, - &pjsua.calls[i].skinfo, 0, - &pjsua.calls[i].med_tp); - } - - /* Init PJSIP : */ - - /* Initialize transaction layer: */ - - status = pjsip_tsx_layer_init_module(pjsua.endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Transaction layer initialization error", - status); - goto on_error; - } - - /* Initialize UA layer module: */ - - status = pjsip_ua_init_module( pjsua.endpt, NULL ); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "UA layer initialization error", status); - goto on_error; - } - - /* Initialize and register pjsua's application module: */ + if (pjsua_var.ua_cfg.thread_cnt > PJ_ARRAY_SIZE(pjsua_var.thread)) + pjsua_var.ua_cfg.thread_cnt = PJ_ARRAY_SIZE(pjsua_var.thread); - { - pjsip_module my_mod = - { - NULL, NULL, /* prev, next. */ - { "mod-pjsua", 9 }, /* Name. */ - -1, /* Id */ - PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ - NULL, /* load() */ - NULL, /* start() */ - NULL, /* stop() */ - NULL, /* unload() */ - &mod_pjsua_on_rx_request, /* on_rx_request() */ - &mod_pjsua_on_rx_response, /* on_rx_response() */ - NULL, /* on_tx_request. */ - NULL, /* on_tx_response() */ - NULL, /* on_tsx_state() */ - }; - - pjsua.mod = my_mod; - - status = pjsip_endpt_register_module(pjsua.endpt, &pjsua.mod); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to register pjsua module", - status); - goto on_error; + for (i=0; imsg_info.to->uri; - - /* Just return default account if To URI is not SIP: */ - if (!PJSIP_URI_SCHEME_IS_SIP(uri) && - !PJSIP_URI_SCHEME_IS_SIPS(uri)) - { - return pjsua.default_acc; - } - - - sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); - - /* Find account which has matching username and domain. */ - for (acc_index=0; acc_index < pjsua.config.acc_cnt; ++acc_index) { - - pjsua_acc *acc = &pjsua.acc[acc_index]; - - if (pj_stricmp(&acc->user_part, &sip_uri->user)==0 && - pj_stricmp(&acc->host_part, &sip_uri->host)==0) - { - /* Match ! */ - return acc_index; - } - } - - /* No matching, try match domain part only. */ - for (acc_index=0; acc_index < pjsua.config.acc_cnt; ++acc_index) { - - pjsua_acc *acc = &pjsua.acc[acc_index]; + pj_time_val timeout, now; - if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) { - /* Match ! */ - return acc_index; - } - } + pj_gettimeofday(&timeout); + timeout.msec += msec; + pj_time_val_normalize(&timeout); - /* Still no match, use default account */ - return pjsua.default_acc; + do { + while (pjsua_handle_events(10) > 0) + ; + pj_gettimeofday(&now); + } while (PJ_TIME_VAL_LT(now, timeout)); } - /* - * Find account for outgoing request. + * Destroy pjsua. */ -PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *str_url) +PJ_DEF(pj_status_t) pjsua_destroy(void) { - pj_str_t tmp; - pjsip_uri *uri; - pjsip_sip_uri *sip_uri; - unsigned i; - - pj_strdup_with_null(pjsua.pool, &tmp, str_url); + int i; /* Must be signed */ - uri = pjsip_parse_uri(pjsua.pool, tmp.ptr, tmp.slen, 0); - if (!uri) - return pjsua.config.acc_cnt-1; + /* Signal threads to quit: */ + pjsua_var.thread_quit_flag = 1; - if (!PJSIP_URI_SCHEME_IS_SIP(uri) && - !PJSIP_URI_SCHEME_IS_SIPS(uri)) - { - /* Return the first account with proxy */ - for (i=0; ihost)==0) - break; } + + if (pjsua_var.endpt) { + /* Terminate all calls. */ + pjsua_call_hangup_all(); - if (i != PJ_ARRAY_SIZE(pjsua.acc)) - return i; + /* Terminate all presence subscriptions. */ + pjsua_pres_shutdown(); - /* Just use default account */ - return pjsua.default_acc; -} + /* Unregister, if required: */ + for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + if (pjsua_var.acc[i].regc) { + pjsua_acc_set_registration(i, PJ_FALSE); + } + } -/* - * Init account - */ -static pj_status_t init_acc(unsigned acc_index) -{ - pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index]; - pjsua_acc *acc = &pjsua.acc[acc_index]; - pjsip_uri *uri; - pjsip_sip_uri *sip_uri; - - /* Need to parse local_uri to get the elements: */ - - uri = pjsip_parse_uri(pjsua.pool, acc_cfg->id.ptr, - acc_cfg->id.slen, 0); - if (uri == NULL) { - pjsua_perror(THIS_FILE, "Invalid local URI", - PJSIP_EINVALIDURI); - return PJSIP_EINVALIDURI; + /* Wait for some time to allow unregistration to complete: */ + PJ_LOG(4,(THIS_FILE, "Shutting down...")); + busy_sleep(1000); } - /* Local URI MUST be a SIP or SIPS: */ + /* Destroy media */ + pjsua_media_subsys_destroy(); - if (!PJSIP_URI_SCHEME_IS_SIP(uri) && - !PJSIP_URI_SCHEME_IS_SIPS(uri)) - { - pjsua_perror(THIS_FILE, "Invalid local URI", - PJSIP_EINVALIDSCHEME); - return PJSIP_EINVALIDSCHEME; + /* Destroy endpoint. */ + if (pjsua_var.endpt) { + pjsip_endpt_destroy(pjsua_var.endpt); + pjsua_var.endpt = NULL; } + /* Destroy mutex */ + if (pjsua_var.mutex) { + pj_mutex_destroy(pjsua_var.mutex); + pjsua_var.mutex = NULL; + } - /* Get the SIP URI object: */ - - sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri); - - acc->user_part = sip_uri->user; - acc->host_part = sip_uri->host; - - /* Build Contact header */ - - if (acc_cfg->contact.slen == 0) { - char contact[128]; - const char *addr; - int port; - int len; - - addr = pjsua.config.sip_host.ptr; - port = pjsua.config.sip_port; - - /* The local Contact is the username@ip-addr, where - * - username is taken from the local URI, - * - ip-addr in UDP transport's address name (which may have been - * resolved from STUN. - */ - - /* Build temporary contact string. */ - - if (sip_uri->user.slen) { - - /* With the user part. */ - len = pj_ansi_snprintf(contact, sizeof(contact), - "", - (int)sip_uri->user.slen, - sip_uri->user.ptr, - addr, port); - } else { - - /* Without user part */ - - len = pj_ansi_snprintf(contact, sizeof(contact), - "", - addr, port); - } - - if (len < 1 || len >= sizeof(contact)) { - pjsua_perror(THIS_FILE, "Invalid Contact", PJSIP_EURITOOLONG); - return PJSIP_EURITOOLONG; - } + /* Destroy pool and pool factory. */ + if (pjsua_var.pool) { + pj_pool_release(pjsua_var.pool); + pjsua_var.pool = NULL; + pj_caching_pool_destroy(&pjsua_var.cp); + } - /* Duplicate Contact uri. */ - pj_strdup2(pjsua.pool, &acc_cfg->contact, contact); + PJ_LOG(4,(THIS_FILE, "PJSUA destroyed...")); + /* End logging */ + if (pjsua_var.log_file) { + pj_file_close(pjsua_var.log_file); + pjsua_var.log_file = NULL; } + /* Done. */ + return PJ_SUCCESS; +} - /* Build route-set for this account */ - if (pjsua.config.outbound_proxy.slen) { - pj_str_t hname = { "Route", 5}; - pjsip_route_hdr *r; - pj_str_t tmp; - - pj_strdup_with_null(pjsua.pool, &tmp, &pjsua.config.outbound_proxy); - r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL); - pj_list_push_back(&acc->route_set, r); - } - if (acc_cfg->proxy.slen) { - pj_str_t hname = { "Route", 5}; - pjsip_route_hdr *r; - pj_str_t tmp; +/** + * Application is recommended to call this function after all initialization + * is done, so that the library can do additional checking set up + * additional + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DEF(pj_status_t) pjsua_start(void) +{ + pj_status_t status; - pj_strdup_with_null(pjsua.pool, &tmp, &acc_cfg->proxy); - r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL); - pj_list_push_back(&acc->route_set, r); - } + status = pjsua_call_subsys_start(); + if (status != PJ_SUCCESS) + return status; - /* Mark account as valid */ - pjsua.acc[acc_index].valid = PJ_TRUE; + status = pjsua_media_subsys_start(); + if (status != PJ_SUCCESS) + return status; + status = pjsua_pres_start(); + if (status != PJ_SUCCESS) + return status; return PJ_SUCCESS; } -/* - * Add a new account. + +/** + * Poll pjsua for events, and if necessary block the caller thread for + * the specified maximum interval (in miliseconds). */ -PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg, - pjsua_acc_id *acc_index) +PJ_DEF(int) pjsua_handle_events(unsigned msec_timeout) { - unsigned index; + unsigned count = 0; + pj_time_val tv; pj_status_t status; - PJ_ASSERT_RETURN(pjsua.config.acc_cnt < - PJ_ARRAY_SIZE(pjsua.config.acc_config), - PJ_ETOOMANY); - - /* Find empty account index. */ - for (index=0; index < PJ_ARRAY_SIZE(pjsua.acc); ++index) { - if (pjsua.acc[index].valid == PJ_FALSE) - break; - } - - /* Expect to find a slot */ - PJ_ASSERT_RETURN(index < PJ_ARRAY_SIZE(pjsua.acc), PJ_EBUG); - - copy_acc_config(pjsua.pool, &pjsua.config.acc_config[index], cfg); - - status = init_acc(index); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error adding account", status); - return status; - } + tv.sec = 0; + tv.msec = msec_timeout; + pj_time_val_normalize(&tv); - if (acc_index) - *acc_index = index; + status = pjsip_endpt_handle_events2(pjsua_var.endpt, &tv, &count); - pjsua.config.acc_cnt++; + if (status != PJ_SUCCESS) + return -status; - return PJ_SUCCESS; + return count; } /* - * Delete account. + * Create memory pool. */ -PJ_DEF(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_index) +PJ_DEF(pj_pool_t*) pjsua_pool_create( const char *name, pj_size_t init_size, + pj_size_t increment) { - PJ_ASSERT_RETURN(acc_index < (int)pjsua.config.acc_cnt, - PJ_EINVAL); - PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP); - - /* Delete registration */ - if (pjsua.acc[acc_index].regc != NULL) - pjsua_acc_set_registration(acc_index, PJ_FALSE); - - /* Invalidate */ - pjsua.acc[acc_index].valid = PJ_FALSE; - - return PJ_SUCCESS; + /* Pool factory is thread safe, no need to lock */ + return pj_pool_create(&pjsua_var.cp.factory, name, init_size, increment, + NULL); } /* - * Start pjsua stack. - * This will start the registration process, if registration is configured. + * Internal function to get SIP endpoint instance of pjsua, which is + * needed for example to register module, create transports, etc. + * Probably is only valid after #pjsua_init() is called. */ -PJ_DEF(pj_status_t) pjsua_start(void) +PJ_DEF(pjsip_endpoint*) pjsua_get_pjsip_endpt(void) { - int i; /* Must be signed */ - unsigned count; - pj_status_t status = PJ_SUCCESS; + return pjsua_var.endpt; +} +/* + * Internal function to get media endpoint instance. + * Only valid after #pjsua_init() is called. + */ +PJ_DEF(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void) +{ + return pjsua_var.med_endpt; +} - /* Add UDP transport: */ - if (pjsua.sip_sock > 0) { - /* Init the published name for the transport. - * Depending whether STUN is used, this may be the STUN mapped - * address, or socket's bound address. - */ - pjsip_host_port addr_name; +/***************************************************************************** + * PJSUA SIP Transport API. + */ - addr_name.host = pjsua.config.sip_host; - addr_name.port = pjsua.config.sip_port; +/* + * Create and initialize SIP socket (and possibly resolve public + * address via STUN, depending on config). + */ +static pj_status_t create_sip_udp_sock(pj_in_addr bound_addr, + int port, + pj_bool_t use_stun, + const pjsua_stun_config *stun_param, + pj_sock_t *p_sock, + pj_sockaddr_in *p_pub_addr) +{ + pjsua_stun_config stun; + pj_sock_t sock; + pj_status_t status; - /* Create UDP transport from previously created UDP socket: */ + PJSUA_LOCK(); - status = pjsip_udp_transport_attach( pjsua.endpt, pjsua.sip_sock, - &addr_name, 1, - NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to start UDP transport", - status); - goto on_error; - } + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "socket() error", status); + goto on_return; } - /* Initialize all unused accounts with default id and contact. - */ - { - char buf[80]; - pj_str_t tmp, id; - - tmp.ptr = buf; - tmp.slen = pj_ansi_sprintf(tmp.ptr, "Local ", - pjsua.config.sip_host.ptr, - pjsua.config.sip_port); - pj_strdup_with_null( pjsua.pool, &id, &tmp); - - for (i=pjsua.config.acc_cnt; iid = id; - acc_cfg->contact = id; - } + status = pj_sock_bind_in(sock, bound_addr.s_addr, (pj_uint16_t)port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "bind() error", status); + pj_sock_close(sock); + goto on_return; } - - /* Add another account as the last one. - * This account corresponds to local endpoint, and is user-less. - * This is also the default account. - */ - if (pjsua.config.acc_cnt < PJ_ARRAY_SIZE(pjsua.config.acc_config)) { - pjsua.default_acc = pjsua.config.acc_cnt; - pjsua.acc[pjsua.default_acc].auto_gen = 1; - pjsua.config.acc_cnt++; + /* Copy and normalize STUN param */ + if (use_stun) { + pj_memcpy(&stun, stun_param, sizeof(*stun_param)); + pjsua_normalize_stun_config(&stun); + } else { + pj_memset(&stun, 0, sizeof(pjsua_stun_config)); } - - /* Initialize accounts: */ - for (i=0; i<(int)pjsua.config.acc_cnt; ++i) { - status = init_acc(i); + /* Get the published address, either by STUN or by resolving + * the name of local host. + */ + if (stun.stun_srv1.slen) { + status = pj_stun_get_mapped_addr(&pjsua_var.cp.factory, 1, &sock, + &stun.stun_srv1, + stun.stun_port1, + &stun.stun_srv2, + stun.stun_port2, + p_pub_addr); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing account", status); - goto on_error; + pjsua_perror(THIS_FILE, "Error resolving with STUN", status); + pj_sock_close(sock); + goto on_return; } - } + } else { - /* Create worker thread(s), if required: */ + const pj_str_t *hostname = pj_gethostname(); + struct pj_hostent he; - for (i=0; i<(int)pjsua.config.thread_cnt; ++i) { - status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_poll, - NULL, 0, 0, &pjsua.threads[i]); + status = pj_gethostbyname(hostname, &he); if (status != PJ_SUCCESS) { - pjsua.quit_flag = 1; - for (--i; i>=0; --i) { - pj_thread_join(pjsua.threads[i]); - pj_thread_destroy(pjsua.threads[i]); - } - goto on_error; - } - } - - /* Start registration: */ - - /* Create client registration session: */ - for (i=0; i<(int)pjsua.config.acc_cnt; ++i) { - status = pjsua_regc_init(i); - if (status != PJ_SUCCESS) - goto on_error; - - /* Perform registration, if required. */ - if (pjsua.acc[i].regc) { - pjsua_acc_set_registration(i, PJ_TRUE); + pjsua_perror(THIS_FILE, "Unable to resolve local host", status); + pj_sock_close(sock); + goto on_return; } - } - - /* Re-init buddies */ - count = pjsua.config.buddy_cnt; - pjsua.config.buddy_cnt = 0; - for (i=0; i<(int)count; ++i) { - pj_str_t uri = pjsua.config.buddy_uri[i]; - pjsua_buddy_add(&uri, NULL); + pj_memset(p_pub_addr, 0, sizeof(pj_sockaddr_in)); + p_pub_addr->sin_family = PJ_AF_INET; + p_pub_addr->sin_port = pj_htons((pj_uint16_t)port); + p_pub_addr->sin_addr = *(pj_in_addr*)he.h_addr; } + *p_sock = sock; - /* Refresh presence */ - pjsua_pres_refresh(); +on_return: + PJSUA_UNLOCK(); - PJ_LOG(3,(THIS_FILE, "PJSUA version %s started", PJ_VERSION)); - return PJ_SUCCESS; + PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d", + pj_inet_ntoa(p_pub_addr->sin_addr), + (int)pj_ntohs(p_pub_addr->sin_port))); -on_error: - pjsua_destroy(); return status; } -/* Sleep with polling */ -static void busy_sleep(unsigned msec) +/* + * Create SIP transport. + */ +PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, + const pjsua_transport_config *cfg, + pjsua_transport_id *p_id) { - pj_time_val timeout, now; - - pj_gettimeofday(&timeout); - timeout.msec += msec; - pj_time_val_normalize(&timeout); + pjsip_transport *tp; + unsigned id; + pj_status_t status; - do { - pjsua_poll(NULL); - pj_gettimeofday(&now); - } while (PJ_TIME_VAL_LT(now, timeout)); -} + PJSUA_LOCK(); -/** - * Get maxinum number of conference ports. - */ -PJ_DEF(unsigned) pjsua_conf_max_ports(void) -{ - return pjsua.config.conf_ports; -} + /* Find empty transport slot */ + for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) { + if (pjsua_var.tpdata[id].tp == NULL) + break; + } + if (id == PJ_ARRAY_SIZE(pjsua_var.tpdata)) { + status = PJ_ETOOMANY; + pjsua_perror(THIS_FILE, "Error creating transport", status); + goto on_return; + } -/** - * Enum all conference port ID. - */ -PJ_DEF(pj_status_t) pjsua_conf_enum_port_ids( pjsua_conf_port_id id[], - unsigned *count) -{ - return pjmedia_conf_enum_ports( pjsua.mconf, (unsigned*)id, count); -} + /* Create the transport */ + if (type == PJSIP_TRANSPORT_UDP) { + pjsua_transport_config config; + pj_sock_t sock; + pj_sockaddr_in pub_addr; + pjsip_host_port addr_name; + /* Supply default config if it's not specified */ + if (cfg == NULL) { + pjsua_transport_config_default(&config); + cfg = &config; + } -/** - * Get information about the specified conference port - */ -PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id, - pjsua_conf_port_info *info) -{ - pjmedia_conf_port_info cinfo; - unsigned i, count; - pj_status_t status; + /* Create the socket and possibly resolve the address with STUN */ + status = create_sip_udp_sock(cfg->ip_addr, cfg->port, cfg->use_stun, + &cfg->stun_config, &sock, &pub_addr); + if (status != PJ_SUCCESS) + goto on_return; - status = pjmedia_conf_get_port_info( pjsua.mconf, id, &cinfo); - if (status != PJ_SUCCESS) - return status; + addr_name.host = pj_str(pj_inet_ntoa(pub_addr.sin_addr)); + addr_name.port = pj_ntohs(pub_addr.sin_port); - pj_memset(info, 0, sizeof(*info)); - info->slot_id = id; - info->name = cinfo.name; - info->clock_rate = cinfo.clock_rate; - info->channel_count = cinfo.channel_count; - info->samples_per_frame = cinfo.samples_per_frame; - info->bits_per_sample = cinfo.bits_per_sample; - - /* Build array of listeners */ - count = pjsua.config.conf_ports; - for (i=0; ilisteners[info->listener_cnt++] = i; + /* Create UDP transport */ + status = pjsip_udp_transport_attach( pjsua_var.endpt, sock, + &addr_name, 1, + &tp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating SIP UDP transport", + status); + pj_sock_close(sock); + goto on_return; } + + } else { + status = PJSIP_EUNSUPTRANSPORT; + pjsua_perror(THIS_FILE, "Error creating transport", status); + goto on_return; } - return PJ_SUCCESS; -} + /* Save the transport */ + pjsua_var.tpdata[id].tp = tp; + /* Return the ID */ + if (p_id) *p_id = id; -/** - * Connect conference port. - */ -PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id src_port, - pjsua_conf_port_id dst_port) -{ - return pjmedia_conf_connect_port(pjsua.mconf, src_port, dst_port, 0); -} + status = PJ_SUCCESS; +on_return: -/** - * Connect conference port connection. - */ -PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id src_port, - pjsua_conf_port_id dst_port) -{ - return pjmedia_conf_disconnect_port(pjsua.mconf, src_port, dst_port); -} + PJSUA_UNLOCK(); + return PJ_SUCCESS; +} -/** - * Create a file player. +/* + * Register transport that has been created by application. */ -PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename, - pjsua_player_id *id) +PJ_DEF(pj_status_t) pjsua_transport_register( pjsip_transport *tp, + pjsua_transport_id *p_id) { - unsigned slot; - char path[128]; - pjmedia_port *port; - pj_status_t status; + unsigned id; - if (pjsua.player_cnt >= PJ_ARRAY_SIZE(pjsua.player)) - return PJ_ETOOMANY; + PJSUA_LOCK(); - pj_memcpy(path, filename->ptr, filename->slen); - path[filename->slen] = '\0'; - status = pjmedia_wav_player_port_create(pjsua.pool, path, - pjsua.samples_per_frame * - 1000 / pjsua.clock_rate, - 0, 0, NULL, - &port); - if (status != PJ_SUCCESS) - return status; + /* Find empty transport slot */ + for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) { + if (pjsua_var.tpdata[id].tp == NULL) + break; + } - status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, - port, filename, &slot); - if (status != PJ_SUCCESS) { - pjmedia_port_destroy(port); - return status; + if (id == PJ_ARRAY_SIZE(pjsua_var.tpdata)) { + pjsua_perror(THIS_FILE, "Error creating transport", PJ_ETOOMANY); + PJSUA_UNLOCK(); + return PJ_ETOOMANY; } - pjsua.player[pjsua.player_cnt].port = port; - pjsua.player[pjsua.player_cnt].slot = slot; + /* Save the transport */ + pjsua_var.tpdata[id].tp = tp; - if (id) - *id = pjsua.player_cnt; + /* Return the ID */ + if (p_id) *p_id = id; - ++pjsua.player_cnt; + PJSUA_UNLOCK(); return PJ_SUCCESS; } -/** - * Get conference port associated with player. +/* + * Enumerate all transports currently created in the system. */ -PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id) +PJ_DEF(pj_status_t) pjsua_enum_transports( pjsua_transport_id id[], + unsigned *p_count ) { - PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL); - return pjsua.player[id].slot; -} - + unsigned i, count; -/** - * Re-wind playback. - */ -PJ_DEF(pj_status_t) pjsua_player_set_pos(pjsua_player_id id, - pj_uint32_t samples) -{ - PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL); - PJ_ASSERT_RETURN(pjsua.player[id].port != NULL, PJ_EINVALIDOP); + PJSUA_LOCK(); - return pjmedia_wav_player_port_set_pos(pjsua.player[id].port, samples); -} + for (i=0, count=0; i=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL); + *p_count = count; - if (pjsua.player[id].port) { - pjmedia_port_destroy(pjsua.player[id].port); - pjsua.player[id].port = NULL; - pjsua.player[id].slot = 0xFFFF; - pjsua.player_cnt--; - } + PJSUA_UNLOCK(); return PJ_SUCCESS; } -/** - * Create a file recorder. +/* + * Get information about transports. */ -PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename, - pjsua_recorder_id *id) +PJ_DEF(pj_status_t) pjsua_transport_get_info( pjsua_transport_id id, + pjsua_transport_info *info) { - unsigned slot; - char path[128]; - pjmedia_port *port; - pj_status_t status; - - if (pjsua.recorder_cnt >= PJ_ARRAY_SIZE(pjsua.recorder)) - return PJ_ETOOMANY; - - pj_memcpy(path, filename->ptr, filename->slen); - path[filename->slen] = '\0'; - status = pjmedia_wav_writer_port_create(pjsua.pool, path, - pjsua.clock_rate, 1, - pjsua.samples_per_frame, - 16, 0, 0, NULL, - &port); - if (status != PJ_SUCCESS) - return status; + pjsip_transport *tp; - status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, - port, filename, &slot); - if (status != PJ_SUCCESS) { - pjmedia_port_destroy(port); - return status; - } + pj_memset(info, 0, sizeof(*info)); - pjsua.recorder[pjsua.recorder_cnt].port = port; - pjsua.recorder[pjsua.recorder_cnt].slot = slot; + /* Make sure id is in range. */ + PJ_ASSERT_RETURN(id>=0 && idid = id; + info->type = tp->key.type; + info->type_name = pj_str(tp->type_name); + info->info = pj_str(tp->info); + info->flag = tp->flag; + info->addr_len = tp->addr_len; + info->local_addr = tp->local_addr; + info->local_name = tp->local_name; + info->usage_count = pj_atomic_get(tp->ref_cnt); + + PJSUA_UNLOCK(); + + return PJ_EINVALIDOP; } -/** - * Get conference port associated with recorder. +/* + * Disable a transport or re-enable it. */ -PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id) +PJ_DEF(pj_status_t) pjsua_transport_set_enable( pjsua_transport_id id, + pj_bool_t enabled) { - PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.recorder), PJ_EINVAL); - return pjsua.recorder[id].slot; -} + /* Make sure id is in range. */ + PJ_ASSERT_RETURN(id>=0 && id=0 && id < PJ_ARRAY_SIZE(pjsua.recorder), PJ_EINVAL); - if (pjsua.recorder[id].port) { - pjmedia_port_destroy(pjsua.recorder[id].port); - pjsua.recorder[id].port = NULL; - pjsua.recorder[id].slot = 0xFFFF; - pjsua.recorder_cnt--; - } + /* To be done!! */ + PJ_TODO(pjsua_transport_set_enable); - return PJ_SUCCESS; + return PJ_EINVALIDOP; } -/** - * Enum sound devices. + +/* + * Close the transport. */ -PJ_DEF(pj_status_t) pjsua_enum_snd_devices( unsigned *count, - pjmedia_snd_dev_info info[]) +PJ_DEF(pj_status_t) pjsua_transport_close( pjsua_transport_id id, + pj_bool_t force ) { - int i, dev_count; + /* Make sure id is in range. */ + PJ_ASSERT_RETURN(id>=0 && id (int)*count) - dev_count = *count; + /* Make sure that transport exists */ + PJ_ASSERT_RETURN(pjsua_var.tpdata[id].tp != NULL, PJ_EINVAL); - for (i=0; ihdr_list.next; + while (hdr && hdr != &msg_data->hdr_list) { + pjsip_hdr *new_hdr; - /* Wait for some time to allow unregistration to complete: */ - PJ_LOG(4,(THIS_FILE, "Shutting down...")); - busy_sleep(1000); - } + new_hdr = pjsip_hdr_clone(tdata->pool, hdr); + pjsip_msg_add_hdr(tdata->msg, new_hdr); - /* If we have master port, destroying master port will recursively - * destroy conference bridge, otherwise must destroy it manually. - */ - if (pjsua.master_port) { - pjmedia_master_port_destroy(pjsua.master_port); - pjsua.master_port = NULL; - } else { - if (pjsua.snd_port) { - pjmedia_snd_port_destroy(pjsua.snd_port); - pjsua.snd_port = NULL; - } - if (pjsua.mconf) { - pjmedia_conf_destroy(pjsua.mconf); - pjsua.mconf = NULL; - } + hdr = hdr->next; } - /* Destroy file players */ - for (i=0; imsg->body == NULL); + if (allow_body && msg_data->content_type.slen && msg_data->msg_body.slen) { + pjsip_media_type ctype; + pjsip_msg_body *body; - /* Destroy file recorders */ - for (i=0; ipool, &msg_data->content_type, &ctype); + body = pjsip_msg_body_create(tdata->pool, &ctype.type, &ctype.subtype, + &msg_data->msg_body); + tdata->msg->body = body; } +} - /* Close transports */ - for (i=0; i<(int)pjsua.config.max_calls; ++i) { - if (pjsua.calls[i].med_tp) { - (*pjsua.calls[i].med_tp->op->destroy)(pjsua.calls[i].med_tp); - pjsua.calls[i].med_tp = NULL; - } - } +/* + * Add route_set to outgoing requests + */ +void pjsua_set_msg_route_set( pjsip_tx_data *tdata, + const pjsip_route_hdr *route_set ) +{ + const pjsip_route_hdr *r; - /* Destroy media endpoint. */ - if (pjsua.med_endpt) { + r = route_set->next; + while (r != route_set) { + pjsip_route_hdr *new_r; - /* Shutdown all codecs: */ -# if PJMEDIA_HAS_SPEEX_CODEC - pjmedia_codec_speex_deinit(); -# endif /* PJMEDIA_HAS_SPEEX_CODEC */ + new_r = pjsip_hdr_clone(tdata->pool, r); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)new_r); -# if PJMEDIA_HAS_GSM_CODEC - pjmedia_codec_gsm_deinit(); -# endif /* PJMEDIA_HAS_GSM_CODEC */ + r = r->next; + } +} -# if PJMEDIA_HAS_G711_CODEC - pjmedia_codec_g711_deinit(); -# endif /* PJMEDIA_HAS_G711_CODEC */ -# if PJMEDIA_HAS_L16_CODEC - pjmedia_codec_l16_deinit(); -# endif /* PJMEDIA_HAS_L16_CODEC */ +/* + * Simple version of MIME type parsing (it doesn't support parameters) + */ +void pjsua_parse_media_type( pj_pool_t *pool, + const pj_str_t *mime, + pjsip_media_type *media_type) +{ + pj_str_t tmp; + char *pos; + pj_memset(media_type, 0, sizeof(*media_type)); - pjmedia_endpt_destroy(pjsua.med_endpt); - pjsua.med_endpt = NULL; - } + pj_strdup_with_null(pool, &tmp, mime); - /* Destroy endpoint. */ - if (pjsua.endpt) { - pjsip_endpt_destroy(pjsua.endpt); - pjsua.endpt = NULL; + pos = pj_strchr(&tmp, '/'); + if (pos) { + media_type->type.ptr = tmp.ptr; + media_type->type.slen = (pos-tmp.ptr); + media_type->subtype.ptr = pos+1; + media_type->subtype.slen = tmp.ptr+tmp.slen-pos-1; + } else { + media_type->type = tmp; } +} - /* Destroy caching pool. */ - pj_caching_pool_destroy(&pjsua.cp); - - - PJ_LOG(4,(THIS_FILE, "PJSUA destroyed...")); - /* End logging */ - logging_shutdown(); +/* + * Verify that valid SIP url is given. + */ +PJ_DEF(pj_status_t) pjsua_verify_sip_url(const char *c_url) +{ + pjsip_uri *p; + pj_pool_t *pool; + char *url; + int len = (c_url ? pj_ansi_strlen(c_url) : 0); - /* Done. */ + if (!len) return -1; - return PJ_SUCCESS; -} + pool = pj_pool_create(&pjsua_var.cp.factory, "check%p", 1024, 0, NULL); + if (!pool) return -1; + url = pj_pool_alloc(pool, len+1); + pj_ansi_strcpy(url, c_url); -/** - * Get SIP endpoint instance. - * Only valid after pjsua_init(). - */ -PJ_DEF(pjsip_endpoint*) pjsua_get_pjsip_endpt(void) -{ - return pjsua.endpt; -} + p = pjsip_parse_uri(pool, url, len, 0); + if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0) + p = NULL; -/** - * Get media endpoint instance. - * Only valid after pjsua_init(). - */ -PJ_DEF(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void) -{ - return pjsua.med_endpt; + pj_pool_release(pool); + return p ? 0 : -1; } - diff --git a/pjsip/src/pjsua-lib/pjsua_im.c b/pjsip/src/pjsua-lib/pjsua_im.c index 807f2fac..9ef58a06 100644 --- a/pjsip/src/pjsua-lib/pjsua_im.c +++ b/pjsip/src/pjsua-lib/pjsua_im.c @@ -17,17 +17,10 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include -#include -#include "pjsua_imp.h" +#include -/* - * pjsua_im.c - * - * To handle incoming MESSAGE outside dialog. - * Incoming MESSAGE inside dialog is hanlded in pjsua_call.c. - */ -#define THIS_FILE "pjsua_im.c" +#define THIS_FILE "pjsua_im.h" /* Declare MESSAGE method */ @@ -50,6 +43,7 @@ const pjsip_method pjsip_message_method = /* Proto */ static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata); + /* The module instance. */ static pjsip_module mod_pjsua_im = { @@ -136,30 +130,48 @@ pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata, * Private: process pager message. * This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing(). */ -void pjsua_im_process_pager(int call_index, const pj_str_t *from, +void pjsua_im_process_pager(int call_id, const pj_str_t *from, const pj_str_t *to, pjsip_rx_data *rdata) { + pjsip_contact_hdr *contact_hdr; + pj_str_t contact; pjsip_msg_body *body = rdata->msg_info.msg->body; /* Body MUST have been checked before */ pj_assert(body != NULL); + + /* Build remote contact */ + contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, + NULL); + if (contact_hdr) { + contact.ptr = pj_pool_alloc(rdata->tp_info.pool, + PJSIP_MAX_URL_SIZE); + contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, + contact_hdr->uri, contact.ptr, + PJSIP_MAX_URL_SIZE); + } else { + contact.slen = 0; + } + + if (pj_stricmp(&body->content_type.type, &STR_MIME_TEXT)==0 && pj_stricmp(&body->content_type.subtype, &STR_MIME_PLAIN)==0) { - pj_str_t text; - - /* Build the text. */ - text.ptr = rdata->msg_info.msg->body->data; - text.slen = rdata->msg_info.msg->body->len; - - if (pjsua.cb.on_pager) - (*pjsua.cb.on_pager)(call_index, from, to, &text); + const pj_str_t mime_text_plain = pj_str("text/plain"); + pj_str_t text_body; + + /* Save text body */ + text_body.ptr = rdata->msg_info.msg->body->data; + text_body.slen = rdata->msg_info.msg->body->len; + + if (pjsua_var.ua_cfg.cb.on_pager) { + (*pjsua_var.ua_cfg.cb.on_pager)(call_id, from, to, &contact, + &mime_text_plain, &text_body); + } } else { - /* Expecting typing indication */ - pj_status_t status; pj_bool_t is_typing; @@ -171,8 +183,10 @@ void pjsua_im_process_pager(int call_index, const pj_str_t *from, return; } - if (pjsua.cb.on_typing) - (*pjsua.cb.on_typing)(call_index, from, to, is_typing); + if (pjsua_var.ua_cfg.cb.on_typing) { + (*pjsua_var.ua_cfg.cb.on_typing)(call_id, from, to, &contact, + is_typing); + } } } @@ -210,7 +224,7 @@ static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata) pj_list_init(&hdr_list); pj_list_push_back(&hdr_list, accept_hdr); - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, &hdr_list, NULL); return PJ_TRUE; @@ -219,7 +233,7 @@ static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata) /* Respond with 200 first, so that remote doesn't retransmit in case * the UI takes too long to process the message. */ - status = pjsip_endpt_respond( pjsua.endpt, NULL, rdata, 200, NULL, + status = pjsip_endpt_respond( pjsua_var.endpt, NULL, rdata, 200, NULL, NULL, NULL, NULL); /* For the source URI, we use Contact header if present, since @@ -261,45 +275,158 @@ static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata) /* Outgoing IM callback. */ static void im_callback(void *token, pjsip_event *e) { - pj_str_t *text = token; + pjsua_im_data *im_data = token; if (e->type == PJSIP_EVENT_TSX_STATE) { pjsip_transaction *tsx = e->body.tsx_state.tsx; + /* Ignore provisional response, if any */ + if (tsx->status_code < 200) + return; + + + /* Handle authentication challenges */ + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG && + (tsx->status_code == 401 || tsx->status_code == 407)) + { + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pjsip_auth_clt_sess auth; + pj_status_t status; + + PJ_LOG(4,(THIS_FILE, "Resending IM with authentication")); + + /* Create temporary authentication session */ + pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0); + + pjsip_auth_clt_set_credentials(&auth, + pjsua_var.acc[im_data->acc_id].cred_cnt, + pjsua_var.acc[im_data->acc_id].cred); + + status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx, + &tdata); + if (status == PJ_SUCCESS) { + pjsua_im_data *im_data2; + + /* Must duplicate im_data */ + im_data2 = pjsua_im_data_dup(tdata->pool, im_data); + + /* Re-send request */ + status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, + im_data2, &im_callback); + if (status == PJ_SUCCESS) { + /* Done */ + return; + } + } + } + if (tsx->status_code/100 == 2) { PJ_LOG(4,(THIS_FILE, "Message \'%s\' delivered successfully", - text->ptr)); + im_data->body.ptr)); } else { PJ_LOG(3,(THIS_FILE, - "Failed to deliver message \'%s\': %s [st_code=%d]", - text->ptr, - pjsip_get_status_text(tsx->status_code)->ptr, - tsx->status_code)); + "Failed to deliver message \'%s\': %d/%.*s", + im_data->body.ptr, + tsx->status_code, + (int)tsx->status_text.slen, + tsx->status_text.ptr)); } + + if (pjsua_var.ua_cfg.cb.on_pager_status) + pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id, + &im_data->to, + &im_data->body, + im_data->user_data, + tsx->status_code, + &tsx->status_text); } } -/** - * Send IM outside dialog. +/* Outgoing typing indication callback. + * (used to reauthenticate request) */ -PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const pj_str_t *dst_uri, - const pj_str_t *str) +static void typing_callback(void *token, pjsip_event *e) +{ + pjsua_im_data *im_data = token; + + if (e->type == PJSIP_EVENT_TSX_STATE) { + + pjsip_transaction *tsx = e->body.tsx_state.tsx; + + /* Ignore provisional response, if any */ + if (tsx->status_code < 200) + return; + + /* Handle authentication challenges */ + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG && + (tsx->status_code == 401 || tsx->status_code == 407)) + { + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pjsip_auth_clt_sess auth; + pj_status_t status; + + PJ_LOG(4,(THIS_FILE, "Resending IM with authentication")); + + /* Create temporary authentication session */ + pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0); + + pjsip_auth_clt_set_credentials(&auth, + pjsua_var.acc[im_data->acc_id].cred_cnt, + pjsua_var.acc[im_data->acc_id].cred); + + status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx, + &tdata); + if (status == PJ_SUCCESS) { + pjsua_im_data *im_data2; + + /* Must duplicate im_data */ + im_data2 = pjsua_im_data_dup(tdata->pool, im_data); + + /* Re-send request */ + status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, + im_data2, &typing_callback); + if (status == PJ_SUCCESS) { + /* Done */ + return; + } + } + } + + } +} + + +/* + * Send instant messaging outside dialog, using the specified account for + * route set and authentication. + */ +PJ_DEF(pj_status_t) pjsua_im_send( pjsua_acc_id acc_id, + const pj_str_t *to, + const pj_str_t *mime_type, + const pj_str_t *content, + const pjsua_msg_data *msg_data, + void *user_data) { pjsip_tx_data *tdata; + const pj_str_t mime_text_plain = pj_str("text/plain"); const pj_str_t STR_CONTACT = { "Contact", 7 }; - const pj_str_t mime_text = pj_str("text"); - const pj_str_t mime_plain = pj_str("plain"); - pj_str_t *text; + pjsip_media_type media_type; + pjsua_im_data *im_data; pj_status_t status; + /* To and message body must be specified. */ + PJ_ASSERT_RETURN(to && content, PJ_EINVAL); + /* Create request. */ - status = pjsip_endpt_create_request(pjsua.endpt, &pjsip_message_method, - dst_uri, - &pjsua.config.acc_config[acc_index].id, - dst_uri, NULL, NULL, -1, NULL, &tdata); + status = pjsip_endpt_create_request(pjsua_var.endpt, + &pjsip_message_method, to, + &pjsua_var.acc[acc_id].cfg.id, + to, NULL, NULL, -1, NULL, &tdata); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create request", status); return status; @@ -313,27 +440,46 @@ PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const pj_str_t *dst_uri, pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_CONTACT, - &pjsua.config.acc_config[acc_index].contact)); + &pjsua_var.acc[acc_id].cfg.contact)); - /* Duplicate text. - * We need to keep the text because we will display it when we fail to - * send the message. + /* Create IM data to keep message details and give it back to + * application on the callback */ - text = pj_pool_alloc(tdata->pool, sizeof(pj_str_t)); - pj_strdup_with_null(tdata->pool, text, str); + im_data = pj_pool_zalloc(tdata->pool, sizeof(*im_data)); + im_data->acc_id = acc_id; + im_data->call_id = PJSUA_INVALID_ID; + pj_strdup_with_null(tdata->pool, &im_data->to, to); + pj_strdup_with_null(tdata->pool, &im_data->body, content); + im_data->user_data = user_data; + + + /* Set default media type if none is specified */ + if (mime_type == NULL) { + mime_type = &mime_text_plain; + } + + /* Parse MIME type */ + pjsua_parse_media_type(tdata->pool, mime_type, &media_type); /* Add message body */ - tdata->msg->body = pjsip_msg_body_create( tdata->pool, &mime_text, - &mime_plain, text); + tdata->msg->body = pjsip_msg_body_create( tdata->pool, &media_type.type, + &media_type.subtype, + &im_data->body); if (tdata->msg->body == NULL) { pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM); pjsip_tx_data_dec_ref(tdata); return PJ_ENOMEM; } + /* Add additional headers etc. */ + pjsua_process_msg_data(tdata, msg_data); + + /* Add route set */ + pjsua_set_msg_route_set(tdata, &pjsua_var.acc[acc_id].route_set); + /* Send request (statefully) */ - status = pjsip_endpt_send_request( pjsua.endpt, tdata, -1, - text, &im_callback); + status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, + im_data, &im_callback); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to send request", status); return status; @@ -343,21 +489,23 @@ PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const pj_str_t *dst_uri, } -/** +/* * Send typing indication outside dialog. */ -PJ_DEF(pj_status_t) pjsua_im_typing(int acc_index, const pj_str_t *dst_uri, - pj_bool_t is_typing) +PJ_DEF(pj_status_t) pjsua_im_typing( pjsua_acc_id acc_id, + const pj_str_t *to, + pj_bool_t is_typing, + const pjsua_msg_data *msg_data) { const pj_str_t STR_CONTACT = { "Contact", 7 }; + pjsua_im_data *im_data; pjsip_tx_data *tdata; pj_status_t status; /* Create request. */ - status = pjsip_endpt_create_request( pjsua.endpt, &pjsip_message_method, - dst_uri, - &pjsua.config.acc_config[acc_index].id, - dst_uri, NULL, NULL, -1, NULL, &tdata); + status = pjsip_endpt_create_request( pjsua_var.endpt, &pjsip_message_method, + to, &pjsua_var.acc[acc_id].cfg.id, + to, NULL, NULL, -1, NULL, &tdata); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create request", status); return status; @@ -373,16 +521,23 @@ PJ_DEF(pj_status_t) pjsua_im_typing(int acc_index, const pj_str_t *dst_uri, pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_CONTACT, - &pjsua.config.acc_config[acc_index].contact)); + &pjsua_var.acc[acc_id].cfg.contact)); /* Create "application/im-iscomposing+xml" msg body. */ tdata->msg->body = pjsip_iscomposing_create_body( tdata->pool, is_typing, NULL, NULL, -1); + /* Add additional headers etc. */ + pjsua_process_msg_data(tdata, msg_data); + + /* Create data to reauthenticate */ + im_data = pj_pool_zalloc(tdata->pool, sizeof(*im_data)); + im_data->acc_id = acc_id; + /* Send request (statefully) */ - status = pjsip_endpt_send_request( pjsua.endpt, tdata, -1, - NULL, NULL); + status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, + im_data, &typing_callback); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to send request", status); return status; @@ -401,12 +556,12 @@ pj_status_t pjsua_im_init(void) pj_status_t status; /* Register module */ - status = pjsip_endpt_register_module(pjsua.endpt, &mod_pjsua_im); + status = pjsip_endpt_register_module(pjsua_var.endpt, &mod_pjsua_im); if (status != PJ_SUCCESS) return status; /* Register support for MESSAGE method. */ - pjsip_endpt_add_capability( pjsua.endpt, &mod_pjsua_im, PJSIP_H_ALLOW, + pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ALLOW, NULL, 1, &msg_tag); return PJ_SUCCESS; diff --git a/pjsip/src/pjsua-lib/pjsua_imp.h b/pjsip/src/pjsua-lib/pjsua_imp.h deleted file mode 100644 index 117a2d12..00000000 --- a/pjsip/src/pjsua-lib/pjsua_imp.h +++ /dev/null @@ -1,259 +0,0 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#ifndef __PJSUA_IMP_H__ -#define __PJSUA_IMP_H__ - - - - - -/** - * Structure to be attached to invite dialog. - * Given a dialog "dlg", application can retrieve this structure - * by accessing dlg->mod_data[pjsua.mod.id]. - */ -struct pjsua_call -{ - unsigned index; /**< Index in pjsua array. */ - pjsip_inv_session *inv; /**< The invite session. */ - pjsip_status_code last_code; /** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + + +#define THIS_FILE "pjsua_media.c" + +#define PTIME 10 +#define FPS (1000/PTIME) +#define DEFAULT_RTP_PORT 4000 + + +/* Close existing sound device */ +static void close_snd_dev(void); + + +/** + * Init media subsystems. + */ +pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) +{ + pj_str_t codec_id; + pj_status_t status; + + /* Copy configuration */ + pj_memcpy(&pjsua_var.media_cfg, cfg, sizeof(*cfg)); + + /* Normalize configuration */ +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + pjsua_var.media_cfg.clock_rate = 44100; +#endif + + if (pjsua_var.media_cfg.has_ioqueue && + pjsua_var.media_cfg.thread_cnt == 0) + { + pjsua_var.media_cfg.thread_cnt = 1; + } + + if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) { + pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2; + } + + /* Create media endpoint. */ + status = pjmedia_endpt_create(&pjsua_var.cp.factory, + pjsua_var.media_cfg.has_ioqueue? NULL : + pjsip_endpt_get_ioqueue(pjsua_var.endpt), + pjsua_var.media_cfg.thread_cnt, + &pjsua_var.med_endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Media stack initialization has returned error", + status); + return status; + } + + /* Register all codecs */ +#if PJMEDIA_HAS_SPEEX_CODEC + /* Register speex. */ + status = pjmedia_codec_speex_init(pjsua_var.med_endpt, + PJMEDIA_SPEEX_NO_UWB, + -1, -1); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing Speex codec", + status); + return status; + } +#endif /* PJMEDIA_HAS_SPEEX_CODEC */ + +#if PJMEDIA_HAS_GSM_CODEC + /* Register GSM */ + status = pjmedia_codec_gsm_init(pjsua_var.med_endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing GSM codec", + status); + return status; + } +#endif /* PJMEDIA_HAS_GSM_CODEC */ + +#if PJMEDIA_HAS_G711_CODEC + /* Register PCMA and PCMU */ + status = pjmedia_codec_g711_init(pjsua_var.med_endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing G711 codec", + status); + return status; + } +#endif /* PJMEDIA_HAS_G711_CODEC */ + +#if PJMEDIA_HAS_L16_CODEC + /* Register L16 family codecs, but disable all */ + status = pjmedia_codec_l16_init(pjsua_var.med_endpt, 0); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing L16 codecs", + status); + return status; + } + + /* Disable ALL L16 codecs */ + codec_id = pj_str("L16"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), + &codec_id, PJMEDIA_CODEC_PRIO_DISABLED); + +#endif /* PJMEDIA_HAS_L16_CODEC */ + + + /* Save additional conference bridge parameters for future + * reference. + */ + pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate * + PTIME / 1000; + pjsua_var.mconf_cfg.channel_count = 1; + pjsua_var.mconf_cfg.bits_per_sample = 16; + + /* Init conference bridge. */ + status = pjmedia_conf_create(pjsua_var.pool, + pjsua_var.media_cfg.max_media_ports, + pjsua_var.media_cfg.clock_rate, + pjsua_var.mconf_cfg.channel_count, + pjsua_var.mconf_cfg.samples_per_frame, + pjsua_var.mconf_cfg.bits_per_sample, + PJMEDIA_CONF_NO_DEVICE, + &pjsua_var.mconf); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Media stack initialization has returned error", + status); + return status; + } + + /* Create null port just in case user wants to use null sound. */ + status = pjmedia_null_port_create(pjsua_var.pool, + pjsua_var.media_cfg.clock_rate, + pjsua_var.mconf_cfg.channel_count, + pjsua_var.mconf_cfg.samples_per_frame, + pjsua_var.mconf_cfg.bits_per_sample, + &pjsua_var.null_port); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + return PJ_SUCCESS; +} + + +/* + * Create RTP and RTCP socket pair, and possibly resolve their public + * address via STUN. + */ +static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, + pjmedia_sock_info *skinfo) +{ + enum { + RTP_RETRY = 100 + }; + int i; + static pj_uint16_t rtp_port; + pj_sockaddr_in mapped_addr[2]; + pj_status_t status = PJ_SUCCESS; + pj_sock_t sock[2]; + + if (rtp_port == 0) + rtp_port = (pj_uint16_t)cfg->port; + + for (i=0; i<2; ++i) + sock[i] = PJ_INVALID_SOCKET; + + + /* Loop retry to bind RTP and RTCP sockets. */ + for (i=0; iip_addr.s_addr, rtp_port); + if (status != PJ_SUCCESS) { + pj_sock_close(sock[0]); + sock[0] = PJ_INVALID_SOCKET; + continue; + } + + /* Create and bind RTCP socket. */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[1]); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "socket() error", status); + pj_sock_close(sock[0]); + return status; + } + + status = pj_sock_bind_in(sock[1], cfg->ip_addr.s_addr, + (pj_uint16_t)(rtp_port+1)); + if (status != PJ_SUCCESS) { + pj_sock_close(sock[0]); + sock[0] = PJ_INVALID_SOCKET; + + pj_sock_close(sock[1]); + sock[1] = PJ_INVALID_SOCKET; + continue; + } + + /* + * If we're configured to use STUN, then find out the mapped address, + * and make sure that the mapped RTCP port is adjacent with the RTP. + */ + if (cfg->stun_config.stun_srv1.slen) { + status=pj_stun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock, + &cfg->stun_config.stun_srv1, + cfg->stun_config.stun_port1, + &cfg->stun_config.stun_srv2, + cfg->stun_config.stun_port2, + mapped_addr); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "STUN resolve error", status); + goto on_error; + } + + if (pj_ntohs(mapped_addr[1].sin_port) == + pj_ntohs(mapped_addr[0].sin_port)+1) + { + /* Success! */ + break; + } + + pj_sock_close(sock[0]); + sock[0] = PJ_INVALID_SOCKET; + + pj_sock_close(sock[1]); + sock[1] = PJ_INVALID_SOCKET; + + } else { + const pj_str_t *hostname; + pj_sockaddr_in addr; + + /* Get local IP address. */ + hostname = pj_gethostname(); + + pj_memset( &addr, 0, sizeof(addr)); + addr.sin_family = PJ_AF_INET; + status = pj_sockaddr_in_set_str_addr( &addr, hostname); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unresolvable local hostname", + status); + goto on_error; + } + + for (i=0; i<2; ++i) + pj_memcpy(&mapped_addr[i], &addr, sizeof(addr)); + + mapped_addr[0].sin_port=pj_htons((pj_uint16_t)rtp_port); + mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(rtp_port+1)); + break; + } + } + + if (sock[0] == PJ_INVALID_SOCKET) { + PJ_LOG(1,(THIS_FILE, + "Unable to find appropriate RTP/RTCP ports combination")); + goto on_error; + } + + + skinfo->rtp_sock = sock[0]; + pj_memcpy(&skinfo->rtp_addr_name, + &mapped_addr[0], sizeof(pj_sockaddr_in)); + + skinfo->rtcp_sock = sock[1]; + pj_memcpy(&skinfo->rtcp_addr_name, + &mapped_addr[1], sizeof(pj_sockaddr_in)); + + PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d", + pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr), + pj_ntohs(skinfo->rtp_addr_name.sin_port))); + PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d", + pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr), + pj_ntohs(skinfo->rtcp_addr_name.sin_port))); + + rtp_port += 2; + return PJ_SUCCESS; + +on_error: + for (i=0; i<2; ++i) { + if (sock[i] != PJ_INVALID_SOCKET) + pj_sock_close(sock[i]); + } + return status; +} + + +/* + * Start pjsua media subsystem. + */ +pj_status_t pjsua_media_subsys_start(void) +{ + pj_status_t status; + + /* Create media for calls, if none is specified */ + if (pjsua_var.calls[0].med_tp == NULL) { + pjsua_transport_config transport_cfg; + + /* Create default transport config */ + pjsua_transport_config_default(&transport_cfg); + transport_cfg.port = DEFAULT_RTP_PORT; + + status = pjsua_media_transports_create(&transport_cfg); + if (status != PJ_SUCCESS) + return status; + } + + /* Create sound port if none is created yet */ + if (pjsua_var.snd_port == NULL) { + status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev); + if (status != PJ_SUCCESS) { + /* Error opening sound device, use null device */ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(THIS_FILE, + "Error opening default sound device (%s (status=%d)). " + "Will use NULL device instead", + errmsg, status)); + + status = pjsua_set_null_snd_dev(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error opening NULL sound device", + status); + return status; + } + } + } + + return PJ_SUCCESS; +} + + +/* + * Destroy pjsua media subsystem. + */ +pj_status_t pjsua_media_subsys_destroy(void) +{ + unsigned i; + + close_snd_dev(); + + if (pjsua_var.mconf) { + pjmedia_conf_destroy(pjsua_var.mconf); + pjsua_var.mconf = NULL; + } + + if (pjsua_var.null_port) { + pjmedia_port_destroy(pjsua_var.null_port); + pjsua_var.null_port = NULL; + } + + /* Destroy file players */ + for (i=0; iop->destroy)(pjsua_var.calls[i].med_tp); + pjsua_var.calls[i].med_tp = NULL; + } + } + + /* Destroy media endpoint. */ + if (pjsua_var.med_endpt) { + + /* Shutdown all codecs: */ +# if PJMEDIA_HAS_SPEEX_CODEC + pjmedia_codec_speex_deinit(); +# endif /* PJMEDIA_HAS_SPEEX_CODEC */ + +# if PJMEDIA_HAS_GSM_CODEC + pjmedia_codec_gsm_deinit(); +# endif /* PJMEDIA_HAS_GSM_CODEC */ + +# if PJMEDIA_HAS_G711_CODEC + pjmedia_codec_g711_deinit(); +# endif /* PJMEDIA_HAS_G711_CODEC */ + +# if PJMEDIA_HAS_L16_CODEC + pjmedia_codec_l16_deinit(); +# endif /* PJMEDIA_HAS_L16_CODEC */ + + + pjmedia_endpt_destroy(pjsua_var.med_endpt); + pjsua_var.med_endpt = NULL; + } + + return PJ_SUCCESS; +} + + +/* + * Create UDP media transports for all the calls. This function creates + * one UDP media transport for each call. + */ +PJ_DEF(pj_status_t) +pjsua_media_transports_create(const pjsua_transport_config *app_cfg) +{ + pjsua_transport_config cfg; + unsigned i; + pj_status_t status; + + + /* Make sure pjsua_init() has been called */ + PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP); + + PJSUA_LOCK(); + + /* Delete existing media transports */ + for (i=0; iop->destroy(pjsua_var.calls[i].med_tp); + pjsua_var.calls[i].med_tp = NULL; + } + } + + /* Copy config */ + pj_memcpy(&cfg, app_cfg, sizeof(*app_cfg)); + pjsua_normalize_stun_config(&cfg.stun_config); + + /* Create each media transport */ + for (i=0; iop->destroy(pjsua_var.calls[i].med_tp); + pjsua_var.calls[i].med_tp = NULL; + } + } + + PJSUA_UNLOCK(); + + return status; +} + + +/* + * Get maxinum number of conference ports. + */ +PJ_DEF(unsigned) pjsua_conf_get_max_ports(void) +{ + return pjsua_var.media_cfg.max_media_ports; +} + + +/* + * Get current number of active ports in the bridge. + */ +PJ_DEF(unsigned) pjsua_conf_get_active_ports(void) +{ + unsigned ports[256]; + unsigned count = PJ_ARRAY_SIZE(ports); + pj_status_t status; + + status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count); + if (status != PJ_SUCCESS) + count = 0; + + return count; +} + + +/* + * Enumerate all conference ports. + */ +PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[], + unsigned *count) +{ + return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count); +} + + +/* + * Get information about the specified conference port + */ +PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id, + pjsua_conf_port_info *info) +{ + pjmedia_conf_port_info cinfo; + unsigned i, count; + pj_status_t status; + + status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo); + if (status != PJ_SUCCESS) + return status; + + pj_memset(info, 0, sizeof(*info)); + info->slot_id = id; + info->name = cinfo.name; + info->clock_rate = cinfo.clock_rate; + info->channel_count = cinfo.channel_count; + info->samples_per_frame = cinfo.samples_per_frame; + info->bits_per_sample = cinfo.bits_per_sample; + + /* Build array of listeners */ + count = pjsua_var.media_cfg.max_media_ports; + for (i=0; ilisteners[info->listener_cnt++] = i; + } + } + + return PJ_SUCCESS; +} + + +/* + * Establish unidirectional media flow from souce to sink. + */ +PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source, + pjsua_conf_port_id sink) +{ + return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0); +} + + +/* + * Disconnect media flow from the source to destination port. + */ +PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source, + pjsua_conf_port_id sink) +{ + return pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink); +} + + +/***************************************************************************** + * File player. + */ + +/* + * Create a file player, and automatically connect this player to + * the conference bridge. + */ +PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename, + unsigned options, + void *user_data, + pjsua_player_id *p_id) +{ + unsigned slot, file_id; + char path[128]; + pjmedia_port *port; + pj_status_t status; + + if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player)) + return PJ_ETOOMANY; + + PJSUA_LOCK(); + + for (file_id=0; file_idptr, filename->slen); + path[filename->slen] = '\0'; + status = pjmedia_wav_player_port_create(pjsua_var.pool, path, + pjsua_var.mconf_cfg.samples_per_frame * + 1000 / pjsua_var.media_cfg.clock_rate, + 0, 0, user_data, &port); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + pjsua_perror(THIS_FILE, "Unable to open file for playback", status); + return status; + } + + status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool, + port, filename, &slot); + if (status != PJ_SUCCESS) { + pjmedia_port_destroy(port); + PJSUA_UNLOCK(); + return status; + } + + pjsua_var.player[file_id].port = port; + pjsua_var.player[file_id].slot = slot; + + if (p_id) *p_id = file_id; + + ++pjsua_var.player_cnt; + + PJSUA_UNLOCK(); + return PJ_SUCCESS; +} + + +/* + * Get conference port ID associated with player. + */ +PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id=0 && id=0 && id= PJ_ARRAY_SIZE(pjsua_var.recorder)) + return PJ_ETOOMANY; + + PJSUA_LOCK(); + + for (file_id=0; file_idptr, filename->slen); + path[filename->slen] = '\0'; + status = pjmedia_wav_writer_port_create(pjsua_var.pool, path, + pjsua_var.media_cfg.clock_rate, + pjsua_var.mconf_cfg.channel_count, + pjsua_var.mconf_cfg.samples_per_frame, + pjsua_var.mconf_cfg.bits_per_sample, + 0, 0, user_data, &port); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + pjsua_perror(THIS_FILE, "Unable to open file for recording", status); + return status; + } + + status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool, + port, filename, &slot); + if (status != PJ_SUCCESS) { + pjmedia_port_destroy(port); + PJSUA_UNLOCK(); + return status; + } + + pjsua_var.recorder[file_id].port = port; + pjsua_var.recorder[file_id].slot = slot; + + if (p_id) *p_id = file_id; + + ++pjsua_var.rec_cnt; + + PJSUA_UNLOCK(); + return PJ_SUCCESS; +} + + +/* + * Get conference port associated with recorder. + */ +PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id=0 && id *count) dev_count = *count; + + for (i=0; iname, cap_info->name)); + + pjmedia_snd_port_disconnect(pjsua_var.snd_port); + pjmedia_snd_port_destroy(pjsua_var.snd_port); + pjsua_var.snd_port = NULL; + } + + /* Close null sound device */ + if (pjsua_var.null_snd) { + PJ_LOG(4,(THIS_FILE, "Closing null sound device..")); + pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE); + pjsua_var.null_snd = NULL; + } +} + +/* + * Select or change sound device. Application may call this function at + * any time to replace current sound device. + */ +PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev, + int playback_dev) +{ + pjmedia_port *conf_port; + const pjmedia_snd_dev_info *cap_info, *play_info; + pj_status_t status; + + /* Close existing sound port */ + close_snd_dev(); + + + cap_info = pjmedia_snd_get_dev_info(capture_dev); + play_info = pjmedia_snd_get_dev_info(playback_dev); + + PJ_LOG(4,(THIS_FILE, "Opening %s sound playback device and " + "%s sound capture device..", + play_info->name, cap_info->name)); + + /* Create the sound device. Sound port will start immediately. */ + status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev, + playback_dev, + pjsua_var.media_cfg.clock_rate, 1, + pjsua_var.media_cfg.clock_rate/FPS, + 16, 0, &pjsua_var.snd_port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to open sound device", status); + return status; + } + + /* Get the port0 of the conference bridge. */ + conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf); + pj_assert(conf_port != NULL); + + /* Connect to the conference port */ + status = pjmedia_snd_port_connect(pjsua_var.snd_port, conf_port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to connect conference port to " + "sound device", status); + pjmedia_snd_port_destroy(pjsua_var.snd_port); + pjsua_var.snd_port = NULL; + return status; + } + + /* Save the device IDs */ + pjsua_var.cap_dev = capture_dev; + pjsua_var.play_dev = playback_dev; + + return PJ_SUCCESS; +} + + +/* + * Use null sound device. + */ +PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void) +{ + pjmedia_port *conf_port; + pj_status_t status; + + /* Close existing sound device */ + close_snd_dev(); + + PJ_LOG(4,(THIS_FILE, "Opening null sound device..")); + + /* Get the port0 of the conference bridge. */ + conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf); + pj_assert(conf_port != NULL); + + /* Create master port, connecting port0 of the conference bridge to + * a null port. + */ + status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port, + conf_port, 0, &pjsua_var.null_snd); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create null sound device", + status); + return status; + } + + /* Start the master port */ + status = pjmedia_master_port_start(pjsua_var.null_snd); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * Codecs. + */ + +/* + * Enum all supported codecs in the system. + */ +PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[], + unsigned *p_count ) +{ + pjmedia_codec_mgr *codec_mgr; + pjmedia_codec_info info[32]; + unsigned i, count, prio[32]; + pj_status_t status; + + codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt); + count = PJ_ARRAY_SIZE(info); + status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio); + if (status != PJ_SUCCESS) { + *p_count = 0; + return status; + } + + if (count > *p_count) count = *p_count; + + for (i=0; i -#include "pjsua_imp.h" +#include + + +#define THIS_FILE "pjsua_pres.c" + /* - * pjsua_pres.c - * - * Presence related stuffs. + * Get total number of buddies. */ +PJ_DEF(unsigned) pjsua_get_buddy_count(void) +{ + return pjsua_var.buddy_cnt; +} -#define THIS_FILE "pjsua_pres.c" +/* + * Check if buddy ID is valid. + */ +PJ_DEF(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id) +{ + return buddy_id>=0 && buddy_id=0 && + buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy), + PJ_EINVAL); + + PJSUA_LOCK(); + + pj_memset(info, 0, sizeof(pjsua_buddy_info)); + + buddy = &pjsua_var.buddy[buddy_id]; + info->id = buddy->index; + if (pjsua_var.buddy[buddy_id].uri.slen == 0) { + PJSUA_UNLOCK(); + return PJ_SUCCESS; + } + + /* uri */ + info->uri.ptr = info->buf_ + total; + pj_strncpy(&info->uri, &buddy->uri, sizeof(info->buf_)-total); + total += info->uri.slen; + + /* contact */ + info->contact.ptr = info->buf_ + total; + pj_strncpy(&info->contact, &buddy->contact, sizeof(info->buf_)-total); + total += info->contact.slen; + + /* status and status text */ + if (buddy->sub == NULL || buddy->status.info_cnt==0) { + info->status = PJSUA_BUDDY_STATUS_UNKNOWN; + info->status_text = pj_str("?"); + } else if (pjsua_var.buddy[buddy_id].status.info[0].basic_open) { + info->status = PJSUA_BUDDY_STATUS_ONLINE; + info->status_text = pj_str("Online"); + } else { + info->status = PJSUA_BUDDY_STATUS_OFFLINE; + info->status_text = pj_str("Offline"); + } + + /* monitor pres */ + info->monitor_pres = buddy->monitor; + + PJSUA_UNLOCK(); + return PJ_SUCCESS; +} + + +/* + * Reset buddy descriptor. + */ +static void reset_buddy(pjsua_buddy_id id) +{ + pj_memset(&pjsua_var.buddy[id], 0, sizeof(pjsua_var.buddy[id])); + pjsua_var.buddy[id].index = id; +} + + +/* + * Add new buddy. + */ +PJ_DEF(pj_status_t) pjsua_buddy_add( const pjsua_buddy_config *cfg, + pjsua_buddy_id *p_buddy_id) +{ + pjsip_name_addr *url; + pjsip_sip_uri *sip_uri; + int index; + pj_str_t tmp; + + PJ_ASSERT_RETURN(pjsua_var.buddy_cnt <= + PJ_ARRAY_SIZE(pjsua_var.buddy), + PJ_ETOOMANY); + + PJSUA_LOCK(); + + /* Find empty slot */ + for (index=0; indexuri); + url = (pjsip_name_addr*)pjsip_parse_uri(pjsua_var.pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + + if (url == NULL) { + pjsua_perror(THIS_FILE, "Unable to add buddy", PJSIP_EINVALIDURI); + PJSUA_UNLOCK(); + return PJSIP_EINVALIDURI; + } + + /* Reset buddy, to make sure everything is cleared with default + * values + */ + reset_buddy(index); + + /* Save URI */ + pjsua_var.buddy[index].uri = tmp; + + sip_uri = (pjsip_sip_uri*) url->uri; + pjsua_var.buddy[index].name = sip_uri->user; + pjsua_var.buddy[index].display = url->display; + pjsua_var.buddy[index].host = sip_uri->host; + pjsua_var.buddy[index].port = sip_uri->port; + pjsua_var.buddy[index].monitor = cfg->subscribe; + if (pjsua_var.buddy[index].port == 0) + pjsua_var.buddy[index].port = 5060; + + if (p_buddy_id) + *p_buddy_id = index; + + pjsua_var.buddy_cnt++; + + pjsua_buddy_subscribe_pres(index, cfg->subscribe); + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * Delete buddy. + */ +PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id) +{ + PJ_ASSERT_RETURN(buddy_id>=0 && + buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy), + PJ_EINVAL); + + PJSUA_LOCK(); + + if (pjsua_var.buddy[buddy_id].uri.slen == 0) { + PJSUA_UNLOCK(); + return PJ_SUCCESS; + } + + /* Unsubscribe presence */ + pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE); + + /* Remove buddy */ + pjsua_var.buddy[buddy_id].uri.slen = 0; + pjsua_var.buddy_cnt--; + + /* Reset buddy struct */ + reset_buddy(buddy_id); + + PJSUA_UNLOCK(); + return PJ_SUCCESS; +} + + +/* + * Enable/disable buddy's presence monitoring. + */ +PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id, + pj_bool_t subscribe) +{ + pjsua_buddy *buddy; + + PJ_ASSERT_RETURN(buddy_id>=0 && + buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy), + PJ_EINVAL); + + PJSUA_LOCK(); + + buddy = &pjsua_var.buddy[buddy_id]; + buddy->monitor = subscribe; + pjsua_pres_refresh(); + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * Dump presence subscriptions to log file. + */ +PJ_DEF(void) pjsua_pres_dump(pj_bool_t verbose) +{ + unsigned acc_id; + unsigned i; + + + PJSUA_LOCK(); + + /* + * When no detail is required, just dump number of server and client + * subscriptions. + */ + if (verbose == PJ_FALSE) { + + int count = 0; + + for (acc_id=0; acc_idnext; + } + } + } + + PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d", + count)); + + count = 0; + + for (i=0; isub), + uapres->remote)); + + uapres = uapres->next; + } + } + } + /* + * Dumping all client (UAC) subscriptions + */ + PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:")); -/* ************************************************************************** - * THE FOLLOWING PART HANDLES SERVER SUBSCRIPTION - * ************************************************************************** + if (pjsua_var.buddy_cnt == 0) { + + PJ_LOG(3,(THIS_FILE, " - no buddy list - ")); + + } else { + for (i=0; iremote, pjsip_evsub_get_state_name(sub))); if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { - pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); pj_list_erase(uapres); } } + + PJSUA_UNLOCK(); } /* This is called when request is received. @@ -80,7 +438,7 @@ static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event) */ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) { - int acc_index; + int acc_id; pjsua_acc_config *acc_config; pjsip_method *req_method = &rdata->msg_info.msg->line.req.method; pjsua_srv_pres *uapres; @@ -96,9 +454,11 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) /* Incoming SUBSCRIBE: */ + PJSUA_LOCK(); + /* Find which account for the incoming request. */ - acc_index = pjsua_acc_find_for_incoming(rdata); - acc_config = &pjsua.config.acc_config[acc_index]; + acc_id = pjsua_acc_find_for_incoming(rdata); + acc_config = &pjsua_var.acc[acc_id].cfg; /* Create UAS dialog: */ status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, @@ -108,7 +468,8 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) pjsua_perror(THIS_FILE, "Unable to create UAS dialog for subscription", status); - return PJ_FALSE; + PJSUA_UNLOCK(); + return PJ_TRUE; } /* Init callback: */ @@ -121,7 +482,8 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) pjsip_dlg_terminate(dlg); pjsua_perror(THIS_FILE, "Unable to create server subscription", status); - return PJ_FALSE; + PJSUA_UNLOCK(); + return PJ_TRUE; } /* Attach our data to the subscription: */ @@ -135,10 +497,10 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) else uapres->remote[status] = '\0'; - pjsip_evsub_set_mod_data(sub, pjsua.mod.id, uapres); + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, uapres); /* Add server subscription to the list: */ - pj_list_push_back(&pjsua.acc[acc_index].pres_srv_list, uapres); + pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres); /* Create and send 200 (OK) to the SUBSCRIBE request: */ @@ -148,6 +510,7 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) status); pj_list_erase(uapres); pjsip_pres_terminate(sub, PJ_FALSE); + PJSUA_UNLOCK(); return PJ_FALSE; } @@ -155,10 +518,10 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) /* Set our online status: */ pj_memset(&pres_status, 0, sizeof(pres_status)); pres_status.info_cnt = 1; - pres_status.info[0].basic_open = pjsua.acc[acc_index].online_status; - //Both pjsua.local_uri and pjsua.contact_uri are enclosed in "<" and ">" + pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status; + //Both pjsua_var.local_uri and pjsua_var.contact_uri are enclosed in "<" and ">" //causing XML parsing to fail. - //pres_status.info[0].contact = pjsua.local_uri; + //pres_status.info[0].contact = pjsua_var.local_uri; pjsip_pres_set_status(sub, &pres_status); @@ -173,45 +536,68 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) status); pj_list_erase(uapres); pjsip_pres_terminate(sub, PJ_FALSE); + PJSUA_UNLOCK(); return PJ_FALSE; } /* Done: */ + PJSUA_UNLOCK(); + return PJ_TRUE; } +/* Terminate server subscription for the account */ +void pjsua_pres_delete_acc(int acc_id) +{ + pjsua_srv_pres *uapres; + + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; + + while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) { + + pjsip_pres_status pres_status; + pj_str_t reason = { "noresource", 10 }; + pjsip_tx_data *tdata; + + pjsip_pres_get_status(uapres->sub, &pres_status); + + pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status; + pjsip_pres_set_status(uapres->sub, &pres_status); + + if (pjsip_pres_notify(uapres->sub, + PJSIP_EVSUB_STATE_TERMINATED, NULL, + &reason, &tdata)==PJ_SUCCESS) + { + pjsip_pres_send_request(uapres->sub, tdata); + } + + uapres = uapres->next; + } +} + + /* Refresh subscription (e.g. when our online status has changed) */ -static void refresh_server_subscription(int acc_index) +static void refresh_server_subscription(int acc_id) { pjsua_srv_pres *uapres; - uapres = pjsua.acc[acc_index].pres_srv_list.next; + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; - while (uapres != &pjsua.acc[acc_index].pres_srv_list) { + while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) { pjsip_pres_status pres_status; pjsip_tx_data *tdata; pjsip_pres_get_status(uapres->sub, &pres_status); - if (pres_status.info[0].basic_open != pjsua.acc[acc_index].online_status) { - pres_status.info[0].basic_open = pjsua.acc[acc_index].online_status; + if (pres_status.info[0].basic_open != pjsua_var.acc[acc_id].online_status) { + pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status; pjsip_pres_set_status(uapres->sub, &pres_status); - if (pjsua.quit_flag) { - pj_str_t reason = { "noresource", 10 }; - if (pjsip_pres_notify(uapres->sub, - PJSIP_EVSUB_STATE_TERMINATED, NULL, - &reason, &tdata)==PJ_SUCCESS) - { - pjsip_pres_send_request(uapres->sub, tdata); - } - } else { - if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS) - pjsip_pres_send_request(uapres->sub, tdata); - } + if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS) + pjsip_pres_send_request(uapres->sub, tdata); } uapres = uapres->next; @@ -220,9 +606,8 @@ static void refresh_server_subscription(int acc_index) -/* ************************************************************************** - * THE FOLLOWING PART HANDLES CLIENT SUBSCRIPTION - * ************************************************************************** +/*************************************************************************** + * Client subscription. */ /* Callback called when *client* subscription state has changed. */ @@ -232,26 +617,86 @@ static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) PJ_UNUSED_ARG(event); - buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); + PJSUA_LOCK(); + + buddy = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); if (buddy) { PJ_LOG(3,(THIS_FILE, "Presence subscription to %.*s is %s", - (int)pjsua.config.buddy_uri[buddy->index].slen, - pjsua.config.buddy_uri[buddy->index].ptr, + (int)pjsua_var.buddy[buddy->index].uri.slen, + pjsua_var.buddy[buddy->index].uri.ptr, pjsip_evsub_get_state_name(sub))); if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { buddy->sub = NULL; buddy->status.info_cnt = 0; - pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); } /* Call callback */ - if (pjsua.cb.on_buddy_state) - (*pjsua.cb.on_buddy_state)(buddy->index); + if (pjsua_var.ua_cfg.cb.on_buddy_state) + (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index); + } + + PJSUA_UNLOCK(); +} + + +/* Callback when transaction state has changed. */ +static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub, + pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsua_buddy *buddy; + pjsip_contact_hdr *contact_hdr; + + PJSUA_LOCK(); + + buddy = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!buddy) { + PJSUA_UNLOCK(); + return; + } + + /* We only use this to update buddy's Contact, when it's not + * set. + */ + if (buddy->contact.slen != 0) { + /* Contact already set */ + PJSUA_UNLOCK(); + return; } + + /* Only care about 2xx response to outgoing SUBSCRIBE */ + if (tsx->status_code/100 != 2 || + tsx->role != PJSIP_UAC_ROLE || + event->type != PJSIP_EVENT_RX_MSG || + pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method)!=0) + { + PJSUA_UNLOCK(); + return; + } + + /* Find contact header. */ + contact_hdr = pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg, + PJSIP_H_CONTACT, NULL); + if (!contact_hdr) { + PJSUA_UNLOCK(); + return; + } + + buddy->contact.ptr = pj_pool_alloc(pjsua_var.pool, PJSIP_MAX_URL_SIZE); + buddy->contact.slen = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, + contact_hdr->uri, + buddy->contact.ptr, + PJSIP_MAX_URL_SIZE); + if (buddy->contact.slen < 0) + buddy->contact.slen = 0; + + PJSUA_UNLOCK(); } + /* Callback called when we receive NOTIFY */ static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, @@ -262,7 +707,9 @@ static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub, { pjsua_buddy *buddy; - buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); + PJSUA_LOCK(); + + buddy = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); if (buddy) { /* Update our info. */ pjsip_pres_get_status(sub, &buddy->status); @@ -276,6 +723,8 @@ static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub, PJ_UNUSED_ARG(p_st_text); PJ_UNUSED_ARG(res_hdr); PJ_UNUSED_ARG(p_body); + + PJSUA_UNLOCK(); } @@ -283,8 +732,7 @@ static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub, static pjsip_evsub_user pres_callback = { &pjsua_evsub_on_state, - - NULL, /* on_tsx_state: don't care about transaction state. */ + &pjsua_evsub_on_tsx_state, NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless * we want to authenticate @@ -304,20 +752,23 @@ static pjsip_evsub_user pres_callback = /* It does what it says.. */ static void subscribe_buddy_presence(unsigned index) { - int acc_index; - pjsua_acc_config *acc_config; + pjsua_buddy *buddy; + int acc_id; + pjsua_acc *acc; pjsip_dialog *dlg; pjsip_tx_data *tdata; pj_status_t status; - acc_index = pjsua_acc_find_for_outgoing(&pjsua.config.buddy_uri[index]); + buddy = &pjsua_var.buddy[index]; + acc_id = pjsua_acc_find_for_outgoing(&buddy->uri); - acc_config = &pjsua.config.acc_config[acc_index]; + acc = &pjsua_var.acc[acc_id]; + /* Create UAC dialog */ status = pjsip_dlg_create_uac( pjsip_ua_instance(), - &acc_config->id, - &acc_config->contact, - &pjsua.config.buddy_uri[index], + &acc->cfg.id, + &acc->cfg.contact, + &buddy->uri, NULL, &dlg); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create dialog", @@ -325,38 +776,42 @@ static void subscribe_buddy_presence(unsigned index) return; } - if (acc_config->cred_count) { + /* Set route-set */ + if (!pj_list_empty(&acc->route_set)) { + pjsip_dlg_set_route_set(dlg, &acc->route_set); + } + + /* Set credentials */ + if (acc->cred_cnt) { pjsip_auth_clt_set_credentials( &dlg->auth_sess, - acc_config->cred_count, - acc_config->cred_info); + acc->cred_cnt, acc->cred); } status = pjsip_pres_create_uac( dlg, &pres_callback, - &pjsua.buddies[index].sub); + &buddy->sub); if (status != PJ_SUCCESS) { - pjsua.buddies[index].sub = NULL; + pjsua_var.buddy[index].sub = NULL; pjsua_perror(THIS_FILE, "Unable to create presence client", status); pjsip_dlg_terminate(dlg); return; } - pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id, - &pjsua.buddies[index]); + pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy); - status = pjsip_pres_initiate(pjsua.buddies[index].sub, -1, &tdata); + status = pjsip_pres_initiate(buddy->sub, -1, &tdata); if (status != PJ_SUCCESS) { - pjsip_pres_terminate(pjsua.buddies[index].sub, PJ_FALSE); - pjsua.buddies[index].sub = NULL; + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + buddy->sub = NULL; pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE", status); return; } - status = pjsip_pres_send_request(pjsua.buddies[index].sub, tdata); + status = pjsip_pres_send_request(buddy->sub, tdata); if (status != PJ_SUCCESS) { - pjsip_pres_terminate(pjsua.buddies[index].sub, PJ_FALSE); - pjsua.buddies[index].sub = NULL; + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + buddy->sub = NULL; pjsua_perror(THIS_FILE, "Unable to send initial SUBSCRIBE", status); return; @@ -367,27 +822,27 @@ static void subscribe_buddy_presence(unsigned index) /* It does what it says... */ static void unsubscribe_buddy_presence(unsigned index) { + pjsua_buddy *buddy; pjsip_tx_data *tdata; pj_status_t status; - if (pjsua.buddies[index].sub == NULL) + buddy = &pjsua_var.buddy[index]; + + if (buddy->sub == NULL) return; - if (pjsip_evsub_get_state(pjsua.buddies[index].sub) == - PJSIP_EVSUB_STATE_TERMINATED) - { - pjsua.buddies[index].sub = NULL; + if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsua_var.buddy[index].sub = NULL; return; } - status = pjsip_pres_initiate( pjsua.buddies[index].sub, 0, &tdata); + status = pjsip_pres_initiate( buddy->sub, 0, &tdata); if (status == PJ_SUCCESS) - status = pjsip_pres_send_request( pjsua.buddies[index].sub, tdata ); + status = pjsip_pres_send_request( buddy->sub, tdata ); if (status != PJ_SUCCESS) { - - pjsip_pres_terminate(pjsua.buddies[index].sub, PJ_FALSE); - pjsua.buddies[index].sub = NULL; + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + buddy->sub = NULL; pjsua_perror(THIS_FILE, "Unable to unsubscribe presence", status); } @@ -395,16 +850,19 @@ static void unsubscribe_buddy_presence(unsigned index) /* It does what it says.. */ -static void refresh_client_subscription(void) +static void refresh_client_subscriptions(void) { unsigned i; - for (i=0; iindex = buddy->index; - info->is_valid = pjsua.config.buddy_uri[index].slen; - if (!info->is_valid) - return PJ_SUCCESS; - - info->name = buddy->name; - info->display_name = buddy->display; - info->host = buddy->host; - info->port = buddy->port; - info->uri = pjsua.config.buddy_uri[index]; - - if (buddy->sub == NULL || buddy->status.info_cnt==0) { - info->status = PJSUA_BUDDY_STATUS_UNKNOWN; - info->status_text = pj_str("?"); - } else if (pjsua.buddies[index].status.info[0].basic_open) { - info->status = PJSUA_BUDDY_STATUS_ONLINE; - info->status_text = pj_str("Online"); - } else { - info->status = PJSUA_BUDDY_STATUS_OFFLINE; - info->status_text = pj_str("Offline"); - } - - return PJ_SUCCESS; -} - - -/** - * Add new buddy. - */ -PJ_DEF(pj_status_t) pjsua_buddy_add( const pj_str_t *uri, - pjsua_buddy_id *buddy_index) -{ - pjsip_name_addr *url; - pjsip_sip_uri *sip_uri; - int index; - pj_str_t tmp; - - PJ_ASSERT_RETURN(pjsua.config.buddy_cnt <= - PJ_ARRAY_SIZE(pjsua.config.buddy_uri), - PJ_ETOOMANY); - - /* Find empty slot */ - for (index=0; indexuri; - pjsua.buddies[index].name = sip_uri->user; - pjsua.buddies[index].display = url->display; - pjsua.buddies[index].host = sip_uri->host; - pjsua.buddies[index].port = sip_uri->port; - if (pjsua.buddies[index].port == 0) - pjsua.buddies[index].port = 5060; - - if (buddy_index) - *buddy_index = index; - - pjsua.config.buddy_cnt++; - - return PJ_SUCCESS; + return status; } - -/** - * Delete buddy. +/* + * Start presence subsystem. */ -PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id index) -{ - PJ_ASSERT_RETURN(index < (int)PJ_ARRAY_SIZE(pjsua.config.buddy_uri), - PJ_EINVAL); - - if (pjsua.config.buddy_uri[index].slen == 0) - return PJ_SUCCESS; - - /* Unsubscribe presence */ - pjsua_buddy_subscribe_pres(index, PJ_FALSE); - - /* Remove buddy */ - pjsua.config.buddy_uri[index].slen = 0; - pjsua.config.buddy_cnt--; - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id index, - pj_bool_t monitor) -{ - pjsua_buddy *buddy; - - PJ_ASSERT_RETURN(index < (int)PJ_ARRAY_SIZE(pjsua.config.buddy_uri), - PJ_EINVAL); - - buddy = &pjsua.buddies[index]; - buddy->monitor = monitor; - pjsua_pres_refresh(); - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjsua_acc_set_online_status( pjsua_acc_id acc_index, - pj_bool_t is_online) +pj_status_t pjsua_pres_start(void) { - PJ_ASSERT_RETURN(acc_index < (int)PJ_ARRAY_SIZE(pjsua.acc), - PJ_EINVAL); - PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP); - - pjsua.acc[acc_index].online_status = is_online; - pjsua_pres_refresh(); + /* Nothing to do (is it?) */ return PJ_SUCCESS; } /* - * Refresh presence + * Refresh presence subscriptions */ -PJ_DEF(void) pjsua_pres_refresh() +void pjsua_pres_refresh() { unsigned i; - refresh_client_subscription(); + refresh_client_subscriptions(); - for (i=0; inext; - } - } - } - - PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d", - count)); - - count = 0; - - for (i=0; isub), - uapres->remote)); - - uapres = uapres->next; - } - } - } - - /* - * Dumping all client (UAC) subscriptions - */ - PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:")); - - if (pjsua.config.buddy_cnt == 0) { - - PJ_LOG(3,(THIS_FILE, " - no buddy list - ")); - - } else { - for (i=0; i - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include "pjsua_imp.h" - - -/* - * pjsua_reg.c - * - * Client registration handler. - */ - -#define THIS_FILE "pjsua_reg.c" - - -/* - * This callback is called by pjsip_regc when outgoing register - * request has completed. - */ -static void regc_cb(struct pjsip_regc_cbparam *param) -{ - - pjsua_acc *acc = param->token; - - /* - * Print registration status. - */ - if (param->status!=PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "SIP registration error", - param->status); - pjsip_regc_destroy(acc->regc); - acc->regc = NULL; - - } else if (param->code < 0 || param->code >= 300) { - PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)", - param->code, - pjsip_get_status_text(param->code)->ptr)); - pjsip_regc_destroy(acc->regc); - acc->regc = NULL; - - } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { - - if (param->expiration < 1) { - pjsip_regc_destroy(acc->regc); - acc->regc = NULL; - PJ_LOG(3,(THIS_FILE, "%s: unregistration success", - pjsua.config.acc_config[acc->index].id.ptr)); - } else { - PJ_LOG(3, (THIS_FILE, - "%s: registration success, status=%d (%s), " - "will re-register in %d seconds", - pjsua.config.acc_config[acc->index].id.ptr, - param->code, - pjsip_get_status_text(param->code)->ptr, - param->expiration)); - } - - } else { - PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); - } - - acc->reg_last_err = param->status; - acc->reg_last_code = param->code; - - if (pjsua.cb.on_reg_state) - (*pjsua.cb.on_reg_state)(acc->index); -} - - -/** - * Get number of accounts. - */ -PJ_DEF(unsigned) pjsua_get_acc_count(void) -{ - return pjsua.config.acc_cnt; -} - - -/** - * Get account info. - */ -PJ_DEF(pj_status_t) pjsua_acc_get_info( pjsua_acc_id acc_index, - pjsua_acc_info *info) -{ - pjsua_acc *acc = &pjsua.acc[acc_index]; - pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index]; - - PJ_ASSERT_RETURN(info != NULL, PJ_EINVAL); - - pj_memset(info, 0, sizeof(pjsua_acc_info)); - - PJ_ASSERT_RETURN(acc_index < (int)pjsua.config.acc_cnt, - PJ_EINVAL); - PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP); - - - info->index = acc_index; - info->acc_id = acc_cfg->id; - info->has_registration = (acc->regc != NULL); - info->online_status = acc->online_status; - - if (acc->reg_last_err) { - info->status = acc->reg_last_err; - pj_strerror(acc->reg_last_err, info->buf, sizeof(info->buf)); - info->status_text = pj_str(info->buf); - } else if (acc->reg_last_code) { - info->status = acc->reg_last_code; - info->status_text = *pjsip_get_status_text(acc->reg_last_code); - } else { - info->status = 0; - info->status_text = pj_str("In Progress"); - } - - if (acc->regc) { - pjsip_regc_info regc_info; - pjsip_regc_get_info(acc->regc, ®c_info); - info->expires = regc_info.next_reg; - } - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[], - unsigned *count ) -{ - unsigned i, c; - - for (i=0, c=0; c<*count && ireg_uri.slen == 0) { - PJ_LOG(3,(THIS_FILE, "Registrar URI is not specified")); - return PJ_SUCCESS; - } - - /* initialize SIP registration if registrar is configured */ - - status = pjsip_regc_create( pjsua.endpt, - &pjsua.acc[acc_index], - ®c_cb, - &pjsua.acc[acc_index].regc); - - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create client registration", - status); - return status; - } - - - status = pjsip_regc_init( pjsua.acc[acc_index].regc, - &acc_config->reg_uri, - &acc_config->id, - &acc_config->id, - 1, &acc_config->contact, - acc_config->reg_timeout); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Client registration initialization error", - status); - return status; - } - - if (acc_config->cred_count) { - pjsip_regc_set_credentials( pjsua.acc[acc_index].regc, - acc_config->cred_count, - acc_config->cred_info ); - } - - pjsip_regc_set_route_set( pjsua.acc[acc_index].regc, - &pjsua.acc[acc_index].route_set ); - - return PJ_SUCCESS; -} - diff --git a/pjsip/src/pjsua-lib/pjsua_settings.c b/pjsip/src/pjsua-lib/pjsua_settings.c deleted file mode 100644 index db30312d..00000000 --- a/pjsip/src/pjsua-lib/pjsua_settings.c +++ /dev/null @@ -1,1435 +0,0 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#include -#include -#include "pjsua_imp.h" - - -/* - * pjsua_settings.c - * - * Anything to do with configuration and state dump. - */ - -#define THIS_FILE "pjsua_opt.c" - - -const char *pjsua_inv_state_names[] = -{ - "NULL ", - "CALLING ", - "INCOMING ", - "EARLY ", - "CONNECTING", - "CONFIRMED ", - "DISCONNCTD", - "TERMINATED", -}; - - - -/* Show usage */ -static void usage(void) -{ - puts ("Usage:"); - puts (" pjsua [options]"); - puts (""); - puts ("General options:"); - puts (" --help Display this help screen"); - puts (" --version Display version info"); - puts (""); - puts ("Logging 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)) (default=5)"); - puts (" --app-log-level=N Set log max level for stdout display (default=4)"); - puts (""); - puts ("SIP Account options:"); - puts (" --registrar=url Set the URL of registrar server"); - puts (" --id=url Set the URL of local ID (used in From header)"); - puts (" --contact=url Optionally override the Contact information"); - puts (" --proxy=url Optional URL of proxy server to visit"); - puts (" --realm=string Set realm"); - puts (" --username=string Set authentication username"); - puts (" --password=string Set authentication password"); - puts (" --reg-timeout=SEC Optional registration interval (default 55)"); - puts (""); - puts ("SIP Account Control:"); - puts (" --next-account Add more account"); - puts (""); - puts ("Transport Options:"); - puts (" --local-port=port Set TCP/UDP port"); - puts (" --outbound=url Set the URL of outbound proxy server"); - puts (" --use-stun1=host[:port]"); - puts (" --use-stun2=host[:port] Resolve local IP with the specified STUN servers"); - puts (""); - puts ("Media Options:"); - puts (" --add-codec=name Manually add codec (default is to enable all)"); - puts (" --clock-rate=N Override sound device clock rate"); - puts (" --null-audio Use NULL audio device"); - puts (" --play-file=file Play WAV file in conference bridge"); - puts (" --auto-play Automatically play the file (to incoming calls only)"); - puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP"); - puts (" --auto-conf Automatically put incoming calls to conference"); - puts (" --rtp-port=N Base port to try for RTP (default=4000)"); - puts (" --complexity=N Specify encoding complexity (0-10, default=none(-1))"); - puts (" --quality=N Specify encoding quality (0-10, default=4)"); - puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)"); - puts (""); - puts ("Buddy List (can be more than one):"); - puts (" --add-buddy url Add the specified URL to the buddy list."); - puts (""); - puts ("User Agent options:"); - puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)"); - puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)"); - puts (" --uas-refresh=N Interval in UAS to send re-INVITE (default:-1)"); - puts (" --uas-duration=N Maximum duration of incoming call (default:-1)"); - puts (""); - fflush(stdout); -} - - - -/* - * Verify that valid SIP url is given. - */ -PJ_DEF(pj_status_t) pjsua_verify_sip_url(const char *c_url) -{ - pjsip_uri *p; - pj_pool_t *pool; - char *url; - int len = (c_url ? pj_ansi_strlen(c_url) : 0); - - if (!len) return -1; - - pool = pj_pool_create(&pjsua.cp.factory, "check%p", 1024, 0, NULL); - if (!pool) return -1; - - url = pj_pool_alloc(pool, len+1); - pj_ansi_strcpy(url, c_url); - - 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) { - PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename)); - fflush(stdout); - 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))) { - PJ_LOG(1,(THIS_FILE, - "Too many arguments specified in cmd line/config file")); - fflush(stdout); - 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; - -} - -static int my_atoi(const char *cs) -{ - pj_str_t s; - return pj_strtoul(pj_cstr(&s, cs)); -} - - -/* Parse arguments. */ -PJ_DEF(pj_status_t) pjsua_parse_args(int argc, char *argv[], - pjsua_config *cfg, - pj_str_t *uri_to_call) -{ - 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, OPT_AUTO_PLAY, OPT_AUTO_LOOP, - OPT_AUTO_CONF, OPT_CLOCK_RATE, - OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC, - OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, - OPT_NEXT_ACCOUNT, OPT_MAX_CALLS, OPT_UAS_REFRESH, - OPT_UAS_DURATION, - }; - struct pj_getopt_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}, - { "clock-rate", 1, 0, OPT_CLOCK_RATE}, - { "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}, - { "auto-play", 0, 0, OPT_AUTO_PLAY}, - { "auto-loop", 0, 0, OPT_AUTO_LOOP}, - { "auto-conf", 0, 0, OPT_AUTO_CONF}, - { "play-file", 1, 0, OPT_PLAY_FILE}, - { "rtp-port", 1, 0, OPT_RTP_PORT}, - { "add-codec", 1, 0, OPT_ADD_CODEC}, - { "complexity", 1, 0, OPT_COMPLEXITY}, - { "quality", 1, 0, OPT_QUALITY}, - { "ptime", 1, 0, OPT_PTIME}, - { "next-account",0,0, OPT_NEXT_ACCOUNT}, - { "max-calls", 1, 0, OPT_MAX_CALLS}, - { "uas-refresh",1, 0, OPT_UAS_REFRESH}, - { "uas-duration",1,0, OPT_UAS_DURATION}, - { NULL, 0, 0, 0} - }; - pj_status_t status; - pjsua_acc_config *cur_acc; - char errmsg[80]; - char *config_file = NULL; - unsigned i; - - /* Run pj_getopt once to see if user specifies config file to read. */ - while ((c=pj_getopt_long(argc, argv, "", long_options, - &option_index)) != -1) - { - switch (c) { - case OPT_CONFIG_FILE: - config_file = pj_optarg; - break; - } - if (config_file) - break; - } - - if (config_file) { - status = read_config_file(pjsua.pool, config_file, &argc, &argv); - if (status != 0) - return status; - } - - cfg->acc_cnt = 0; - cur_acc = &cfg->acc_config[0]; - - - /* Reinitialize and re-run pj_getopt again, possibly with new arguments - * read from config file. - */ - pj_optind = 0; - while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) { - char *p; - pj_str_t tmp; - long lval; - - switch (c) { - - case OPT_LOG_FILE: - cfg->log_filename = pj_str(pj_optarg); - break; - - case OPT_LOG_LEVEL: - c = pj_strtoul(pj_cstr(&tmp, pj_optarg)); - if (c < 0 || c > 6) { - PJ_LOG(1,(THIS_FILE, - "Error: expecting integer value 0-6 " - "for --log-level")); - return PJ_EINVAL; - } - cfg->log_level = c; - pj_log_set_level( c ); - break; - - case OPT_APP_LOG_LEVEL: - cfg->app_log_level = pj_strtoul(pj_cstr(&tmp, pj_optarg)); - if (cfg->app_log_level < 0 || cfg->app_log_level > 6) { - PJ_LOG(1,(THIS_FILE, - "Error: expecting integer value 0-6 " - "for --app-log-level")); - return PJ_EINVAL; - } - break; - - case OPT_HELP: - usage(); - return PJ_EINVAL; - - case OPT_VERSION: /* version */ - pj_dump_config(); - return PJ_EINVAL; - - case OPT_NULL_AUDIO: - cfg->null_audio = 1; - break; - - case OPT_CLOCK_RATE: - lval = pj_strtoul(pj_cstr(&tmp, pj_optarg)); - if (lval < 8000 || lval > 48000) { - PJ_LOG(1,(THIS_FILE, "Error: expecting value between " - "8000-48000 for clock rate")); - return PJ_EINVAL; - } - cfg->clock_rate = lval; - break; - - case OPT_LOCAL_PORT: /* local-port */ - lval = pj_strtoul(pj_cstr(&tmp, pj_optarg)); - if (lval < 1 || lval > 65535) { - PJ_LOG(1,(THIS_FILE, - "Error: expecting integer value for " - "--local-port")); - return PJ_EINVAL; - } - cfg->udp_port = (pj_uint16_t)lval; - break; - - case OPT_PROXY: /* proxy */ - if (pjsua_verify_sip_url(pj_optarg) != 0) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid SIP URL '%s' " - "in proxy argument", pj_optarg)); - return PJ_EINVAL; - } - cur_acc->proxy = pj_str(pj_optarg); - break; - - case OPT_OUTBOUND_PROXY: /* outbound proxy */ - if (pjsua_verify_sip_url(pj_optarg) != 0) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid SIP URL '%s' " - "in outbound proxy argument", pj_optarg)); - return PJ_EINVAL; - } - cfg->outbound_proxy = pj_str(pj_optarg); - break; - - case OPT_REGISTRAR: /* registrar */ - if (pjsua_verify_sip_url(pj_optarg) != 0) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid SIP URL '%s' in " - "registrar argument", pj_optarg)); - return PJ_EINVAL; - } - cur_acc->reg_uri = pj_str(pj_optarg); - break; - - case OPT_REG_TIMEOUT: /* reg-timeout */ - cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg)); - if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid value for --reg-timeout " - "(expecting 1-3600)")); - return PJ_EINVAL; - } - break; - - case OPT_ID: /* id */ - if (pjsua_verify_sip_url(pj_optarg) != 0) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid SIP URL '%s' " - "in local id argument", pj_optarg)); - return PJ_EINVAL; - } - cur_acc->id = pj_str(pj_optarg); - break; - - case OPT_CONTACT: /* contact */ - if (pjsua_verify_sip_url(pj_optarg) != 0) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid SIP URL '%s' " - "in contact argument", pj_optarg)); - return PJ_EINVAL; - } - cur_acc->contact = pj_str(pj_optarg); - break; - - case OPT_NEXT_ACCOUNT: /* Add more account. */ - cfg->acc_cnt++; - cur_acc = &cfg->acc_config[cfg->acc_cnt - 1]; - break; - - case OPT_USERNAME: /* Default authentication user */ - cur_acc->cred_info[0].username = pj_str(pj_optarg); - break; - - case OPT_REALM: /* Default authentication realm. */ - cur_acc->cred_info[0].realm = pj_str(pj_optarg); - break; - - case OPT_PASSWORD: /* authentication password */ - cur_acc->cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; - cur_acc->cred_info[0].data = pj_str(pj_optarg); - break; - - case OPT_USE_STUN1: /* STUN server 1 */ - p = pj_ansi_strchr(pj_optarg, ':'); - if (p) { - *p = '\0'; - cfg->stun_srv1 = pj_str(pj_optarg); - cfg->stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1)); - if (cfg->stun_port1 < 1 || cfg->stun_port1 > 65535) { - PJ_LOG(1,(THIS_FILE, - "Error: expecting port number with " - "option --use-stun1")); - return PJ_EINVAL; - } - } else { - cfg->stun_port1 = 3478; - cfg->stun_srv1 = pj_str(pj_optarg); - } - break; - - case OPT_USE_STUN2: /* STUN server 2 */ - p = pj_ansi_strchr(pj_optarg, ':'); - if (p) { - *p = '\0'; - cfg->stun_srv2 = pj_str(pj_optarg); - cfg->stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1)); - if (cfg->stun_port2 < 1 || cfg->stun_port2 > 65535) { - PJ_LOG(1,(THIS_FILE, - "Error: expecting port number with " - "option --use-stun2")); - return PJ_EINVAL; - } - } else { - cfg->stun_port2 = 3478; - cfg->stun_srv2 = pj_str(pj_optarg); - } - break; - - case OPT_ADD_BUDDY: /* Add to buddy list. */ - if (pjsua_verify_sip_url(pj_optarg) != 0) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid URL '%s' in " - "--add-buddy option", pj_optarg)); - return -1; - } - if (cfg->buddy_cnt == PJSUA_MAX_BUDDIES) { - PJ_LOG(1,(THIS_FILE, - "Error: too many buddies in buddy list.")); - return -1; - } - cfg->buddy_uri[cfg->buddy_cnt++] = pj_str(pj_optarg); - break; - - case OPT_AUTO_PLAY: - cfg->auto_play = 1; - break; - - case OPT_AUTO_LOOP: - cfg->auto_loop = 1; - break; - - case OPT_AUTO_CONF: - cfg->auto_conf = 1; - break; - - case OPT_PLAY_FILE: - cfg->wav_file = pj_str(pj_optarg); - break; - - case OPT_RTP_PORT: - cfg->start_rtp_port = my_atoi(pj_optarg); - if (cfg->start_rtp_port < 1 || cfg->start_rtp_port > 65535) { - PJ_LOG(1,(THIS_FILE, - "Error: rtp-port argument value " - "(expecting 1-65535")); - return -1; - } - break; - - case OPT_ADD_CODEC: - cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg); - break; - - case OPT_COMPLEXITY: - cfg->complexity = my_atoi(pj_optarg); - if (cfg->complexity < 0 || cfg->complexity > 10) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid --complexity (expecting 0-10")); - return -1; - } - break; - - case OPT_QUALITY: - cfg->quality = my_atoi(pj_optarg); - if (cfg->quality < 0 || cfg->quality > 10) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid --quality (expecting 0-10")); - return -1; - } - break; - - case OPT_PTIME: - cfg->ptime = my_atoi(pj_optarg); - if (cfg->ptime < 10 || cfg->ptime > 1000) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid --ptime option")); - return -1; - } - break; - - case OPT_AUTO_ANSWER: - cfg->auto_answer = my_atoi(pj_optarg); - if (cfg->auto_answer < 100 || cfg->auto_answer > 699) { - PJ_LOG(1,(THIS_FILE, - "Error: invalid code in --auto-answer " - "(expecting 100-699")); - return -1; - } - break; - - case OPT_MAX_CALLS: - cfg->max_calls = my_atoi(pj_optarg); - if (cfg->max_calls < 1 || cfg->max_calls > 255) { - PJ_LOG(1,(THIS_FILE,"Too many calls for max-calls (1-255)")); - return -1; - } - break; - - case OPT_UAS_REFRESH: - cfg->uas_refresh = my_atoi(pj_optarg); - if (cfg->uas_refresh < 1) { - PJ_LOG(1,(THIS_FILE, - "Invalid value for --uas-refresh (must be >0)")); - return -1; - } - break; - - case OPT_UAS_DURATION: - cfg->uas_duration = my_atoi(pj_optarg); - if (cfg->uas_duration < 1) { - PJ_LOG(1,(THIS_FILE, - "Invalid value for --uas-duration " - "(must be >0)")); - return -1; - } - break; - } - } - - if (pj_optind != argc) { - pj_str_t uri_arg; - - if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) { - PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind])); - return -1; - } - uri_arg = pj_str(argv[pj_optind]); - if (uri_to_call) - *uri_to_call = uri_arg; - pj_optind++; - - /* Add URI to call to buddy list if it's not already there */ - for (i=0; ibuddy_cnt; ++i) { - if (pj_stricmp(&cfg->buddy_uri[i], &uri_arg)==0) - break; - } - if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) { - cfg->buddy_uri[cfg->buddy_cnt++] = uri_arg; - } - - } else { - if (uri_to_call) - uri_to_call->slen = 0; - } - - if (pj_optind != argc) { - PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind])); - return PJ_EINVAL; - } - - if (cfg->acc_config[0].id.slen && cfg->acc_cnt==0) - cfg->acc_cnt = 1; - - for (i=0; iacc_cnt; ++i) { - if (cfg->acc_config[i].cred_info[0].username.slen || - cfg->acc_config[i].cred_info[0].realm.slen) - { - cfg->acc_config[i].cred_count = 1; - cfg->acc_config[i].cred_info[0].scheme = pj_str("digest"); - } - } - - if (pjsua_test_config(cfg, errmsg, sizeof(errmsg)) != PJ_SUCCESS) { - PJ_LOG(1,(THIS_FILE, "Error: %s", errmsg)); - return -1; - } - - return PJ_SUCCESS; -} - - - -static void print_call(const char *title, - int call_index, - char *buf, pj_size_t size) -{ - int len; - pjsip_inv_session *inv = pjsua.calls[call_index].inv; - pjsip_dialog *dlg = inv->dlg; - char userinfo[128]; - - /* Dump invite sesion info. */ - - len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); - if (len < 1) - pj_ansi_strcpy(userinfo, "<--uri too long-->"); - else - userinfo[len] = '\0'; - - len = pj_ansi_snprintf(buf, size, "%s[%s] %s", - title, - pjsua_inv_state_names[inv->state], - userinfo); - if (len < 1 || len >= (int)size) { - pj_ansi_strcpy(buf, "<--uri too long-->"); - len = 18; - } else - buf[len] = '\0'; -} - -static const char *good_number(char *buf, pj_int32_t val) -{ - if (val < 1000) { - pj_ansi_sprintf(buf, "%d", val); - } else if (val < 1000000) { - pj_ansi_sprintf(buf, "%d.%dK", - val / 1000, - (val % 1000) / 100); - } else { - pj_ansi_sprintf(buf, "%d.%02dM", - val / 1000000, - (val % 1000000) / 10000); - } - - return buf; -} - -static void dump_media_session(const char *indent, - char *buf, unsigned maxlen, - pjmedia_session *session) -{ - unsigned i; - char *p = buf, *end = buf+maxlen; - int len; - pjmedia_session_info info; - - pjmedia_session_get_info(session, &info); - - for (i=0; i end-p) { - *p = '\0'; - return; - } - - p += len; - *p++ = '\n'; - *p = '\0'; - - if (stat.rx.update_cnt == 0) - strcpy(last_update, "never"); - else { - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, stat.rx.update); - sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", - now.sec / 3600, - (now.sec % 3600) / 60, - now.sec % 60, - now.msec); - } - - len = pj_ansi_snprintf(p, end-p, - "%s RX pt=%d, stat last update: %s\n" - "%s total %spkt %sB (%sB +IP hdr)\n" - "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n" - "%s (msec) min avg max last\n" - "%s loss period: %7.3f %7.3f %7.3f %7.3f\n" - "%s jitter : %7.3f %7.3f %7.3f %7.3f%s", - indent, info.stream_info[i].fmt.pt, - last_update, - indent, - good_number(packets, stat.rx.pkt), - good_number(bytes, stat.rx.bytes), - good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32), - indent, - stat.rx.loss, - stat.rx.loss * 100.0 / stat.rx.pkt, - stat.rx.dup, - stat.rx.dup * 100.0 / stat.rx.pkt, - stat.rx.reorder, - stat.rx.reorder * 100.0 / stat.rx.pkt, - indent, indent, - stat.rx.loss_period.min / 1000.0, - stat.rx.loss_period.avg / 1000.0, - stat.rx.loss_period.max / 1000.0, - stat.rx.loss_period.last / 1000.0, - indent, - stat.rx.jitter.min / 1000.0, - stat.rx.jitter.avg / 1000.0, - stat.rx.jitter.max / 1000.0, - stat.rx.jitter.last / 1000.0, - "" - ); - - if (len < 1 || len > end-p) { - *p = '\0'; - return; - } - - p += len; - *p++ = '\n'; - *p = '\0'; - - if (stat.tx.update_cnt == 0) - strcpy(last_update, "never"); - else { - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, stat.tx.update); - sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", - now.sec / 3600, - (now.sec % 3600) / 60, - now.sec % 60, - now.msec); - } - - len = pj_ansi_snprintf(p, end-p, - "%s TX pt=%d, ptime=%dms, stat last update: %s\n" - "%s total %spkt %sB (%sB +IP hdr)\n" - "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n" - "%s (msec) min avg max last\n" - "%s loss period: %7.3f %7.3f %7.3f %7.3f\n" - "%s jitter : %7.3f %7.3f %7.3f %7.3f%s", - indent, - info.stream_info[i].tx_pt, - info.stream_info[i].param->info.frm_ptime * - info.stream_info[i].param->setting.frm_per_pkt, - last_update, - - indent, - good_number(packets, stat.tx.pkt), - good_number(bytes, stat.tx.bytes), - good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32), - - indent, - stat.tx.loss, - stat.tx.loss * 100.0 / stat.tx.pkt, - stat.tx.dup, - stat.tx.dup * 100.0 / stat.tx.pkt, - stat.tx.reorder, - stat.tx.reorder * 100.0 / stat.tx.pkt, - - indent, indent, - stat.tx.loss_period.min / 1000.0, - stat.tx.loss_period.avg / 1000.0, - stat.tx.loss_period.max / 1000.0, - stat.tx.loss_period.last / 1000.0, - indent, - stat.tx.jitter.min / 1000.0, - stat.tx.jitter.avg / 1000.0, - stat.tx.jitter.max / 1000.0, - stat.tx.jitter.last / 1000.0, - "" - ); - - if (len < 1 || len > end-p) { - *p = '\0'; - return; - } - - p += len; - *p++ = '\n'; - *p = '\0'; - - len = pj_ansi_snprintf(p, end-p, - "%s RTT msec : %7.3f %7.3f %7.3f %7.3f", - indent, - stat.rtt.min / 1000.0, - stat.rtt.avg / 1000.0, - stat.rtt.max / 1000.0, - stat.rtt.last / 1000.0 - ); - if (len < 1 || len > end-p) { - *p = '\0'; - return; - } - - p += len; - *p++ = '\n'; - *p = '\0'; - } -} - -PJ_DEF(void) pjsua_call_dump(int call_index, int with_media, - char *buffer, unsigned maxlen, - const char *indent) -{ - pjsua_call *call = &pjsua.calls[call_index]; - pj_time_val duration, res_delay, con_delay; - char tmp[128]; - char *p, *end; - int len; - - *buffer = '\0'; - p = buffer; - end = buffer + maxlen; - len = 0; - - PJ_ASSERT_ON_FAIL(call_index >= 0 && - call_index < PJ_ARRAY_SIZE(pjsua.calls), return); - - if (call->inv == NULL) - return; - - print_call(indent, call_index, tmp, sizeof(tmp)); - - len = pj_ansi_strlen(tmp); - pj_ansi_strcpy(buffer, tmp); - - p += len; - *p++ = '\r'; - *p++ = '\n'; - - /* Calculate call duration */ - if (call->inv->state >= PJSIP_INV_STATE_CONFIRMED) { - pj_gettimeofday(&duration); - PJ_TIME_VAL_SUB(duration, call->conn_time); - con_delay = call->conn_time; - PJ_TIME_VAL_SUB(con_delay, call->start_time); - } else { - duration.sec = duration.msec = 0; - con_delay.sec = con_delay.msec = 0; - } - - /* Calculate first response delay */ - if (call->inv->state >= PJSIP_INV_STATE_EARLY) { - res_delay = call->res_time; - PJ_TIME_VAL_SUB(res_delay, call->start_time); - } else { - res_delay.sec = res_delay.msec = 0; - } - - /* Print duration */ - len = pj_ansi_snprintf(p, end-p, - "%s Call time: %02dh:%02dm:%02ds, " - "1st res in %d ms, conn in %dms", - indent, - (duration.sec / 3600), - ((duration.sec % 3600)/60), - (duration.sec % 60), - PJ_TIME_VAL_MSEC(res_delay), - PJ_TIME_VAL_MSEC(con_delay)); - - if (len > 0 && len < end-p) { - p += len; - *p++ = '\n'; - *p = '\0'; - } - - /* Dump session statistics */ - if (with_media && call->session) - dump_media_session(indent, p, end-p, call->session); - -} - -/* - * Dump application states. - */ -PJ_DEF(void) pjsua_dump(pj_bool_t detail) -{ - unsigned old_decor; - char buf[1024]; - - PJ_LOG(3,(THIS_FILE, "Start dumping application states:")); - - old_decor = pj_log_get_decor(); - pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR)); - - if (detail) - pj_dump_config(); - - pjsip_endpt_dump(pjsua.endpt, detail); - pjmedia_endpt_dump(pjsua.med_endpt); - pjsip_tsx_layer_dump(detail); - pjsip_ua_dump(detail); - - - /* Dump all invite sessions: */ - PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:")); - - if (pjsua.call_cnt == 0) { - - PJ_LOG(3,(THIS_FILE, " - no sessions -")); - - } else { - unsigned i; - - for (i=0; iid.slen) { - pj_ansi_sprintf(line, "--id %.*s\n", - (int)acc_cfg->id.slen, - acc_cfg->id.ptr); - pj_strcat2(result, line); - } - - /* Registrar server */ - if (acc_cfg->reg_uri.slen) { - pj_ansi_sprintf(line, "--registrar %.*s\n", - (int)acc_cfg->reg_uri.slen, - acc_cfg->reg_uri.ptr); - pj_strcat2(result, line); - - pj_ansi_sprintf(line, "--reg-timeout %u\n", - acc_cfg->reg_timeout); - pj_strcat2(result, line); - } - - - /* Proxy */ - if (acc_cfg->proxy.slen) { - pj_ansi_sprintf(line, "--proxy %.*s\n", - (int)acc_cfg->proxy.slen, - acc_cfg->proxy.ptr); - pj_strcat2(result, line); - } - - if (acc_cfg->cred_info[0].realm.slen) { - pj_ansi_sprintf(line, "--realm %.*s\n", - (int)acc_cfg->cred_info[0].realm.slen, - acc_cfg->cred_info[0].realm.ptr); - pj_strcat2(result, line); - } - - if (acc_cfg->cred_info[0].username.slen) { - pj_ansi_sprintf(line, "--username %.*s\n", - (int)acc_cfg->cred_info[0].username.slen, - acc_cfg->cred_info[0].username.ptr); - pj_strcat2(result, line); - } - - if (acc_cfg->cred_info[0].data.slen) { - pj_ansi_sprintf(line, "--password %.*s\n", - (int)acc_cfg->cred_info[0].data.slen, - acc_cfg->cred_info[0].data.ptr); - pj_strcat2(result, line); - } - -} - - - -/* - * Dump settings. - */ -PJ_DEF(int) pjsua_dump_settings(const pjsua_config *config, - char *buf, pj_size_t max) -{ - unsigned acc_index; - unsigned i; - pj_str_t cfg; - char line[128]; - - PJ_UNUSED_ARG(max); - - if (config == NULL) - config = &pjsua.config; - - cfg.ptr = buf; - cfg.slen = 0; - - - /* Logging. */ - pj_strcat2(&cfg, "#\n# Logging options:\n#\n"); - pj_ansi_sprintf(line, "--log-level %d\n", - config->log_level); - pj_strcat2(&cfg, line); - - pj_ansi_sprintf(line, "--app-log-level %d\n", - config->app_log_level); - pj_strcat2(&cfg, line); - - if (config->log_filename.slen) { - pj_ansi_sprintf(line, "--log-file %s\n", - config->log_filename.ptr); - pj_strcat2(&cfg, line); - } - - - /* Save account settings. */ - for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) { - - save_account_settings(acc_index, &cfg); - - if (acc_index < config->acc_cnt-1) - pj_strcat2(&cfg, "--next-account\n"); - } - - - pj_strcat2(&cfg, "#\n# Network settings:\n#\n"); - - /* Outbound proxy */ - if (config->outbound_proxy.slen) { - pj_ansi_sprintf(line, "--outbound %.*s\n", - (int)config->outbound_proxy.slen, - config->outbound_proxy.ptr); - pj_strcat2(&cfg, line); - } - - - /* Transport. */ - pj_ansi_sprintf(line, "--local-port %d\n", config->udp_port); - pj_strcat2(&cfg, line); - - - /* STUN */ - if (config->stun_port1) { - pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n", - (int)config->stun_srv1.slen, - config->stun_srv1.ptr, - config->stun_port1); - pj_strcat2(&cfg, line); - } - - if (config->stun_port2) { - pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n", - (int)config->stun_srv2.slen, - config->stun_srv2.ptr, - config->stun_port2); - pj_strcat2(&cfg, line); - } - - - pj_strcat2(&cfg, "#\n# Media settings:\n#\n"); - - - /* Media */ - if (config->null_audio) - pj_strcat2(&cfg, "--null-audio\n"); - if (config->auto_play) - pj_strcat2(&cfg, "--auto-play\n"); - if (config->auto_loop) - pj_strcat2(&cfg, "--auto-loop\n"); - if (config->auto_conf) - pj_strcat2(&cfg, "--auto-conf\n"); - if (config->wav_file.slen) { - pj_ansi_sprintf(line, "--play-file %s\n", - config->wav_file.ptr); - pj_strcat2(&cfg, line); - } - /* Media clock rate. */ - if (config->clock_rate) { - pj_ansi_sprintf(line, "--clock-rate %d\n", - config->clock_rate); - pj_strcat2(&cfg, line); - } - - - /* Encoding quality and complexity */ - if (config->quality > 0) { - pj_ansi_sprintf(line, "--quality %d\n", - config->quality); - pj_strcat2(&cfg, line); - } - if (config->complexity > 0) { - pj_ansi_sprintf(line, "--complexity %d\n", - config->complexity); - pj_strcat2(&cfg, line); - } - - /* ptime */ - if (config->ptime) { - pj_ansi_sprintf(line, "--ptime %d\n", - config->ptime); - pj_strcat2(&cfg, line); - } - - /* Start RTP port. */ - pj_ansi_sprintf(line, "--rtp-port %d\n", - config->start_rtp_port); - pj_strcat2(&cfg, line); - - /* Add codec. */ - for (i=0; icodec_cnt; ++i) { - pj_ansi_sprintf(line, "--add-codec %s\n", - config->codec_arg[i].ptr); - pj_strcat2(&cfg, line); - } - - pj_strcat2(&cfg, "#\n# User agent:\n#\n"); - - /* Auto-answer. */ - if (config->auto_answer != 0) { - pj_ansi_sprintf(line, "--auto-answer %d\n", - config->auto_answer); - pj_strcat2(&cfg, line); - } - - /* Max calls. */ - pj_ansi_sprintf(line, "--max-calls %d\n", - config->max_calls); - pj_strcat2(&cfg, line); - - /* Uas-refresh. */ - if (config->uas_refresh > 0) { - pj_ansi_sprintf(line, "--uas-refresh %d\n", - config->uas_refresh); - pj_strcat2(&cfg, line); - } - - /* Uas-duration. */ - if (config->uas_duration > 0) { - pj_ansi_sprintf(line, "--uas-duration %d\n", - config->uas_duration); - pj_strcat2(&cfg, line); - } - - pj_strcat2(&cfg, "#\n# Buddies:\n#\n"); - - /* Add buddies. */ - for (i=0; ibuddy_cnt; ++i) { - pj_ansi_sprintf(line, "--add-buddy %.*s\n", - (int)config->buddy_uri[i].slen, - config->buddy_uri[i].ptr); - pj_strcat2(&cfg, line); - } - - - *(cfg.ptr + cfg.slen) = '\0'; - return cfg.slen; -} - -/* - * Save settings. - */ -PJ_DEF(pj_status_t) pjsua_save_settings(const char *filename, - const pjsua_config *config) -{ - pj_str_t cfg; - pj_pool_t *pool; - FILE *fhnd; - - /* Create pool for temporary buffer. */ - pool = pj_pool_create(&pjsua.cp.factory, "settings", 4000, 0, NULL); - if (!pool) - return PJ_ENOMEM; - - - cfg.ptr = pj_pool_alloc(pool, 3800); - if (!cfg.ptr) { - pj_pool_release(pool); - return PJ_EBUG; - } - - - cfg.slen = pjsua_dump_settings(config, cfg.ptr, 3800); - if (cfg.slen < 1) { - pj_pool_release(pool); - return PJ_ENOMEM; - } - - - /* Write to file. */ - fhnd = fopen(filename, "wt"); - if (!fhnd) { - pj_pool_release(pool); - return pj_get_os_error(); - } - - fwrite(cfg.ptr, cfg.slen, 1, fhnd); - fclose(fhnd); - - pj_pool_release(pool); - return PJ_SUCCESS; -} - -/** - * Get pjsua running config. - */ -PJ_DEF(void) pjsua_get_config(pj_pool_t *pool, - pjsua_config *cfg) -{ - unsigned i; - - pjsua_copy_config(pool, cfg, &pjsua.config); - - /* Compact buddy uris. */ - for (i=0; iacc_cnt; - } -} - - - -/***************************************************************************** - * This is a very simple PJSIP module, whose sole purpose is to display - * incoming and outgoing messages to log. This module will have priority - * higher than transport layer, which means: - * - * - incoming messages will come to this module first before reaching - * transaction layer. - * - * - outgoing messages will come to this module last, after the message - * has been 'printed' to contiguous buffer by transport layer and - * appropriate transport instance has been decided for this message. - * - */ - -/* Notification on incoming messages */ -static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata) -{ - PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n" - "%s\n" - "--end msg--", - rdata->msg_info.len, - pjsip_rx_data_get_info(rdata), - rdata->pkt_info.src_name, - rdata->pkt_info.src_port, - rdata->msg_info.msg_buf)); - - /* Always return false, otherwise messages will not get processed! */ - return PJ_FALSE; -} - -/* Notification on outgoing messages */ -static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata) -{ - - /* Important note: - * tp_info field is only valid after outgoing messages has passed - * transport layer. So don't try to access tp_info when the module - * has lower priority than transport layer. - */ - - PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n" - "%s\n" - "--end msg--", - (tdata->buf.cur - tdata->buf.start), - pjsip_tx_data_get_info(tdata), - tdata->tp_info.dst_name, - tdata->tp_info.dst_port, - tdata->buf.start)); - - /* Always return success, otherwise message will not get sent! */ - return PJ_SUCCESS; -} - -/* The module instance. */ -pjsip_module pjsua_msg_logger = -{ - NULL, NULL, /* prev, next. */ - { "mod-pjsua-log", 13 }, /* Name. */ - -1, /* Id */ - PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ - NULL, /* load() */ - NULL, /* start() */ - NULL, /* stop() */ - NULL, /* unload() */ - &logging_on_rx_msg, /* on_rx_request() */ - &logging_on_rx_msg, /* on_rx_response() */ - &logging_on_tx_msg, /* on_tx_request. */ - &logging_on_tx_msg, /* on_tx_response() */ - NULL, /* on_tsx_state() */ - -}; - -- cgit v1.2.3