diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-06-13 22:57:13 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-06-13 22:57:13 +0000 |
commit | 5263415f8300e09213e4dd3b684d3c16b8263f9f (patch) | |
tree | 619e3c631a84a5791e4fa9fdf56601afcc0830a9 /pjsip/src | |
parent | 94e741d055535156504bfbb182c8b63412299fb9 (diff) |
-- REWRITE OF PJSUA API --
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@503 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src')
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_acc.c | 776 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 2591 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_console_app.c | 926 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 2113 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_im.c | 281 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_imp.h | 259 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_media.c | 1044 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_pres.c | 908 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_reg.c | 281 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_settings.c | 1435 |
10 files changed, 4831 insertions, 5783 deletions
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 <benny@prijono.org> + * + * 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 <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + + +#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_id<PJ_ARRAY_SIZE(pjsua_var.acc) && + pjsua_var.acc[acc_id].valid; +} + + +/* + * Copy account configuration. + */ +static void copy_acc_config(pj_pool_t *pool, + pjsua_acc_config *dst, + const pjsua_acc_config *src) +{ + unsigned i; + + pj_memcpy(dst, src, sizeof(pjsua_acc_config)); + + pj_strdup_with_null(pool, &dst->id, &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; i<src->proxy_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; i<src->cred_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, "<sip:%.*s:%d;transport=%s>", + (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; i<pjsua_var.ua_cfg.outbound_proxy_cnt; ++i) { + pj_str_t hname = { "Route", 5}; + pjsip_route_hdr *r; + pj_str_t tmp; + + pj_strdup_with_null(pjsua_var.pool, &tmp, + &pjsua_var.ua_cfg.outbound_proxy[i]); + r = pjsip_parse_hdr(pjsua_var.pool, &hname, tmp.ptr, tmp.slen, NULL); + if (r == NULL) { + pjsua_perror(THIS_FILE, "Invalid outbound proxy URI", + PJSIP_EINVALIDURI); + return PJSIP_EINVALIDURI; + } + pj_list_push_back(&acc->route_set, r); + } + + for (i=0; i<acc_cfg->proxy_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; i<acc_cfg->cred_count; ++i) { + acc->cred[acc->cred_cnt++] = acc_cfg->cred_info[i]; + } + for (i=0; i<pjsua_var.ua_cfg.cred_count && + acc->cred_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, "<sip:%.*s:%d>", + (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 && i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + ids[c] = i; + ++c; + } + + *count = c; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * Enum accounts info. + */ +PJ_DEF(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[], + unsigned *count ) +{ + unsigned i, c; + + PJ_ASSERT_RETURN(info && *count, PJ_EINVAL); + + PJSUA_LOCK(); + + for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + + pjsua_acc_get_info(i, &info[c]); + ++c; + } + + *count = c; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * This is an internal function to find the most appropriate account to + * used to reach to the specified URL. + */ +PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *url) +{ + pj_str_t tmp; + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + unsigned acc_id; + + PJSUA_LOCK(); + + PJ_TODO(dont_use_pjsua_pool); + + pj_strdup_with_null(pjsua_var.pool, &tmp, url); + + uri = pjsip_parse_uri(pjsua_var.pool, tmp.ptr, tmp.slen, 0); + if (!uri) { + acc_id = pjsua_var.default_acc; + goto on_return; + } + + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri)) + { + /* Return the first account with proxy */ + for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) { + if (!pjsua_var.acc[acc_id].valid) + continue; + if (!pj_list_empty(&pjsua_var.acc[acc_id].route_set)) + break; + } + + if (acc_id != PJ_ARRAY_SIZE(pjsua_var.acc)) { + /* Found rather matching account */ + goto on_return; + } + + /* Not found, use default account */ + acc_id = pjsua_var.default_acc; + goto on_return; + } + + sip_uri = pjsip_uri_get_uri(uri); + + /* Find matching domain */ + for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) { + if (!pjsua_var.acc[acc_id].valid) + continue; + if (pj_stricmp(&pjsua_var.acc[acc_id].host_part, &sip_uri->host)==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 <pjsua-lib/pjsua.h> -#include <pj/log.h> -#include "pjsua_imp.h" +#include <pjsua-lib/pjsua_internal.h> -/* - * 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); - PJ_UNUSED_ARG(ht); +/* + * Called when session received new offer. + */ +static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer); + +/* + * 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; i<PJ_ARRAY_SIZE(pjsua_var.calls); ++i) + reset_call(i); - PJ_LOG(3,(THIS_FILE, "Media session for call %d is destroyed", - call_index)); + /* Copy config */ + pjsua_config_dup(pjsua_var.pool, &pjsua_var.ua_cfg, cfg); - } + /* Initialize invite session callback. */ + 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; - return PJ_SUCCESS; -} + /* Initialize invite session module: */ + status = pjsip_inv_usage_init(pjsua_var.endpt, &inv_cb); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); -/** - * Get maximum number of calls configured in pjsua. - */ -PJ_DEF(unsigned) pjsua_call_get_max_count(void) -{ - return pjsua.config.max_calls; + return status; } -/** - * Get current number of active calls. +/* + * Start call subsystem. */ -PJ_DEF(unsigned) pjsua_call_get_count(void) +pj_status_t pjsua_call_subsys_start(void) { - return pjsua.call_cnt; + /* Nothing to do */ + return PJ_SUCCESS; } -/** - * Check if the specified call is active. +/* + * Get maximum number of calls configured in pjsua. */ -PJ_DEF(pj_bool_t) pjsua_call_is_active(unsigned call_index) +PJ_DEF(unsigned) pjsua_call_get_max_count(void) { - PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, - PJ_EINVAL); - return pjsua.calls[call_index].inv != NULL && - pjsua.calls[call_index].inv->state != 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); + unsigned i, c; - if (call->conn_time.sec) { - info->connect_duration = call->dis_time; - PJ_TIME_VAL_SUB(info->total_duration, call->conn_time); - } + PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL); - } else if (info->state == PJSIP_INV_STATE_CONFIRMED) { + PJSUA_LOCK(); - 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); + for (i=0, c=0; c<*count && i<pjsua_var.ua_cfg.max_calls; ++i) { + if (!pjsua_var.calls[i].inv) + continue; + ids[c] = i; + ++c; } - info->last_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_id<PJ_ARRAY_SIZE(pjsua_var.acc), PJ_EINVAL); + /* Options must be zero for now */ + PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); + + PJSUA_LOCK(); + + acc = &pjsua_var.acc[acc_id]; + if (!acc->valid) { + 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_index<pjsua.config.max_calls; ++call_index) { - if (pjsua.calls[call_index].inv == NULL) + for (call_id=0; call_id<pjsua_var.ua_cfg.max_calls; ++call_id) { + if (pjsua_var.calls[call_id].inv == NULL) break; } - if (call_index == pjsua.config.max_calls) { - PJ_LOG(3,(THIS_FILE, "Error: too many calls!")); + if (call_id == pjsua_var.ua_cfg.max_calls) { + pjsua_perror(THIS_FILE, "Error making file", PJ_ETOOMANY); + PJSUA_UNLOCK(); return PJ_ETOOMANY; } + call = &pjsua_var.calls[call_id]; + /* Mark call start time. */ - pj_gettimeofday(&pjsua.calls[call_index].start_time); + pj_gettimeofday(&call->start_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,898 +490,478 @@ 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]; - - 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; - } - - /* 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; - - 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.cb.on_call_state) - (*pjsua.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); - - /* Remove timers. */ - schedule_call_timer(call, &call->refresh_tm, REFRESH_CALL_TIMER, 0); - schedule_call_timer(call, &call->hangup_tm, HANGUP_CALL_TIMER, 0); - - /* Free call */ - call->inv = NULL; - --pjsua.call_cnt; - } + 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; } /* - * Callback called by event framework when the xfer subscription state - * has changed. + * Check if call has an active media session. */ -static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id) { - - 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; - - 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; - - PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated")); - } + 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; } /* - * Follow transfer (REFER) request. + * Get the conference port identification associated with the call. */ -static void on_call_transfered( pjsip_inv_session *inv, - pjsip_rx_data *rdata ) +PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id) { - 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); - - 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; - } + 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; +} - /* 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 < 200) - code = 200; - if (code >= 300) { - /* Application rejects call transfer request */ - pjsip_dlg_respond( inv->dlg, rdata, code, NULL, NULL, NULL); - return; - } +/* + * 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; - 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)); + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); - /* Init callback */ - pj_memset(&xfer_cb, 0, sizeof(xfer_cb)); - xfer_cb.on_evsub_state = &xfer_on_evsub_state; + pj_memset(info, 0, sizeof(*info)); - /* 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; - } + PJSUA_LOCK(); - /* Accept the REFER request, send 200 (OK). */ - pjsip_xfer_accept(sub, rdata, code, NULL); + call = &pjsua_var.calls[call_id]; - /* 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; + if (call->inv == NULL) { + PJSUA_UNLOCK(); + return PJ_SUCCESS; } - /* Send initial NOTIFY request */ - status = pjsip_xfer_send_request( sub, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status); - return; - } + pjsip_dlg_inc_lock(call->inv->dlg); - /* 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'; - /* Now make the outgoing call. */ - tmp = pj_str(uri); - status = pjsua_call_make_call(existing_call->acc_index, &tmp, &new_call); - if (status != PJ_SUCCESS) { + /* id and role */ + info->id = call_id; + info->role = call->inv->role; - /* 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; + /* 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; } - /* 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; - - /* Put the invite_data in the subscription. */ - pjsip_evsub_set_mod_data(sub, pjsua.mod.id, &pjsua.calls[new_call]); -} + /* 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)); + /* state, state_text */ + info->state = call->inv->state; + info->state_text = pj_str((char*)pjsip_inv_state_name(info->state)); -/* - * 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) -{ - pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id]; + /* 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; - 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); + 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)); } - 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; + + /* media status and dir */ + info->media_status = call->media_st; + info->media_dir = call->media_dir; - rdata = e->body.tsx_state.src.rdata; - msg = rdata->msg_info.msg; - /* Request MUST have message body, with Content-Type equal to - * "text/plain". - */ - if (pjsua_im_accept_pager(rdata, &accept_hdr) == PJ_FALSE) { + /* conference slot number */ + info->conf_slot = call->conf_slot; - pjsip_hdr hdr_list; + /* calculate duration */ + if (info->state >= PJSIP_INV_STATE_DISCONNECTED) { - pj_list_init(&hdr_list); - pj_list_push_back(&hdr_list, accept_hdr); + info->total_duration = call->dis_time; + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); - pjsip_dlg_respond( inv->dlg, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, - NULL, &hdr_list, NULL ); - return; + if (call->conn_time.sec) { + info->connect_duration = call->dis_time; + PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time); } - /* 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); + } else if (info->state == PJSIP_INV_STATE_CONFIRMED) { - /* Process MESSAGE request */ - pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str, - &inv->dlg->local.info_str, rdata); - } + 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); + } -/* - * 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); + pjsip_dlg_dec_lock(call->inv->dlg); + PJSUA_UNLOCK(); - PJ_TODO(HANDLE_FORKED_DIALOG); + return PJ_SUCCESS; } /* - * Create inactive SDP for call hold. + * Attach application specific data to the call. */ -static pj_status_t create_inactive_sdp(pjsua_call *call, - pjmedia_sdp_session **p_answer) +PJ_DEF(pj_status_t) pjsua_call_set_user_data( pjsua_call_id call_id, + void *user_data) { - 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); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create local SDP", status); - return status; - } - - /* 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.pool, "inactive", NULL); - pjmedia_sdp_media_add_attr(sdp->media[0], attr); - - *p_answer = sdp; + 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; - return status; + return PJ_SUCCESS; } + /* - * Called when session received new offer. + * Get user data attached to the call. */ -static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, - const pjmedia_sdp_session *offer) +PJ_DEF(void*) pjsua_call_get_user_data(pjsua_call_id call_id) { - 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]; - - /* - * See if remote is offering active media (i.e. not on-hold) - */ - is_remote_active = PJ_TRUE; - - 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; - } - - PJ_LOG(4,(THIS_FILE, "Received SDP offer, remote media is %s", - (is_remote_active ? "active" : "inactive"))); - - /* 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 ); - } - - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create local SDP", status); - return; - } - - status = pjsip_inv_set_sdp_answer(call->inv, answer); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to set answer", status); - return; - } - + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + NULL); + return pjsua_var.calls[call_id].user_data; } -#if 0 -/* Disconnect call */ -static void call_disconnect(pjsip_inv_session *inv, - int st_code) -{ - pjsip_tx_data *tdata; - pj_status_t status; - - status = pjsip_inv_end_session(inv, st_code, NULL, &tdata); - if (status == PJ_SUCCESS) - status = pjsip_inv_send_msg(inv, tdata); - - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to disconnect call", 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 response to incoming INVITE request. */ -static void pjsua_call_on_media_update(pjsip_inv_session *inv, - pj_status_t status) +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; - 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]; - - call = inv->dlg->mod_data[pjsua.mod.id]; - - if (status != PJ_SUCCESS) { - - 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); - } - return; - - } - - /* 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); - 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; - } + 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); - status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); - 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_LOCK(); - if (pjsua.config.null_audio) - return; + call = &pjsua_var.calls[call_id]; - /* 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; + if (call->inv == NULL) { + PJ_LOG(3,(THIS_FILE, "Call %d already disconnected", call_id)); + PJSUA_UNLOCK(); + return PJSIP_ESESSIONTERMINATED; } - /* 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.med_endpt, &sess_info, - &call->med_tp, - call, &call->session ); + /* Create response message */ + status = pjsip_inv_answer(call->inv, code, reason, NULL, &tdata); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create media session", + pjsua_perror(THIS_FILE, "Error creating response", status); - //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); - return; + PJSUA_UNLOCK(); + return status; } + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); - /* 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.mconf, call->inv->pool, - media_port, - &port_name, - &call->conf_slot); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create conference slot", + /* Send the message */ + status = pjsip_inv_send_msg(call->inv, tdata); + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error sending response", status); - call_destroy_media(call->index); - //call_disconnect(inv, PJSIP_SC_INTERNAL_SERVER_ERROR); - return; - } - - /* 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) - { - pjmedia_conf_connect_port( pjsua.mconf, pjsua.player[0].slot, - call->conf_slot, 0); + PJSUA_UNLOCK(); - } - if (pjsua.config.auto_loop && call->inv->role == PJSIP_ROLE_UAS) { - - pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, - call->conf_slot, 0); - - } - if (pjsua.config.auto_conf) { - unsigned i; - - pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot, 0); - pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0, 0); - - for (i=0; i < pjsua.config.max_calls; ++i) { - - if (!pjsua.calls[i].session) - continue; - - 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); - } - - } - - /* 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); - } - - - /* Done. */ - { - struct pjmedia_session_info sess_info; - char info[80]; - int info_len = 0; - unsigned i; - - pjmedia_session_get_info(call->session, &sess_info); - for (i=0; i<sess_info.stream_cnt; ++i) { - int len; - const char *dir; - pjmedia_stream_info *strm_info = &sess_info.stream_info[i]; - - switch (strm_info->dir) { - 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)); - } + return status; } /* - * Hangup call. + * Hangup call by using method that is appropriate according to the + * call state. */ -PJ_DEF(void) pjsua_call_hangup(int call_index) +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; - int code; pj_status_t status; pjsip_tx_data *tdata; - call = &pjsua.calls[call_index]; + 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")); - return; + PJSUA_UNLOCK(); + return 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; + 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; + } - status = pjsip_inv_end_session(call->inv, code, NULL, &tdata); + 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); - return; + PJSUA_UNLOCK(); + return status; } /* 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; + if (tdata == NULL) { + PJSUA_UNLOCK(); + return PJ_SUCCESS; + } + + /* 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, "Failed to send end session message", status); - return; + PJSUA_UNLOCK(); + return status; } + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; } /* - * Put call on-Hold. + * Put the specified call on hold. */ -PJ_DEF(pj_status_t) pjsua_call_set_hold(int call_index) +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; - call = &pjsua.calls[call_index]; + 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) + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); return status; + } - /* Send re-INVITE with new offer */ + /* 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; } + PJSUA_UNLOCK(); + return PJ_SUCCESS; } /* - * re-INVITE. + * Send re-INVITE (to release hold). */ -PJ_DEF(pj_status_t) pjsua_call_reinvite(int call_index) +PJ_DEF(pj_status_t) pjsua_call_reinvite( pjsua_call_id call_id, + pj_bool_t unhold, + const pjsua_msg_data *msg_data) { pjmedia_sdp_session *sdp; pjsip_tx_data *tdata; pjsua_call *call; pj_status_t status; - call = &pjsua.calls[call_index]; + + 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 re-INVITE call that is not confirmed")); + PJSUA_UNLOCK(); return PJSIP_ESESSIONSTATE; } /* Create SDP */ - status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1, - &call->skinfo, &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; } - /* Send re-INVITE with new offer */ + /* 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; } + PJSUA_UNLOCK(); + return PJ_SUCCESS; } /* - * Transfer call. + * Initiate call transfer to the specified address. */ -PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest) +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; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); - call = &pjsua.calls[call_index]; + 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; } @@ -1476,6 +972,7 @@ PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest) status = pjsip_xfer_create_uac(call->inv->dlg, NULL, &sub); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create xfer", status); + PJSUA_UNLOCK(); return status; } @@ -1485,13 +982,18 @@ PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest) 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; } + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + /* 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; } @@ -1500,50 +1002,81 @@ PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest) * may want to hold the INVITE, or terminate the invite, or whatever. */ + PJSUA_UNLOCK(); + return PJ_SUCCESS; + } -/** - * Dial DTMF. +/* + * Send DTMF digits to remote using RFC 2833 payload formats. */ -PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( unsigned call_index, +PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id, const pj_str_t *digits) { - pjsua_call *call = &pjsua.calls[call_index]; + pjsua_call *call; + pj_status_t status; - PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, PJ_EINVAL); + 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->session) { PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); - return -1; + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; } - return pjmedia_session_dial_dtmf( call->session, 0, digits); + status = pjmedia_session_dial_dtmf( call->session, 0, digits); + + PJSUA_UNLOCK(); + + return status; } /** * Send instant messaging inside INVITE session. */ -PJ_DEF(pj_status_t) pjsua_call_send_im(int call_index, const pj_str_t *str) +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 = pj_str("text"); - const pj_str_t mime_plain = pj_str("plain"); + 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; - call = &pjsua.calls[call_index]; + + 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; } /* Lock dialog. */ pjsip_dlg_inc_lock(call->inv->dlg); - + + /* 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); @@ -1556,17 +1089,33 @@ PJ_DEF(pj_status_t) pjsua_call_send_im(int call_index, const pj_str_t *str) 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, &mime_text, - &mime_plain, str); + 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); + + /* 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; + + /* Send the request. */ - status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL); + 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 send MESSAGE request", status); goto on_return; @@ -1574,24 +1123,33 @@ PJ_DEF(pj_status_t) pjsua_call_send_im(int call_index, const pj_str_t *str) on_return: pjsip_dlg_dec_lock(call->inv->dlg); + PJSUA_UNLOCK(); return status; } -/** +/* * 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) +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; pjsip_tx_data *tdata; pj_status_t status; - call = &pjsua.calls[call_index]; + + 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; } @@ -1610,6 +1168,9 @@ PJ_DEF(pj_status_t) pjsua_call_send_typing_ind(int call_index, 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); + /* Send the request. */ status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL); if (status != PJ_SUCCESS) { @@ -1619,6 +1180,7 @@ PJ_DEF(pj_status_t) pjsua_call_send_typing_ind(int call_index, on_return: pjsip_dlg_dec_lock(call->inv->dlg); + PJSUA_UNLOCK(); return status; } @@ -1630,65 +1192,1070 @@ PJ_DEF(void) pjsua_call_hangup_all(void) { unsigned i; - for (i=0; i<pjsua.config.max_calls; ++i) { - pjsip_tx_data *tdata; - int st_code; - pjsua_call *call; + PJSUA_LOCK(); - if (pjsua.calls[i].inv == NULL) - continue; + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + if (pjsua_var.calls[i].inv) + pjsua_call_hangup(i, 0, NULL, NULL); + } - call = &pjsua.calls[i]; + PJSUA_UNLOCK(); +} - if (call->inv->state == PJSIP_INV_STATE_CONFIRMED) { - st_code = 200; - } else { - st_code = PJSIP_SC_GONE; + +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; +} + + +/* 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; i<info.stream_cnt; ++i) { + pjmedia_rtcp_stat stat; + const char *rem_addr; + int rem_port; + const char *dir; + char last_update[40]; + char packets[16], bytes[16], ipbytes[16]; + pj_time_val now; + + pjmedia_session_get_stream_stat(session, i, &stat); + rem_addr = pj_inet_ntoa(info.stream_info[i].rem_addr.sin_addr); + rem_port = pj_ntohs(info.stream_info[i].rem_addr.sin_port); + + if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING) + dir = "sendonly"; + else if (info.stream_info[i].dir == PJMEDIA_DIR_DECODING) + dir = "recvonly"; + else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING_DECODING) + dir = "sendrecv"; + else + dir = "inactive"; + + + 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; + } + + 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; } - if (pjsip_inv_end_session(call->inv, st_code, NULL, &tdata)==0) { - if (tdata) - pjsip_inv_send_msg(call->inv, tdata); + 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_status_t pjsua_call_init(void) +/* Print call info */ +static void print_call(const char *title, + int call_id, + char *buf, pj_size_t size) { - /* Initialize invite session callback. */ - pjsip_inv_callback inv_cb; - pj_status_t status; + int len; + pjsip_inv_session *inv = pjsua_var.calls[call_id].inv; + pjsip_dialog *dlg = inv->dlg; + char userinfo[128]; - 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; + /* 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, + 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'; +} - /* Initialize invite session module: */ - status = pjsip_inv_usage_init(pjsua.endpt, &inv_cb); + +/* + * Dump call and media statistics to string. + */ +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; + pj_time_val duration, res_delay, con_delay; + char tmp[128]; + char *p, *end; + int len; + + 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]; + + *buffer = '\0'; + p = buffer; + end = buffer + maxlen; + len = 0; + + if (call->inv == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } + + print_call(indent, call_id, 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); + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * Destroy the call's media + */ +static pj_status_t call_destroy_media(int call_id) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + + 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; + + PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed", + call_id)); + + } + + call->media_st = PJSUA_CALL_MEDIA_NONE; + + return PJ_SUCCESS; +} + + +/* + * 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) +{ + pjsua_call *call; + + PJSUA_LOCK(); + + call = inv->dlg->mod_data[pjsua_var.mod.id]; + + if (!call) { + PJSUA_UNLOCK(); + 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; + 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. + */ + 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, "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; + } + + /* 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 retrieve currently active local SDP", + status); + //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + PJSUA_UNLOCK(); + return; + } + + + status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); + if (status != PJ_SUCCESS) { + 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; i<sess_info.stream_cnt; ++i) { + int len; + const char *dir; + pjmedia_stream_info *strm_info = &sess_info.stream_info[i]; + + switch (strm_info->dir) { + 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; + } + + /* 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; } -/** - * Replace media transport. + +/* + * Called when session received new offer. */ -PJ_DEF(pj_status_t) pjsua_set_call_media_transport( unsigned call_index, - const pjmedia_sock_info *i, - pjmedia_transport *tp) +static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer) { - pjsua_call *call = &pjsua.calls[call_index]; + 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; + + 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; + } - if (i) - pj_memcpy(&call->skinfo, i, sizeof(pjmedia_sock_info)); + 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(); +} + + +/* + * Callback called by event framework when the xfer subscription state + * has changed. + */ +static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ - if (call->med_tp) - (*call->med_tp->op->destroy)(call->med_tp); + PJ_UNUSED_ARG(event); - call->med_tp = tp; - return PJ_SUCCESS; + /* + * 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; + + call = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!call) + return; + + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + call->xfer_sub = NULL; + + PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated")); + } +} + + +/* + * Follow transfer (REFER) request. + */ +static void on_call_transfered( pjsip_inv_session *inv, + pjsip_rx_data *rdata ) +{ + 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_var.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); + + 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; + } + + /* 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); + + if (code < 200) + code = 200; + if (code >= 300) { + /* Application rejects call transfer request */ + pjsip_dlg_respond( inv->dlg, rdata, code, NULL, NULL, NULL); + return; + } + + 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; + + /* 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; + } + + /* Accept the REFER request, send 200 (OK). */ + pjsip_xfer_accept(sub, rdata, code, NULL); + + /* 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; + } + + /* Send initial NOTIFY request */ + status = pjsip_xfer_send_request( sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status); + return; + } + + /* 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'; + + /* 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) { + + /* 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; + } + + /* 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]); +} + + + +/* + * 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) +{ + pjsua_call *call = inv->dlg->mod_data[pjsua_var.mod.id]; + + PJSUA_LOCK(); + + 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; + + rdata = e->body.tsx_state.src.rdata; + msg = rdata->msg_info.msg; + + /* 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_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 ); + 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); + + /* Process MESSAGE request */ + pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str, + &inv->dlg->local.info_str, rdata); + + } + 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); + } + } + } + + + 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 <benny@prijono.org> - * - * 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 <pjsua-lib/pjsua.h> -#include <pjsua-lib/pjsua_console_app.h> -#include <stdlib.h> /* atoi */ -#include <stdio.h> - -#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<max; ++i) { - if (pjsua_call_is_active(i)) { - current_call = i; - return PJ_TRUE; - } - } - - for (i=0; i<current_call; ++i) { - if (pjsua_call_is_active(i)) { - current_call = i; - return PJ_TRUE; - } - } - - current_call = -1; - return PJ_FALSE; -} - - -/* - * Find previous call. - */ -static pj_bool_t find_prev_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<count; ++i) { - pjsua_buddy_info info; - - if (pjsua_buddy_get_info(i, &info) != PJ_SUCCESS) - continue; - - if (!info.is_valid) - continue; - - printf(" [%2d] <%7s> %.*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" - " <Enter> 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 (; i<len; ++i) { - if (!pj_isdigit(buf[i])) { - puts("Invalid input"); - return; - } - } - - result->nb_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; i<count; ++i) { - char txlist[PJSUA_MAX_CALLS*4+10]; - unsigned j; - pjsua_conf_port_info info; - - pjsua_conf_get_port_info(id[i], &info); - - txlist[0] = '\0'; - for (j=0; j<info.listener_cnt; ++j) { - char s[10]; - pj_ansi_sprintf(s, "#%d ", info.listeners[j]); - pj_ansi_strcat(txlist, s); - } - printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n", - info.slot_id, - info.clock_rate/1000, - info.samples_per_frame * 1000 / info.clock_rate, - (int)info.name.slen, - info.name.ptr, - txlist); - - } - puts(""); -} - - -void pjsua_console_app_main(const pj_str_t *uri_to_call) -{ - char menuin[10]; - char buf[128]; - char text[128]; - int i, count; - char *uri; - pj_str_t tmp; - struct input_result result; - pjsua_call_info call_info; - pjsua_acc_info acc_info; - - - /* If user specifies URI to call, then call the URI */ - if (uri_to_call->slen) { - 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<atoi(menuin); ++i) { - pj_status_t status; - - tmp = pj_str(uri); - status = pjsua_call_make_call(current_acc, &tmp, NULL); - if (status != PJ_SUCCESS) - break; - } - break; - - case 'i': - /* Send instant messaeg */ - - /* i is for call index to send message, if any */ - i = -1; - - /* Make compiler happy. */ - uri = NULL; - - /* Input destination. */ - ui_input_url("Send IM to", buf, sizeof(buf), &result); - if (result.nb_result != NO_NB) { - - if (result.nb_result == -1) { - puts("You can't send broadcast IM like that!"); - continue; - - } else if (result.nb_result == 0) { - - i = current_call; - - } 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; - } - - - /* Send typing indication. */ - if (i != -1) - pjsua_call_send_typing_ind(i, PJ_TRUE); - else { - pj_str_t tmp_uri = pj_str(uri); - pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE); - } - - /* Input the IM . */ - if (!simple_input("Message", text, sizeof(text))) { - /* - * Cancelled. - * Send typing notification too, saying we're not typing. - */ - if (i != -1) - pjsua_call_send_typing_ind(i, PJ_FALSE); - else { - pj_str_t tmp_uri = pj_str(uri); - pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE); - } - continue; - } - - tmp = pj_str(text); - - /* Send the IM */ - if (i != -1) - pjsua_call_send_im(i, &tmp); - else { - pj_str_t tmp_uri = pj_str(uri); - pjsua_im_send(current_acc, &tmp_uri, &tmp); - } - - break; - - case 'a': - - if (current_call != -1) { - pjsua_call_get_info(current_call, &call_info); - } else { - /* Make compiler happy */ - call_info.active = 0; - call_info.role = PJSIP_ROLE_UAC; - call_info.state = PJSIP_INV_STATE_DISCONNECTED; - } - - if (current_call == -1 || - call_info.active==0 || - call_info.role != PJSIP_ROLE_UAS || - call_info.state >= 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<count; ++i) - pjsua_buddy_subscribe_pres(i, menuin[0]=='s'); - } else if (result.nb_result == 0) { - puts("Sorry, can only subscribe to buddy's presence, " - "not from existing call"); - } else { - pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s')); - } - - } else if (result.uri_result) { - puts("Sorry, can only subscribe to buddy's presence, " - "not arbitrary URL (for now)"); - } - - break; - - case 'r': - switch (menuin[1]) { - case 'r': - /* - * Re-Register. - */ - pjsua_acc_set_registration(current_acc, PJ_TRUE); - break; - case 'u': - /* - * Unregister - */ - pjsua_acc_set_registration(current_acc, PJ_FALSE); - break; - } - break; - - case 't': - pjsua_acc_get_info(current_acc, &acc_info); - acc_info.online_status = !acc_info.online_status; - pjsua_acc_set_online_status(current_acc, acc_info.online_status); - printf("Setting %s online status to %s\n", - acc_info.acc_id.ptr, - (acc_info.online_status?"online":"offline")); - break; - - case 'c': - switch (menuin[1]) { - case 'l': - conf_list(); - break; - case 'c': - case 'd': - { - char src_port[10], dst_port[10]; - pj_status_t status; - const char *src_title, *dst_title; - - conf_list(); - - src_title = (menuin[1]=='c'? - "Connect src port #": - "Disconnect src port #"); - dst_title = (menuin[1]=='c'? - "To dst port #": - "From dst port #"); - - if (!simple_input(src_title, src_port, sizeof(src_port))) - break; - - if (!simple_input(dst_title, dst_port, sizeof(dst_port))) - break; - - if (menuin[1]=='c') { - status = pjsua_conf_connect(atoi(src_port), - atoi(dst_port)); - } else { - status = pjsua_conf_disconnect(atoi(src_port), - atoi(dst_port)); - } - if (status == PJ_SUCCESS) { - puts("Success"); - } else { - puts("ERROR!!"); - } - } - break; - } - break; - - case 'd': - if (menuin[1] == 'c') { - char settings[2000]; - int len; - - len = pjsua_dump_settings(NULL, settings, - sizeof(settings)); - if (len < 1) - PJ_LOG(3,(THIS_FILE, "Error: not enough buffer")); - else - PJ_LOG(3,(THIS_FILE, - "Dumping configuration (%d bytes):\n%s\n", - len, settings)); - } else { - pjsua_dump(menuin[1]=='d'); - } - break; - - case 'q': - goto on_exit; - - default: - if (menuin[0] != '\n' && menuin[0] != '\r') { - printf("Invalid input %s", menuin); - } - keystroke_help(); - break; - } - } - -on_exit: - ; -} - - -/***************************************************************************** - * Error display: - */ - -/* - * Display error message for the specified error code. - */ -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(1,(sender, "%s: %s [code=%d]", title, errmsg, status)); -} - - - -pjsua_callback console_callback = -{ - &console_on_call_state, - NULL, /* on_incoming_call */ - NULL, /* default accept transfer */ - &console_on_reg_state, - &console_on_buddy_state, - &console_on_pager, - &console_on_typing, -}; diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 01b6c8c4..e269fa07 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -17,178 +17,118 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <pjsua-lib/pjsua.h> -#include "pjsua_imp.h" +#include <pjsua-lib/pjsua_internal.h> -/* - * 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 "<sip:user@127.0.0.1>" +/* 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; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) + pjsua_var.acc[i].index = i; - cfg->auto_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; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) { - cfg->acc_config[i].reg_timeout = 55; - } - + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata); ++i) + pjsua_var.tpdata[i].index = i; } -#define strncpy_with_null(dst,src,len) \ -do { \ - strncpy(dst, src, len); \ - dst[len-1] = '\0'; \ -} while (0) - +/***************************************************************************** + * 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. + * + */ -PJ_DEF(pj_status_t) pjsua_test_config( const pjsua_config *cfg, - char *errmsg, - int len) +/* Notification on incoming messages */ +static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata) { - unsigned i; - - /* If UDP port is zero, then sip_host and sip_port must be specified */ - if (cfg->udp_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; i<cfg->acc_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; j<acc_cfg->cred_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,742 +174,199 @@ 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; - } - - 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; - } - - } else { - - const pj_str_t *hostname = pj_gethostname(); - struct pj_hostent he; - - status = pj_gethostbyname(hostname, &he); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to resolve local host", status); - pj_sock_close(sock); - return status; - } + /* Redirect log function to ours */ + pj_log_set_log_func( &log_writer ); - 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; + /* Close existing file, if any */ + if (pjsua_var.log_file) { + pj_file_close(pjsua_var.log_file); + pjsua_var.log_file = NULL; } - *p_sock = sock; - 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(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; i<RTP_RETRY; ++i, rtp_port += 2) { - - /* Create and bind RTP socket. */ - status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "socket() error", status); - return status; - } + /* If output log file is desired, create the file: */ + if (pjsua_var.log_cfg.log_filename.slen) { - status = pj_sock_bind_in(sock[0], 0, rtp_port); - if (status != PJ_SUCCESS) { - pj_sock_close(sock[0]); - sock[0] = PJ_INVALID_SOCKET; - continue; - } + status = pj_file_open(pjsua_var.pool, + pjsua_var.log_cfg.log_filename.ptr, + PJ_O_WRONLY, + &pjsua_var.log_file); - /* 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]); + pjsua_perror(THIS_FILE, "Error creating log file", status); return status; } - - status = pj_sock_bind_in(sock[1], 0, (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 (pjsua_has_stun()) { - status=pj_stun_get_mapped_addr(&pjsua.cp.factory, 2, sock, - &pjsua.config.stun_srv1, - pjsua.config.stun_port1, - &pjsua.config.stun_srv2, - pjsua.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; + /* 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; } + /* Enable SIP message logging */ + if (pjsua_var.log_cfg.msg_logging) + pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_msg_logger); - 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; } - -/** - * Create pjsua application. - * This initializes pjlib/pjlib-util, and creates memory pool factory to - * be used by application. +/***************************************************************************** + * PJSUA Base API. */ -PJ_DEF(pj_status_t) pjsua_create(void) -{ - pj_status_t status; - /* Init PJLIB: */ - - status = pj_init(); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "pj_init() error", status); - return status; - } - - /* Init PJLIB-UTIL: */ - - status = pjlib_util_init(); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "pjlib_util_init() error", status); - return status; - } - - /* Init memory pool: */ - - /* Init caching pool. */ - pj_caching_pool_init(&pjsua.cp, &pj_pool_factory_default_policy, 0); - - /* Create memory pool for application. */ - pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL); +/* Worker thread function. */ +static int worker_thread(void *arg) +{ + enum { TIMEOUT = 10 }; - /* Must create endpoint to initialize SIP parser. */ - /* Create global endpoint: */ + PJ_UNUSED_ARG(arg); - status = pjsip_endpt_create(&pjsua.cp.factory, - pj_gethostname()->ptr, - &pjsua.endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create SIP endpoint", status); - return status; - } + while (!pjsua_var.thread_quit_flag) { + int count; - /* 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; + count = pjsua_handle_events(TIMEOUT); + if (count < 0) + pj_thread_sleep(TIMEOUT); } - - return PJ_SUCCESS; + return 0; } - /* - * Init media. + * Instantiate pjsua application. */ -static pj_status_t init_media(void) +PJ_DEF(pj_status_t) pjsua_create(void) { - int i; - unsigned options; - pj_str_t codec_id; 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 */ - -#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; - } -#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; - } -#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; - } + /* Init pjsua data */ + init_data(); - /* 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); + /* Set default logging settings */ + pjsua_logging_config_default(&pjsua_var.log_cfg); -#endif /* PJMEDIA_HAS_L16_CODEC */ + /* Init PJLIB: */ + status = pj_init(); + 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); - } + /* Init PJLIB-UTIL: */ + status = pjlib_util_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - /* Init options for conference bridge. */ - options = PJMEDIA_CONF_NO_DEVICE; + /* Init caching pool. */ + pj_caching_pool_init(&pjsua_var.cp, &pj_pool_factory_default_policy, 0); - /* 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; - } + /* Create memory pool for application. */ + pjsua_var.pool = pjsua_pool_create("pjsua", 4000, 4000); + + PJ_ASSERT_RETURN(pjsua_var.pool, PJ_ENOMEM); - /* 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); + /* Create mutex */ + status = pj_mutex_create_recursive(pjsua_var.pool, "pjsua", + &pjsua_var.mutex); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Media stack initialization has returned error", - status); + pjsua_perror(THIS_FILE, "Unable to create mutex", status); return 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; - } - - /* 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); - - } 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; - } - - /* 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; j<src_acc->cred_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; i<src->codec_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; i<src->acc_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; i<src->buddy_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; - } - } + /* 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); - /* 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. + * Initialize pjsua with the specified settings. All the settings are + * optional, and the default values will be used when the config is not + * specified. */ -PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, - const pjsua_callback *cb) +PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, + const pjsua_logging_config *log_cfg, + const pjsua_media_config *media_cfg) { - char errmsg[80]; - unsigned i; + pjsua_config default_cfg; + pjsua_media_config default_media_cfg; pj_status_t status; - /* Init accounts: */ - for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) { - pjsua.acc[i].index = i; - pjsua.acc[i].online_status = PJ_TRUE; - pj_list_init(&pjsua.acc[i].route_set); - pj_list_init(&pjsua.acc[i].pres_srv_list); - } - - /* Init call array: */ - for (i=0; i<PJ_ARRAY_SIZE(pjsua.calls); ++i) { - pjsua.calls[i].index = i; - pjsua.calls[i].refresh_tm._timer_id = -1; - pjsua.calls[i].hangup_tm._timer_id = -1; - pjsua.calls[i].conf_slot = 0; - } - - /* Init buddies array */ - for (i=0; i<PJ_ARRAY_SIZE(pjsua.buddies); ++i) { - pjsua.buddies[i].index = i; - } - - /* Copy configuration */ - pjsua_copy_config(pjsua.pool, &pjsua.config, cfg); - - /* Copy callback */ - pj_memcpy(&pjsua.cb, cb, sizeof(pjsua_callback)); + /* Create default configurations when the config is not supplied */ - /* Test configuration */ - if (pjsua_test_config(&pjsua.config, errmsg, sizeof(errmsg))) { - PJ_LOG(1,(THIS_FILE, "Error in configuration: %s", errmsg)); - status = -1; - goto on_error; + if (ua_cfg == NULL) { + pjsua_config_default(&default_cfg); + ua_cfg = &default_cfg; } - - /* Init PJLIB logging: */ - - pj_log_set_level(pjsua.config.log_level); - pj_log_set_decor(pjsua.config.log_decor); - - status = logging_init(); - if (status != PJ_SUCCESS) - goto on_error; - - - /* Create SIP UDP socket */ - if (pjsua.config.udp_port) { - - status = create_sip_udp_sock( pjsua.config.udp_port, - &pjsua.sip_sock, - &pjsua.sip_sock_name); - if (status != PJ_SUCCESS) - goto on_error; - - pj_strdup2_with_null(pjsua.pool, &pjsua.config.sip_host, - pj_inet_ntoa(pjsua.sip_sock_name.sin_addr)); - pjsua.config.sip_port = pj_ntohs(pjsua.sip_sock_name.sin_port); - - } else { - - /* Check that SIP host and port is configured */ - if (cfg->sip_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; + if (media_cfg == NULL) { + pjsua_media_config_default(&default_media_cfg); + media_cfg = &default_media_cfg; } - - /* Init media endpoint */ - status = init_media(); - if (status != PJ_SUCCESS) - goto on_error; - - - /* Init RTP sockets, only when UDP transport is enabled */ - for (i=0; pjsua.config.start_rtp_port && i<pjsua.config.max_calls; ++i) { - status = create_rtp_rtcp_sock(&pjsua.calls[i].skinfo); - if (status != PJ_SUCCESS) { - unsigned j; - for (j=0; j<i; ++j) { - if (pjsua.calls[i].med_tp) - pjsua.calls[i].med_tp->op->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); + /* 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); } - /* Init PJSIP : */ + /* Init SIP UA: */ /* Initialize transaction layer: */ + status = pjsip_tsx_layer_init_module(pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - 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_var.endpt, NULL ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - 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: */ + /* Initialize and register PJSUA application module. */ { - pjsip_module my_mod = + const pjsip_module mod_initializer = { NULL, NULL, /* prev, next. */ { "mod-pjsua", 9 }, /* Name. */ @@ -981,47 +383,70 @@ PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, NULL, /* on_tsx_state() */ }; - pjsua.mod = my_mod; + pjsua_var.mod = mod_initializer; - 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; - } + status = pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_var.mod); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); } - /* Initialize invite session module: */ + - status = pjsua_call_init(); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Invite usage initialization error", - status); + /* Initialize PJSUA call subsystem: */ + status = pjsua_call_subsys_init(ua_cfg); + if (status != PJ_SUCCESS) goto on_error; - } + + + /* Initialize PJSUA media subsystem */ + status = pjsua_media_subsys_init(media_cfg); + if (status != PJ_SUCCESS) + goto on_error; + /* Init core SIMPLE module : */ + status = pjsip_evsub_init_module(pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - pjsip_evsub_init_module(pjsua.endpt); /* Init presence module: */ + status = pjsip_pres_init_module( pjsua_var.endpt, pjsip_evsub_instance()); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance()); /* Init xfer/REFER module */ - - pjsip_xfer_init_module( pjsua.endpt ); + status = pjsip_xfer_init_module( pjsua_var.endpt ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); /* Init pjsua presence handler: */ - - pjsua_pres_init(); + status = pjsua_pres_init(); + if (status != PJ_SUCCESS) + goto on_error; /* Init out-of-dialog MESSAGE request handler. */ + status = pjsua_im_init(); + if (status != PJ_SUCCESS) + goto on_error; - pjsua_im_init(); + /* Start worker thread if needed. */ + if (pjsua_var.ua_cfg.thread_cnt) { + unsigned i; + 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); + + for (i=0; i<pjsua_var.ua_cfg.thread_cnt; ++i) { + status = pj_thread_create(pjsua_var.pool, "pjsua", &worker_thread, + NULL, 0, 0, &pjsua_var.thread[i]); + if (status != PJ_SUCCESS) + goto on_error; + } + } + + /* Done! */ + + PJ_LOG(3,(THIS_FILE, "pjsua version %s for %s initialized", + PJ_VERSION, PJ_OS_NAME)); - /* Done. */ return PJ_SUCCESS; on_error: @@ -1030,836 +455,600 @@ on_error: } -/* - * Find account for incoming request. - */ -PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata) +/* Sleep with polling */ +static void busy_sleep(unsigned msec) { - pjsip_uri *uri; - pjsip_sip_uri *sip_uri; - unsigned acc_index; - - 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.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; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) { - if (!pjsua.acc[i].valid) - continue; - if (pjsua.config.acc_config[i].proxy.slen) - break; + /* Wait worker threads to quit: */ + for (i=0; i<(int)pjsua_var.ua_cfg.thread_cnt; ++i) { + if (pjsua_var.thread[i]) { + pj_thread_join(pjsua_var.thread[i]); + pj_thread_destroy(pjsua_var.thread[i]); + pjsua_var.thread[i] = NULL; } - - if (i != PJ_ARRAY_SIZE(pjsua.acc)) - return i; - - /* Not found, use default account */ - return pjsua.default_acc; - } - - sip_uri = pjsip_uri_get_uri(uri); - - /* Find matching domain */ - for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) { - if (!pjsua.acc[i].valid) - continue; - if (pj_stricmp(&pjsua.acc[i].host_part, &sip_uri->host)==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; } - - /* 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), - "<sip:%.*s@%s:%d>", - (int)sip_uri->user.slen, - sip_uri->user.ptr, - addr, port); - } else { - - /* Without user part */ - - len = pj_ansi_snprintf(contact, sizeof(contact), - "<sip:%s:%d>", - addr, port); - } - - if (len < 1 || len >= sizeof(contact)) { - pjsua_perror(THIS_FILE, "Invalid Contact", PJSIP_EURITOOLONG); - return PJSIP_EURITOOLONG; - } - - /* Duplicate Contact uri. */ - - pj_strdup2(pjsua.pool, &acc_cfg->contact, contact); - + /* Destroy mutex */ + if (pjsua_var.mutex) { + pj_mutex_destroy(pjsua_var.mutex); + pjsua_var.mutex = NULL; } - - /* 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); + /* 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); } - if (acc_cfg->proxy.slen) { - pj_str_t hname = { "Route", 5}; - pjsip_route_hdr *r; - pj_str_t tmp; - 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); - } - - /* Mark account as valid */ - pjsua.acc[acc_index].valid = PJ_TRUE; + 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; } -/* - * Add a new account. + +/** + * 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_acc_add( const pjsua_acc_config *cfg, - pjsua_acc_id *acc_index) +PJ_DEF(pj_status_t) pjsua_start(void) { - unsigned index; 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); + status = pjsua_call_subsys_start(); + if (status != PJ_SUCCESS) return status; - } - if (acc_index) - *acc_index = index; + status = pjsua_media_subsys_start(); + if (status != PJ_SUCCESS) + return status; - pjsua.config.acc_cnt++; + status = pjsua_pres_start(); + if (status != PJ_SUCCESS) + return status; return PJ_SUCCESS; } -/* - * Delete 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_del(pjsua_acc_id acc_index) +PJ_DEF(int) pjsua_handle_events(unsigned msec_timeout) { - PJ_ASSERT_RETURN(acc_index < (int)pjsua.config.acc_cnt, - PJ_EINVAL); - PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP); + unsigned count = 0; + pj_time_val tv; + pj_status_t status; + + tv.sec = 0; + tv.msec = msec_timeout; + pj_time_val_normalize(&tv); - /* Delete registration */ - if (pjsua.acc[acc_index].regc != NULL) - pjsua_acc_set_registration(acc_index, PJ_FALSE); + status = pjsip_endpt_handle_events2(pjsua_var.endpt, &tv, &count); - /* Invalidate */ - pjsua.acc[acc_index].valid = PJ_FALSE; + if (status != PJ_SUCCESS) + return -status; - return PJ_SUCCESS; + return count; } /* - * Start pjsua stack. - * This will start the registration process, if registration is configured. + * Create memory pool. */ -PJ_DEF(pj_status_t) pjsua_start(void) +PJ_DEF(pj_pool_t*) pjsua_pool_create( const char *name, pj_size_t init_size, + pj_size_t increment) { - int i; /* Must be signed */ - unsigned count; - pj_status_t status = PJ_SUCCESS; - - - /* Add UDP transport: */ - if (pjsua.sip_sock > 0) { + /* Pool factory is thread safe, no need to lock */ + return pj_pool_create(&pjsua_var.cp.factory, name, init_size, increment, + NULL); +} - /* 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; - addr_name.host = pjsua.config.sip_host; - addr_name.port = pjsua.config.sip_port; +/* + * 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(pjsip_endpoint*) pjsua_get_pjsip_endpt(void) +{ + return pjsua_var.endpt; +} - /* Create UDP transport from previously created UDP socket: */ +/* + * 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; +} - 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; - } - } - /* Initialize all unused accounts with default id and contact. - */ - { - char buf[80]; - pj_str_t tmp, id; +/***************************************************************************** + * PJSUA SIP Transport API. + */ - tmp.ptr = buf; - tmp.slen = pj_ansi_sprintf(tmp.ptr, "Local <sip:%s:%d>", - pjsua.config.sip_host.ptr, - pjsua.config.sip_port); - pj_strdup_with_null( pjsua.pool, &id, &tmp); +/* + * 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; - for (i=pjsua.config.acc_cnt; i<PJ_ARRAY_SIZE(pjsua.config.acc_config); - ++i) - { - pjsua_acc_config *acc_cfg = - &pjsua.config.acc_config[pjsua.config.acc_cnt]; + PJSUA_LOCK(); - acc_cfg->id = id; - acc_cfg->contact = id; - } + 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; } - - /* 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++; + 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; } - - /* Initialize accounts: */ - for (i=0; i<(int)pjsua.config.acc_cnt; ++i) { - status = init_acc(i); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing account", status); - goto on_error; - } + /* 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)); } - - /* Create worker thread(s), if required: */ - - 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]); + /* 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.quit_flag = 1; - for (--i; i>=0; --i) { - pj_thread_join(pjsua.threads[i]); - pj_thread_destroy(pjsua.threads[i]); - } - goto on_error; + pjsua_perror(THIS_FILE, "Error resolving with STUN", status); + pj_sock_close(sock); + goto on_return; } - } - /* Start registration: */ + } else { - /* 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; + const pj_str_t *hostname = pj_gethostname(); + struct pj_hostent he; - /* Perform registration, if required. */ - if (pjsua.acc[i].regc) { - pjsua_acc_set_registration(i, PJ_TRUE); + status = pj_gethostbyname(hostname, &he); + if (status != PJ_SUCCESS) { + 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; + pjsip_transport *tp; + unsigned id; + pj_status_t status; - pj_gettimeofday(&timeout); - timeout.msec += msec; - pj_time_val_normalize(&timeout); + PJSUA_LOCK(); - do { - pjsua_poll(NULL); - pj_gettimeofday(&now); - } while (PJ_TIME_VAL_LT(now, timeout)); -} - -/** - * 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; i<count; ++i) { - if (cinfo.listener[i]) { - info->listeners[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<PJ_ARRAY_SIZE(pjsua_var.tpdata) && count<*p_count; + ++i) + { + if (!pjsua_var.tpdata[i].tp) + continue; + id[count++] = i; + } -/** - * Get conference port associated with player. - */ -PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id) -{ - PJ_ASSERT_RETURN(id>=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; + pjsip_transport *tp; - 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; - - 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 && id<PJ_ARRAY_SIZE(pjsua_var.tpdata), PJ_EINVAL); - if (*id) - *id = pjsua.recorder_cnt; + /* Make sure that transport exists */ + PJ_ASSERT_RETURN(pjsua_var.tpdata[id].tp != NULL, PJ_EINVAL); - ++pjsua.recorder_cnt; + PJSUA_LOCK(); - return PJ_SUCCESS; + tp = pjsua_var.tpdata[id].tp; + if (tp == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } + + info->id = 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<PJ_ARRAY_SIZE(pjsua_var.tpdata), PJ_EINVAL); + /* Make sure that transport exists */ + PJ_ASSERT_RETURN(pjsua_var.tpdata[id].tp != NULL, PJ_EINVAL); -/** - * Destroy recorder (will complete recording). - */ -PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id) -{ - PJ_ASSERT_RETURN(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<PJ_ARRAY_SIZE(pjsua_var.tpdata), PJ_EINVAL); - dev_count = pjmedia_snd_get_dev_count(); - if (dev_count > (int)*count) - dev_count = *count; + /* Make sure that transport exists */ + PJ_ASSERT_RETURN(pjsua_var.tpdata[id].tp != NULL, PJ_EINVAL); - for (i=0; i<dev_count; ++i) { - const pjmedia_snd_dev_info *dev_info; - dev_info = pjmedia_snd_get_dev_info(i); - pj_memcpy(&info[i], dev_info, sizeof(pjmedia_snd_dev_info)); - } - *count = dev_count; - return PJ_SUCCESS; -} + /* To be done!! */ -/** - * Select or change sound device. - */ -PJ_DEF(pj_status_t) pjsua_set_snd_dev( int snd_capture_id, - int snd_player_id) -{ - pjsua.config.snd_capture_id = snd_capture_id; - pjsua.config.snd_player_id = snd_player_id; - return PJ_SUCCESS; + PJ_TODO(pjsua_transport_close); + + return PJ_EINVALIDOP; } /* - * Destroy pjsua. + * Add additional headers etc in msg_data specified by application + * when sending requests. */ -PJ_DEF(pj_status_t) pjsua_destroy(void) +void pjsua_process_msg_data(pjsip_tx_data *tdata, + const pjsua_msg_data *msg_data) { - int i; /* Must be signed */ - - /* Signal threads to quit: */ - pjsua.quit_flag = 1; - - /* Wait worker threads to quit: */ - for (i=0; i<(int)pjsua.config.thread_cnt; ++i) { - - if (pjsua.threads[i]) { - pj_thread_join(pjsua.threads[i]); - pj_thread_destroy(pjsua.threads[i]); - pjsua.threads[i] = NULL; - } - } - - - if (pjsua.endpt) { - /* Terminate all calls. */ - pjsua_call_hangup_all(); + pj_bool_t allow_body; + const pjsip_hdr *hdr; - /* Terminate all presence subscriptions. */ - pjsua_pres_shutdown(); + if (!msg_data) + return; - /* Unregister, if required: */ - for (i=0; i<(int)pjsua.config.acc_cnt; ++i) { - if (pjsua.acc[i].regc) { - pjsua_acc_set_registration(i, PJ_FALSE); - } - } + hdr = msg_data->hdr_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; i<PJ_ARRAY_SIZE(pjsua.player); ++i) { - if (pjsua.player[i].port) { - pjmedia_port_destroy(pjsua.player[i].port); - pjsua.player[i].port = NULL; - } - } + allow_body = (tdata->msg->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; i<PJ_ARRAY_SIZE(pjsua.recorder); ++i) { - if (pjsua.recorder[i].port) { - pjmedia_port_destroy(pjsua.recorder[i].port); - pjsua.recorder[i].port = NULL; - } + pjsua_parse_media_type(tdata->pool, &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 <pjsua-lib/pjsua.h> -#include <pj/log.h> -#include "pjsua_imp.h" +#include <pjsua-lib/pjsua_internal.h> -/* - * 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 <benny@prijono.org> - * - * 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; /**<Last status code 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. */ - int acc_index; /**< Account index being used. */ - pjmedia_session *session; /**< The media session. */ - unsigned 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. */ - void *app_data; /**< Application data. */ - pj_timer_entry refresh_tm;/**< Timer to send re-INVITE. */ - pj_timer_entry hangup_tm; /**< Timer to hangup call. */ -}; - -typedef struct pjsua_call pjsua_call; - - -/** - * Buddy data. - */ -struct pjsua_buddy -{ - unsigned index; /**< Buddy index. */ - 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. */ -}; - -typedef struct pjsua_buddy pjsua_buddy; - - -/** - * Server presence subscription list head. - */ -struct pjsua_srv_pres -{ - PJ_DECL_LIST_MEMBER(struct pjsua_srv_pres); - pjsip_evsub *sub; - char *remote; -}; - -typedef struct pjsua_srv_pres pjsua_srv_pres; - - - -/** - * Account - */ -struct pjsua_acc -{ - pj_bool_t valid; /**< Is this account valid? */ - pj_bool_t auto_gen; /**< Is this account generated. */ - 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. */ - - 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; /**< Route set. */ - - pj_bool_t online_status; /**< Our online status. */ - pjsua_srv_pres pres_srv_list; /**< Server subscription list. */ - - void *app_data; /**< Application data. */ -}; - - -/** - * @see pjsua_acc - */ -typedef struct pjsua_acc pjsua_acc; - - -/* PJSUA application variables. */ -struct pjsua -{ - /* Control: */ - pj_caching_pool cp; /**< Global pool factory. */ - pjsip_endpoint *endpt; /**< Global endpoint. */ - pj_pool_t *pool; /**< pjsua's private pool. */ - pjsip_module mod; /**< pjsua's PJSIP module. */ - - - /* Config: */ - pjsua_config config; /**< PJSUA configs */ - - /* Log file: */ - FILE *log_file; /**< Log file. */ - - /* Application callback - : */ - pjsua_callback cb; /**< Application callback. */ - - /* Media: */ - pjmedia_endpt *med_endpt; /**< Media endpoint. */ - unsigned clock_rate; /**< Conference bridge's clock rate.*/ - unsigned samples_per_frame; /**< Bridge's frame size. */ - pjmedia_conf *mconf; /**< Media conference. */ - - pjmedia_snd_port *snd_port; /**< Sound device port. */ - pjmedia_master_port *master_port; /**< Master port, when no snd dev */ - - unsigned player_cnt; /**< Number of file player. */ - - /** Array of file players */ - struct { - unsigned slot; /**< WAV player slot in bridge */ - pjmedia_port *port; /**< WAV player port. */ - } player[32]; - - unsigned recorder_cnt; /**< Number of file recorders. */ - - /** Array of file recorders */ - struct { - unsigned slot; /**< Slot # in conf bridge. */ - pjmedia_port *port; /**< The recorder media port. */ - } recorder[32]; - - /* Account: */ - int default_acc; /**< Default account to use. */ - pjsua_acc acc[PJSUA_MAX_ACC]; /** Client regs array. */ - - - /* Threading (optional): */ - pj_thread_t *threads[8]; /**< Thread instances. */ - pj_bool_t quit_flag; /**< To signal thread to quit. */ - - /* Transport (UDP): */ - pj_sock_t sip_sock; /**< SIP UDP socket. */ - pj_sockaddr_in sip_sock_name; /**< Public/STUN UDP socket addr. */ - - - /* PJSUA Calls: */ - unsigned call_cnt; /**< Number of calls. */ - pjsua_call calls[PJSUA_MAX_CALLS]; /** Calls array. */ - - /* SIMPLE and buddy status: */ - pjsua_buddy buddies[PJSUA_MAX_BUDDIES]; -}; - - -/** PJSUA instance. */ -extern struct pjsua pjsua; - - -void pjsua_copy_config( pj_pool_t *pool, pjsua_config *dst, - const pjsua_config *src); - - -/** - * Init pjsua call module. - */ -pj_status_t pjsua_call_init(void); - - -/** - * Handle incoming invite request. - */ -pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata); - - -/** - * Initialize client registration session. - * - * @param app_callback Optional callback - */ -pj_status_t pjsua_regc_init(int acc_index); - - -/** - * Init presence. - */ -pj_status_t pjsua_pres_init(); - - -/** - * Refresh both presence client and server subscriptions. - */ -void pjsua_pres_refresh(void); - -/** - * Terminate all subscriptions - */ -void pjsua_pres_shutdown(void); - -/** - * Init IM module handler to handle incoming MESSAGE outside dialog. - */ -pj_status_t pjsua_im_init(); - -/** - * Create Accept header for MESSAGE. - */ -pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool); - -/** - * 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); - - -extern pjsip_module pjsua_msg_logger; - -#endif /* __PJSUA_IMP_H__ */ - diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c new file mode 100644 index 00000000..9fae480e --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -0,0 +1,1044 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * 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 <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + + +#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; i<RTP_RETRY; ++i, rtp_port += 2) { + + /* Create and bind RTP socket. */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "socket() error", status); + return status; + } + + status = pj_sock_bind_in(sock[0], cfg->ip_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; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) { + if (pjsua_var.player[i].port) { + pjmedia_port_destroy(pjsua_var.player[i].port); + pjsua_var.player[i].port = NULL; + } + } + + /* Destroy file recorders */ + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) { + if (pjsua_var.recorder[i].port) { + pjmedia_port_destroy(pjsua_var.recorder[i].port); + pjsua_var.recorder[i].port = NULL; + } + } + + /* Close media transports */ + for (i=0; i<(int)pjsua_var.ua_cfg.max_calls; ++i) { + if (pjsua_var.calls[i].med_tp) { + (*pjsua_var.calls[i].med_tp->op->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; i<pjsua_var.ua_cfg.max_calls; ++i) { + if (pjsua_var.calls[i].med_tp != NULL) { + pjsua_var.calls[i].med_tp->op->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; i<pjsua_var.ua_cfg.max_calls; ++i) { + + status = create_rtp_rtcp_sock(&cfg, &pjsua_var.calls[i].skinfo); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket", + status); + goto on_error; + } + status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL, + &pjsua_var.calls[i].skinfo, 0, + &pjsua_var.calls[i].med_tp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create media transport", + status); + goto on_error; + } + } + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; + +on_error: + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + if (pjsua_var.calls[i].med_tp != NULL) { + pjsua_var.calls[i].med_tp->op->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; i<count; ++i) { + if (cinfo.listener[i]) { + info->listeners[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_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) { + if (pjsua_var.player[file_id].port == NULL) + break; + } + + if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) { + /* This is unexpected */ + PJSUA_UNLOCK(); + pj_assert(0); + return PJ_EBUG; + } + + pj_memcpy(path, filename->ptr, 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<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL); + + return pjsua_var.player[id].slot; +} + + +/* + * Set playback position. + */ +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_var.player), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL); + + return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples); +} + + +/* + * Close the file, remove the player from the bridge, and free + * resources associated with the file player. + */ +PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL); + + PJSUA_LOCK(); + + if (pjsua_var.player[id].port) { + pjmedia_conf_remove_port(pjsua_var.mconf, + pjsua_var.player[id].slot); + pjmedia_port_destroy(pjsua_var.player[id].port); + pjsua_var.player[id].port = NULL; + pjsua_var.player[id].slot = 0xFFFF; + pjsua_var.player_cnt--; + } + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * File recorder. + */ + +/* + * Create a file recorder, and automatically connect this recorder to + * the conference bridge. + */ +PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename, + unsigned file_format, + const pj_str_t *encoding, + pj_ssize_t max_size, + unsigned options, + void *user_data, + pjsua_recorder_id *p_id) +{ + unsigned slot, file_id; + char path[128]; + pjmedia_port *port; + pj_status_t status; + + if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) + return PJ_ETOOMANY; + + PJSUA_LOCK(); + + for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) { + if (pjsua_var.recorder[file_id].port == NULL) + break; + } + + if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) { + /* This is unexpected */ + PJSUA_UNLOCK(); + pj_assert(0); + return PJ_EBUG; + } + + pj_memcpy(path, filename->ptr, 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<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL); + + return pjsua_var.recorder[id].slot; +} + + +/* + * Destroy recorder (this will complete recording). + */ +PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL); + + PJSUA_LOCK(); + + if (pjsua_var.recorder[id].port) { + pjmedia_conf_remove_port(pjsua_var.mconf, + pjsua_var.recorder[id].slot); + pjmedia_port_destroy(pjsua_var.recorder[id].port); + pjsua_var.recorder[id].port = NULL; + pjsua_var.recorder[id].slot = 0xFFFF; + pjsua_var.rec_cnt--; + } + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * Sound devices. + */ + +/* + * Enum sound devices. + */ +PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[], + unsigned *count) +{ + unsigned i, dev_count; + + dev_count = pjmedia_snd_get_dev_count(); + + if (dev_count > *count) dev_count = *count; + + for (i=0; i<dev_count; ++i) { + const pjmedia_snd_dev_info *ci; + + ci = pjmedia_snd_get_dev_info(i); + pj_memcpy(&info[i], ci, sizeof(*ci)); + } + + *count = dev_count; + + return PJ_SUCCESS; +} + + +/* Close existing sound device */ +static void close_snd_dev(void) +{ + /* Close sound device */ + if (pjsua_var.snd_port) { + const pjmedia_snd_dev_info *cap_info, *play_info; + + cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev); + play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev); + + PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and " + "%s sound capture device", + play_info->name, 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<count; ++i) { + pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_)); + id[i].codec_id = pj_str(id[i].buf_); + id[i].priority = (pj_uint8_t) prio[i]; + } + + *p_count = count; + + return PJ_SUCCESS; +} + + +/* + * Change codec priority. + */ +PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id, + pj_uint8_t priority ) +{ + pjmedia_codec_mgr *codec_mgr; + + codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt); + + return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id, + priority); +} + + +/* + * Get codec parameters. + */ +PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id, + pjmedia_codec_param *param ) +{ + const pjmedia_codec_info *info; + pjmedia_codec_mgr *codec_mgr; + unsigned count = 1; + pj_status_t status; + + codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt); + + status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id, + &count, &info, NULL); + if (status != PJ_SUCCESS) + return status; + + if (count != 1) + return PJ_ENOTFOUND; + + status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param); + return status; +} + + +/* + * Set codec parameters. + */ +PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id, + const pjmedia_codec_param *param) +{ + PJ_TODO(set_codec_param); + return PJ_SUCCESS; +} + + diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c index b24ffd3c..b1764662 100644 --- a/pjsip/src/pjsua-lib/pjsua_pres.c +++ b/pjsip/src/pjsua-lib/pjsua_pres.c @@ -17,21 +17,374 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <pjsua-lib/pjsua.h> -#include "pjsua_imp.h" +#include <pjsua-lib/pjsua_internal.h> + + +#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<PJ_ARRAY_SIZE(pjsua_var.buddy) && + pjsua_var.buddy[buddy_id].uri.slen != 0; +} + + +/* + * Enum buddy IDs. + */ +PJ_DEF(pj_status_t) pjsua_enum_buddies( pjsua_buddy_id ids[], + unsigned *count) +{ + unsigned i, c; + + PJ_ASSERT_RETURN(ids && count, PJ_EINVAL); + + PJSUA_LOCK(); + + for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + if (!pjsua_var.buddy[i].uri.slen) + continue; + ids[c] = i; + ++c; + } + + *count = c; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; + +} + + +/* + * Get detailed buddy info. + */ +PJ_DEF(pj_status_t) pjsua_buddy_get_info( pjsua_buddy_id buddy_id, + pjsua_buddy_info *info) +{ + int total=0; + pjsua_buddy *buddy; + + PJ_ASSERT_RETURN(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; index<PJ_ARRAY_SIZE(pjsua_var.buddy); ++index) { + if (pjsua_var.buddy[index].uri.slen == 0) + break; + } + + /* Expect to find an empty slot */ + if (index == PJ_ARRAY_SIZE(pjsua_var.buddy)) { + PJSUA_UNLOCK(); + /* This shouldn't happen */ + pj_assert(!"index < PJ_ARRAY_SIZE(pjsua_var.buddy)"); + return PJ_ETOOMANY; + } + + + /* Get name and display name for buddy */ + pj_strdup_with_null(pjsua_var.pool, &tmp, &cfg->uri); + 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_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) { + + if (!pjsua_var.acc[acc_id].valid) + continue; + + if (!pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) { + struct pjsua_srv_pres *uapres; + + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; + while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) { + ++count; + uapres = uapres->next; + } + } + } + + PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d", + count)); + + count = 0; + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + if (pjsua_var.buddy[i].uri.slen == 0) + continue; + if (pjsua_var.buddy[i].sub) { + ++count; + } + } + + PJ_LOG(3,(THIS_FILE, "Number of client/UAC subscriptions: %d", + count)); + PJSUA_UNLOCK(); + return; + } + + + /* + * Dumping all server (UAS) subscriptions + */ + PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:")); + + for (acc_id=0; acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) { + + if (!pjsua_var.acc[acc_id].valid) + continue; + + PJ_LOG(3,(THIS_FILE, " %.*s", + (int)pjsua_var.acc[acc_id].cfg.id.slen, + pjsua_var.acc[acc_id].cfg.id.ptr)); + + if (pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) { + + PJ_LOG(3,(THIS_FILE, " - none - ")); + + } else { + struct pjsua_srv_pres *uapres; + + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; + while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) { + + PJ_LOG(3,(THIS_FILE, " %10s %s", + pjsip_evsub_get_state_name(uapres->sub), + 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; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + + if (pjsua_var.buddy[i].uri.slen == 0) + continue; + + if (pjsua_var.buddy[i].sub) { + PJ_LOG(3,(THIS_FILE, " %10s %.*s", + pjsip_evsub_get_state_name(pjsua_var.buddy[i].sub), + (int)pjsua_var.buddy[i].uri.slen, + pjsua_var.buddy[i].uri.ptr)); + } else { + PJ_LOG(3,(THIS_FILE, " %10s %.*s", + "(null)", + (int)pjsua_var.buddy[i].uri.slen, + pjsua_var.buddy[i].uri.ptr)); + } + } + } + + PJSUA_UNLOCK(); +} + + +/*************************************************************************** + * Server subscription. */ /* Proto */ @@ -60,19 +413,24 @@ static pjsip_module mod_pjsua_pres = /* Callback called when *server* subscription state has changed. */ static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event) { - pjsua_srv_pres *uapres = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); + pjsua_srv_pres *uapres; PJ_UNUSED_ARG(event); + PJSUA_LOCK(); + + uapres = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); if (uapres) { PJ_LOG(3,(THIS_FILE, "Server subscription to %s is %s", uapres->remote, 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; i<pjsua.config.buddy_cnt; ++i) { + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { - if (pjsua.buddies[i].monitor && !pjsua.buddies[i].sub) { + if (!pjsua_var.buddy[i].uri.slen) + continue; + + if (pjsua_var.buddy[i].monitor && !pjsua_var.buddy[i].sub) { subscribe_buddy_presence(i); - } else if (!pjsua.buddies[i].monitor && pjsua.buddies[i].sub) { + } else if (!pjsua_var.buddy[i].monitor && pjsua_var.buddy[i].sub) { unsubscribe_buddy_presence(i); } @@ -417,182 +875,46 @@ static void refresh_client_subscription(void) */ pj_status_t pjsua_pres_init() { + unsigned i; pj_status_t status; - status = pjsip_endpt_register_module( pjsua.endpt, &mod_pjsua_pres); + status = pjsip_endpt_register_module( pjsua_var.endpt, &mod_pjsua_pres); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to register pjsua presence module", status); } - return status; -} - -/* - * Get buddy count. - */ -PJ_DEF(unsigned) pjsua_get_buddy_count(void) -{ - return pjsua.config.buddy_cnt; -} - - -/** - * Get buddy info. - */ -PJ_DEF(pj_status_t) pjsua_buddy_get_info(pjsua_buddy_id index, - pjsua_buddy_info *info) -{ - pjsua_buddy *buddy; - - PJ_ASSERT_RETURN(index < (int)PJ_ARRAY_SIZE(pjsua.config.buddy_uri), - PJ_EINVAL); - - pj_memset(info, 0, sizeof(pjsua_buddy_info)); - - buddy = &pjsua.buddies[index]; - info->index = 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; index<PJ_ARRAY_SIZE(pjsua.config.buddy_uri); ++index) { - if (pjsua.config.buddy_uri[index].slen == 0) - break; + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + reset_buddy(i); } - /* Expect to find an empty slot */ - PJ_ASSERT_RETURN(index < PJ_ARRAY_SIZE(pjsua.config.buddy_uri), - PJ_ETOOMANY); - - - /* Get name and display name for buddy */ - pj_strdup_with_null(pjsua.pool, &tmp, uri); - url = (pjsip_name_addr*)pjsip_parse_uri(pjsua.pool, tmp.ptr, tmp.slen, - PJSIP_PARSE_URI_AS_NAMEADDR); - - if (url == NULL) - return PJSIP_EINVALIDURI; - - /* Save URI */ - pjsua.config.buddy_uri[index] = tmp; - - sip_uri = (pjsip_sip_uri*) url->uri; - 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; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) - refresh_server_subscription(i); + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (pjsua_var.acc[i].valid) + refresh_server_subscription(i); + } } @@ -601,121 +923,17 @@ PJ_DEF(void) pjsua_pres_refresh() */ void pjsua_pres_shutdown(void) { - unsigned acc_index; unsigned i; - for (acc_index=0; acc_index<(int)pjsua.config.acc_cnt; ++acc_index) { - pjsua.acc[acc_index].online_status = 0; + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + pjsua_pres_delete_acc(i); } - for (i=0; i<pjsua.config.buddy_cnt; ++i) { - pjsua.buddies[i].monitor = 0; + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + pjsua_var.buddy[i].monitor = 0; } pjsua_pres_refresh(); } - -/* - * Dump presence status. - */ -void pjsua_pres_dump(pj_bool_t detail) -{ - unsigned acc_index; - unsigned i; - - - /* - * When no detail is required, just dump number of server and client - * subscriptions. - */ - if (detail == PJ_FALSE) { - - int count = 0; - - for (acc_index=0; acc_index < (int)pjsua.config.acc_cnt; ++acc_index) { - - if (!pj_list_empty(&pjsua.acc[acc_index].pres_srv_list)) { - struct pjsua_srv_pres *uapres; - - uapres = pjsua.acc[acc_index].pres_srv_list.next; - while (uapres != &pjsua.acc[acc_index].pres_srv_list) { - ++count; - uapres = uapres->next; - } - } - } - - PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d", - count)); - - count = 0; - - for (i=0; i<pjsua.config.buddy_cnt; ++i) { - if (pjsua.buddies[i].sub) { - ++count; - } - } - - PJ_LOG(3,(THIS_FILE, "Number of client/UAC subscriptions: %d", - count)); - return; - } - - - /* - * Dumping all server (UAS) subscriptions - */ - PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:")); - - for (acc_index=0; acc_index < (int)pjsua.config.acc_cnt; ++acc_index) { - - PJ_LOG(3,(THIS_FILE, " %.*s", - (int)pjsua.config.acc_config[acc_index].id.slen, - pjsua.config.acc_config[acc_index].id.ptr)); - - if (pj_list_empty(&pjsua.acc[acc_index].pres_srv_list)) { - - PJ_LOG(3,(THIS_FILE, " - none - ")); - - } else { - struct pjsua_srv_pres *uapres; - - uapres = pjsua.acc[acc_index].pres_srv_list.next; - while (uapres != &pjsua.acc[acc_index].pres_srv_list) { - - PJ_LOG(3,(THIS_FILE, " %10s %s", - pjsip_evsub_get_state_name(uapres->sub), - 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<pjsua.config.buddy_cnt; ++i) { - - if (pjsua.buddies[i].sub) { - PJ_LOG(3,(THIS_FILE, " %10s %.*s", - pjsip_evsub_get_state_name(pjsua.buddies[i].sub), - (int)pjsua.config.buddy_uri[i].slen, - pjsua.config.buddy_uri[i].ptr)); - } else { - PJ_LOG(3,(THIS_FILE, " %10s %.*s", - "(null)", - (int)pjsua.config.buddy_uri[i].slen, - pjsua.config.buddy_uri[i].ptr)); - } - } - } -} - diff --git a/pjsip/src/pjsua-lib/pjsua_reg.c b/pjsip/src/pjsua-lib/pjsua_reg.c deleted file mode 100644 index 54dbcbd2..00000000 --- a/pjsip/src/pjsua-lib/pjsua_reg.c +++ /dev/null @@ -1,281 +0,0 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> - * - * 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 <pjsua-lib/pjsua.h> -#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 && i<PJ_ARRAY_SIZE(pjsua.acc); ++i) { - if (!pjsua.acc[i].valid) - continue; - - pjsua_acc_get_info(i, &info[c]); - ++c; - } - - *count = c; - return PJ_SUCCESS; -} - - -/** - * Enum accounts id. - */ -PJ_DEF(pj_status_t) pjsua_acc_enum_id( pjsua_acc_id ids[], - unsigned *count ) -{ - unsigned i, c; - - for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua.acc); ++i) { - if (!pjsua.acc[i].valid) - continue; - ids[c] = i; - ++c; - } - - *count = c; - return PJ_SUCCESS; -} - - -/* - * Update registration. If renew is false, then unregistration will be performed. - */ -PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_index, - pj_bool_t renew) -{ - pj_status_t status = 0; - pjsip_tx_data *tdata = 0; - - PJ_ASSERT_RETURN(acc_index < (int)pjsua.config.acc_cnt, - PJ_EINVAL); - PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP); - - if (renew) { - if (pjsua.acc[acc_index].regc == NULL) { - status = pjsua_regc_init(acc_index); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create registration", - status); - return PJ_EINVALIDOP; - } - } - if (!pjsua.acc[acc_index].regc) - return PJ_EINVALIDOP; - - status = pjsip_regc_register(pjsua.acc[acc_index].regc, 1, - &tdata); - - } else { - if (pjsua.acc[acc_index].regc == NULL) { - PJ_LOG(3,(THIS_FILE, "Currently not registered")); - return PJ_EINVALIDOP; - } - status = pjsip_regc_unregister(pjsua.acc[acc_index].regc, &tdata); - } - - if (status == PJ_SUCCESS) - status = pjsip_regc_send( pjsua.acc[acc_index].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"))); - } - - return status; -} - -/* - * Initialize client registration. - */ -pj_status_t pjsua_regc_init(int acc_index) -{ - pjsua_acc_config *acc_config; - pj_status_t status; - - acc_config = &pjsua.config.acc_config[acc_index]; - - if (acc_config->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.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 <benny@prijono.org> - * - * 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 <pjsua-lib/pjsua.h> -#include <stdio.h> -#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; i<cfg->buddy_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; i<cfg->acc_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<info.stream_cnt; ++i) { - pjmedia_rtcp_stat stat; - const char *rem_addr; - int rem_port; - const char *dir; - char last_update[40]; - char packets[16], bytes[16], ipbytes[16]; - pj_time_val now; - - pjmedia_session_get_stream_stat(session, i, &stat); - rem_addr = pj_inet_ntoa(info.stream_info[i].rem_addr.sin_addr); - rem_port = pj_ntohs(info.stream_info[i].rem_addr.sin_port); - - if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING) - dir = "sendonly"; - else if (info.stream_info[i].dir == PJMEDIA_DIR_DECODING) - dir = "recvonly"; - else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING_DECODING) - dir = "sendrecv"; - else - dir = "inactive"; - - - 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; - } - - 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; i<pjsua.config.max_calls; ++i) { - if (pjsua.calls[i].inv) { - pjsua_call_dump(i, detail, buf, sizeof(buf), " "); - PJ_LOG(3,(THIS_FILE, "%s", buf)); - } - } - } - - /* Dump presence status */ - pjsua_pres_dump(detail); - - pj_log_set_decor(old_decor); - PJ_LOG(3,(THIS_FILE, "Dump complete")); -} - - -/* - * Load settings. - */ -PJ_DECL(pj_status_t) pjsua_load_settings(const char *filename, - pjsua_config *cfg, - pj_str_t *uri_to_call) -{ - int argc = 3; - char *argv[4] = { "pjsua", "--config-file", NULL, NULL}; - - argv[2] = (char*)filename; - return pjsua_parse_args(argc, argv, cfg, uri_to_call); -} - - -/* - * Save account settings - */ -static void save_account_settings(int acc_index, pj_str_t *result) -{ - char line[128]; - pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index]; - - - pj_ansi_sprintf(line, "#\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); - } - - - /* 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; i<config->codec_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; i<config->buddy_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; i<PJ_ARRAY_SIZE(pjsua.config.buddy_uri)-1; ++i) { - if (pjsua.config.buddy_uri[i].slen == 0) { - unsigned j; - - for (j=i+1; j<PJ_ARRAY_SIZE(pjsua.config.buddy_uri); ++j) { - if (pjsua.config.buddy_uri[j].slen != 0) - break; - } - - if (j == PJ_ARRAY_SIZE(pjsua.config.buddy_uri)) - break; - else - pjsua.config.buddy_uri[i] = pjsua.config.buddy_uri[j]; - } - } - - /* Compact accounts. */ - for (i=0; i<PJ_ARRAY_SIZE(pjsua.config.acc_config)-1; ++i) { - - if (pjsua.acc[i].valid == PJ_FALSE || pjsua.acc[i].auto_gen) { - unsigned j; - - for (j=i+1; j<PJ_ARRAY_SIZE(pjsua.config.acc_config); ++j) { - if (pjsua.acc[j].valid && !pjsua.acc[j].auto_gen) - break; - } - - if (j == PJ_ARRAY_SIZE(pjsua.config.acc_config)) { - break; - } else { - pj_memcpy(&pjsua.config.acc_config[i] , - &pjsua.config.acc_config[j], - sizeof(pjsua_acc_config)); - } - } - - } - - /* Remove auto generated account from config */ - for (i=0; i<PJ_ARRAY_SIZE(pjsua.config.acc_config); ++i) { - if (pjsua.acc[i].auto_gen) - --cfg->acc_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() */ - -}; - |