📄 sip_inv.c
字号:
/* $Id: sip_inv.c 981 2007-02-19 22:19:23Z bennylp $ */
/*
* Copyright (C) 2003-2007 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/sip_module.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_event.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>
#define THIS_FILE "sip_invite_session.c"
static const char *inv_state_names[] =
{
"NULL",
"CALLING",
"INCOMING",
"EARLY",
"CONNECTING",
"CONFIRMED",
"DISCONNCTD",
"TERMINATED",
};
/*
* 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 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;
pj_bool_t sdp_done;
};
/*
* Module load()
*/
static pj_status_t mod_inv_load(pjsip_endpoint *endpt)
{
pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6}};
pj_str_t accepted = { "application/sdp", 15 };
/* Register supported methods: INVITE, ACK, BYE, CANCEL */
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;
/* 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);
/* Call on_state_changed() callback. */
if (mod_inv.cb.on_state_changed && inv->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)
{
pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
}
}
/*
* 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 = 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);
}
}
/*
* Send ACK for 2xx response.
*/
static pj_status_t inv_send_ack(pjsip_inv_session *inv, pjsip_rx_data *rdata)
{
pjsip_tx_data *tdata;
pj_status_t status;
PJ_LOG(5,(inv->obj_name, "Received %s, sending ACK",
pjsip_rx_data_get_info(rdata)));
status = pjsip_dlg_create_request(inv->dlg, &pjsip_ack_method,
rdata->msg_info.cseq->cseq, &tdata);
if (status != PJ_SUCCESS) {
/* Better luck next time */
pj_assert(!"Unable to create ACK!");
return status;
}
status = pjsip_dlg_send_request(inv->dlg, tdata, -1, NULL);
if (status != PJ_SUCCESS) {
/* Better luck next time */
pj_assert(!"Unable to send ACK!");
return status;
}
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 = 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 ACK if pending INVITE transaction has not finished. */
if (inv->invite_tsx &&
inv->invite_tsx->state < PJSIP_TSX_STATE_COMPLETED)
{
return PJ_TRUE;
}
/* Terminate INVITE transaction, if it's still present. */
if (inv->invite_tsx &&
inv->invite_tsx->state <= PJSIP_TSX_STATE_COMPLETED)
{
pj_assert(inv->invite_tsx->status_code >= 200);
pjsip_tsx_terminate(inv->invite_tsx,
inv->invite_tsx->status_code);
inv->invite_tsx = 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;
}
/*
* 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)
{
inv_send_ack(inv, rdata);
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 */
if (mod_inv.cb.on_tsx_state_changed && inv->notify)
(*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;
}
/*
* 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 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(dlg->pool, sizeof(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 = 0;
/* 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(dlg->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);
/* 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));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -