📄 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)); return PJ_SUCCESS;}/* * 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){ pjsip_msg *msg; pjsip_allow_hdr *allow; pjsip_supported_hdr *sup_hdr;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -