Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F7313496
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
127 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/deps/pjsip/pjsip/src/pjsip-ua/sip_inv.c b/deps/pjsip/pjsip/src/pjsip-ua/sip_inv.c
index a317aae5..468077d3 100644
--- a/deps/pjsip/pjsip/src/pjsip-ua/sip_inv.c
+++ b/deps/pjsip/pjsip/src/pjsip-ua/sip_inv.c
@@ -1,4602 +1,4615 @@
/* $Id: sip_inv.c 4521 2013-05-18 05:54:22Z 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 <pjsip-ua/sip_inv.h>
#include <pjsip-ua/sip_100rel.h>
#include <pjsip-ua/sip_timer.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_event.h>
#include <pjsip/sip_multipart.h>
#include <pjsip/sip_transaction.h>
#include <pjmedia/sdp.h>
#include <pjmedia/sdp_neg.h>
#include <pjmedia/errno.h>
#include <pj/string.h>
#include <pj/pool.h>
#include <pj/assert.h>
#include <pj/os.h>
#include <pj/log.h>
#include <pj/rand.h>
/*
* Note on offer/answer:
*
* The offer/answer framework in this implementation assumes the occurence
* of SDP in a particular request/response according to this table:
offer answer Note:
========================================================================
INVITE X INVITE may contain offer
18x/INVITE X X Response may contain offer or answer
2xx/INVITE X X Response may contain offer or answer
ACK X ACK may contain answer
PRACK X PRACK can only contain answer
2xx/PRACK Response may not have offer nor answer
UPDATE X UPDATE may only contain offer
2xx/UPDATE X Response may only contain answer
========================================================================
*
*/
#define THIS_FILE "sip_inv.c"
static const char *inv_state_names[] =
{
"NULL",
"CALLING",
"INCOMING",
"EARLY",
"CONNECTING",
"CONFIRMED",
"DISCONNCTD",
"TERMINATED",
};
/* UPDATE method */
static const pjsip_method pjsip_update_method =
{
PJSIP_OTHER_METHOD,
{ "UPDATE", 6 }
};
#define POOL_INIT_SIZE 256
#define POOL_INC_SIZE 256
/*
* Static prototypes.
*/
static pj_status_t mod_inv_load(pjsip_endpoint *endpt);
static pj_status_t mod_inv_unload(void);
static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata);
static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata);
static void mod_inv_on_tsx_state(pjsip_transaction*, pjsip_event*);
static void inv_on_state_null( pjsip_inv_session *inv, pjsip_event *e);
static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e);
static void inv_on_state_incoming( pjsip_inv_session *inv, pjsip_event *e);
static void inv_on_state_early( pjsip_inv_session *inv, pjsip_event *e);
static void inv_on_state_connecting( pjsip_inv_session *inv, pjsip_event *e);
static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e);
static void inv_on_state_disconnected( pjsip_inv_session *inv, pjsip_event *e);
static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
pjsip_transaction *tsx,
pjsip_rx_data *rdata);
static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv );
static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
const pjmedia_sdp_session *c_sdp);
static pj_status_t process_answer( pjsip_inv_session *inv,
int st_code,
pjsip_tx_data *tdata,
const pjmedia_sdp_session *local_sdp);
static pj_status_t handle_timer_response(pjsip_inv_session *inv,
const pjsip_rx_data *rdata,
pj_bool_t end_sess_on_failure);
static void (*inv_state_handler[])( pjsip_inv_session *inv, pjsip_event *e) =
{
&inv_on_state_null,
&inv_on_state_calling,
&inv_on_state_incoming,
&inv_on_state_early,
&inv_on_state_connecting,
&inv_on_state_confirmed,
&inv_on_state_disconnected,
};
static struct mod_inv
{
pjsip_module mod;
pjsip_endpoint *endpt;
pjsip_inv_callback cb;
} mod_inv =
{
{
NULL, NULL, /* prev, next. */
{ "mod-invite", 10 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */
&mod_inv_load, /* load() */
NULL, /* start() */
NULL, /* stop() */
&mod_inv_unload, /* unload() */
&mod_inv_on_rx_request, /* on_rx_request() */
&mod_inv_on_rx_response, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
&mod_inv_on_tsx_state, /* on_tsx_state() */
}
};
/* Invite session data to be attached to transaction. */
struct tsx_inv_data
{
pjsip_inv_session *inv; /* The invite session */
pj_bool_t sdp_done; /* SDP negotiation done for this tsx? */
pj_bool_t retrying; /* Resend (e.g. due to 401/407) */
pj_str_t done_tag; /* To tag in RX response with answer */
pj_bool_t done_early;/* Negotiation was done for early med? */
};
/*
* Module load()
*/
static pj_status_t mod_inv_load(pjsip_endpoint *endpt)
{
pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6},
{ "UPDATE", 6}};
pj_str_t accepted = { "application/sdp", 15 };
/* Register supported methods: INVITE, ACK, BYE, CANCEL, UPDATE */
pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ALLOW, NULL,
PJ_ARRAY_SIZE(allowed), allowed);
/* Register "application/sdp" in Accept header */
pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ACCEPT, NULL,
1, &accepted);
return PJ_SUCCESS;
}
/*
* Module unload()
*/
static pj_status_t mod_inv_unload(void)
{
/* Should remove capability here */
return PJ_SUCCESS;
}
/*
* Set session state.
*/
void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state,
pjsip_event *e)
{
pjsip_inv_state prev_state = inv->state;
pj_bool_t dont_notify = PJ_FALSE;
pj_status_t status;
/* Prevent STATE_CALLING from being reported more than once because
* of authentication
* https://trac.pjsip.org/repos/ticket/1318
*/
if (state==PJSIP_INV_STATE_CALLING &&
(inv->cb_called & (1 << PJSIP_INV_STATE_CALLING)) != 0)
{
dont_notify = PJ_TRUE;
}
/* If state is confirmed, check that SDP negotiation is done,
* otherwise disconnect the session.
*/
if (state == PJSIP_INV_STATE_CONFIRMED) {
struct tsx_inv_data *tsx_inv_data = NULL;
if (inv->invite_tsx) {
tsx_inv_data = (struct tsx_inv_data*)
inv->invite_tsx->mod_data[mod_inv.mod.id];
}
if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE &&
(tsx_inv_data && !tsx_inv_data->sdp_done) )
{
pjsip_tx_data *bye;
PJ_LOG(4,(inv->obj_name, "SDP offer/answer incomplete, ending the "
"session"));
status = pjsip_inv_end_session(inv, PJSIP_SC_NOT_ACCEPTABLE,
NULL, &bye);
if (status == PJ_SUCCESS && bye)
status = pjsip_inv_send_msg(inv, bye);
return;
}
}
/* Set state. */
inv->state = state;
/* If state is DISCONNECTED, cause code MUST have been set. */
pj_assert(inv->state != PJSIP_INV_STATE_DISCONNECTED ||
inv->cause != 0);
/* Mark the callback as called for this state */
inv->cb_called |= (1 << state);
/* Call on_state_changed() callback. */
if (mod_inv.cb.on_state_changed && inv->notify && !dont_notify)
(*mod_inv.cb.on_state_changed)(inv, e);
/* Only decrement when previous state is not already DISCONNECTED */
if (inv->state == PJSIP_INV_STATE_DISCONNECTED &&
prev_state != PJSIP_INV_STATE_DISCONNECTED)
{
if (inv->last_ack) {
pjsip_tx_data_dec_ref(inv->last_ack);
inv->last_ack = NULL;
}
if (inv->invite_req) {
pjsip_tx_data_dec_ref(inv->invite_req);
inv->invite_req = NULL;
}
pjsip_100rel_end_session(inv);
pjsip_timer_end_session(inv);
pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
/* Release the flip-flop pools */
pj_pool_release(inv->pool_prov);
inv->pool_prov = NULL;
pj_pool_release(inv->pool_active);
inv->pool_active = NULL;
}
}
/*
* Set cause code.
*/
void inv_set_cause(pjsip_inv_session *inv, int cause_code,
const pj_str_t *cause_text)
{
if (cause_code > inv->cause) {
inv->cause = (pjsip_status_code) cause_code;
if (cause_text)
pj_strdup(inv->pool, &inv->cause_text, cause_text);
else if (cause_code/100 == 2)
inv->cause_text = pj_str("Normal call clearing");
else
inv->cause_text = *pjsip_get_status_text(cause_code);
}
}
/*
* Check if outgoing request needs to have SDP answer.
* This applies for both ACK and PRACK requests.
*/
static const pjmedia_sdp_session *inv_has_pending_answer(pjsip_inv_session *inv,
pjsip_transaction *tsx)
{
pjmedia_sdp_neg_state neg_state;
const pjmedia_sdp_session *sdp = NULL;
pj_status_t status;
/* If SDP negotiator is ready, start negotiation. */
/* Start nego when appropriate. */
neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) :
PJMEDIA_SDP_NEG_STATE_NULL;
if (neg_state == PJMEDIA_SDP_NEG_STATE_DONE) {
/* Nothing to do */
} else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
pjmedia_sdp_neg_has_local_answer(inv->neg) )
{
struct tsx_inv_data *tsx_inv_data;
struct tsx_inv_data dummy;
/* Get invite session's transaction data.
* Note that tsx may be NULL, for example when application sends
* delayed ACK request (at this time, the original INVITE
* transaction may have been destroyed.
*/
if (tsx) {
tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
} else {
tsx_inv_data = &dummy;
pj_bzero(&dummy, sizeof(dummy));
dummy.inv = inv;
}
status = inv_negotiate_sdp(inv);
if (status != PJ_SUCCESS)
return NULL;
/* Mark this transaction has having SDP offer/answer done. */
tsx_inv_data->sdp_done = 1;
status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
} else {
/* This remark is only valid for ACK.
PJ_LOG(4,(inv->dlg->obj_name,
"FYI, the SDP negotiator state (%s) is in a mess "
"when sending this ACK/PRACK request",
pjmedia_sdp_neg_state_str(neg_state)));
*/
}
return sdp;
}
/*
* Send ACK for 2xx response.
*/
static pj_status_t inv_send_ack(pjsip_inv_session *inv, pjsip_event *e)
{
pjsip_rx_data *rdata;
pjsip_event ack_e;
pj_status_t status;
if (e->type == PJSIP_EVENT_TSX_STATE)
rdata = e->body.tsx_state.src.rdata;
else if (e->type == PJSIP_EVENT_RX_MSG)
rdata = e->body.rx_msg.rdata;
else {
pj_assert(!"Unsupported event type");
return PJ_EBUG;
}
PJ_LOG(5,(inv->obj_name, "Received %s, sending ACK",
pjsip_rx_data_get_info(rdata)));
/* Check if we have cached ACK request. Must not use the cached ACK
* if it's still marked as pending by transport (#1011)
*/
if (inv->last_ack && rdata->msg_info.cseq->cseq == inv->last_ack_cseq &&
!inv->last_ack->is_pending)
{
pjsip_tx_data_add_ref(inv->last_ack);
} else if (mod_inv.cb.on_send_ack) {
/* If application handles ACK transmission manually, just notify the
* callback
*/
PJ_LOG(5,(inv->obj_name, "Received %s, notifying application callback",
pjsip_rx_data_get_info(rdata)));
(*mod_inv.cb.on_send_ack)(inv, rdata);
return PJ_SUCCESS;
} else {
status = pjsip_inv_create_ack(inv, rdata->msg_info.cseq->cseq,
&inv->last_ack);
if (status != PJ_SUCCESS)
return status;
}
PJSIP_EVENT_INIT_TX_MSG(ack_e, inv->last_ack);
/* Send ACK */
status = pjsip_dlg_send_request(inv->dlg, inv->last_ack, -1, NULL);
if (status != PJ_SUCCESS) {
/* Better luck next time */
pj_assert(!"Unable to send ACK!");
return status;
}
/* Set state to CONFIRMED (if we're not in CONFIRMED yet).
* But don't set it to CONFIRMED if we're already DISCONNECTED
* (this may have been a late 200/OK response.
*/
if (inv->state < PJSIP_INV_STATE_CONFIRMED) {
inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &ack_e);
}
return PJ_SUCCESS;
}
/*
* Module on_rx_request()
*
* This callback is called for these events:
* - endpoint receives request which was unhandled by higher priority
* modules (e.g. transaction layer, dialog layer).
* - dialog distributes incoming request to its usages.
*/
static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata)
{
pjsip_method *method;
pjsip_dialog *dlg;
pjsip_inv_session *inv;
/* Only wants to receive request from a dialog. */
dlg = pjsip_rdata_get_dlg(rdata);
if (dlg == NULL)
return PJ_FALSE;
inv = (pjsip_inv_session*) dlg->mod_data[mod_inv.mod.id];
/* Report to dialog that we handle INVITE, CANCEL, BYE, ACK.
* If we need to send response, it will be sent in the state
* handlers.
*/
method = &rdata->msg_info.msg->line.req.method;
if (method->id == PJSIP_INVITE_METHOD) {
return PJ_TRUE;
}
/* BYE and CANCEL must have existing invite session */
if (method->id == PJSIP_BYE_METHOD ||
method->id == PJSIP_CANCEL_METHOD)
{
if (inv == NULL)
return PJ_FALSE;
return PJ_TRUE;
}
/* On receipt ACK request, when state is CONNECTING,
* move state to CONFIRMED.
*/
if (method->id == PJSIP_ACK_METHOD && inv) {
/* Ignore if we don't have INVITE in progress */
if (!inv->invite_tsx) {
return PJ_TRUE;
}
/* Ignore ACK if pending INVITE transaction has not finished. */
if (inv->invite_tsx->state < PJSIP_TSX_STATE_COMPLETED) {
return PJ_TRUE;
}
/* Ignore ACK with different CSeq
* https://trac.pjsip.org/repos/ticket/1391
*/
if (rdata->msg_info.cseq->cseq != inv->invite_tsx->cseq) {
return PJ_TRUE;
}
/* Terminate INVITE transaction, if it's still present. */
if (inv->invite_tsx->state <= PJSIP_TSX_STATE_COMPLETED) {
/* Before we terminate INVITE transaction, process the SDP
* in the ACK request, if any.
* Only do this when invite state is not already disconnected
* (http://trac.pjsip.org/repos/ticket/640).
*/
if (inv->state < PJSIP_INV_STATE_DISCONNECTED) {
inv_check_sdp_in_incoming_msg(inv, inv->invite_tsx, rdata);
/* Check if local offer got no SDP answer and INVITE session
* is in CONFIRMED state.
*/
if (pjmedia_sdp_neg_get_state(inv->neg)==
PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER &&
inv->state==PJSIP_INV_STATE_CONFIRMED)
{
pjmedia_sdp_neg_cancel_offer(inv->neg);
}
}
/* Now we can terminate the INVITE transaction */
pj_assert(inv->invite_tsx->status_code >= 200);
pjsip_tsx_terminate(inv->invite_tsx,
inv->invite_tsx->status_code);
inv->invite_tsx = NULL;
if (inv->last_answer) {
pjsip_tx_data_dec_ref(inv->last_answer);
inv->last_answer = NULL;
}
}
/* On receipt of ACK, only set state to confirmed when state
* is CONNECTING (e.g. we don't want to set the state to confirmed
* when we receive ACK retransmission after sending non-2xx!)
*/
if (inv->state == PJSIP_INV_STATE_CONNECTING) {
pjsip_event event;
PJSIP_EVENT_INIT_RX_MSG(event, rdata);
inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &event);
}
}
return PJ_FALSE;
}
/* This function will process Session Timer headers in received
* 2xx or 422 response of INVITE/UPDATE request.
*/
static pj_status_t handle_timer_response(pjsip_inv_session *inv,
const pjsip_rx_data *rdata,
pj_bool_t end_sess_on_failure)
{
pjsip_status_code st_code;
pj_status_t status;
status = pjsip_timer_process_resp(inv, rdata, &st_code);
if (status != PJ_SUCCESS && end_sess_on_failure) {
pjsip_tx_data *tdata;
pj_status_t status2;
status2 = pjsip_inv_end_session(inv, st_code, NULL, &tdata);
if (tdata && status2 == PJ_SUCCESS)
pjsip_inv_send_msg(inv, tdata);
}
return status;
}
/*
* Module on_rx_response().
*
* This callback is called for these events:
* - dialog distributes incoming 2xx response to INVITE (outside
* transaction) to its usages.
* - endpoint distributes strayed responses.
*/
static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata)
{
pjsip_dialog *dlg;
pjsip_inv_session *inv;
pjsip_msg *msg = rdata->msg_info.msg;
dlg = pjsip_rdata_get_dlg(rdata);
/* Ignore responses outside dialog */
if (dlg == NULL)
return PJ_FALSE;
/* Ignore responses not belonging to invite session */
inv = pjsip_dlg_get_inv_session(dlg);
if (inv == NULL)
return PJ_FALSE;
/* This MAY be retransmission of 2xx response to INVITE.
* If it is, we need to send ACK.
*/
if (msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/100==2 &&
rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD &&
inv->invite_tsx == NULL)
{
pjsip_event e;
PJSIP_EVENT_INIT_RX_MSG(e, rdata);
inv_send_ack(inv, &e);
return PJ_TRUE;
}
/* No other processing needs to be done here. */
return PJ_FALSE;
}
/*
* Module on_tsx_state()
*
* This callback is called by dialog framework for all transactions
* inside the dialog for all its dialog usages.
*/
static void mod_inv_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e)
{
pjsip_dialog *dlg;
pjsip_inv_session *inv;
dlg = pjsip_tsx_get_dlg(tsx);
if (dlg == NULL)
return;
inv = pjsip_dlg_get_inv_session(dlg);
if (inv == NULL)
return;
/* Call state handler for the invite session. */
(*inv_state_handler[inv->state])(inv, e);
/* Call on_tsx_state. CANCEL request is a special case and has been
* reported earlier in inv_respond_incoming_cancel()
*/
if (mod_inv.cb.on_tsx_state_changed && inv->notify &&
!(tsx->method.id==PJSIP_CANCEL_METHOD &&
e->body.tsx_state.type==PJSIP_EVENT_RX_MSG))
{
(*mod_inv.cb.on_tsx_state_changed)(inv, tsx, e);
}
/* Clear invite transaction when tsx is confirmed.
* Previously we set invite_tsx to NULL only when transaction has
* terminated, but this didn't work when ACK has the same Via branch
* value as the INVITE (see http://www.pjsip.org/trac/ticket/113)
*/
if (tsx->state>=PJSIP_TSX_STATE_CONFIRMED && tsx == inv->invite_tsx) {
inv->invite_tsx = NULL;
if (inv->last_answer) {
pjsip_tx_data_dec_ref(inv->last_answer);
inv->last_answer = NULL;
}
}
}
/*
* Initialize the invite module.
*/
PJ_DEF(pj_status_t) pjsip_inv_usage_init( pjsip_endpoint *endpt,
const pjsip_inv_callback *cb)
{
pj_status_t status;
/* Check arguments. */
PJ_ASSERT_RETURN(endpt && cb, PJ_EINVAL);
/* Some callbacks are mandatory */
PJ_ASSERT_RETURN(cb->on_state_changed && cb->on_new_session, PJ_EINVAL);
/* Check if module already registered. */
PJ_ASSERT_RETURN(mod_inv.mod.id == -1, PJ_EINVALIDOP);
/* Copy param. */
pj_memcpy(&mod_inv.cb, cb, sizeof(pjsip_inv_callback));
mod_inv.endpt = endpt;
/* Register the module. */
status = pjsip_endpt_register_module(endpt, &mod_inv.mod);
if (status != PJ_SUCCESS)
return status;
return PJ_SUCCESS;
}
/*
* Get the instance of invite module.
*/
PJ_DEF(pjsip_module*) pjsip_inv_usage_instance(void)
{
return &mod_inv.mod;
}
/*
* Return the invite session for the specified dialog.
*/
PJ_DEF(pjsip_inv_session*) pjsip_dlg_get_inv_session(pjsip_dialog *dlg)
{
return (pjsip_inv_session*) dlg->mod_data[mod_inv.mod.id];
}
/*
* Get INVITE state name.
*/
PJ_DEF(const char *) pjsip_inv_state_name(pjsip_inv_state state)
{
PJ_ASSERT_RETURN(state >= PJSIP_INV_STATE_NULL &&
state <= PJSIP_INV_STATE_DISCONNECTED,
"??");
return inv_state_names[state];
}
/*
* Create UAC invite session.
*/
PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg,
const pjmedia_sdp_session *local_sdp,
unsigned options,
pjsip_inv_session **p_inv)
{
pjsip_inv_session *inv;
pj_status_t status;
/* Verify arguments. */
PJ_ASSERT_RETURN(dlg && p_inv, PJ_EINVAL);
/* Must lock dialog first */
pjsip_dlg_inc_lock(dlg);
/* Normalize options */
if (options & PJSIP_INV_REQUIRE_100REL)
options |= PJSIP_INV_SUPPORT_100REL;
if (options & PJSIP_INV_REQUIRE_TIMER)
options |= PJSIP_INV_SUPPORT_TIMER;
/* Create the session */
inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session);
pj_assert(inv != NULL);
inv->pool = dlg->pool;
inv->role = PJSIP_ROLE_UAC;
inv->state = PJSIP_INV_STATE_NULL;
inv->dlg = dlg;
inv->options = options;
inv->notify = PJ_TRUE;
inv->cause = (pjsip_status_code) 0;
/* Create flip-flop pool (see ticket #877) */
/* (using inv->obj_name as temporary variable for pool names */
pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg->pool);
inv->pool_prov = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name,
POOL_INIT_SIZE, POOL_INC_SIZE);
inv->pool_active = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name,
POOL_INIT_SIZE, POOL_INC_SIZE);
/* Object name will use the same dialog pointer. */
pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg);
/* Create negotiator if local_sdp is specified. */
if (local_sdp) {
status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
local_sdp, &inv->neg);
if (status != PJ_SUCCESS) {
pjsip_dlg_dec_lock(dlg);
return status;
}
}
/* Register invite as dialog usage. */
status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv);
if (status != PJ_SUCCESS) {
pjsip_dlg_dec_lock(dlg);
return status;
}
/* Increment dialog session */
pjsip_dlg_inc_session(dlg, &mod_inv.mod);
/* Create 100rel handler */
pjsip_100rel_attach(inv);
/* Done */
*p_inv = inv;
pjsip_dlg_dec_lock(dlg);
PJ_LOG(5,(inv->obj_name, "UAC invite session created for dialog %s",
dlg->obj_name));
return PJ_SUCCESS;
}
PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
{
pjsip_rdata_sdp_info *sdp_info;
pjsip_msg_body *body = rdata->msg_info.msg->body;
pjsip_ctype_hdr *ctype_hdr = rdata->msg_info.ctype;
pjsip_media_type app_sdp;
sdp_info = (pjsip_rdata_sdp_info*)
rdata->endpt_info.mod_data[mod_inv.mod.id];
if (sdp_info)
return sdp_info;
sdp_info = PJ_POOL_ZALLOC_T(rdata->tp_info.pool,
pjsip_rdata_sdp_info);
PJ_ASSERT_RETURN(mod_inv.mod.id >= 0, sdp_info);
rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
pjsip_media_type_init2(&app_sdp, "application", "sdp");
if (body && ctype_hdr &&
pj_stricmp(&ctype_hdr->media.type, &app_sdp.type)==0 &&
pj_stricmp(&ctype_hdr->media.subtype, &app_sdp.subtype)==0)
{
sdp_info->body.ptr = (char*)body->data;
sdp_info->body.slen = body->len;
} else if (body && ctype_hdr &&
pj_stricmp2(&ctype_hdr->media.type, "multipart")==0 &&
(pj_stricmp2(&ctype_hdr->media.subtype, "mixed")==0 ||
pj_stricmp2(&ctype_hdr->media.subtype, "alternative")==0))
{
pjsip_multipart_part *part;
part = pjsip_multipart_find_part(body, &app_sdp, NULL);
if (part) {
sdp_info->body.ptr = (char*)part->body->data;
sdp_info->body.slen = part->body->len;
}
}
if (sdp_info->body.ptr) {
pj_status_t status;
status = pjmedia_sdp_parse(rdata->tp_info.pool,
sdp_info->body.ptr,
sdp_info->body.slen,
&sdp_info->sdp);
if (status == PJ_SUCCESS)
status = pjmedia_sdp_validate2(sdp_info->sdp, PJ_FALSE);
if (status != PJ_SUCCESS) {
sdp_info->sdp = NULL;
PJ_PERROR(1,(THIS_FILE, status,
"Error parsing/validating SDP body"));
}
sdp_info->sdp_err = status;
}
return sdp_info;
}
/*
* Verify incoming INVITE request.
*/
PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
pj_pool_t *tmp_pool,
unsigned *options,
const pjmedia_sdp_session *r_sdp,
const pjmedia_sdp_session *l_sdp,
pjsip_dialog *dlg,
pjsip_endpoint *endpt,
pjsip_tx_data **p_tdata)
{
pjsip_msg *msg = NULL;
pjsip_allow_hdr *allow = NULL;
pjsip_supported_hdr *sup_hdr = NULL;
pjsip_require_hdr *req_hdr = NULL;
pjsip_contact_hdr *c_hdr = NULL;
int code = 200;
unsigned rem_option = 0;
pj_status_t status = PJ_SUCCESS;
pjsip_hdr res_hdr_list;
pjsip_rdata_sdp_info *sdp_info;
/* Init return arguments. */
if (p_tdata) *p_tdata = NULL;
/* Verify arguments. */
PJ_ASSERT_RETURN(tmp_pool != NULL && options != NULL, PJ_EINVAL);
/* Normalize options */
if (*options & PJSIP_INV_REQUIRE_100REL)
*options |= PJSIP_INV_SUPPORT_100REL;
if (*options & PJSIP_INV_REQUIRE_TIMER)
*options |= PJSIP_INV_SUPPORT_TIMER;
if (*options & PJSIP_INV_REQUIRE_ICE)
*options |= PJSIP_INV_SUPPORT_ICE;
if (rdata) {
/* Get the message in rdata */
msg = rdata->msg_info.msg;
/* Must be INVITE request. */
PJ_ASSERT_RETURN(msg && msg->type == PJSIP_REQUEST_MSG &&
msg->line.req.method.id == PJSIP_INVITE_METHOD,
PJ_EINVAL);
}
/* If tdata is specified, then either dlg or endpt must be specified */
PJ_ASSERT_RETURN((!p_tdata) || (endpt || dlg), PJ_EINVAL);
/* Get the endpoint */
endpt = endpt ? endpt : dlg->endpt;
/* Init response header list */
pj_list_init(&res_hdr_list);
/* Check the Contact header */
if (msg) {
c_hdr = (pjsip_contact_hdr*)
pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL);
}
if (msg && (!c_hdr || !c_hdr->uri)) {
/* Missing Contact header or Contact contains "*" */
pjsip_warning_hdr *w;
pj_str_t warn_text;
warn_text = pj_str("Bad/missing Contact header");
w = pjsip_warning_hdr_create(tmp_pool, 399,
pjsip_endpt_name(endpt),
&warn_text);
if (w) {
pj_list_push_back(&res_hdr_list, w);
}
code = PJSIP_SC_BAD_REQUEST;
status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
goto on_return;
}
/* Check the request body, see if it's something that we support,
* only when the body hasn't been parsed before.
*/
if (r_sdp == NULL && rdata) {
sdp_info = pjsip_rdata_get_sdp_info(rdata);
} else {
sdp_info = NULL;
}
if (r_sdp==NULL && msg && msg->body) {
/* Check if body really contains SDP. */
if (sdp_info->body.ptr == NULL) {
/* Couldn't find "application/sdp" */
code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE;
status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
if (p_tdata) {
/* Add Accept header to response */
pjsip_accept_hdr *acc;
acc = pjsip_accept_hdr_create(tmp_pool);
PJ_ASSERT_RETURN(acc, PJ_ENOMEM);
acc->values[acc->count++] = pj_str("application/sdp");
pj_list_push_back(&res_hdr_list, acc);
}
goto on_return;
}
if (sdp_info->sdp_err != PJ_SUCCESS) {
/* Unparseable or invalid SDP */
code = PJSIP_SC_BAD_REQUEST;
if (p_tdata) {
/* Add Warning header. */
pjsip_warning_hdr *w;
w = pjsip_warning_hdr_create_from_status(tmp_pool,
pjsip_endpt_name(endpt),
sdp_info->sdp_err);
PJ_ASSERT_RETURN(w, PJ_ENOMEM);
pj_list_push_back(&res_hdr_list, w);
}
goto on_return;
}
r_sdp = sdp_info->sdp;
}
if (r_sdp) {
/* Negotiate with local SDP */
if (l_sdp) {
pjmedia_sdp_neg *neg;
/* Local SDP must be valid! */
PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(l_sdp))==PJ_SUCCESS,
status);
/* Create SDP negotiator */
status = pjmedia_sdp_neg_create_w_remote_offer(
tmp_pool, l_sdp, r_sdp, &neg);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Negotiate SDP */
status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0);
if (status != PJ_SUCCESS) {
/* Incompatible media */
code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
if (p_tdata) {
pjsip_accept_hdr *acc;
pjsip_warning_hdr *w;
/* Add Warning header. */
w = pjsip_warning_hdr_create_from_status(
tmp_pool,
pjsip_endpt_name(endpt), status);
PJ_ASSERT_RETURN(w, PJ_ENOMEM);
pj_list_push_back(&res_hdr_list, w);
/* Add Accept header to response */
acc = pjsip_accept_hdr_create(tmp_pool);
PJ_ASSERT_RETURN(acc, PJ_ENOMEM);
acc->values[acc->count++] = pj_str("application/sdp");
pj_list_push_back(&res_hdr_list, acc);
}
goto on_return;
}
}
}
/* Check supported methods, see if peer supports UPDATE.
* We just assume that peer supports standard INVITE, ACK, CANCEL, and BYE
* implicitly by sending this INVITE.
*/
if (msg) {
allow = (pjsip_allow_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_ALLOW,
NULL);
}
if (allow) {
unsigned i;
const pj_str_t STR_UPDATE = { "UPDATE", 6 };
for (i=0; i<allow->count; ++i) {
if (pj_stricmp(&allow->values[i], &STR_UPDATE)==0)
break;
}
if (i != allow->count) {
/* UPDATE is present in Allow */
rem_option |= PJSIP_INV_SUPPORT_UPDATE;
}
}
/* Check Supported header */
if (msg) {
sup_hdr = (pjsip_supported_hdr*)
pjsip_msg_find_hdr(msg, PJSIP_H_SUPPORTED, NULL);
}
if (sup_hdr) {
unsigned i;
const pj_str_t STR_100REL = { "100rel", 6};
const pj_str_t STR_TIMER = { "timer", 5};
const pj_str_t STR_ICE = { "ice", 3 };
for (i=0; i<sup_hdr->count; ++i) {
if (pj_stricmp(&sup_hdr->values[i], &STR_100REL)==0)
rem_option |= PJSIP_INV_SUPPORT_100REL;
else if (pj_stricmp(&sup_hdr->values[i], &STR_TIMER)==0)
rem_option |= PJSIP_INV_SUPPORT_TIMER;
else if (pj_stricmp(&sup_hdr->values[i], &STR_ICE)==0)
rem_option |= PJSIP_INV_SUPPORT_ICE;
}
}
/* Check Require header */
if (msg) {
req_hdr = (pjsip_require_hdr*)
pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL);
}
if (req_hdr) {
unsigned i;
const pj_str_t STR_100REL = { "100rel", 6};
const pj_str_t STR_REPLACES = { "replaces", 8 };
const pj_str_t STR_TIMER = { "timer", 5 };
const pj_str_t STR_ICE = { "ice", 3 };
unsigned unsupp_cnt = 0;
pj_str_t unsupp_tags[PJSIP_GENERIC_ARRAY_MAX_COUNT];
for (i=0; i<req_hdr->count; ++i) {
if ((*options & PJSIP_INV_SUPPORT_100REL) &&
pj_stricmp(&req_hdr->values[i], &STR_100REL)==0)
{
rem_option |= PJSIP_INV_REQUIRE_100REL;
} else if ((*options & PJSIP_INV_SUPPORT_TIMER) &&
pj_stricmp(&req_hdr->values[i], &STR_TIMER)==0)
{
rem_option |= PJSIP_INV_REQUIRE_TIMER;
} else if (pj_stricmp(&req_hdr->values[i], &STR_REPLACES)==0) {
pj_bool_t supp;
supp = pjsip_endpt_has_capability(endpt, PJSIP_H_SUPPORTED,
NULL, &STR_REPLACES);
if (!supp)
unsupp_tags[unsupp_cnt++] = req_hdr->values[i];
} else if ((*options & PJSIP_INV_SUPPORT_ICE) &&
pj_stricmp(&req_hdr->values[i], &STR_ICE)==0)
{
rem_option |= PJSIP_INV_REQUIRE_ICE;
} else if (!pjsip_endpt_has_capability(endpt, PJSIP_H_SUPPORTED,
NULL, &req_hdr->values[i]))
{
/* Unknown/unsupported extension tag! */
unsupp_tags[unsupp_cnt++] = req_hdr->values[i];
}
}
/* Check if there are required tags that we don't support */
if (unsupp_cnt) {
code = PJSIP_SC_BAD_EXTENSION;
status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
if (p_tdata) {
pjsip_unsupported_hdr *unsupp_hdr;
const pjsip_hdr *h;
/* Add Unsupported header. */
unsupp_hdr = pjsip_unsupported_hdr_create(tmp_pool);
PJ_ASSERT_RETURN(unsupp_hdr != NULL, PJ_ENOMEM);
unsupp_hdr->count = unsupp_cnt;
for (i=0; i<unsupp_cnt; ++i)
unsupp_hdr->values[i] = unsupp_tags[i];
pj_list_push_back(&res_hdr_list, unsupp_hdr);
/* Add Supported header. */
h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED,
NULL);
pj_assert(h);
if (h) {
sup_hdr = (pjsip_supported_hdr*)
pjsip_hdr_clone(tmp_pool, h);
pj_list_push_back(&res_hdr_list, sup_hdr);
}
}
goto on_return;
}
}
/* Check if there are local requirements that are not supported
* by peer.
*/
if ( msg && (((*options & PJSIP_INV_REQUIRE_100REL)!=0 &&
(rem_option & PJSIP_INV_SUPPORT_100REL)==0) ||
((*options & PJSIP_INV_REQUIRE_TIMER)!=0 &&
(rem_option & PJSIP_INV_SUPPORT_TIMER)==0)))
{
code = PJSIP_SC_EXTENSION_REQUIRED;
status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
if (p_tdata) {
const pjsip_hdr *h;
/* Add Require header. */
req_hdr = pjsip_require_hdr_create(tmp_pool);
PJ_ASSERT_RETURN(req_hdr != NULL, PJ_ENOMEM);
if (*options & PJSIP_INV_REQUIRE_100REL)
req_hdr->values[req_hdr->count++] = pj_str("100rel");
if (*options & PJSIP_INV_REQUIRE_TIMER)
req_hdr->values[req_hdr->count++] = pj_str("timer");
pj_list_push_back(&res_hdr_list, req_hdr);
/* Add Supported header. */
h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED,
NULL);
pj_assert(h);
if (h) {
sup_hdr = (pjsip_supported_hdr*)
pjsip_hdr_clone(tmp_pool, h);
pj_list_push_back(&res_hdr_list, sup_hdr);
}
}
goto on_return;
}
/* If remote Require something that we support, make us Require
* that feature too.
*/
if (rem_option & PJSIP_INV_REQUIRE_100REL) {
pj_assert(*options & PJSIP_INV_SUPPORT_100REL);
*options |= PJSIP_INV_REQUIRE_100REL;
}
if (rem_option & PJSIP_INV_REQUIRE_TIMER) {
pj_assert(*options & PJSIP_INV_SUPPORT_TIMER);
*options |= PJSIP_INV_REQUIRE_TIMER;
}
on_return:
/* Create response if necessary */
if (code != 200 && p_tdata) {
pjsip_tx_data *tdata;
const pjsip_hdr *h;
if (!rdata) {
return PJSIP_ERRNO_FROM_SIP_STATUS(code);
}
if (dlg) {
status = pjsip_dlg_create_response(dlg, rdata, code, NULL,
&tdata);
} else {
status = pjsip_endpt_create_response(endpt, rdata, code, NULL,
&tdata);
}
if (status != PJ_SUCCESS)
return status;
/* Add response headers. */
h = res_hdr_list.next;
while (h != &res_hdr_list) {
pjsip_hdr *cloned;
cloned = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, h);
PJ_ASSERT_RETURN(cloned, PJ_ENOMEM);
pjsip_msg_add_hdr(tdata->msg, cloned);
h = h->next;
}
*p_tdata = tdata;
/* Can not return PJ_SUCCESS when response message is produced.
* Ref: PROTOS test ~#2490
*/
if (status == PJ_SUCCESS)
status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
}
return status;
}
/*
* Verify incoming INVITE request.
*/
PJ_DEF(pj_status_t) pjsip_inv_verify_request2(pjsip_rx_data *rdata,
unsigned *options,
const pjmedia_sdp_session *r_sdp,
const pjmedia_sdp_session *l_sdp,
pjsip_dialog *dlg,
pjsip_endpoint *endpt,
pjsip_tx_data **p_tdata)
{
return pjsip_inv_verify_request3(rdata, rdata->tp_info.pool,
options, r_sdp, l_sdp, dlg,
endpt, p_tdata);
}
/*
* Verify incoming INVITE request.
*/
PJ_DEF(pj_status_t) pjsip_inv_verify_request( pjsip_rx_data *rdata,
unsigned *options,
const pjmedia_sdp_session *l_sdp,
pjsip_dialog *dlg,
pjsip_endpoint *endpt,
pjsip_tx_data **p_tdata)
{
return pjsip_inv_verify_request3(rdata, rdata->tp_info.pool,
options, NULL, l_sdp, dlg,
endpt, p_tdata);
}
/*
* Create UAS invite session.
*/
PJ_DEF(pj_status_t) pjsip_inv_create_uas( pjsip_dialog *dlg,
pjsip_rx_data *rdata,
const pjmedia_sdp_session *local_sdp,
unsigned options,
pjsip_inv_session **p_inv)
{
pjsip_inv_session *inv;
struct tsx_inv_data *tsx_inv_data;
pjsip_msg *msg;
pjsip_rdata_sdp_info *sdp_info;
pj_status_t status;
/* Verify arguments. */
PJ_ASSERT_RETURN(dlg && rdata && p_inv, PJ_EINVAL);
/* Dialog MUST have been initialised. */
PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata) != NULL, PJ_EINVALIDOP);
msg = rdata->msg_info.msg;
/* rdata MUST contain INVITE request */
PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG &&
msg->line.req.method.id == PJSIP_INVITE_METHOD,
PJ_EINVALIDOP);
/* Lock dialog */
pjsip_dlg_inc_lock(dlg);
/* Normalize options */
if (options & PJSIP_INV_REQUIRE_100REL)
options |= PJSIP_INV_SUPPORT_100REL;
if (options & PJSIP_INV_REQUIRE_TIMER)
options |= PJSIP_INV_SUPPORT_TIMER;
/* Create the session */
inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session);
pj_assert(inv != NULL);
inv->pool = dlg->pool;
inv->role = PJSIP_ROLE_UAS;
inv->state = PJSIP_INV_STATE_NULL;
inv->dlg = dlg;
inv->options = options;
inv->notify = PJ_TRUE;
inv->cause = (pjsip_status_code) 0;
/* Create flip-flop pool (see ticket #877) */
/* (using inv->obj_name as temporary variable for pool names */
pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg->pool);
inv->pool_prov = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name,
POOL_INIT_SIZE, POOL_INC_SIZE);
inv->pool_active = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name,
POOL_INIT_SIZE, POOL_INC_SIZE);
/* Object name will use the same dialog pointer. */
pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg);
/* Process SDP in message body, if present. */
sdp_info = pjsip_rdata_get_sdp_info(rdata);
if (sdp_info->sdp_err) {
pjsip_dlg_dec_lock(dlg);
return sdp_info->sdp_err;
}
/* Create negotiator. */
if (sdp_info->sdp) {
status = pjmedia_sdp_neg_create_w_remote_offer(inv->pool, local_sdp,
sdp_info->sdp,
&inv->neg);
} else if (local_sdp) {
status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
local_sdp, &inv->neg);
} else {
status = PJ_SUCCESS;
}
if (status != PJ_SUCCESS) {
pjsip_dlg_dec_lock(dlg);
return status;
}
/* Register invite as dialog usage. */
status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv);
if (status != PJ_SUCCESS) {
pjsip_dlg_dec_lock(dlg);
return status;
}
/* Increment session in the dialog. */
pjsip_dlg_inc_session(dlg, &mod_inv.mod);
/* Save the invite transaction. */
inv->invite_tsx = pjsip_rdata_get_tsx(rdata);
/* Attach our data to the transaction. */
tsx_inv_data = PJ_POOL_ZALLOC_T(inv->invite_tsx->pool, struct tsx_inv_data);
tsx_inv_data->inv = inv;
inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data;
/* Create 100rel handler */
if (inv->options & PJSIP_INV_REQUIRE_100REL) {
pjsip_100rel_attach(inv);
}
/* Done */
pjsip_dlg_dec_lock(dlg);
*p_inv = inv;
PJ_LOG(5,(inv->obj_name, "UAS invite session created for dialog %s",
dlg->obj_name));
return PJ_SUCCESS;
}
/*
* Forcefully terminate the session.
*/
PJ_DEF(pj_status_t) pjsip_inv_terminate( pjsip_inv_session *inv,
int st_code,
pj_bool_t notify)
{
PJ_ASSERT_RETURN(inv, PJ_EINVAL);
/* Lock dialog. */
pjsip_dlg_inc_lock(inv->dlg);
/* Set callback notify flag. */
inv->notify = notify;
/* If there's pending transaction, terminate the transaction.
* This may subsequently set the INVITE session state to
* disconnected.
*/
if (inv->invite_tsx &&
inv->invite_tsx->state <= PJSIP_TSX_STATE_COMPLETED)
{
pjsip_tsx_terminate(inv->invite_tsx, st_code);
}
/* Set cause. */
inv_set_cause(inv, st_code, NULL);
/* Forcefully terminate the session if state is not DISCONNECTED */
if (inv->state != PJSIP_INV_STATE_DISCONNECTED) {
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, NULL);
}
/* Done.
* The dec_lock() below will actually destroys the dialog if it
* has no other session.
*/
pjsip_dlg_dec_lock(inv->dlg);
return PJ_SUCCESS;
}
/*
* Restart UAC session, possibly because app or us wants to re-send the
* INVITE request due to 401/407 challenge or 3xx response.
*/
PJ_DEF(pj_status_t) pjsip_inv_uac_restart(pjsip_inv_session *inv,
pj_bool_t new_offer)
{
PJ_ASSERT_RETURN(inv, PJ_EINVAL);
inv->state = PJSIP_INV_STATE_NULL;
inv->invite_tsx = NULL;
if (inv->last_answer) {
pjsip_tx_data_dec_ref(inv->last_answer);
inv->last_answer = NULL;
}
if (new_offer && inv->neg) {
pjmedia_sdp_neg_state neg_state;
neg_state = pjmedia_sdp_neg_get_state(inv->neg);
if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
pjmedia_sdp_neg_cancel_offer(inv->neg);
}
}
return PJ_SUCCESS;
}
static void *clone_sdp(pj_pool_t *pool, const void *data, unsigned len)
{
PJ_UNUSED_ARG(len);
return pjmedia_sdp_session_clone(pool, (const pjmedia_sdp_session*)data);
}
static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len)
{
return pjmedia_sdp_print((const pjmedia_sdp_session*)body->data, buf, len);
}
PJ_DEF(pj_status_t) pjsip_create_sdp_body( pj_pool_t *pool,
pjmedia_sdp_session *sdp,
pjsip_msg_body **p_body)
{
const pj_str_t STR_APPLICATION = { "application", 11};
const pj_str_t STR_SDP = { "sdp", 3 };
pjsip_msg_body *body;
body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
PJ_ASSERT_RETURN(body != NULL, PJ_ENOMEM);
pjsip_media_type_init(&body->content_type, (pj_str_t*)&STR_APPLICATION,
(pj_str_t*)&STR_SDP);
body->data = sdp;
body->len = 0;
body->clone_data = &clone_sdp;
body->print_body = &print_sdp;
*p_body = body;
return PJ_SUCCESS;
}
static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
const pjmedia_sdp_session *c_sdp)
{
pjsip_msg_body *body;
pj_status_t status;
status = pjsip_create_sdp_body(pool,
pjmedia_sdp_session_clone(pool, c_sdp),
&body);
if (status != PJ_SUCCESS)
return NULL;
return body;
}
/*
* Create initial INVITE request.
*/
PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv,
pjsip_tx_data **p_tdata )
{
pjsip_tx_data *tdata;
const pjsip_hdr *hdr;
pj_bool_t has_sdp;
pj_status_t status;
/* Verify arguments. */
PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
/* State MUST be NULL or CONFIRMED. */
PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL ||
inv->state == PJSIP_INV_STATE_CONFIRMED,
PJ_EINVALIDOP);
/* Lock dialog. */
pjsip_dlg_inc_lock(inv->dlg);
/* Create the INVITE request. */
status = pjsip_dlg_create_request(inv->dlg, pjsip_get_invite_method(), -1,
&tdata);
if (status != PJ_SUCCESS)
goto on_return;
/* If this is the first INVITE, then copy the headers from inv_hdr.
* These are the headers parsed from the request URI when the
* dialog was created.
*/
if (inv->state == PJSIP_INV_STATE_NULL) {
hdr = inv->dlg->inv_hdr.next;
while (hdr != &inv->dlg->inv_hdr) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, hdr));
hdr = hdr->next;
}
}
/* See if we have SDP to send. */
if (inv->neg) {
pjmedia_sdp_neg_state neg_state;
neg_state = pjmedia_sdp_neg_get_state(inv->neg);
has_sdp = (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
(neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
pjmedia_sdp_neg_has_local_answer(inv->neg)));
} else {
has_sdp = PJ_FALSE;
}
/* Add SDP, if any. */
if (has_sdp) {
const pjmedia_sdp_session *offer;
status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
if (status != PJ_SUCCESS) {
pjsip_tx_data_dec_ref(tdata);
goto on_return;
}
tdata->msg->body = create_sdp_body(tdata->pool, offer);
}
/* Add Allow header. */
if (inv->dlg->add_allow) {
hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_ALLOW, NULL);
if (hdr) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, hdr));
}
}
/* Add Supported header */
hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_SUPPORTED, NULL);
if (hdr) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, hdr));
}
/* Add Require header. */
if ((inv->options & PJSIP_INV_REQUIRE_100REL) ||
(inv->options & PJSIP_INV_REQUIRE_TIMER))
{
pjsip_require_hdr *hreq;
hreq = pjsip_require_hdr_create(tdata->pool);
if (inv->options & PJSIP_INV_REQUIRE_100REL)
hreq->values[hreq->count++] = pj_str("100rel");
if (inv->options & PJSIP_INV_REQUIRE_TIMER)
hreq->values[hreq->count++] = pj_str("timer");
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hreq);
}
status = pjsip_timer_update_req(inv, tdata);
if (status != PJ_SUCCESS)
goto on_return;
/* Done. */
*p_tdata = tdata;
on_return:
pjsip_dlg_dec_lock(inv->dlg);
return status;
}
/* Util: swap pool */
static void swap_pool(pj_pool_t **p1, pj_pool_t **p2)
{
pj_pool_t *tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
/*
* Initiate SDP negotiation in the SDP negotiator.
*/
static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv )
{
pj_status_t status;
PJ_ASSERT_RETURN(pjmedia_sdp_neg_get_state(inv->neg) ==
PJMEDIA_SDP_NEG_STATE_WAIT_NEGO,
PJMEDIA_SDPNEG_EINSTATE);
status = pjmedia_sdp_neg_negotiate(inv->pool_prov, inv->neg, 0);
PJ_LOG(5,(inv->obj_name, "SDP negotiation done, status=%d", status));
if (mod_inv.cb.on_media_update && inv->notify)
(*mod_inv.cb.on_media_update)(inv, status);
/* Invite session may have been terminated by the application even
* after a successful SDP negotiation, for example when no audio
* codec is present in the offer (see ticket #1034).
*/
if (inv->state != PJSIP_INV_STATE_DISCONNECTED) {
/* Swap the flip-flop pool when SDP negotiation success. */
if (status == PJ_SUCCESS) {
swap_pool(&inv->pool_prov, &inv->pool_active);
}
/* Reset the provisional pool regardless SDP negotiation result. */
pj_pool_reset(inv->pool_prov);
} else {
status = PJSIP_ERRNO_FROM_SIP_STATUS(inv->cause);
}
return status;
}
/*
* Check in incoming message for SDP offer/answer.
*/
static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
pjsip_transaction *tsx,
pjsip_rx_data *rdata)
{
struct tsx_inv_data *tsx_inv_data;
pj_status_t status;
pjsip_msg *msg;
pjsip_rdata_sdp_info *sdp_info;
/* Check if SDP is present in the message. */
msg = rdata->msg_info.msg;
if (msg->body == NULL) {
/* Message doesn't have body. */
return PJ_SUCCESS;
}
sdp_info = pjsip_rdata_get_sdp_info(rdata);
if (sdp_info->body.ptr == NULL) {
/* Message body is not "application/sdp" */
return PJMEDIA_SDP_EINSDP;
}
+ /* Only accept SDP in INVITE, UPDATE and ACK requests, 18x (reliable) and 183 provisional responses
+ * and 200 final response.
+ */
+ if (!(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_INVITE_METHOD) &&
+ !(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_ACK_METHOD) &&
+ !(msg->type == PJSIP_REQUEST_MSG && pjsip_method_cmp(&msg->line.req.method, &pjsip_update_method)==0) &&
+ !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/10==18 && pjsip_100rel_is_reliable(rdata)) &&
+ !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 183) &&
+ !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 200)) {
+ PJ_LOG(4,(inv->obj_name, "ignored SDP body"));
+ return PJ_SUCCESS;
+ }
+
/* Get/attach invite session's transaction data */
tsx_inv_data = (struct tsx_inv_data*) tsx->mod_data[mod_inv.mod.id];
if (tsx_inv_data == NULL) {
tsx_inv_data = PJ_POOL_ZALLOC_T(tsx->pool, struct tsx_inv_data);
tsx_inv_data->inv = inv;
tsx->mod_data[mod_inv.mod.id] = tsx_inv_data;
}
/* MUST NOT do multiple SDP offer/answer in a single transaction,
* EXCEPT if:
* - this is an initial UAC INVITE transaction (i.e. not re-INVITE), and
* - the previous negotiation was done on an early media (18x) and
* this response is a final/2xx response, and
* - the 2xx response has different To tag than the 18x response
* (i.e. the request has forked).
*
* The exception above is to add a rudimentary support for early media
* forking (sample case: custom ringback). See this ticket for more
* info: http://trac.pjsip.org/repos/ticket/657
*/
if (tsx_inv_data->sdp_done) {
pj_str_t res_tag;
int st_code;
res_tag = rdata->msg_info.to->tag;
st_code = rdata->msg_info.msg->line.status.code;
/* Allow final/early response after SDP has been negotiated in early
* media, IF this response is a final/early response with different
* tag.
*/
if (tsx->role == PJSIP_ROLE_UAC &&
(st_code/100 == 2 ||
(st_code==183 && pjsip_cfg()->endpt.follow_early_media_fork)) &&
tsx_inv_data->done_early &&
pj_stricmp(&tsx_inv_data->done_tag, &res_tag))
{
const pjmedia_sdp_session *reoffer_sdp = NULL;
PJ_LOG(4,(inv->obj_name, "Received forked %s response "
"after SDP negotiation has been done in early "
"media. Renegotiating SDP..",
(st_code==183? "early" : "final" )));
/* Retrieve original SDP offer from INVITE request */
reoffer_sdp = (const pjmedia_sdp_session*)
tsx->last_tx->msg->body->data;
/* Feed the original offer to negotiator */
status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov,
inv->neg,
reoffer_sdp);
if (status != PJ_SUCCESS) {
PJ_LOG(1,(inv->obj_name, "Error updating local offer for "
"forked 2xx/183 response (err=%d)", status));
return status;
}
} else {
if (rdata->msg_info.msg->body) {
PJ_LOG(4,(inv->obj_name, "SDP negotiation done, message "
"body is ignored"));
}
return PJ_SUCCESS;
}
}
/* Process the SDP body. */
if (sdp_info->sdp_err) {
PJ_PERROR(4,(THIS_FILE, sdp_info->sdp_err,
"Error parsing SDP in %s",
pjsip_rx_data_get_info(rdata)));
return PJMEDIA_SDP_EINSDP;
}
pj_assert(sdp_info->sdp != NULL);
/* The SDP can be an offer or answer, depending on negotiator's state */
if (inv->neg == NULL ||
pjmedia_sdp_neg_get_state(inv->neg) == PJMEDIA_SDP_NEG_STATE_DONE)
{
/* This is an offer. */
PJ_LOG(5,(inv->obj_name, "Got SDP offer in %s",
pjsip_rx_data_get_info(rdata)));
if (inv->neg == NULL) {
status=pjmedia_sdp_neg_create_w_remote_offer(inv->pool, NULL,
sdp_info->sdp,
&inv->neg);
} else {
status=pjmedia_sdp_neg_set_remote_offer(inv->pool_prov, inv->neg,
sdp_info->sdp);
}
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(THIS_FILE, status, "Error processing SDP offer in %",
pjsip_rx_data_get_info(rdata)));
return PJMEDIA_SDP_EINSDP;
}
/* Inform application about remote offer. */
if (mod_inv.cb.on_rx_offer && inv->notify) {
(*mod_inv.cb.on_rx_offer)(inv, sdp_info->sdp);
}
/* application must have supplied an answer at this point. */
if (pjmedia_sdp_neg_get_state(inv->neg) !=
PJMEDIA_SDP_NEG_STATE_WAIT_NEGO)
{
return PJ_EINVALIDOP;
}
} else if (pjmedia_sdp_neg_get_state(inv->neg) ==
PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
{
int status_code;
/* This is an answer.
* Process and negotiate remote answer.
*/
PJ_LOG(5,(inv->obj_name, "Got SDP answer in %s",
pjsip_rx_data_get_info(rdata)));
status = pjmedia_sdp_neg_set_remote_answer(inv->pool_prov, inv->neg,
sdp_info->sdp);
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(THIS_FILE, status, "Error processing SDP answer in %s",
pjsip_rx_data_get_info(rdata)));
return PJMEDIA_SDP_EINSDP;
}
/* Negotiate SDP */
inv_negotiate_sdp(inv);
/* Mark this transaction has having SDP offer/answer done, and
* save the reference to the To tag
*/
tsx_inv_data->sdp_done = 1;
status_code = rdata->msg_info.msg->line.status.code;
tsx_inv_data->done_early = (status_code/100==1);
pj_strdup(tsx->pool, &tsx_inv_data->done_tag,
&rdata->msg_info.to->tag);
} else {
PJ_LOG(5,(THIS_FILE, "Ignored SDP in %s: negotiator state is %s",
pjsip_rx_data_get_info(rdata),
pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state(inv->neg))));
}
return PJ_SUCCESS;
}
/*
* Process INVITE answer, for both initial and subsequent re-INVITE
*/
static pj_status_t process_answer( pjsip_inv_session *inv,
int st_code,
pjsip_tx_data *tdata,
const pjmedia_sdp_session *local_sdp)
{
pj_status_t status;
const pjmedia_sdp_session *sdp = NULL;
/* If local_sdp is specified, then we MUST NOT have answered the
* offer before.
*/
if (local_sdp && (st_code/100==1 || st_code/100==2)) {
if (inv->neg == NULL) {
status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
local_sdp,
&inv->neg);
} else if (pjmedia_sdp_neg_get_state(inv->neg)==
PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER)
{
status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov, inv->neg,
local_sdp);
} else {
/* Can not specify local SDP at this state. */
pj_assert(0);
status = PJMEDIA_SDPNEG_EINSTATE;
}
if (status != PJ_SUCCESS)
return status;
}
/* If SDP negotiator is ready, start negotiation. */
if (st_code/100==2 || (st_code/10==18 && st_code!=180)) {
pjmedia_sdp_neg_state neg_state;
/* Start nego when appropriate. */
neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) :
PJMEDIA_SDP_NEG_STATE_NULL;
if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
status = pjmedia_sdp_neg_get_neg_local(inv->neg, &sdp);
} else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
pjmedia_sdp_neg_has_local_answer(inv->neg) )
{
struct tsx_inv_data *tsx_inv_data;
/* Get invite session's transaction data */
tsx_inv_data = (struct tsx_inv_data*)
inv->invite_tsx->mod_data[mod_inv.mod.id];
status = inv_negotiate_sdp(inv);
if (status != PJ_SUCCESS)
return status;
/* Mark this transaction has having SDP offer/answer done. */
tsx_inv_data->sdp_done = 1;
status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
}
}
/* Include SDP when it's available for 2xx and 18x (but not 180) response.
* Subsequent response will include this SDP.
*
* Note note:
* - When offer/answer has been completed in reliable 183, we MUST NOT
* send SDP in 2xx response. So if we don't have SDP to send, clear
* the SDP in the message body ONLY if 100rel is active in this
* session.
*/
if (sdp) {
tdata->msg->body = create_sdp_body(tdata->pool, sdp);
} else {
if (inv->options & PJSIP_INV_REQUIRE_100REL) {
tdata->msg->body = NULL;
}
}
return PJ_SUCCESS;
}
/*
* Create first response to INVITE
*/
PJ_DEF(pj_status_t) pjsip_inv_initial_answer( pjsip_inv_session *inv,
pjsip_rx_data *rdata,
int st_code,
const pj_str_t *st_text,
const pjmedia_sdp_session *sdp,
pjsip_tx_data **p_tdata)
{
pjsip_tx_data *tdata;
pj_status_t status;
pjsip_status_code st_code2;
/* Verify arguments. */
PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
/* Must have INVITE transaction. */
PJ_ASSERT_RETURN(inv->invite_tsx, PJ_EBUG);
pj_log_push_indent();
pjsip_dlg_inc_lock(inv->dlg);
/* Create response */
status = pjsip_dlg_create_response(inv->dlg, rdata, st_code, st_text,
&tdata);
if (status != PJ_SUCCESS)
goto on_return;
/* Invoke Session Timers module */
status = pjsip_timer_process_req(inv, rdata, &st_code2);
if (status != PJ_SUCCESS) {
pj_status_t status2;
status2 = pjsip_dlg_modify_response(inv->dlg, tdata, st_code2, NULL);
if (status2 != PJ_SUCCESS) {
pjsip_tx_data_dec_ref(tdata);
goto on_return;
}
status2 = pjsip_timer_update_resp(inv, tdata);
if (status2 == PJ_SUCCESS)
*p_tdata = tdata;
else
pjsip_tx_data_dec_ref(tdata);
goto on_return;
}
/* Process SDP in answer */
status = process_answer(inv, st_code, tdata, sdp);
if (status != PJ_SUCCESS) {
pjsip_tx_data_dec_ref(tdata);
goto on_return;
}
/* Save this answer */
inv->last_answer = tdata;
pjsip_tx_data_add_ref(inv->last_answer);
PJ_LOG(5,(inv->dlg->obj_name, "Initial answer %s",
pjsip_tx_data_get_info(inv->last_answer)));
/* Invoke Session Timers */
pjsip_timer_update_resp(inv, tdata);
*p_tdata = tdata;
on_return:
pjsip_dlg_dec_lock(inv->dlg);
pj_log_pop_indent();
return status;
}
/*
* Answer initial INVITE
* Re-INVITE will be answered automatically, and will not use this function.
*/
PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv,
int st_code,
const pj_str_t *st_text,
const pjmedia_sdp_session *local_sdp,
pjsip_tx_data **p_tdata )
{
pjsip_tx_data *last_res;
pj_status_t status;
/* Verify arguments. */
PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
/* Must have INVITE transaction. */
PJ_ASSERT_RETURN(inv->invite_tsx, PJ_EBUG);
/* Must have created an answer before */
PJ_ASSERT_RETURN(inv->last_answer, PJ_EINVALIDOP);
pj_log_push_indent();
pjsip_dlg_inc_lock(inv->dlg);
/* Modify last response. */
last_res = inv->last_answer;
status = pjsip_dlg_modify_response(inv->dlg, last_res, st_code, st_text);
if (status != PJ_SUCCESS)
goto on_return;
/* For non-2xx final response, strip message body */
if (st_code >= 300) {
last_res->msg->body = NULL;
}
/* Process SDP in answer */
status = process_answer(inv, st_code, last_res, local_sdp);
if (status != PJ_SUCCESS) {
pjsip_tx_data_dec_ref(last_res);
goto on_return;
}
/* Invoke Session Timers */
pjsip_timer_update_resp(inv, last_res);
*p_tdata = last_res;
on_return:
pjsip_dlg_dec_lock(inv->dlg);
pj_log_pop_indent();
return status;
}
/*
* Set local SDP offer/answer.
*/
PJ_DEF(pj_status_t) pjsip_inv_set_local_sdp(pjsip_inv_session *inv,
const pjmedia_sdp_session *sdp )
{
const pjmedia_sdp_session *offer;
pj_status_t status;
PJ_ASSERT_RETURN(inv && sdp, PJ_EINVAL);
/* If we have remote SDP offer, set local answer to respond to the offer,
* otherwise we set/modify our local offer (and create an SDP negotiator
* if we don't have one yet).
*/
if (inv->neg) {
pjmedia_sdp_neg_state neg_state = pjmedia_sdp_neg_get_state(inv->neg);
if ((neg_state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER ||
neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) &&
pjmedia_sdp_neg_get_neg_remote(inv->neg, &offer) == PJ_SUCCESS)
{
status = pjsip_inv_set_sdp_answer(inv, sdp);
} else if (neg_state == PJMEDIA_SDP_NEG_STATE_DONE) {
status = pjmedia_sdp_neg_modify_local_offer(inv->pool,
inv->neg, sdp);
} else
return PJMEDIA_SDPNEG_EINSTATE;
} else {
status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
sdp, &inv->neg);
}
return status;
}
/*
* Set SDP answer.
*/
PJ_DEF(pj_status_t) pjsip_inv_set_sdp_answer( pjsip_inv_session *inv,
const pjmedia_sdp_session *sdp )
{
pj_status_t status;
PJ_ASSERT_RETURN(inv && sdp, PJ_EINVAL);
pjsip_dlg_inc_lock(inv->dlg);
status = pjmedia_sdp_neg_set_local_answer( inv->pool_prov, inv->neg, sdp);
pjsip_dlg_dec_lock(inv->dlg);
return status;
}
/*
* End session.
*/
PJ_DEF(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv,
int st_code,
const pj_str_t *st_text,
pjsip_tx_data **p_tdata )
{
pjsip_tx_data *tdata;
pj_status_t status;
/* Verify arguments. */
PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
pj_log_push_indent();
/* Set cause code. */
inv_set_cause(inv, st_code, st_text);
/* Create appropriate message. */
switch (inv->state) {
case PJSIP_INV_STATE_CALLING:
case PJSIP_INV_STATE_EARLY:
case PJSIP_INV_STATE_INCOMING:
if (inv->role == PJSIP_ROLE_UAC) {
/* For UAC when session has not been confirmed, create CANCEL. */
/* MUST have the original UAC INVITE transaction. */
PJ_ASSERT_RETURN(inv->invite_tsx != NULL, PJ_EBUG);
/* But CANCEL should only be called when we have received a
* provisional response. If we haven't received any responses,
* just destroy the transaction.
*/
if (inv->invite_tsx->status_code < 100) {
/* Do not stop INVITE retransmission, see ticket #506 */
//pjsip_tsx_stop_retransmit(inv->invite_tsx);
inv->cancelling = PJ_TRUE;
inv->pending_cancel = PJ_TRUE;
*p_tdata = NULL;
PJ_LOG(4, (inv->obj_name, "Delaying CANCEL since no "
"provisional response is received yet"));
pj_log_pop_indent();
return PJ_SUCCESS;
}
/* The CSeq here assumes that the dialog is started with an
* INVITE session. This may not be correct; dialog can be
* started as SUBSCRIBE session.
* So fix this!
*/
status = pjsip_endpt_create_cancel(inv->dlg->endpt,
inv->invite_tsx->last_tx,
&tdata);
if (status != PJ_SUCCESS) {
pj_log_pop_indent();
return status;
}
/* Set timeout for the INVITE transaction, in case UAS is not
* able to respond the INVITE with 487 final response. The
* timeout value is 64*T1.
*/
pjsip_tsx_set_timeout(inv->invite_tsx, 64 * pjsip_cfg()->tsx.t1);
} else {
/* For UAS, send a final response. */
tdata = inv->invite_tsx->last_tx;
PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP);
//status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code,
// st_text);
status = pjsip_inv_answer(inv, st_code, st_text, NULL, &tdata);
}
break;
case PJSIP_INV_STATE_CONNECTING:
case PJSIP_INV_STATE_CONFIRMED:
/* End Session Timer */
pjsip_timer_end_session(inv);
/* For established dialog, send BYE */
status = pjsip_dlg_create_request(inv->dlg, pjsip_get_bye_method(),
-1, &tdata);
break;
case PJSIP_INV_STATE_DISCONNECTED:
/* No need to do anything. */
pj_log_pop_indent();
return PJSIP_ESESSIONTERMINATED;
default:
pj_assert(!"Invalid operation!");
pj_log_pop_indent();
return PJ_EINVALIDOP;
}
if (status != PJ_SUCCESS) {
pj_log_pop_indent();
return status;
}
/* Done */
inv->cancelling = PJ_TRUE;
*p_tdata = tdata;
pj_log_pop_indent();
return PJ_SUCCESS;
}
/* Following redirection recursion, get next target from the target set and
* notify user.
*
* Returns PJ_FALSE if recursion fails (either because there's no more target
* or user rejects the recursion). If we return PJ_FALSE, caller should
* disconnect the session.
*
* Note:
* the event 'e' argument may be NULL.
*/
static pj_bool_t inv_uac_recurse(pjsip_inv_session *inv, int code,
const pj_str_t *reason, pjsip_event *e)
{
pjsip_redirect_op op;
pjsip_target *target;
/* Won't redirect if the callback is not implemented. */
if (mod_inv.cb.on_redirected == NULL)
return PJ_FALSE;
if (reason == NULL)
reason = pjsip_get_status_text(code);
/* Set status of current target */
pjsip_target_assign_status(inv->dlg->target_set.current, inv->dlg->pool,
code, reason);
/* Fetch next target from the target set. We only want to
* process SIP/SIPS URI for now.
*/
for (;;) {
target = pjsip_target_set_get_next(&inv->dlg->target_set);
if (target == NULL) {
/* No more target. */
return PJ_FALSE;
}
if (!PJSIP_URI_SCHEME_IS_SIP(target->uri) &&
!PJSIP_URI_SCHEME_IS_SIPS(target->uri))
{
code = PJSIP_SC_UNSUPPORTED_URI_SCHEME;
reason = pjsip_get_status_text(code);
/* Mark this target as unusable and fetch next target. */
pjsip_target_assign_status(target, inv->dlg->pool, code, reason);
} else {
/* Found a target */
break;
}
}
/* We have target in 'target'. Set this target as current target
* and notify callback.
*/
pjsip_target_set_set_current(&inv->dlg->target_set, target);
op = (*mod_inv.cb.on_redirected)(inv, target->uri, e);
/* Check what the application wants to do now */
switch (op) {
case PJSIP_REDIRECT_ACCEPT:
case PJSIP_REDIRECT_ACCEPT_REPLACE:
case PJSIP_REDIRECT_STOP:
/* Must increment session counter, that's the convention of the
* pjsip_inv_process_redirect().
*/
pjsip_dlg_inc_session(inv->dlg, &mod_inv.mod);
/* Act on the recursion */
pjsip_inv_process_redirect(inv, op, e);
return PJ_TRUE;
case PJSIP_REDIRECT_PENDING:
/* Increment session so that the dialog/session is not destroyed
* while we're waiting for user confirmation.
*/
pjsip_dlg_inc_session(inv->dlg, &mod_inv.mod);
/* Also clear the invite_tsx variable, otherwise when this tsx is
* terminated, it will also terminate the session.
*/
inv->invite_tsx = NULL;
/* Done. The processing will continue once the application calls
* pjsip_inv_process_redirect().
*/
return PJ_TRUE;
case PJSIP_REDIRECT_REJECT:
/* Recursively call this function again to fetch next target, if any.
*/
return inv_uac_recurse(inv, PJSIP_SC_REQUEST_TERMINATED, NULL, e);
}
pj_assert(!"Should not reach here");
return PJ_FALSE;
}
/* Process redirection/recursion */
PJ_DEF(pj_status_t) pjsip_inv_process_redirect( pjsip_inv_session *inv,
pjsip_redirect_op op,
pjsip_event *e)
{
const pjsip_status_code cancel_code = PJSIP_SC_REQUEST_TERMINATED;
pjsip_event usr_event;
pj_status_t status = PJ_SUCCESS;
PJ_ASSERT_RETURN(inv && op != PJSIP_REDIRECT_PENDING, PJ_EINVAL);
if (e == NULL) {
PJSIP_EVENT_INIT_USER(usr_event, NULL, NULL, NULL, NULL);
e = &usr_event;
}
pjsip_dlg_inc_lock(inv->dlg);
/* Decrement session. That's the convention here to prevent the dialog
* or session from being destroyed while we're waiting for user
* confirmation.
*/
pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
/* See what the application wants to do now */
switch (op) {
case PJSIP_REDIRECT_ACCEPT:
case PJSIP_REDIRECT_ACCEPT_REPLACE:
/* User accept the redirection. Reset the session and resend the
* INVITE request.
*/
{
pjsip_tx_data *tdata;
pjsip_via_hdr *via;
/* Get the original INVITE request. */
tdata = inv->invite_req;
pjsip_tx_data_add_ref(tdata);
/* Restore strict route set.
* See http://trac.pjsip.org/repos/ticket/492
*/
pjsip_restore_strict_route_set(tdata);
/* Set target */
tdata->msg->line.req.uri = (pjsip_uri*)
pjsip_uri_clone(tdata->pool, inv->dlg->target_set.current->uri);
/* Remove branch param in Via header. */
via = (pjsip_via_hdr*)
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
via->branch_param.slen = 0;
/* Process PJSIP_REDIRECT_ACCEPT_REPLACE */
if (op == PJSIP_REDIRECT_ACCEPT_REPLACE) {
pjsip_to_hdr *to;
pjsip_dialog *dlg = inv->dlg;
enum { TMP_LEN = 128 };
char tmp[TMP_LEN];
int len;
/* Replace To header */
to = PJSIP_MSG_TO_HDR(tdata->msg);
to->uri = (pjsip_uri*)
pjsip_uri_clone(tdata->pool,
dlg->target_set.current->uri);
to->tag.slen = 0;
pj_list_init(&to->other_param);
/* Re-init dialog remote info */
dlg->remote.info = (pjsip_to_hdr*)
pjsip_hdr_clone(dlg->pool, to);
/* Remove header param from remote info */
if (PJSIP_URI_SCHEME_IS_SIP(dlg->remote.info->uri) ||
PJSIP_URI_SCHEME_IS_SIPS(dlg->remote.info->uri))
{
pjsip_sip_uri *sip_uri = (pjsip_sip_uri *)
pjsip_uri_get_uri(dlg->remote.info->uri);
if (!pj_list_empty(&sip_uri->header_param)) {
/* Remove all header param */
pj_list_init(&sip_uri->header_param);
}
}
/* Print the remote info. */
len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
dlg->remote.info->uri, tmp, TMP_LEN);
if (len < 1) {
pj_ansi_strcpy(tmp, "<-error: uri too long->");
len = pj_ansi_strlen(tmp);
}
pj_strdup2_with_null(dlg->pool, &dlg->remote.info_str, tmp);
/* Secure? */
dlg->secure = PJSIP_URI_SCHEME_IS_SIPS(to->uri);
}
/* Reset message destination info (see #1248). */
pj_bzero(&tdata->dest_info, sizeof(tdata->dest_info));
/* Must invalidate the message! */
pjsip_tx_data_invalidate_msg(tdata);
/* Reset the session */
pjsip_inv_uac_restart(inv, PJ_FALSE);
/* (re)Send the INVITE request */
status = pjsip_inv_send_msg(inv, tdata);
}
break;
case PJSIP_REDIRECT_STOP:
/* User doesn't want the redirection. Disconnect the session now. */
inv_set_cause(inv, cancel_code, pjsip_get_status_text(cancel_code));
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
/* Caller should expect that the invite session is gone now, so
* we don't need to set status to PJSIP_ESESSIONTERMINATED here.
*/
break;
case PJSIP_REDIRECT_REJECT:
/* Current target is rejected. Fetch next target if any. */
if (inv_uac_recurse(inv, cancel_code, NULL, NULL) == PJ_FALSE) {
inv_set_cause(inv, cancel_code,
pjsip_get_status_text(cancel_code));
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
/* Tell caller that the invite session is gone now */
status = PJSIP_ESESSIONTERMINATED;
}
break;
case PJSIP_REDIRECT_PENDING:
pj_assert(!"Should not happen");
break;
}
pjsip_dlg_dec_lock(inv->dlg);
return status;
}
/*
* Create re-INVITE.
*/
PJ_DEF(pj_status_t) pjsip_inv_reinvite( pjsip_inv_session *inv,
const pj_str_t *new_contact,
const pjmedia_sdp_session *new_offer,
pjsip_tx_data **p_tdata )
{
pj_status_t status;
pjsip_contact_hdr *contact_hdr = NULL;
/* Check arguments. */
PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
/* Must NOT have a pending INVITE transaction */
if (inv->invite_tsx!=NULL)
return PJ_EINVALIDOP;
pj_log_push_indent();
pjsip_dlg_inc_lock(inv->dlg);
if (new_contact) {
pj_str_t tmp;
const pj_str_t STR_CONTACT = { "Contact", 7 };
pj_strdup_with_null(inv->dlg->pool, &tmp, new_contact);
contact_hdr = (pjsip_contact_hdr*)
pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT,
tmp.ptr, tmp.slen, NULL);
if (!contact_hdr) {
status = PJSIP_EINVALIDURI;
goto on_return;
}
}
if (new_offer) {
if (!inv->neg) {
status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
new_offer,
&inv->neg);
if (status != PJ_SUCCESS)
goto on_return;
} else switch (pjmedia_sdp_neg_get_state(inv->neg)) {
case PJMEDIA_SDP_NEG_STATE_NULL:
pj_assert(!"Unexpected SDP neg state NULL");
status = PJ_EBUG;
goto on_return;
case PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER:
PJ_LOG(4,(inv->obj_name,
"pjsip_inv_reinvite: already have an offer, new "
"offer is ignored"));
break;
case PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER:
status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov,
inv->neg,
new_offer);
if (status != PJ_SUCCESS)
goto on_return;
break;
case PJMEDIA_SDP_NEG_STATE_WAIT_NEGO:
PJ_LOG(4,(inv->obj_name,
"pjsip_inv_reinvite: SDP in WAIT_NEGO state, new "
"offer is ignored"));
break;
case PJMEDIA_SDP_NEG_STATE_DONE:
status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov,
inv->neg,
new_offer);
if (status != PJ_SUCCESS)
goto on_return;
break;
}
}
if (contact_hdr)
inv->dlg->local.contact = contact_hdr;
status = pjsip_inv_invite(inv, p_tdata);
on_return:
pjsip_dlg_dec_lock(inv->dlg);
pj_log_pop_indent();
return status;
}
/*
* Create UPDATE.
*/
PJ_DEF(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv,
const pj_str_t *new_contact,
const pjmedia_sdp_session *offer,
pjsip_tx_data **p_tdata )
{
pjsip_contact_hdr *contact_hdr = NULL;
pjsip_tx_data *tdata = NULL;
pjmedia_sdp_session *sdp_copy;
const pjsip_hdr *hdr;
pj_status_t status = PJ_SUCCESS;
/* Verify arguments. */
PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
/* Dialog must have been established */
PJ_ASSERT_RETURN(inv->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED,
PJ_EINVALIDOP);
/* Invite session must not have been disconnected */
PJ_ASSERT_RETURN(inv->state < PJSIP_INV_STATE_DISCONNECTED,
PJ_EINVALIDOP);
pj_log_push_indent();
/* Lock dialog. */
pjsip_dlg_inc_lock(inv->dlg);
/* Process offer, if any */
if (offer) {
if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) {
PJ_LOG(4,(inv->dlg->obj_name,
"Invalid SDP offer/answer state for UPDATE"));
status = PJ_EINVALIDOP;
goto on_error;
}
/* Notify negotiator about the new offer. This will fix the offer
* with correct SDP origin.
*/
status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, inv->neg,
offer);
if (status != PJ_SUCCESS)
goto on_error;
/* Retrieve the "fixed" offer from negotiator */
pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
}
/* Update Contact if required */
if (new_contact) {
pj_str_t tmp;
const pj_str_t STR_CONTACT = { "Contact", 7 };
pj_strdup_with_null(inv->dlg->pool, &tmp, new_contact);
contact_hdr = (pjsip_contact_hdr*)
pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT,
tmp.ptr, tmp.slen, NULL);
if (!contact_hdr) {
status = PJSIP_EINVALIDURI;
goto on_error;
}
inv->dlg->local.contact = contact_hdr;
}
/* Create request */
status = pjsip_dlg_create_request(inv->dlg, &pjsip_update_method,
-1, &tdata);
if (status != PJ_SUCCESS)
goto on_error;
/* Attach SDP body */
if (offer) {
sdp_copy = pjmedia_sdp_session_clone(tdata->pool, offer);
pjsip_create_sdp_body(tdata->pool, sdp_copy, &tdata->msg->body);
}
/* Session Timers spec (RFC 4028) says that Supported header MUST be put
* in refresh requests. So here we'll just put the Supported header in
* all cases regardless of whether session timers is used or not, just
* in case this is a common behavior.
*/
hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_SUPPORTED, NULL);
if (hdr) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, hdr));
}
status = pjsip_timer_update_req(inv, tdata);
if (status != PJ_SUCCESS)
goto on_error;
/* Unlock dialog. */
pjsip_dlg_dec_lock(inv->dlg);
*p_tdata = tdata;
pj_log_pop_indent();
return PJ_SUCCESS;
on_error:
if (tdata)
pjsip_tx_data_dec_ref(tdata);
/* Unlock dialog. */
pjsip_dlg_dec_lock(inv->dlg);
pj_log_pop_indent();
return status;
}
/*
* Create an ACK request.
*/
PJ_DEF(pj_status_t) pjsip_inv_create_ack(pjsip_inv_session *inv,
int cseq,
pjsip_tx_data **p_tdata)
{
const pjmedia_sdp_session *sdp = NULL;
pj_status_t status;
PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
/* Lock dialog. */
pjsip_dlg_inc_lock(inv->dlg);
/* Destroy last_ack */
if (inv->last_ack) {
pjsip_tx_data_dec_ref(inv->last_ack);
inv->last_ack = NULL;
}
/* Create new ACK request */
status = pjsip_dlg_create_request(inv->dlg, pjsip_get_ack_method(),
cseq, &inv->last_ack);
if (status != PJ_SUCCESS) {
pjsip_dlg_dec_lock(inv->dlg);
return status;
}
/* See if we have pending SDP answer to send */
sdp = inv_has_pending_answer(inv, inv->invite_tsx);
if (sdp) {
inv->last_ack->msg->body = create_sdp_body(inv->last_ack->pool, sdp);
}
/* Keep this for subsequent response retransmission */
inv->last_ack_cseq = cseq;
pjsip_tx_data_add_ref(inv->last_ack);
/* Done */
*p_tdata = inv->last_ack;
/* Unlock dialog. */
pjsip_dlg_dec_lock(inv->dlg);
return PJ_SUCCESS;
}
/*
* Send a request or response message.
*/
PJ_DEF(pj_status_t) pjsip_inv_send_msg( pjsip_inv_session *inv,
pjsip_tx_data *tdata)
{
pj_status_t status;
/* Verify arguments. */
PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
pj_log_push_indent();
PJ_LOG(5,(inv->obj_name, "Sending %s",
pjsip_tx_data_get_info(tdata)));
if (tdata->msg->type == PJSIP_REQUEST_MSG) {
struct tsx_inv_data *tsx_inv_data;
pjsip_dlg_inc_lock(inv->dlg);
/* Check again that we didn't receive incoming re-INVITE */
if (tdata->msg->line.req.method.id==PJSIP_INVITE_METHOD &&
inv->invite_tsx)
{
pjsip_tx_data_dec_ref(tdata);
pjsip_dlg_dec_lock(inv->dlg);
status = PJ_EINVALIDOP;
goto on_error;
}
/* Associate our data in outgoing invite transaction */
tsx_inv_data = PJ_POOL_ZALLOC_T(inv->pool, struct tsx_inv_data);
tsx_inv_data->inv = inv;
pjsip_dlg_dec_lock(inv->dlg);
status = pjsip_dlg_send_request(inv->dlg, tdata, mod_inv.mod.id,
tsx_inv_data);
if (status != PJ_SUCCESS) {
goto on_error;
}
} else {
pjsip_cseq_hdr *cseq;
/* Can only do this to send response to original INVITE
* request.
*/
PJ_ASSERT_RETURN((cseq=(pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL))!=NULL
&& (cseq->cseq == inv->invite_tsx->cseq),
PJ_EINVALIDOP);
if (inv->options & PJSIP_INV_REQUIRE_100REL) {
status = pjsip_100rel_tx_response(inv, tdata);
} else
{
status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
}
if (status != PJ_SUCCESS) {
goto on_error;
}
}
/* Done */
pj_log_pop_indent();
return PJ_SUCCESS;
on_error:
pj_log_pop_indent();
return status;
}
/*
* Respond to incoming CANCEL request.
*/
static void inv_respond_incoming_cancel(pjsip_inv_session *inv,
pjsip_transaction *cancel_tsx,
pjsip_event *e)
{
pjsip_tx_data *tdata;
pjsip_transaction *invite_tsx;
pjsip_rx_data *rdata;
pj_str_t key;
pj_status_t status;
pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
rdata = e->body.tsx_state.src.rdata;
/* https://trac.pjsip.org/repos/ticket/1651
* Special treatment for CANCEL. Since here we're responding to CANCEL
* automatically (including 487 to INVITE), application will see the
* 200/OK response to CANCEL first in the callback, and then 487 to
* INVITE, before the CANCEL request itself. And worse, pjsua application
* may not see the CANCEL request at all because by the time the CANCEL
* request is reported, call has been disconnected and further events
* from the INVITE session has been suppressed.
*/
if (mod_inv.cb.on_tsx_state_changed && inv->notify)
(*mod_inv.cb.on_tsx_state_changed)(inv, cancel_tsx, e);
/* See if we have matching INVITE server transaction: */
pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS,
pjsip_get_invite_method(), rdata);
invite_tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE);
if (invite_tsx == NULL) {
/* Invite transaction not found!
* Respond CANCEL with 481 (RFC 3261 Section 9.2 page 55)
*/
status = pjsip_dlg_create_response( inv->dlg, rdata, 481, NULL,
&tdata);
} else {
/* Always answer CANCEL will 200 (OK) regardless of
* the state of the INVITE transaction.
*/
status = pjsip_dlg_create_response( inv->dlg, rdata, 200, NULL,
&tdata);
}
/* See if we have created the response successfully. */
if (status != PJ_SUCCESS) return;
/* Send the CANCEL response */
status = pjsip_dlg_send_response(inv->dlg, cancel_tsx, tdata);
if (status != PJ_SUCCESS) return;
/* See if we need to terminate the UAS INVITE transaction
* with 487 (Request Terminated) response.
*/
if (invite_tsx && invite_tsx->status_code < 200) {
pj_assert(invite_tsx->last_tx != NULL);
tdata = invite_tsx->last_tx;
status = pjsip_dlg_modify_response(inv->dlg, tdata, 487, NULL);
if (status == PJ_SUCCESS) {
/* Remove the message body */
tdata->msg->body = NULL;
if (inv->options & PJSIP_INV_REQUIRE_100REL) {
status = pjsip_100rel_tx_response(inv, tdata);
} else {
status = pjsip_dlg_send_response(inv->dlg, invite_tsx,
tdata);
}
}
}
if (invite_tsx)
pj_grp_lock_release(invite_tsx->grp_lock);
}
/*
* Respond to incoming BYE request.
*/
static void inv_respond_incoming_bye( pjsip_inv_session *inv,
pjsip_transaction *bye_tsx,
pjsip_rx_data *rdata,
pjsip_event *e )
{
pj_status_t status;
pjsip_tx_data *tdata;
/* Respond BYE with 200: */
status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata);
if (status != PJ_SUCCESS) return;
status = pjsip_dlg_send_response(inv->dlg, bye_tsx, tdata);
if (status != PJ_SUCCESS) return;
/* Terminate session: */
if (inv->state != PJSIP_INV_STATE_DISCONNECTED) {
inv_set_cause(inv, PJSIP_SC_OK, NULL);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
}
}
/*
* Respond to BYE request.
*/
static void inv_handle_bye_response( pjsip_inv_session *inv,
pjsip_transaction *tsx,
pjsip_rx_data *rdata,
pjsip_event *e )
{
pj_status_t status;
if (e->body.tsx_state.type != PJSIP_EVENT_RX_MSG) {
inv_set_cause(inv, PJSIP_SC_OK, NULL);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
return;
}
/* Handle 401/407 challenge. */
if (tsx->status_code == 401 || tsx->status_code == 407) {
pjsip_tx_data *tdata;
status = pjsip_auth_clt_reinit_req( &inv->dlg->auth_sess,
rdata,
tsx->last_tx,
&tdata);
if (status != PJ_SUCCESS) {
/* Does not have proper credentials.
* End the session anyway.
*/
inv_set_cause(inv, PJSIP_SC_OK, NULL);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
} else {
struct tsx_inv_data *tsx_inv_data;
tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
if (tsx_inv_data)
tsx_inv_data->retrying = PJ_TRUE;
/* Re-send BYE. */
status = pjsip_inv_send_msg(inv, tdata);
}
} else {
/* End the session. */
inv_set_cause(inv, PJSIP_SC_OK, NULL);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
}
}
/*
* Respond to incoming UPDATE request.
*/
static void inv_respond_incoming_update(pjsip_inv_session *inv,
pjsip_rx_data *rdata)
{
pjmedia_sdp_neg_state neg_state;
pj_status_t status;
pjsip_tx_data *tdata = NULL;
pjsip_status_code st_code;
/* Invoke Session Timers module */
status = pjsip_timer_process_req(inv, rdata, &st_code);
if (status != PJ_SUCCESS) {
status = pjsip_dlg_create_response(inv->dlg, rdata, st_code,
NULL, &tdata);
goto on_return;
}
neg_state = pjmedia_sdp_neg_get_state(inv->neg);
/* If UPDATE doesn't contain SDP, just respond with 200/OK.
* This is a valid scenario according to session-timer draft.
*/
if (rdata->msg_info.msg->body == NULL) {
status = pjsip_dlg_create_response(inv->dlg, rdata,
200, NULL, &tdata);
}
/* Send 491 if we receive UPDATE while we're waiting for an answer */
else if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
status = pjsip_dlg_create_response(inv->dlg, rdata,
PJSIP_SC_REQUEST_PENDING, NULL,
&tdata);
}
/* Send 500 with Retry-After header set randomly between 0 and 10 if we
* receive UPDATE while we haven't sent answer.
*/
else if (neg_state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER ||
neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO)
{
pjsip_retry_after_hdr *ra_hdr;
int val;
status = pjsip_dlg_create_response(inv->dlg, rdata,
PJSIP_SC_INTERNAL_SERVER_ERROR,
NULL, &tdata);
val = (pj_rand() % 10);
ra_hdr = pjsip_retry_after_hdr_create(tdata->pool, val);
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)ra_hdr);
} else {
/* We receive new offer from remote */
inv_check_sdp_in_incoming_msg(inv, pjsip_rdata_get_tsx(rdata), rdata);
/* Application MUST have supplied the answer by now.
* If so, negotiate the SDP.
*/
neg_state = pjmedia_sdp_neg_get_state(inv->neg);
if (neg_state != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO ||
(status=inv_negotiate_sdp(inv)) != PJ_SUCCESS)
{
/* Negotiation has failed. If negotiator is still
* stuck at non-DONE state, cancel any ongoing offer.
*/
neg_state = pjmedia_sdp_neg_get_state(inv->neg);
if (neg_state != PJMEDIA_SDP_NEG_STATE_DONE) {
pjmedia_sdp_neg_cancel_offer(inv->neg);
}
status = pjsip_dlg_create_response(inv->dlg, rdata,
PJSIP_SC_NOT_ACCEPTABLE_HERE,
NULL, &tdata);
} else {
/* New media has been negotiated successfully, send 200/OK */
status = pjsip_dlg_create_response(inv->dlg, rdata,
PJSIP_SC_OK, NULL, &tdata);
if (status == PJ_SUCCESS) {
const pjmedia_sdp_session *sdp;
status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
if (status == PJ_SUCCESS)
tdata->msg->body = create_sdp_body(tdata->pool, sdp);
}
}
}
on_return:
/* Invoke Session Timers */
if (status == PJ_SUCCESS)
status = pjsip_timer_update_resp(inv, tdata);
if (status != PJ_SUCCESS) {
if (tdata != NULL) {
pjsip_tx_data_dec_ref(tdata);
tdata = NULL;
}
return;
}
pjsip_dlg_send_response(inv->dlg, pjsip_rdata_get_tsx(rdata), tdata);
}
/*
* Handle incoming response to UAC UPDATE request.
*/
static pj_bool_t inv_handle_update_response( pjsip_inv_session *inv,
pjsip_event *e)
{
pjsip_transaction *tsx = e->body.tsx_state.tsx;
struct tsx_inv_data *tsx_inv_data;
pj_bool_t handled = PJ_FALSE;
pj_status_t status = -1;
tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
pj_assert(tsx_inv_data);
/* Handle 401/407 challenge. */
if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
(tsx->status_code == 401 || tsx->status_code == 407))
{
pjsip_tx_data *tdata;
status = pjsip_auth_clt_reinit_req( &inv->dlg->auth_sess,
e->body.tsx_state.src.rdata,
tsx->last_tx,
&tdata);
if (status != PJ_SUCCESS) {
/* Somehow failed. Probably it's not a good idea to terminate
* the session since this is just a request within dialog. And
* even if we terminate we should send BYE.
*/
/*
inv_set_cause(inv, PJSIP_SC_OK, NULL);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
*/
} else {
if (tsx_inv_data)
tsx_inv_data->retrying = PJ_TRUE;
/* Re-send request. */
status = pjsip_inv_send_msg(inv, tdata);
}
handled = PJ_TRUE;
}
/* Process 422 response */
else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
tsx->status_code == 422)
{
status = handle_timer_response(inv, e->body.tsx_state.src.rdata,
PJ_FALSE);
handled = PJ_TRUE;
}
/* Process 2xx response */
else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
tsx->status_code/100 == 2 &&
e->body.tsx_state.src.rdata->msg_info.msg->body)
{
status = handle_timer_response(inv, e->body.tsx_state.src.rdata,
PJ_FALSE);
status = inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
handled = PJ_TRUE;
}
/* Get/attach invite session's transaction data */
else
{
/* Session-Timer needs to see any error responses, to determine
* whether peer supports UPDATE with empty body.
*/
if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
tsx->role == PJSIP_ROLE_UAC)
{
status = handle_timer_response(inv, e->body.tsx_state.src.rdata,
PJ_FALSE);
handled = PJ_TRUE;
}
}
/* Cancel the negotiation if we don't get successful negotiation by now,
* unless it's authentication challenge and the request is being retried.
*/
if (pjmedia_sdp_neg_get_state(inv->neg) ==
PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER &&
tsx_inv_data && tsx_inv_data->sdp_done == PJ_FALSE &&
!tsx_inv_data->retrying)
{
pjmedia_sdp_neg_cancel_offer(inv->neg);
/* Prevent from us cancelling different offer! */
tsx_inv_data->sdp_done = PJ_TRUE;
}
return handled;
}
/*
* Handle incoming reliable response.
*/
static void inv_handle_incoming_reliable_response(pjsip_inv_session *inv,
pjsip_rx_data *rdata)
{
pjsip_tx_data *tdata;
const pjmedia_sdp_session *sdp;
pj_status_t status;
/* Create PRACK */
status = pjsip_100rel_create_prack(inv, rdata, &tdata);
if (status != PJ_SUCCESS)
return;
/* See if we need to attach SDP answer on the PRACK request */
sdp = inv_has_pending_answer(inv, pjsip_rdata_get_tsx(rdata));
if (sdp) {
tdata->msg->body = create_sdp_body(tdata->pool, sdp);
}
/* Send PRACK (must be using 100rel module!) */
pjsip_100rel_send_prack(inv, tdata);
}
/*
* Handle incoming PRACK.
*/
static void inv_respond_incoming_prack(pjsip_inv_session *inv,
pjsip_rx_data *rdata)
{
pj_status_t status;
/* Run through 100rel module to see if we can accept this
* PRACK request. The 100rel will send 200/OK to PRACK request.
*/
status = pjsip_100rel_on_rx_prack(inv, rdata);
if (status != PJ_SUCCESS)
return;
/* Now check for SDP answer in the PRACK request */
if (rdata->msg_info.msg->body) {
status = inv_check_sdp_in_incoming_msg(inv,
pjsip_rdata_get_tsx(rdata), rdata);
} else {
/* No SDP body */
status = -1;
}
/* If SDP negotiation has been successful, also mark the
* SDP negotiation flag in the invite transaction to be
* done too.
*/
if (status == PJ_SUCCESS && inv->invite_tsx) {
struct tsx_inv_data *tsx_inv_data;
/* Get/attach invite session's transaction data */
tsx_inv_data = (struct tsx_inv_data*)
inv->invite_tsx->mod_data[mod_inv.mod.id];
if (tsx_inv_data == NULL) {
tsx_inv_data = PJ_POOL_ZALLOC_T(inv->invite_tsx->pool,
struct tsx_inv_data);
tsx_inv_data->inv = inv;
inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data;
}
tsx_inv_data->sdp_done = PJ_TRUE;
}
}
/*
* State NULL is before anything is sent/received.
*/
static void inv_on_state_null( pjsip_inv_session *inv, pjsip_event *e)
{
pjsip_transaction *tsx = e->body.tsx_state.tsx;
pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
PJ_ASSERT_ON_FAIL(tsx && dlg, return);
if (tsx->method.id == PJSIP_INVITE_METHOD) {
/* Keep the initial INVITE transaction. */
if (inv->invite_tsx == NULL)
inv->invite_tsx = tsx;
if (dlg->role == PJSIP_ROLE_UAC) {
/* Save the original INVITE request.
* We may need to resend the INVITE if we receive redirection
* or session timer too small response.
*/
if (1) {
if (inv->invite_req) {
pjsip_tx_data_dec_ref(inv->invite_req);
inv->invite_req = NULL;
}
inv->invite_req = tsx->last_tx;
pjsip_tx_data_add_ref(inv->invite_req);
}
switch (tsx->state) {
case PJSIP_TSX_STATE_CALLING:
inv_set_state(inv, PJSIP_INV_STATE_CALLING, e);
break;
default:
inv_on_state_calling(inv, e);
break;
}
} else {
switch (tsx->state) {
case PJSIP_TSX_STATE_TRYING:
inv_set_state(inv, PJSIP_INV_STATE_INCOMING, e);
break;
case PJSIP_TSX_STATE_PROCEEDING:
inv_set_state(inv, PJSIP_INV_STATE_INCOMING, e);
if (tsx->status_code > 100)
inv_set_state(inv, PJSIP_INV_STATE_EARLY, e);
break;
case PJSIP_TSX_STATE_TERMINATED:
/* there is a failure in sending response. */
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
break;
default:
inv_on_state_incoming(inv, e);
break;
}
}
} else {
pj_assert(!"Unexpected transaction type");
}
}
/*
* Generic UAC transaction handler:
* - resend request on 401 or 407 response.
* - terminate dialog on 408 and 481 response.
* - resend request on 422 response.
*/
static pj_bool_t handle_uac_tsx_response(pjsip_inv_session *inv,
pjsip_event *e)
{
/* RFC 3261 Section 12.2.1.2:
* If the response for a request within a dialog is a 481
* (Call/Transaction Does Not Exist) or a 408 (Request Timeout), the UAC
* SHOULD terminate the dialog. A UAC SHOULD also terminate a dialog if
* no response at all is received for the request (the client
* transaction would inform the TU about the timeout.)
*
* For INVITE initiated dialogs, terminating the dialog consists of
* sending a BYE.
*
* Note:
* according to X, this should terminate dialog usage only, not the
* dialog.
*/
pjsip_transaction *tsx = e->body.tsx_state.tsx;
pj_assert(tsx->role == PJSIP_UAC_ROLE);
/* Note that 481 response to CANCEL does not terminate dialog usage,
* but only the transaction.
*/
if (inv->state != PJSIP_INV_STATE_DISCONNECTED &&
((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST &&
tsx->method.id != PJSIP_CANCEL_METHOD) ||
(inv->state != PJSIP_INV_STATE_CONFIRMED &&
tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT) ||
tsx->status_code == PJSIP_SC_TSX_TIMEOUT))
{
pjsip_tx_data *bye;
pj_status_t status;
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
/* Send BYE */
status = pjsip_dlg_create_request(inv->dlg, pjsip_get_bye_method(),
-1, &bye);
if (status == PJ_SUCCESS) {
pjsip_inv_send_msg(inv, bye);
}
return PJ_TRUE; /* Handled */
}
/* Handle 401/407 challenge. */
else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
(tsx->status_code == PJSIP_SC_UNAUTHORIZED ||
tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED))
{
pjsip_tx_data *tdata;
pj_status_t status;
if (tsx->method.id == PJSIP_INVITE_METHOD)
inv->invite_tsx = NULL;
status = pjsip_auth_clt_reinit_req( &inv->dlg->auth_sess,
e->body.tsx_state.src.rdata,
tsx->last_tx, &tdata);
if (status != PJ_SUCCESS) {
/* Somehow failed. Probably it's not a good idea to terminate
* the session since this is just a request within dialog. And
* even if we terminate we should send BYE.
*/
/*
inv_set_cause(inv, PJSIP_SC_OK, NULL);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
*/
} else {
struct tsx_inv_data *tsx_inv_data;
tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
if (tsx_inv_data)
tsx_inv_data->retrying = PJ_TRUE;
/* Re-send request. */
status = pjsip_inv_send_msg(inv, tdata);
}
return PJ_TRUE; /* Handled */
}
/* Handle session timer 422 response. */
else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
tsx->status_code == PJSIP_SC_SESSION_TIMER_TOO_SMALL)
{
handle_timer_response(inv, e->body.tsx_state.src.rdata,
PJ_FALSE);
return PJ_TRUE; /* Handled */
} else {
return PJ_FALSE; /* Unhandled */
}
}
/* Handle call rejection, especially with regard to processing call
* redirection. We need to handle the following scenarios:
* - 3xx response is received -- see if on_redirected() callback is
* implemented. If so, add the Contact URIs in the response to the
* target set and notify user.
* - 4xx - 6xx resposne is received -- see if we're currently recursing,
* if so fetch the next target if any and notify the on_redirected()
* callback.
* - for other cases -- disconnect the session.
*/
static void handle_uac_call_rejection(pjsip_inv_session *inv, pjsip_event *e)
{
pjsip_transaction *tsx = e->body.tsx_state.tsx;
pj_status_t status;
if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 300)) {
if (mod_inv.cb.on_redirected == NULL) {
/* Redirection callback is not implemented, disconnect the
* call.
*/
goto terminate_session;
} else {
const pjsip_msg *res_msg;
res_msg = e->body.tsx_state.src.rdata->msg_info.msg;
/* Gather all Contact URI's in the response and add them
* to target set. The function will take care of removing
* duplicate URI's.
*/
pjsip_target_set_add_from_msg(&inv->dlg->target_set,
inv->dlg->pool, res_msg);
/* Recurse to alternate targets if application allows us */
if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e))
{
/* Recursion fails, terminate session now */
goto terminate_session;
}
/* Done */
}
} else if ((tsx->status_code==401 || tsx->status_code==407) &&
!inv->cancelling)
{
/* Handle authentication failure:
* Resend the request with Authorization header.
*/
pjsip_tx_data *tdata;
status = pjsip_auth_clt_reinit_req(&inv->dlg->auth_sess,
e->body.tsx_state.src.rdata,
tsx->last_tx,
&tdata);
if (status != PJ_SUCCESS) {
/* Does not have proper credentials. If we are currently
* recursing, try the next target. Otherwise end the session.
*/
if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e))
{
/* Recursion fails, terminate session now */
goto terminate_session;
}
} else {
/* Restart session. */
pjsip_inv_uac_restart(inv, PJ_FALSE);
/* Send the request. */
status = pjsip_inv_send_msg(inv, tdata);
}
} else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
tsx->status_code == PJSIP_SC_SESSION_TIMER_TOO_SMALL)
{
/* Handle session timer 422 response:
* Resend the request with requested session timer setting.
*/
status = handle_timer_response(inv, e->body.tsx_state.src.rdata,
PJ_TRUE);
} else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 600)) {
/* Global error */
goto terminate_session;
} else {
/* See if we have alternate target to try */
if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e)) {
/* Recursion fails, terminate session now */
goto terminate_session;
}
}
return;
terminate_session:
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
}
/*
* State CALLING is after sending initial INVITE request but before
* any response (with tag) is received.
*/
static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e)
{
pjsip_transaction *tsx = e->body.tsx_state.tsx;
pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
pj_status_t status;
PJ_ASSERT_ON_FAIL(tsx && dlg, return);
if (tsx == inv->invite_tsx) {
switch (tsx->state) {
case PJSIP_TSX_STATE_CALLING:
inv_set_state(inv, PJSIP_INV_STATE_CALLING, e);
break;
case PJSIP_TSX_STATE_PROCEEDING:
if (inv->pending_cancel) {
pjsip_tx_data *cancel;
inv->pending_cancel = PJ_FALSE;
status = pjsip_inv_end_session(inv, 487, NULL, &cancel);
if (status == PJ_SUCCESS && cancel)
status = pjsip_inv_send_msg(inv, cancel);
}
if (dlg->remote.info->tag.slen) {
inv_set_state(inv, PJSIP_INV_STATE_EARLY, e);
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) {
inv_handle_incoming_reliable_response(
inv, e->body.tsx_state.src.rdata);
}
} else {
/* Ignore 100 (Trying) response, as it doesn't change
* session state. It only ceases retransmissions.
*/
}
break;
case PJSIP_TSX_STATE_COMPLETED:
if (tsx->status_code/100 == 2) {
/* This should not happen.
* When transaction receives 2xx, it should be terminated
*/
pj_assert(0);
/* Process session timer response. */
status = handle_timer_response(inv,
e->body.tsx_state.src.rdata,
PJ_TRUE);
if (status != PJ_SUCCESS)
break;
inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
} else {
handle_uac_call_rejection(inv, e);
}
break;
case PJSIP_TSX_STATE_TERMINATED:
/* INVITE transaction can be terminated either because UAC
* transaction received 2xx response or because of transport
* error.
*/
if (tsx->status_code/100 == 2) {
/* This must be receipt of 2xx response */
/* Process session timer response. */
status = handle_timer_response(inv,
e->body.tsx_state.src.rdata,
PJ_TRUE);
if (status != PJ_SUCCESS)
break;
/* Set state to CONNECTING */
inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
/* Send ACK */
pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
inv_send_ack(inv, e);
} else {
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
}
break;
default:
break;
}
} else if (tsx->role == PJSIP_ROLE_UAC) {
/*
* Handle case when outgoing request is answered with 481 (Call/
* Transaction Does Not Exist), 408, or when it's timed out. In these
* cases, disconnect session (i.e. dialog usage only).
* Note that 481 response to CANCEL does not terminate dialog usage,
* but only the transaction.
*/
if ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST &&
tsx->method.id != PJSIP_CANCEL_METHOD) ||
tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT ||
tsx->status_code == PJSIP_SC_TSX_TIMEOUT ||
tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)
{
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
}
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->state == PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
{
/*
* Handle a very early UPDATE
*/
inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
}
}
/*
* State INCOMING is after we received the request, but before
* responses with tag are sent.
*/
static void inv_on_state_incoming( pjsip_inv_session *inv, pjsip_event *e)
{
pjsip_transaction *tsx = e->body.tsx_state.tsx;
pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
PJ_ASSERT_ON_FAIL(tsx && dlg, return);
if (tsx == inv->invite_tsx) {
/*
* Handle the INVITE state transition.
*/
switch (tsx->state) {
case PJSIP_TSX_STATE_TRYING:
inv_set_state(inv, PJSIP_INV_STATE_INCOMING, e);
break;
case PJSIP_TSX_STATE_PROCEEDING:
/*
* Transaction sent provisional response.
*/
if (tsx->status_code > 100)
inv_set_state(inv, PJSIP_INV_STATE_EARLY, e);
break;
case PJSIP_TSX_STATE_COMPLETED:
/*
* Transaction sent final response.
*/
if (tsx->status_code/100 == 2) {
inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
} else {
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
}
break;
case PJSIP_TSX_STATE_TERMINATED:
/*
* This happens on transport error (e.g. failed to send
* response)
*/
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
break;
default:
pj_assert(!"Unexpected INVITE state");
break;
}
} else if (tsx->method.id == PJSIP_CANCEL_METHOD &&
tsx->role == PJSIP_ROLE_UAS &&
tsx->state < PJSIP_TSX_STATE_COMPLETED &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG )
{
/*
* Handle incoming CANCEL request.
*/
inv_respond_incoming_cancel(inv, tsx, e);
}
}
/*
* State EARLY is for both UAS and UAC, after response with To tag
* is sent/received.
*/
static void inv_on_state_early( pjsip_inv_session *inv, pjsip_event *e)
{
pjsip_transaction *tsx = e->body.tsx_state.tsx;
pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
PJ_ASSERT_ON_FAIL(tsx && dlg, return);
if (tsx == inv->invite_tsx) {
/*
* Handle the INVITE state progress.
*/
switch (tsx->state) {
case PJSIP_TSX_STATE_PROCEEDING:
/* Send/received another provisional response. */
inv_set_state(inv, PJSIP_INV_STATE_EARLY, e);
if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) {
inv_handle_incoming_reliable_response(
inv, e->body.tsx_state.src.rdata);
}
}
break;
case PJSIP_TSX_STATE_COMPLETED:
if (tsx->status_code/100 == 2) {
inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
pj_status_t status;
/* Process session timer response. */
status = handle_timer_response(inv,
e->body.tsx_state.src.rdata,
PJ_TRUE);
if (status != PJ_SUCCESS)
break;
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
}
} else if (tsx->role == PJSIP_ROLE_UAC) {
handle_uac_call_rejection(inv, e);
} else {
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
}
break;
case PJSIP_TSX_STATE_CONFIRMED:
/* For some reason can go here (maybe when ACK for 2xx has
* the same branch value as the INVITE transaction) */
case PJSIP_TSX_STATE_TERMINATED:
/* INVITE transaction can be terminated either because UAC
* transaction received 2xx response or because of transport
* error.
*/
if (tsx->status_code/100 == 2) {
/* This must be receipt of 2xx response */
/* Set state to CONNECTING */
inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
pj_status_t status;
/* Process session timer response. */
status = handle_timer_response(inv,
e->body.tsx_state.src.rdata,
PJ_TRUE);
if (status != PJ_SUCCESS)
break;
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
}
/* if UAC, send ACK and move state to confirmed. */
if (tsx->role == PJSIP_ROLE_UAC) {
pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
inv_send_ack(inv, e);
}
} else {
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
}
break;
default:
pj_assert(!"Unexpected INVITE tsx state");
break;
}
} else if (inv->role == PJSIP_ROLE_UAS &&
tsx->role == PJSIP_ROLE_UAS &&
tsx->method.id == PJSIP_CANCEL_METHOD &&
tsx->state < PJSIP_TSX_STATE_COMPLETED &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG )
{
/*
* Handle incoming CANCEL request.
*/
inv_respond_incoming_cancel(inv, tsx, e);
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->state == PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
{
/*
* Handle incoming UPDATE
*/
inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
} else if (tsx->role == PJSIP_ROLE_UAC &&
(tsx->state == PJSIP_TSX_STATE_COMPLETED ||
tsx->state == PJSIP_TSX_STATE_TERMINATED) &&
pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
{
/*
* Handle response to outgoing UPDATE request.
*/
inv_handle_update_response(inv, e);
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->state == PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
{
/*
* Handle incoming PRACK
*/
inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata);
} else if (tsx->role == PJSIP_ROLE_UAC) {
/* Generic handling for UAC tsx completion */
handle_uac_tsx_response(inv, e);
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->method.id == PJSIP_BYE_METHOD &&
tsx->status_code < 200 &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
{
/* Received BYE before the 2xx/OK response to INVITE.
* Assume that the 2xx/OK response is lost and the BYE
* arrives earlier.
*/
inv_respond_incoming_bye(inv, tsx, e->body.tsx_state.src.rdata, e);
if (inv->invite_tsx->role == PJSIP_ROLE_UAC) {
/* Set timer just in case we will never get the final response
* for INVITE.
*/
pjsip_tsx_set_timeout(inv->invite_tsx, 64*pjsip_cfg()->tsx.t1);
} else if (inv->invite_tsx->status_code < 200) {
pjsip_tx_data *tdata;
pjsip_msg *msg;
/* For UAS, send a final response. */
tdata = inv->invite_tsx->last_tx;
PJ_ASSERT_ON_FAIL(tdata != NULL, return);
msg = tdata->msg;
msg->line.status.code = PJSIP_SC_REQUEST_TERMINATED;
msg->line.status.reason =
*pjsip_get_status_text(PJSIP_SC_REQUEST_TERMINATED);
msg->body = NULL;
pjsip_tx_data_invalidate_msg(tdata);
pjsip_tx_data_add_ref(tdata);
pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
}
}
}
/*
* State CONNECTING is after 2xx response to INVITE is sent/received.
*/
static void inv_on_state_connecting( pjsip_inv_session *inv, pjsip_event *e)
{
pjsip_transaction *tsx = e->body.tsx_state.tsx;
pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
PJ_ASSERT_ON_FAIL(tsx && dlg, return);
if (tsx == inv->invite_tsx) {
/*
* Handle INVITE state progression.
*/
switch (tsx->state) {
case PJSIP_TSX_STATE_CONFIRMED:
/* It can only go here if incoming ACK request has the same Via
* branch parameter as the INVITE transaction.
*/
if (tsx->status_code/100 == 2) {
if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
}
inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, e);
}
break;
case PJSIP_TSX_STATE_TERMINATED:
/* INVITE transaction can be terminated either because UAC
* transaction received 2xx response or because of transport
* error.
*/
if (tsx->status_code/100 != 2) {
if (tsx->role == PJSIP_ROLE_UAC) {
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
} else {
pjsip_tx_data *bye;
pj_status_t status;
/* Send BYE */
status = pjsip_dlg_create_request(inv->dlg,
pjsip_get_bye_method(),
-1, &bye);
if (status == PJ_SUCCESS) {
pjsip_inv_send_msg(inv, bye);
}
}
}
break;
case PJSIP_TSX_STATE_DESTROYED:
/* Do nothing. */
break;
default:
pj_assert(!"Unexpected state");
break;
}
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->method.id == PJSIP_BYE_METHOD &&
tsx->status_code < 200 &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
{
/*
* Handle incoming BYE.
*/
inv_respond_incoming_bye( inv, tsx, e->body.tsx_state.src.rdata, e );
} else if (tsx->method.id == PJSIP_BYE_METHOD &&
tsx->role == PJSIP_ROLE_UAC &&
(tsx->state == PJSIP_TSX_STATE_COMPLETED ||
tsx->state == PJSIP_TSX_STATE_TERMINATED))
{
/*
* Outgoing BYE
*/
inv_handle_bye_response( inv, tsx, e->body.tsx_state.src.rdata, e);
}
else if (tsx->method.id == PJSIP_CANCEL_METHOD &&
tsx->role == PJSIP_ROLE_UAS &&
tsx->status_code < 200 &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
{
/*
* Handle strandled incoming CANCEL.
*/
pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
pjsip_tx_data *tdata;
pj_status_t status;
status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
if (status != PJ_SUCCESS) return;
status = pjsip_dlg_send_response(dlg, tsx, tdata);
if (status != PJ_SUCCESS) return;
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->state == PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_invite_method)==0)
{
pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
pjsip_tx_data *tdata;
pj_status_t status;
/* See https://trac.pjsip.org/repos/ticket/1455
* Handle incoming re-INVITE before current INVITE is confirmed.
* According to RFC 5407:
* - answer with 200 if we don't have pending offer-answer
* - answer with 491 if we *have* pending offer-answer
*
* But unfortunately accepting the re-INVITE would mean we have
* two outstanding INVITEs, and we don't support that because
* we will get confused when we handle the ACK.
*/
status = pjsip_dlg_create_response(inv->dlg, rdata,
PJSIP_SC_REQUEST_PENDING,
NULL, &tdata);
if (status != PJ_SUCCESS)
return;
pjsip_timer_update_resp(inv, tdata);
status = pjsip_dlg_send_response(dlg, tsx, tdata);
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->state == PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
{
/*
* Handle incoming UPDATE
*/
inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
} else if (tsx->role == PJSIP_ROLE_UAC &&
(tsx->state == PJSIP_TSX_STATE_COMPLETED ||
tsx->state == PJSIP_TSX_STATE_TERMINATED) &&
pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
{
/*
* Handle response to outgoing UPDATE request.
*/
if (inv_handle_update_response(inv, e) == PJ_FALSE)
handle_uac_tsx_response(inv, e);
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->state == PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
{
/*
* Handle incoming PRACK
*/
inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata);
} else if (tsx->role == PJSIP_ROLE_UAC) {
/* Generic handling for UAC tsx completion */
handle_uac_tsx_response(inv, e);
}
}
/*
* State CONFIRMED is after ACK is sent/received.
*/
static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e)
{
pjsip_transaction *tsx = e->body.tsx_state.tsx;
pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
PJ_ASSERT_ON_FAIL(tsx && dlg, return);
if (tsx->method.id == PJSIP_BYE_METHOD &&
tsx->role == PJSIP_ROLE_UAC &&
(tsx->state == PJSIP_TSX_STATE_COMPLETED ||
tsx->state == PJSIP_TSX_STATE_TERMINATED))
{
/*
* Outgoing BYE
*/
inv_handle_bye_response( inv, tsx, e->body.tsx_state.src.rdata, e);
}
else if (tsx->method.id == PJSIP_BYE_METHOD &&
tsx->role == PJSIP_ROLE_UAS &&
tsx->status_code < 200 &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
{
/*
* Handle incoming BYE.
*/
inv_respond_incoming_bye( inv, tsx, e->body.tsx_state.src.rdata, e );
}
else if (tsx->method.id == PJSIP_CANCEL_METHOD &&
tsx->role == PJSIP_ROLE_UAS &&
tsx->status_code < 200 &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
{
/*
* Handle strandled incoming CANCEL.
*/
pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
pjsip_tx_data *tdata;
pj_status_t status;
status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
if (status != PJ_SUCCESS) return;
status = pjsip_dlg_send_response(dlg, tsx, tdata);
if (status != PJ_SUCCESS) return;
}
else if (tsx->method.id == PJSIP_INVITE_METHOD &&
tsx->role == PJSIP_ROLE_UAS)
{
/*
* Handle incoming re-INVITE
*/
if (tsx->state == PJSIP_TSX_STATE_TRYING) {
pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
pjsip_tx_data *tdata;
pj_status_t status;
pjsip_rdata_sdp_info *sdp_info;
pjsip_status_code st_code;
/* Check if we have INVITE pending. */
if (inv->invite_tsx && inv->invite_tsx!=tsx) {
int code;
pj_str_t reason;
reason = pj_str("Another INVITE transaction in progress");
if (inv->invite_tsx->role == PJSIP_ROLE_UAC)
code = 491;
else
code = 500;
/* Can not receive re-INVITE while another one is pending. */
status = pjsip_dlg_create_response( inv->dlg, rdata, code,
&reason, &tdata);
if (status != PJ_SUCCESS)
return;
if (code == 500) {
/* MUST include Retry-After header with random value
* between 0-10.
*/
pjsip_retry_after_hdr *ra_hdr;
int val = (pj_rand() % 10);
ra_hdr = pjsip_retry_after_hdr_create(tdata->pool, val);
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)ra_hdr);
}
status = pjsip_dlg_send_response( inv->dlg, tsx, tdata);
return;
}
/* Save the invite transaction. */
inv->invite_tsx = tsx;
/* Process session timers headers in the re-INVITE */
status = pjsip_timer_process_req(inv, rdata, &st_code);
if (status != PJ_SUCCESS) {
status = pjsip_dlg_create_response(inv->dlg, rdata, st_code,
NULL, &tdata);
if (status != PJ_SUCCESS)
return;
pjsip_timer_update_resp(inv, tdata);
status = pjsip_dlg_send_response(dlg, tsx, tdata);
return;
}
/* Send 491 if we receive re-INVITE while another offer/answer
* negotiation is in progress
*/
if (pjmedia_sdp_neg_get_state(inv->neg) !=
PJMEDIA_SDP_NEG_STATE_DONE)
{
status = pjsip_dlg_create_response(inv->dlg, rdata,
PJSIP_SC_REQUEST_PENDING,
NULL, &tdata);
if (status != PJ_SUCCESS)
return;
pjsip_timer_update_resp(inv, tdata);
status = pjsip_dlg_send_response(dlg, tsx, tdata);
return;
}
/* Process SDP in incoming message. */
status = inv_check_sdp_in_incoming_msg(inv, tsx, rdata);
if (status != PJ_SUCCESS) {
/* Not Acceptable */
const pjsip_hdr *accept;
/* The incoming SDP is unacceptable. If the SDP negotiator
* state has just been changed, i.e: DONE -> REMOTE_OFFER,
* revert it back.
*/
if (pjmedia_sdp_neg_get_state(inv->neg) ==
PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER)
{
pjmedia_sdp_neg_cancel_offer(inv->neg);
}
status = pjsip_dlg_create_response(inv->dlg, rdata,
488, NULL, &tdata);
if (status != PJ_SUCCESS)
return;
accept = pjsip_endpt_get_capability(dlg->endpt, PJSIP_H_ACCEPT,
NULL);
if (accept) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
pjsip_hdr_clone(tdata->pool, accept));
}
status = pjsip_dlg_send_response(dlg, tsx, tdata);
return;
}
/* Create 2xx ANSWER */
status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
if (status != PJ_SUCCESS)
return;
/* If the INVITE request has SDP body, send answer.
* Otherwise generate offer from local active SDP.
*/
sdp_info = pjsip_rdata_get_sdp_info(rdata);
if (sdp_info->sdp != NULL) {
status = process_answer(inv, 200, tdata, NULL);
} else {
/* INVITE does not have SDP.
* If on_create_offer() callback is implemented, ask app.
* to generate an offer, otherwise just send active local
* SDP to signal that nothing gets modified.
*/
pjmedia_sdp_session *sdp = NULL;
if (mod_inv.cb.on_create_offer) {
(*mod_inv.cb.on_create_offer)(inv, &sdp);
if (sdp) {
/* Notify negotiator about the new offer. This will
* fix the offer with correct SDP origin.
*/
status =
pjmedia_sdp_neg_modify_local_offer(inv->pool_prov,
inv->neg,
sdp);
/* Retrieve the "fixed" offer from negotiator */
if (status==PJ_SUCCESS) {
const pjmedia_sdp_session *lsdp = NULL;
pjmedia_sdp_neg_get_neg_local(inv->neg, &lsdp);
sdp = (pjmedia_sdp_session*)lsdp;
}
}
}
if (sdp == NULL) {
const pjmedia_sdp_session *active_sdp = NULL;
status = pjmedia_sdp_neg_send_local_offer(inv->pool_prov,
inv->neg,
&active_sdp);
if (status == PJ_SUCCESS)
sdp = (pjmedia_sdp_session*) active_sdp;
}
if (sdp) {
tdata->msg->body = create_sdp_body(tdata->pool, sdp);
}
}
if (status != PJ_SUCCESS) {
/*
* SDP negotiation has failed.
*/
pj_status_t rc;
pj_str_t reason;
/* Delete the 2xx answer */
pjsip_tx_data_dec_ref(tdata);
/* Create 500 response */
reason = pj_str("SDP negotiation failed");
rc = pjsip_dlg_create_response(dlg, rdata, 500, &reason,
&tdata);
if (rc == PJ_SUCCESS) {
pjsip_warning_hdr *w;
const pj_str_t *endpt_name;
endpt_name = pjsip_endpt_name(dlg->endpt);
w = pjsip_warning_hdr_create_from_status(tdata->pool,
endpt_name,
status);
if (w)
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)w);
pjsip_inv_send_msg(inv, tdata);
}
return;
}
/* Invoke Session Timers */
pjsip_timer_update_resp(inv, tdata);
/* Send 2xx regardless of the status of negotiation */
status = pjsip_inv_send_msg(inv, tdata);
} else if (tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
/* This is the case where ACK has the same branch as
* the INVITE request.
*/
if (tsx->status_code/100 == 2 &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
{
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
/* Check if local offer got no SDP answer */
if (pjmedia_sdp_neg_get_state(inv->neg)==
PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
{
pjmedia_sdp_neg_cancel_offer(inv->neg);
}
}
}
}
else if (tsx->method.id == PJSIP_INVITE_METHOD &&
tsx->role == PJSIP_ROLE_UAC)
{
/*
* Handle outgoing re-INVITE
*/
if (tsx->state == PJSIP_TSX_STATE_CALLING) {
/* Must not have other pending INVITE transaction */
pj_assert(inv->invite_tsx==NULL || tsx==inv->invite_tsx);
/* Save pending invite transaction */
inv->invite_tsx = tsx;
} else if (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
tsx->status_code/100 == 2)
{
pj_status_t status;
/* Re-INVITE was accepted. */
/* Process session timer response. */
status = handle_timer_response(inv,
e->body.tsx_state.src.rdata,
PJ_TRUE);
if (status != PJ_SUCCESS)
return;
/* Process SDP */
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
/* Check if local offer got no SDP answer */
if (pjmedia_sdp_neg_get_state(inv->neg)==
PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
{
pjmedia_sdp_neg_cancel_offer(inv->neg);
}
/* Send ACK */
inv_send_ack(inv, e);
} else if (handle_uac_tsx_response(inv, e)) {
/* Handle response that terminates dialog */
/* Nothing to do (already handled) */
} else if (tsx->status_code >= 300 && tsx->status_code < 700 &&
e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED)
{
/* Ticket #1654: do not cancel SDP offer when tsx state changing
* from 'completed' to 'terminated', as it should have already
* been cancelled when tsx state is 'completed'.
*/
pjmedia_sdp_neg_state neg_state;
struct tsx_inv_data *tsx_inv_data;
tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
/* Outgoing INVITE transaction has failed, cancel SDP nego */
neg_state = pjmedia_sdp_neg_get_state(inv->neg);
if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER &&
tsx_inv_data->retrying == PJ_FALSE)
{
pjmedia_sdp_neg_cancel_offer(inv->neg);
}
if (tsx == inv->invite_tsx)
inv->invite_tsx = NULL;
}
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->state == PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
{
/*
* Handle incoming UPDATE
*/
inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
} else if (tsx->role == PJSIP_ROLE_UAC &&
(tsx->state == PJSIP_TSX_STATE_COMPLETED ||
tsx->state == PJSIP_TSX_STATE_TERMINATED) &&
pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
{
/*
* Handle response to outgoing UPDATE request.
*/
if (inv_handle_update_response(inv, e) == PJ_FALSE)
handle_uac_tsx_response(inv, e);
} else if (tsx->role == PJSIP_ROLE_UAS &&
tsx->state == PJSIP_TSX_STATE_TRYING &&
pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
{
/*
* Handle strandled incoming PRACK
*/
inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata);
} else if (tsx->role == PJSIP_ROLE_UAC) {
/*
* Handle 401/407/408/481/422 response
*/
handle_uac_tsx_response(inv, e);
}
}
/*
* After session has been terminated, but before dialog is destroyed
* (because dialog has other usages, or because dialog is waiting for
* the last transaction to terminate).
*/
static void inv_on_state_disconnected( pjsip_inv_session *inv, pjsip_event *e)
{
pjsip_transaction *tsx = e->body.tsx_state.tsx;
pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
PJ_ASSERT_ON_FAIL(tsx && dlg, return);
if (tsx->role == PJSIP_ROLE_UAS &&
tsx->status_code < 200 &&
e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
{
pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
/*
* Respond BYE with 200/OK
*/
if (tsx->method.id == PJSIP_BYE_METHOD) {
inv_respond_incoming_bye( inv, tsx, rdata, e );
} else if (tsx->method.id == PJSIP_CANCEL_METHOD) {
/*
* Respond CANCEL with 200/OK too.
*/
pjsip_tx_data *tdata;
pj_status_t status;
status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
if (status != PJ_SUCCESS) return;
status = pjsip_dlg_send_response(dlg, tsx, tdata);
if (status != PJ_SUCCESS) return;
}
} else if (tsx->role == PJSIP_ROLE_UAC) {
/*
* Handle 401/407/408/481/422 response
*/
handle_uac_tsx_response(inv, e);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Feb 1, 4:37 PM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3489412
Default Alt Text
(127 KB)
Attached To
Mode
rPYNSIPSIMPLE python3-sipsimple
Attached
Detach File
Event Timeline
Log In to Comment