/pjsip_android/apps/pjsip/project/pjsip/src/pjsua-lib/pjsua_call.c
http://csipsimple.googlecode.com/ · C · 2394 lines · 1526 code · 447 blank · 421 comment · 363 complexity · 929fb932131e7c328e17a12e1aa42097 MD5 · raw file
Large files are truncated click here to view the full file
- /* $Id: pjsua_call.c 3771 2011-09-22 08:13:15Z bennylp $ */
- /*
- * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
- * Copyright (C) 2003-2008 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_call.c"
- #if USE_CSIPSIMPLE_HACKS
- pj_bool_t pjsua_no_update = PJ_FALSE;
- #endif
- /* Retry interval of sending re-INVITE for locking a codec when remote
- * SDP answer contains multiple codec, in milliseconds.
- */
- #define LOCK_CODEC_RETRY_INTERVAL 200
- /*
- * Max UPDATE/re-INVITE retry to lock codec
- */
- #define LOCK_CODEC_MAX_RETRY 5
- /* 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);
- /* 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);
- /*
- * 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);
- /*
- * Called when session received new offer.
- */
- static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
- const pjmedia_sdp_session *offer);
- /*
- * Called to generate new offer.
- */
- static void pjsua_call_on_create_offer(pjsip_inv_session *inv,
- 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);
- /*
- * Redirection handler.
- */
- static pjsip_redirect_op pjsua_call_on_redirected(pjsip_inv_session *inv,
- const pjsip_uri *target,
- const pjsip_event *e);
- /* Create SDP for call hold. */
- static pj_status_t create_sdp_of_call_hold(pjsua_call *call,
- pjmedia_sdp_session **p_sdp);
- /*
- * Callback called by event framework when the xfer subscription state
- * has changed.
- */
- static void xfer_client_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
- static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
- /*
- * Reset call descriptor.
- */
- static void reset_call(pjsua_call_id id)
- {
- pjsua_call *call = &pjsua_var.calls[id];
- call->index = id;
- call->inv = NULL;
- call->user_data = NULL;
- call->session = NULL;
- call->audio_idx = -1;
- call->ssrc = pj_rand();
- call->rtp_tx_seq = 0;
- call->rtp_tx_ts = 0;
- call->rtp_tx_seq_ts_set = 0;
- call->xfer_sub = NULL;
- call->last_code = (pjsip_status_code) 0;
- call->conf_slot = PJSUA_INVALID_ID;
- call->last_text.ptr = call->last_text_buf_;
- call->last_text.slen = 0;
- call->conn_time.sec = 0;
- call->conn_time.msec = 0;
- call->res_time.sec = 0;
- call->res_time.msec = 0;
- call->rem_nat_type = PJ_STUN_NAT_TYPE_UNKNOWN;
- call->rem_srtp_use = PJMEDIA_SRTP_DISABLED;
- call->local_hold = PJ_FALSE;
- pj_bzero(&call->lock_codec, sizeof(call->lock_codec));
- }
- /*
- * Init call subsystem.
- */
- pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg)
- {
- pjsip_inv_callback inv_cb;
- unsigned i;
- const pj_str_t str_norefersub = { "norefersub", 10 };
- pj_status_t status;
- /* Init calls array. */
- for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.calls); ++i)
- reset_call(i);
- /* Copy config */
- pjsua_config_dup(pjsua_var.pool, &pjsua_var.ua_cfg, cfg);
- /* Verify settings */
- if (pjsua_var.ua_cfg.max_calls >= PJSUA_MAX_CALLS) {
- pjsua_var.ua_cfg.max_calls = PJSUA_MAX_CALLS;
- }
- /* Check the route URI's and force loose route if required */
- for (i=0; i<pjsua_var.ua_cfg.outbound_proxy_cnt; ++i) {
- status = normalize_route_uri(pjsua_var.pool,
- &pjsua_var.ua_cfg.outbound_proxy[i]);
- if (status != PJ_SUCCESS)
- return status;
- }
- /* Initialize invite session callback. */
- pj_bzero(&inv_cb, 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_create_offer = &pjsua_call_on_create_offer;
- inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed;
- inv_cb.on_redirected = &pjsua_call_on_redirected;
- /* Initialize invite session module: */
- status = pjsip_inv_usage_init(pjsua_var.endpt, &inv_cb);
- PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
- /* Add "norefersub" in Supported header */
- pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_SUPPORTED,
- NULL, 1, &str_norefersub);
- return status;
- }
- /*
- * Start call subsystem.
- */
- pj_status_t pjsua_call_subsys_start(void)
- {
- /* Nothing to do */
- return PJ_SUCCESS;
- }
- /*
- * Get maximum number of calls configured in pjsua.
- */
- PJ_DEF(unsigned) pjsua_call_get_max_count(void)
- {
- return pjsua_var.ua_cfg.max_calls;
- }
- /*
- * Get number of currently active calls.
- */
- PJ_DEF(unsigned) pjsua_call_get_count(void)
- {
- return pjsua_var.call_cnt;
- }
- /*
- * Enum calls.
- */
- PJ_DEF(pj_status_t) pjsua_enum_calls( pjsua_call_id ids[],
- unsigned *count)
- {
- unsigned i, c;
- PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL);
- PJSUA_LOCK();
- 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;
- }
- *count = c;
- PJSUA_UNLOCK();
- return PJ_SUCCESS;
- }
- /* Allocate one call id */
- static pjsua_call_id alloc_call_id(void)
- {
- pjsua_call_id cid;
- #if 1
- /* New algorithm: round-robin */
- if (pjsua_var.next_call_id >= (int)pjsua_var.ua_cfg.max_calls ||
- pjsua_var.next_call_id < 0)
- {
- pjsua_var.next_call_id = 0;
- }
- for (cid=pjsua_var.next_call_id;
- cid<(int)pjsua_var.ua_cfg.max_calls;
- ++cid)
- {
- if (pjsua_var.calls[cid].inv == NULL) {
- ++pjsua_var.next_call_id;
- return cid;
- }
- }
- for (cid=0; cid < pjsua_var.next_call_id; ++cid) {
- if (pjsua_var.calls[cid].inv == NULL) {
- ++pjsua_var.next_call_id;
- return cid;
- }
- }
- #else
- /* Old algorithm */
- for (cid=0; cid<(int)pjsua_var.ua_cfg.max_calls; ++cid) {
- if (pjsua_var.calls[cid].inv == NULL)
- return cid;
- }
- #endif
- return PJSUA_INVALID_ID;
- }
- /* Get signaling secure level.
- * Return:
- * 0: if signaling is not secure
- * 1: if TLS transport is used for immediate hop
- * 2: if end-to-end signaling is secure.
- */
- static int get_secure_level(pjsua_acc_id acc_id, const pj_str_t *dst_uri)
- {
- const pj_str_t tls = pj_str(";transport=tls");
- const pj_str_t sips = pj_str("sips:");
- pjsua_acc *acc = &pjsua_var.acc[acc_id];
- if (pj_stristr(dst_uri, &sips))
- return 2;
-
- if (!pj_list_empty(&acc->route_set)) {
- pjsip_route_hdr *r = acc->route_set.next;
- pjsip_uri *uri = r->name_addr.uri;
- pjsip_sip_uri *sip_uri;
-
- sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
- if (pj_stricmp2(&sip_uri->transport_param, "tls")==0)
- return 1;
- } else {
- if (pj_stristr(dst_uri, &tls))
- return 1;
- }
- return 0;
- }
- /*
- static int call_get_secure_level(pjsua_call *call)
- {
- if (call->inv->dlg->secure)
- return 2;
- if (!pj_list_empty(&call->inv->dlg->route_set)) {
- pjsip_route_hdr *r = call->inv->dlg->route_set.next;
- pjsip_uri *uri = r->name_addr.uri;
- pjsip_sip_uri *sip_uri;
-
- sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
- if (pj_stricmp2(&sip_uri->transport_param, "tls")==0)
- return 1;
- } else {
- pjsip_sip_uri *sip_uri;
- if (PJSIP_URI_SCHEME_IS_SIPS(call->inv->dlg->target))
- return 2;
- if (!PJSIP_URI_SCHEME_IS_SIP(call->inv->dlg->target))
- return 0;
- sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(call->inv->dlg->target);
- if (pj_stricmp2(&sip_uri->transport_param, "tls")==0)
- return 1;
- }
- return 0;
- }
- */
- /*
- * Make outgoing call to the specified URI using the specified account.
- */
- 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)
- {
- pj_pool_t *tmp_pool;
- pjsip_dialog *dlg = NULL;
- pjmedia_sdp_session *offer;
- pjsip_inv_session *inv = NULL;
- pjsua_acc *acc;
- pjsua_call *call;
- int call_id = -1;
- pj_str_t contact;
- pjsip_tx_data *tdata;
- pj_status_t status;
- /* Check that account is valid */
- PJ_ASSERT_RETURN(acc_id>=0 || acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
- PJ_EINVAL);
- /* Check arguments */
- PJ_ASSERT_RETURN(dest_uri, PJ_EINVAL);
- PJSUA_LOCK();
- /* Create sound port if none is instantiated, to check if sound device
- * can be used. But only do this with the conference bridge, as with
- * audio switchboard (i.e. APS-Direct), we can only open the sound
- * device once the correct format has been known
- */
- if (!pjsua_var.is_mswitch && pjsua_var.snd_port==NULL &&
- pjsua_var.null_snd==NULL && !pjsua_var.no_snd)
- {
- pj_status_t status;
- status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
- if (status != PJ_SUCCESS) {
- PJSUA_UNLOCK();
- return status;
- }
- }
- 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. */
- call_id = alloc_call_id();
- if (call_id == PJSUA_INVALID_ID) {
- pjsua_perror(THIS_FILE, "Error making call", PJ_ETOOMANY);
- PJSUA_UNLOCK();
- return PJ_ETOOMANY;
- }
- call = &pjsua_var.calls[call_id];
- /* Associate session with account */
- call->acc_id = acc_id;
- call->call_hold_type = acc->cfg.call_hold_type;
- /* Create temporary pool */
- tmp_pool = pjsua_pool_create("tmpcall10", 512, 256);
- /* Verify that destination URI is valid before calling
- * pjsua_acc_create_uac_contact, or otherwise there
- * a misleading "Invalid Contact URI" error will be printed
- * when pjsua_acc_create_uac_contact() fails.
- */
- if (1) {
- pjsip_uri *uri;
- pj_str_t dup;
- pj_strdup_with_null(tmp_pool, &dup, dest_uri);
- uri = pjsip_parse_uri(tmp_pool, dup.ptr, dup.slen, 0);
- if (uri == NULL) {
- pjsua_perror(THIS_FILE, "Unable to make call",
- PJSIP_EINVALIDREQURI);
- pj_pool_release(tmp_pool);
- PJSUA_UNLOCK();
- return PJSIP_EINVALIDREQURI;
- }
- }
- PJ_LOG(4,(THIS_FILE, "Making call with acc #%d to %.*s", acc_id,
- (int)dest_uri->slen, dest_uri->ptr));
- /* Mark call start time. */
- pj_gettimeofday(&call->start_time);
- /* Reset first response time */
- call->res_time.sec = 0;
- /* Create suitable Contact header unless a Contact header has been
- * set in the account.
- */
- if (acc->contact.slen) {
- contact = acc->contact;
- } else {
- status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
- acc_id, dest_uri);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to generate Contact header",
- status);
- pj_pool_release(tmp_pool);
- PJSUA_UNLOCK();
- return status;
- }
- }
- /* Create outgoing dialog: */
- status = pjsip_dlg_create_uac( pjsip_ua_instance(),
- &acc->cfg.id, &contact,
- dest_uri, dest_uri, &dlg);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Dialog creation failed", status);
- pj_pool_release(tmp_pool);
- PJSUA_UNLOCK();
- return status;
- }
- /* Increment the dialog's lock otherwise when invite session creation
- * fails the dialog will be destroyed prematurely.
- */
- pjsip_dlg_inc_lock(dlg);
- /* Calculate call's secure level */
- call->secure_level = get_secure_level(acc_id, dest_uri);
- /* Init media channel */
- status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC,
- call->secure_level, dlg->pool,
- NULL, NULL);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error initializing media channel", status);
- goto on_error;
- }
- /* Create offer */
- status = pjsua_media_channel_create_sdp(call->index, dlg->pool, NULL,
- &offer, NULL);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error initializing media channel", status);
- goto on_error;
- }
- /* Create the INVITE session: */
- options |= PJSIP_INV_SUPPORT_100REL;
- if (acc->cfg.require_100rel == PJSUA_100REL_MANDATORY)
- options |= PJSIP_INV_REQUIRE_100REL;
- if (acc->cfg.use_timer != PJSUA_SIP_TIMER_INACTIVE) {
- options |= PJSIP_INV_SUPPORT_TIMER;
- if (acc->cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED)
- options |= PJSIP_INV_REQUIRE_TIMER;
- else if (acc->cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS)
- options |= PJSIP_INV_ALWAYS_USE_TIMER;
- }
- status = pjsip_inv_create_uac( dlg, offer, options, &inv);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Invite session creation failed", status);
- goto on_error;
- }
- /* Init Session Timers */
- status = pjsip_timer_init_session(inv, &acc->cfg.timer_setting);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Session Timer init failed", status);
- goto on_error;
- }
- /* Create and associate our data in the session. */
- call->inv = inv;
- dlg->mod_data[pjsua_var.mod.id] = call;
- inv->mod_data[pjsua_var.mod.id] = call;
- /* Attach user data */
- call->user_data = user_data;
- /* If account is locked to specific transport, then lock dialog
- * to this transport too.
- */
- if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
- pjsip_tpselector tp_sel;
- pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
- pjsip_dlg_set_transport(dlg, &tp_sel);
- }
- /* Set dialog 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->cred_cnt, acc->cred);
- }
- /* Set authentication preference */
- pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref);
- /* Create initial INVITE: */
- status = pjsip_inv_invite(inv, &tdata);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to create initial INVITE request",
- status);
- goto on_error;
- }
- /* Add additional headers etc */
- pjsua_process_msg_data( tdata, msg_data);
- /* Must increment call counter now */
- ++pjsua_var.call_cnt;
- /* Send initial INVITE: */
- status = pjsip_inv_send_msg(inv, tdata);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to send initial INVITE request",
- status);
- /* Upon failure to send first request, the invite
- * session would have been cleared.
- */
- inv = NULL;
- goto on_error;
- }
- /* Done. */
- if (p_call_id)
- *p_call_id = call_id;
- pjsip_dlg_dec_lock(dlg);
- pj_pool_release(tmp_pool);
- PJSUA_UNLOCK();
- return PJ_SUCCESS;
- on_error:
- if (dlg) {
- /* This may destroy the dialog */
- pjsip_dlg_dec_lock(dlg);
- }
- if (inv != NULL) {
- pjsip_inv_terminate(inv, PJSIP_SC_OK, PJ_FALSE);
- }
- if (call_id != -1) {
- reset_call(call_id);
- pjsua_media_channel_deinit(call_id);
- }
- pj_pool_release(tmp_pool);
- PJSUA_UNLOCK();
- return status;
- }
- /* Get the NAT type information in remote's SDP */
- static void update_remote_nat_type(pjsua_call *call,
- const pjmedia_sdp_session *sdp)
- {
- const pjmedia_sdp_attr *xnat;
- xnat = pjmedia_sdp_attr_find2(sdp->attr_count, sdp->attr, "X-nat", NULL);
- if (xnat) {
- call->rem_nat_type = (pj_stun_nat_type) (xnat->value.ptr[0] - '0');
- } else {
- call->rem_nat_type = PJ_STUN_NAT_TYPE_UNKNOWN;
- }
- PJ_LOG(5,(THIS_FILE, "Call %d: remote NAT type is %d (%s)", call->index,
- call->rem_nat_type, pj_stun_get_nat_name(call->rem_nat_type)));
- }
- /**
- * Handle incoming INVITE request.
- * Called by pjsua_core.c
- */
- pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
- {
- pj_str_t contact;
- pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
- pjsip_dialog *replaced_dlg = NULL;
- pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
- pjsip_msg *msg = rdata->msg_info.msg;
- pjsip_tx_data *response = NULL;
- unsigned options = 0;
- pjsip_inv_session *inv = NULL;
- int acc_id;
- pjsua_call *call;
- int call_id = -1;
- int sip_err_code;
- pjmedia_sdp_session *offer=NULL, *answer;
- pj_status_t status;
- /* Don't want to handle anything but INVITE */
- if (msg->line.req.method.id != PJSIP_INVITE_METHOD)
- return PJ_FALSE;
- /* Don't want to handle anything that's already associated with
- * existing dialog or transaction.
- */
- if (dlg || tsx)
- return PJ_FALSE;
- /* Don't want to accept the call if shutdown is in progress */
- if (pjsua_var.thread_quit_flag) {
- pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
- PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL,
- NULL, NULL);
- return PJ_TRUE;
- }
- PJSUA_LOCK();
- /* Find free call slot. */
- call_id = alloc_call_id();
- if (call_id == PJSUA_INVALID_ID) {
- pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
- PJSIP_SC_BUSY_HERE, NULL,
- NULL, NULL);
- PJ_LOG(2,(THIS_FILE,
- "Unable to accept incoming call (too many calls)"));
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- /* Clear call descriptor */
- reset_call(call_id);
- call = &pjsua_var.calls[call_id];
- /* Mark call start time. */
- pj_gettimeofday(&call->start_time);
- /* Check INVITE request for Replaces header. If Replaces header is
- * present, the function will make sure that we can handle the request.
- */
- status = pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE,
- &response);
- if (status != PJ_SUCCESS) {
- /*
- * Something wrong with the Replaces header.
- */
- if (response) {
- pjsip_response_addr res_addr;
- pjsip_get_response_addr(response->pool, rdata, &res_addr);
- pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response,
- NULL, NULL);
- } else {
- /* Respond with 500 (Internal Server Error) */
- pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
- NULL, NULL);
- }
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- /* If this INVITE request contains Replaces header, notify application
- * about the request so that application can do subsequent checking
- * if it wants to.
- */
- if (replaced_dlg != NULL && pjsua_var.ua_cfg.cb.on_call_replace_request) {
- pjsua_call *replaced_call;
- int st_code = 200;
- pj_str_t st_text = { "OK", 2 };
- /* Get the replaced call instance */
- replaced_call = (pjsua_call*) replaced_dlg->mod_data[pjsua_var.mod.id];
- /* Notify application */
- pjsua_var.ua_cfg.cb.on_call_replace_request(replaced_call->index,
- rdata, &st_code, &st_text);
- /* Must specify final response */
- PJ_ASSERT_ON_FAIL(st_code >= 200, st_code = 200);
- /* Check if application rejects this request. */
- if (st_code >= 300) {
- if (st_text.slen == 2)
- st_text = *pjsip_get_status_text(st_code);
- pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata,
- st_code, &st_text, NULL, NULL, NULL);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- }
- /*
- * 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_id = call->acc_id = pjsua_acc_find_for_incoming(rdata);
- call->call_hold_type = pjsua_var.acc[acc_id].cfg.call_hold_type;
- /* Get call's secure level */
- if (PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.msg->line.req.uri))
- call->secure_level = 2;
- else if (PJSIP_TRANSPORT_IS_SECURE(rdata->tp_info.transport))
- call->secure_level = 1;
- else
- call->secure_level = 0;
- /* Parse SDP from incoming request */
- if (rdata->msg_info.msg->body) {
- pjsip_rdata_sdp_info *sdp_info;
- sdp_info = pjsip_rdata_get_sdp_info(rdata);
- offer = sdp_info->sdp;
- status = sdp_info->sdp_err;
- if (status==PJ_SUCCESS && sdp_info->sdp==NULL)
- status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
- if (status != PJ_SUCCESS) {
- const pj_str_t reason = pj_str("Bad SDP");
- pjsip_hdr hdr_list;
- pjsip_warning_hdr *w;
- pjsua_perror(THIS_FILE, "Bad SDP in incoming INVITE",
- status);
- w = pjsip_warning_hdr_create_from_status(rdata->tp_info.pool,
- pjsip_endpt_name(pjsua_var.endpt),
- status);
- pj_list_init(&hdr_list);
- pj_list_push_back(&hdr_list, w);
- pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 400,
- &reason, &hdr_list, NULL, NULL);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- /* Do quick checks on SDP before passing it to transports. More elabore
- * checks will be done in pjsip_inv_verify_request2() below.
- */
- if (offer->media_count==0) {
- const pj_str_t reason = pj_str("Missing media in SDP");
- pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 400, &reason,
- NULL, NULL, NULL);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- } else {
- offer = NULL;
- }
- /* Init media channel */
- status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS,
- call->secure_level,
- rdata->tp_info.pool, offer,
- &sip_err_code);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error initializing media channel", status);
- pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata,
- sip_err_code, NULL, NULL, NULL, NULL);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- /* Create answer */
- status = pjsua_media_channel_create_sdp(call->index, rdata->tp_info.pool,
- offer, &answer, &sip_err_code);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error creating SDP answer", status);
- pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata,
- sip_err_code, NULL, NULL, NULL, NULL);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- /* Verify that we can handle the request. */
- options |= PJSIP_INV_SUPPORT_100REL;
- options |= PJSIP_INV_SUPPORT_TIMER;
- if (pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_MANDATORY)
- options |= PJSIP_INV_REQUIRE_100REL;
- if (pjsua_var.media_cfg.enable_ice)
- options |= PJSIP_INV_SUPPORT_ICE;
- if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED)
- options |= PJSIP_INV_REQUIRE_TIMER;
- else if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS)
- options |= PJSIP_INV_ALWAYS_USE_TIMER;
- status = pjsip_inv_verify_request2(rdata, &options, offer, answer, NULL,
- pjsua_var.endpt, &response);
- if (status != PJ_SUCCESS) {
- /*
- * No we can't handle the incoming INVITE request.
- */
- if (response) {
- pjsip_response_addr res_addr;
- pjsip_get_response_addr(response->pool, rdata, &res_addr);
- pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response,
- NULL, NULL);
- } else {
- /* Respond with 500 (Internal Server Error) */
- pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 500, NULL,
- NULL, NULL, NULL);
- }
- pjsua_media_channel_deinit(call->index);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- /* Get suitable Contact header */
- if (pjsua_var.acc[acc_id].contact.slen) {
- contact = pjsua_var.acc[acc_id].contact;
- } else {
- status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact,
- acc_id, rdata);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to generate Contact header",
- status);
- pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
- NULL, NULL);
- pjsua_media_channel_deinit(call->index);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- }
- /* Create dialog: */
- status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
- &contact, &dlg);
- if (status != PJ_SUCCESS) {
- pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
- NULL, NULL);
- pjsua_media_channel_deinit(call->index);
- PJSUA_UNLOCK();
- 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);
- }
- /* Set preference */
- pjsip_auth_clt_set_prefs(&dlg->auth_sess,
- &pjsua_var.acc[acc_id].cfg.auth_pref);
- /* Disable Session Timers if not prefered and the incoming INVITE request
- * did not require it.
- */
- if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_INACTIVE &&
- (options & PJSIP_INV_REQUIRE_TIMER) == 0)
- {
- options &= ~(PJSIP_INV_SUPPORT_TIMER);
- }
- /* If 100rel is optional and UAC supports it, use it. */
- if ((options & PJSIP_INV_REQUIRE_100REL)==0 &&
- pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_OPTIONAL)
- {
- const pj_str_t token = { "100rel", 6};
- pjsip_dialog_cap_status cap_status;
- cap_status = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_SUPPORTED, NULL,
- &token);
- if (cap_status == PJSIP_DIALOG_CAP_SUPPORTED)
- options |= PJSIP_INV_REQUIRE_100REL;
- }
- /* Create invite session: */
- status = pjsip_inv_create_uas( dlg, rdata, answer, options, &inv);
- if (status != PJ_SUCCESS) {
- pjsip_hdr hdr_list;
- pjsip_warning_hdr *w;
- w = pjsip_warning_hdr_create_from_status(dlg->pool,
- pjsip_endpt_name(pjsua_var.endpt),
- status);
- pj_list_init(&hdr_list);
- pj_list_push_back(&hdr_list, w);
- pjsip_dlg_respond(dlg, rdata, 500, NULL, &hdr_list, NULL);
- /* Can't terminate dialog because transaction is in progress.
- pjsip_dlg_terminate(dlg);
- */
- pjsua_media_channel_deinit(call->index);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- /* Init Session Timers */
- status = pjsip_timer_init_session(inv,
- &pjsua_var.acc[acc_id].cfg.timer_setting);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Session Timer init failed", status);
- status = pjsip_inv_end_session(inv, PJSIP_SC_INTERNAL_SERVER_ERROR,
- NULL, &response);
- if (status == PJ_SUCCESS && response)
- status = pjsip_inv_send_msg(inv, response);
- pjsua_media_channel_deinit(call->index);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- /* Update NAT type of remote endpoint, only when there is SDP in
- * incoming INVITE!
- */
- if (pjsua_var.ua_cfg.nat_type_in_sdp &&
- pjmedia_sdp_neg_get_state(inv->neg) > PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
- {
- const pjmedia_sdp_session *remote_sdp;
- if (pjmedia_sdp_neg_get_neg_remote(inv->neg, &remote_sdp)==PJ_SUCCESS)
- update_remote_nat_type(call, remote_sdp);
- }
- /* If account is locked to specific transport, then lock dialog
- * to this transport too.
- */
- if (pjsua_var.acc[acc_id].cfg.transport_id != PJSUA_INVALID_ID) {
- pjsip_tpselector tp_sel;
- pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel);
- pjsip_dlg_set_transport(dlg, &tp_sel);
- }
- /* Must answer with some response to initial INVITE. We'll do this before
- * attaching the call to the invite session/dialog, so that the application
- * will not get notification about this event (on another scenario, it is
- * also possible that inv_send_msg() fails and causes the invite session to
- * be disconnected. If we have the call attached at this time, this will
- * cause the disconnection callback to be called before on_incoming_call()
- * callback is called, which is not right).
- */
- status = pjsip_inv_initial_answer(inv, rdata,
- 100, NULL, NULL, &response);
- if (status != PJ_SUCCESS) {
- if (response == NULL) {
- pjsua_perror(THIS_FILE, "Unable to send answer to incoming INVITE",
- status);
- pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL);
- pjsip_inv_terminate(inv, 500, PJ_FALSE);
- } else {
- pjsip_inv_send_msg(inv, response);
- pjsip_inv_terminate(inv, response->msg->line.status.code,
- PJ_FALSE);
- }
- pjsua_media_channel_deinit(call->index);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- } else {
- status = pjsip_inv_send_msg(inv, response);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to send 100 response", status);
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- }
- /* Create and attach pjsua_var data to the dialog: */
- call->inv = inv;
- dlg->mod_data[pjsua_var.mod.id] = call;
- inv->mod_data[pjsua_var.mod.id] = call;
- ++pjsua_var.call_cnt;
- /* Check if this request should replace existing call */
- if (replaced_dlg) {
- pjsip_inv_session *replaced_inv;
- struct pjsua_call *replaced_call;
- pjsip_tx_data *tdata;
- /* Get the invite session in the dialog */
- replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg);
- /* Get the replaced call instance */
- replaced_call = (pjsua_call*) replaced_dlg->mod_data[pjsua_var.mod.id];
- /* Notify application */
- if (pjsua_var.ua_cfg.cb.on_call_replaced)
- pjsua_var.ua_cfg.cb.on_call_replaced(replaced_call->index,
- call_id);
- PJ_LOG(4,(THIS_FILE, "Answering replacement call %d with 200/OK",
- call_id));
- /* Answer the new call with 200 response */
- status = pjsip_inv_answer(inv, 200, NULL, NULL, &tdata);
- if (status == PJ_SUCCESS)
- status = pjsip_inv_send_msg(inv, tdata);
- if (status != PJ_SUCCESS)
- pjsua_perror(THIS_FILE, "Error answering session", status);
- /* Note that inv may be invalid if 200/OK has caused error in
- * starting the media.
- */
- PJ_LOG(4,(THIS_FILE, "Disconnecting replaced call %d",
- replaced_call->index));
- /* Disconnect replaced invite session */
- status = pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, NULL,
- &tdata);
- if (status == PJ_SUCCESS && tdata)
- status = pjsip_inv_send_msg(replaced_inv, tdata);
- if (status != PJ_SUCCESS)
- pjsua_perror(THIS_FILE, "Error terminating session", status);
- } else {
- /* Notify application if on_incoming_call() is overriden,
- * otherwise hangup the call with 480
- */
- if (pjsua_var.ua_cfg.cb.on_incoming_call) {
- pjsua_var.ua_cfg.cb.on_incoming_call(acc_id, call_id, rdata);
- } else {
- pjsua_call_hangup(call_id, PJSIP_SC_TEMPORARILY_UNAVAILABLE,
- NULL, NULL);
- }
- }
- /* This INVITE request has been handled. */
- PJSUA_UNLOCK();
- return PJ_TRUE;
- }
- /*
- * Check if the specified call has active INVITE session and the INVITE
- * session has not been disconnected.
- */
- PJ_DEF(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id)
- {
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
- return pjsua_var.calls[call_id].inv != NULL &&
- pjsua_var.calls[call_id].inv->state != PJSIP_INV_STATE_DISCONNECTED;
- }
- /*
- * Check if call has an active media session.
- */
- PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id)
- {
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
- return pjsua_var.calls[call_id].session != NULL;
- }
- /*
- * Retrieve the media session associated with this call.
- */
- PJ_DEF(pjmedia_session*) pjsua_call_get_media_session(pjsua_call_id call_id)
- {
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- NULL);
- return pjsua_var.calls[call_id].session;
- }
- /*
- * Retrieve the media transport instance that is used for this call.
- */
- PJ_DEF(pjmedia_transport*) pjsua_call_get_media_transport(pjsua_call_id cid)
- {
- PJ_ASSERT_RETURN(cid>=0 && cid<(int)pjsua_var.ua_cfg.max_calls,
- NULL);
- return pjsua_var.calls[cid].med_tp;
- }
- /* Acquire lock to the specified call_id */
- pj_status_t acquire_call(const char *title,
- pjsua_call_id call_id,
- pjsua_call **p_call,
- pjsip_dialog **p_dlg)
- {
- unsigned retry;
- pjsua_call *call = NULL;
- pj_bool_t has_pjsua_lock = PJ_FALSE;
- pj_status_t status = PJ_SUCCESS;
- pj_time_val time_start, timeout;
- pj_gettimeofday(&time_start);
- timeout.sec = 0;
- timeout.msec = PJSUA_ACQUIRE_CALL_TIMEOUT;
- pj_time_val_normalize(&timeout);
- for (retry=0; ; ++retry) {
- if (retry % 10 == 9) {
- pj_time_val dtime;
- pj_gettimeofday(&dtime);
- PJ_TIME_VAL_SUB(dtime, time_start);
- if (!PJ_TIME_VAL_LT(dtime, timeout))
- break;
- }
-
- has_pjsua_lock = PJ_FALSE;
- status = PJSUA_TRY_LOCK();
- if (status != PJ_SUCCESS) {
- pj_thread_sleep(retry/10);
- continue;
- }
- has_pjsua_lock = PJ_TRUE;
- call = &pjsua_var.calls[call_id];
- if (call->inv == NULL) {
- PJSUA_UNLOCK();
- PJ_LOG(3,(THIS_FILE, "Invalid call_id %d in %s", call_id, title));
- return PJSIP_ESESSIONTERMINATED;
- }
- status = pjsip_dlg_try_inc_lock(call->inv->dlg);
- if (status != PJ_SUCCESS) {
- PJSUA_UNLOCK();
- pj_thread_sleep(retry/10);
- continue;
- }
- PJSUA_UNLOCK();
- break;
- }
- if (status != PJ_SUCCESS) {
- if (has_pjsua_lock == PJ_FALSE)
- PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire PJSUA mutex "
- "(possibly system has deadlocked) in %s",
- title));
- else
- PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex "
- "(possibly system has deadlocked) in %s",
- title));
- return PJ_ETIMEDOUT;
- }
-
- *p_call = call;
- *p_dlg = call->inv->dlg;
- return PJ_SUCCESS;
- }
- /*
- * Get the conference port identification associated with the call.
- */
- PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
- {
- pjsua_call *call;
- pjsua_conf_port_id port_id = PJSUA_INVALID_ID;
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
- /* Use PJSUA_LOCK() instead of acquire_call():
- * https://trac.pjsip.org/repos/ticket/1371
- */
- PJSUA_LOCK();
- if (!pjsua_call_is_active(call_id))
- goto on_return;
- call = &pjsua_var.calls[call_id];
- port_id = call->conf_slot;
- on_return:
- PJSUA_UNLOCK();
- return port_id;
- }
- /*
- * 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_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
- pj_bzero(info, sizeof(*info));
- /* Use PJSUA_LOCK() instead of acquire_call():
- * https://trac.pjsip.org/repos/ticket/1371
- */
- PJSUA_LOCK();
- call = &pjsua_var.calls[call_id];
- if (!call->inv) {
- PJSUA_UNLOCK();
- return PJSIP_ESESSIONTERMINATED;
- }
- /* id and role */
- info->id = call_id;
- info->role = call->inv->role;
- info->acc_id = call->acc_id;
- /* 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;
- }
- /* 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));
- /* 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;
- info->last_status_text.ptr = info->buf_.last_status_text;
- pj_strncpy(&info->last_status_text, &call->inv->cause_text,
- sizeof(info->buf_.last_status_text));
- } else {
- /* last_status, last_status_text */
- info->last_status = call->last_code;
- info->last_status_text.ptr = info->buf_.last_status_text;
- pj_strncpy(&info->last_status_text, &call->last_text,
- sizeof(info->buf_.last_status_text));
- }
-
- /* media status and dir */
- info->media_status = call->media_st;
- info->media_dir = call->media_dir;
- /* conference slot number */
- info->conf_slot = call->conf_slot;
- /* calculate duration */
- if (info->state >= PJSIP_INV_STATE_DISCONNECTED) {
- info->total_duration = call->dis_time;
- PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
- if (call->conn_time.sec) {
- info->connect_duration = call->dis_time;
- PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time);
- }
- } else if (info->state == PJSIP_INV_STATE_CONFIRMED) {
- pj_gettimeofday(&info->total_duration);
- PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
- pj_gettimeofday(&info->connect_duration);
- PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time);
- } else {
- pj_gettimeofday(&info->total_duration);
- PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
- }
- PJSUA_UNLOCK();
- return PJ_SUCCESS;
- }
- /*
- * Check if call remote peer support the specified capability.
- */
- PJ_DEF(pjsip_dialog_cap_status) pjsua_call_remote_has_cap(
- pjsua_call_id call_id,
- int htype,
- const pj_str_t *hname,
- const pj_str_t *token)
- {
- pjsua_call *call;
- pjsip_dialog *dlg;
- pj_status_t status;
- pjsip_dialog_cap_status cap_status;
- status = acquire_call("pjsua_call_peer_has_cap()", call_id, &call, &dlg);
- if (status != PJ_SUCCESS)
- return PJSIP_DIALOG_CAP_UNKNOWN;
- cap_status = pjsip_dlg_remote_has_cap(dlg, htype, hname, token);
- pjsip_dlg_dec_lock(dlg);
- return cap_status;
- }
- /*
- * Attach application specific data to the call.
- */
- PJ_DEF(pj_status_t) pjsua_call_set_user_data( pjsua_call_id call_id,
- void *user_data)
- {
- 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 PJ_SUCCESS;
- }
- /*
- * Get user data attached to the call.
- */
- PJ_DEF(void*) pjsua_call_get_user_data(pjsua_call_id call_id)
- {
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- NULL);
- return pjsua_var.calls[call_id].user_data;
- }
- /*
- * Get remote's NAT type.
- */
- PJ_DEF(pj_status_t) pjsua_call_get_rem_nat_type(pjsua_call_id call_id,
- pj_stun_nat_type *p_type)
- {
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
- PJ_ASSERT_RETURN(p_type != NULL, PJ_EINVAL);
- *p_type = pjsua_var.calls[call_id].rem_nat_type;
- return PJ_SUCCESS;
- }
- /*
- * Send response to incoming INVITE request.
- */
- PJ_DEF(pj_status_t) pjsua_call_answer( pjsua_call_id call_id,
- unsigned code,
- const pj_str_t *reason,
- const pjsua_msg_data *msg_data)
- {
- pjsua_call *call;
- pjsip_dialog *dlg;
- 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 = acquire_call("pjsua_call_answer()", call_id, &call, &dlg);
- if (status != PJ_SUCCESS)
- return status;
- if (call->res_time.sec == 0)
- pj_gettimeofday(&call->res_time);
- if (reason && reason->slen == 0)
- reason = NULL;
- /* Create response message */
- status = pjsip_inv_answer(call->inv, code, reason, NULL, &tdata);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Error creating response",
- status);
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- /* Call might have been disconnected if application is answering with
- * 200/OK and the media failed to start.
- */
- if (call->inv == NULL) {
- pjsip_dlg_dec_lock(dlg);
- return PJSIP_ESESSIONTERMINATED;
- }
- /* Add additional headers etc */
- pjsua_process_msg_data( tdata, msg_data);
- /* Send the message */
- status = pjsip_inv_send_msg(call->inv, tdata);
- if (status != PJ_SUCCESS)
- pjsua_perror(THIS_FILE, "Error sending response",
- status);
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- /*
- * Hangup call by using method that is appropriate according to the
- * call state.
- */
- 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;
- pjsip_dialog *dlg;
- pj_status_t status;
- pjsip_tx_data *tdata;
- if (call_id<0 || call_id>=(int)pjsua_var.ua_cfg.max_calls) {
- PJ_LOG(1,(THIS_FILE, "pjsua_call_hangup(): invalid call id %d",
- call_id));
- }
-
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
- status = acquire_call("pjsua_call_hangup()", call_id, &call, &dlg);
- if (status != PJ_SUCCESS)
- return status;
- 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, reason, &tdata);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE,
- "Failed to create end session message",
- status);
- pjsip_dlg_dec_lock(dlg);
- 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) {
- pjsip_dlg_dec_lock(dlg);
- 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);
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- /* Stop lock codec timer, if it is active */
- if (call->lock_codec.reinv_timer.id) {
- pjsip_endpt_cancel_timer(pjsua_var.endpt,
- &call->lock_codec.reinv_timer);
- call->lock_codec.reinv_timer.id = PJ_FALSE;
- }
- pjsip_dlg_dec_lock(dlg);
- return PJ_SUCCESS;
- }
- /*
- * Accept or reject redirection.
- */
- PJ_DEF(pj_status_t) pjsua_call_process_redirect( pjsua_call_id call_id,
- pjsip_redirect_op cmd)
- {
- pjsua_call *call;
- pjsip_dialog *dlg;
- pj_status_t status;
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
- status = acquire_call("pjsua_call_process_redirect()", call_id,
- &call, &dlg);
- if (status != PJ_SUCCESS)
- return status;
- status = pjsip_inv_process_redirect(call->inv, cmd, NULL);
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- /*
- * Put the specified call on hold.
- */
- PJ_DEF(pj_status_t) pjsua_call_set_hold(pjsua_call_id call_id,
- const pjsua_msg_data *msg_data)
- {
- pjmedia_sdp_session *sdp;
- pjsua_call *call;
- pjsip_dialog *dlg;
- 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 = acquire_call("pjsua_call_set_hold()", call_id, &call, &dlg);
- if (status != PJ_SUCCESS)
- return status;
- if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
- PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed"));
- pjsip_dlg_dec_lock(dlg);
- return PJSIP_ESESSIONSTATE;
- }
- status = create_sdp_of_call_hold(call, &sdp);
- if (status != PJ_SUCCESS) {
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- /* 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);
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- /* Add additional headers etc */
- pjsua_process_msg_data( tdata, msg_data);
- /* Record the tx_data to keep track the operation */
- call->hold_msg = (void*) tdata;
- /* 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);
- call->hold_msg = NULL;
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- /* Set flag that local put the call on hold */
- call->local_hold = PJ_TRUE;
- pjsip_dlg_dec_lock(dlg);
- return PJ_SUCCESS;
- }
- /*
- * Send re-INVITE (to release hold).
- */
- PJ_DEF(pj_status_t) pjsua_call_reinvite( pjsua_call_id call_id,
- unsigned options,
- const pjsua_msg_data *msg_data)
- {
- pjmedia_sdp_session *sdp;
- pj_str_t *new_contact = NULL;
- pjsip_tx_data *tdata;
- pjsua_call *call;
- pjsip_dialog *dlg;
- pj_status_t status;
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
- status = acquire_call("pjsua_call_reinvite()", call_id, &call, &dlg);
- if (status != PJ_SUCCESS)
- return status;
- if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
- PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
- pjsip_dlg_dec_lock(dlg);
- return PJSIP_ESESSIONSTATE;
- }
- /* Create SDP */
- if (call->local_hold && (options & PJSUA_CALL_UNHOLD)==0) {
- status = create_sdp_of_call_hold(call, &sdp);
- } else {
- status = pjsua_media_channel_create_sdp(call->index,
- call->inv->pool_prov,
- NULL, &sdp, NULL);
- call->local_hold = PJ_FALSE;
- }
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint",
- status);
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- if ((options & PJSUA_CALL_UPDATE_CONTACT) &
- pjsua_acc_is_valid(call->acc_id))
- {
- new_contact = &pjsua_var.acc[call->acc_id].contact;
- }
- /* Create re-INVITE with new offer */
- status = pjsip_inv_reinvite( call->inv, new_contact, sdp, &tdata);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
- pjsip_dlg_dec_lock(dlg);
- 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);
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- pjsip_dlg_dec_lock(dlg);
- return PJ_SUCCESS;
- }
- /*
- * Send UPDATE request.
- */
- PJ_DEF(pj_status_t) pjsua_call_update( pjsua_call_id call_id,
- unsigned options,
- const pjsua_msg_data *msg_data)
- {
- pjmedia_sdp_session *sdp;
- pj_str_t *new_contact = NULL;
- pjsip_tx_data *tdata;
- pjsua_call *call;
- pjsip_dialog *dlg;
- pj_status_t status;
- PJ_UNUSED_ARG(options);
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
- status = acquire_call("pjsua_call_update()", call_id, &call, &dlg);
- if (status != PJ_SUCCESS)
- return status;
- /* Create SDP */
- status = pjsua_media_channel_create_sdp(call->index,
- call->inv->pool_prov,
- NULL, &sdp, NULL);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint",
- status);
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- if ((options & PJSUA_CALL_UPDATE_CONTACT) &
- pjsua_acc_is_valid(call->acc_id))
- {
- new_contact = &pjsua_var.acc[call->acc_id].contact;
- }
- /* Create UPDATE with new offer */
- status = pjsip_inv_update(call->inv, new_contact, sdp, &tdata);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to create UPDATE request", status);
- pjsip_dlg_dec_lock(dlg);
- 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 UPDATE request", status);
- pjsip_dlg_dec_lock(dlg);
- return status;
- }
- call->local_hold = PJ_FALSE;
- pjsip_dlg_dec_lock(dlg);
- return PJ_SUCCESS;
- }
- /*
- * Initiate call transfer to the specified address.
- */
- 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;
- pjsip_dialog *dlg;
- pjsip_generic_string_hdr *gs_hdr;
- const pj_str_t str_ref_by = { "Referred-By", 11 };
- struct pjsip_evsub_user xfer_cb;
- pj_status_t status;
- PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
- PJ_EINVAL);
-
- status = acquire_call("pjsua_call_xfer()", call_id, &call, &dlg);
- if (status != PJ_SUCCESS)
- return status;
-
- /* Create xfer client subscription. */
- pj_bzero(&xfer_cb, sizeof(xfer_cb));
- xfer_cb.on_evsub_state = &xfer_client_on_e…