freeswitch/libs/sipcc/core/gsm/fim.c

762 lines
23 KiB
C
Executable File

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//#include "mozilla/Assertions.h"
#include "cpr_types.h"
#include "cpr_stdlib.h"
#include "cpr_stdio.h"
#include "phone.h"
#include "fim.h"
#include "lsm.h"
#include "fsm.h"
#include "sm.h"
#include "ccapi.h"
#include "debug.h"
#include "phone_debug.h"
#include "util_string.h"
#include "sdp.h"
#include "gsm_sdp.h"
#include "ccsip_sdp.h"
#include "platform_api.h"
extern void set_next_sess_video_pref(int pref);
extern sm_rcs_t dcsm_process_event(void *event, int event_id);
#define FIM_MAX_CHNS (LSM_MAX_CALLS)
#define FIM_MAX_SCBS (FSM_TYPE_MAX)
#define FIM_MAX_ICBS (FSM_TYPE_MAX * FIM_MAX_CHNS)
static fim_scb_t *fim_scbs;
static fim_icb_t *fim_icbs;
static void
fim_mwi (cc_mwi_t *msg)
{
cc_action_data_t data;
data.mwi.on = msg->msgSummary.on;
data.mwi.type = msg->msgSummary.type;
data.mwi.newCount = msg->msgSummary.newCount;
data.mwi.oldCount = msg->msgSummary.oldCount;
data.mwi.hpNewCount = msg->msgSummary.hpNewCount;
data.mwi.hpOldCount = msg->msgSummary.hpOldCount;
(void)cc_call_action(msg->call_id, msg->line, CC_ACTION_MWI, &data);
}
static void
fim_init_call_chns (void)
{
int chn;
fsm_types_t type;
fim_icb_t *icb = NULL;
static const char fname[] = "fim_init_call_chns";
fim_scbs = (fim_scb_t *) cpr_calloc(FIM_MAX_SCBS, sizeof(fim_scb_t));
if (fim_scbs == NULL) {
GSM_DEBUG_ERROR(GSM_F_PREFIX"Failed to allocate FIM SCBs.\n", fname);
return;
}
fim_icbs = (fim_icb_t *) cpr_calloc(FIM_MAX_ICBS, sizeof(fim_icb_t));
if (fim_icbs == NULL) {
GSM_DEBUG_ERROR(GSM_F_PREFIX"Failed to allocate FIM ICBs.\n", fname);
cpr_free(fim_scbs);
fim_scbs = NULL;
return;
}
/*
* Initialize the icbs (fim control blocks).
*/
icb = fim_icbs;
for (chn = 0; chn < FIM_MAX_CHNS; chn++) {
for (type = FSM_TYPE_HEAD; type < FSM_TYPE_MAX; type++, icb++) {
icb->call_id = CC_NO_CALL_ID;
icb->scb = &(fim_scbs[type]);
icb->cb = NULL;
/*
* Set the next_chn pointers if this is the head of the chain,
* Set non-head icbs and the last head to NULL.
*/
if ((type == FSM_TYPE_HEAD) && (chn < (FIM_MAX_CHNS - 1))) {
icb->next_chn = icb + FSM_TYPE_MAX;
} else {
icb->next_chn = NULL;
}
/*
* Set the next_icb pointers if this icb is not the last
* one on the chain.
*/
if (type < (FSM_TYPE_MAX - 1)) {
icb->next_icb = icb + 1;
} else {
icb->next_icb = NULL;
}
} /* for (jscb = FSM_TYPE_HEAD; i < FSM_TYPE_MAX; i++) { */
} /* for (ichn = 0; ichn < FIM_MAX_CHNS; ichn++) { */
/*
* Initialize the scbs (state machine control blocks).
*/
icb = fim_icbs;
for (type = FSM_TYPE_HEAD; type < FSM_TYPE_MAX; type++, icb++) {
icb->scb->type = type;
fsm_init_scb(icb, CC_NO_CALL_ID);
}
}
static void
fim_free_call_chn (fim_icb_t *call_chn, line_t line, boolean update_call_cnt)
{
static const char fname[] = "fim_free_call_chn";
fim_icb_t *icb = call_chn;
FIM_DEBUG(get_debug_string(GSM_DBG_PTR), "FIM", call_chn->call_id, fname,
"call_chn", call_chn);
/*
* Go through the chain and free each icb.
*/
for (icb = call_chn; icb != NULL; icb = icb->next_icb) {
if (icb->scb->free_cb != NULL) {
icb->scb->free_cb(icb, icb->call_id);
}
icb->call_id = CC_NO_CALL_ID;
icb->cb = NULL;
}
if (update_call_cnt == TRUE) {
lsm_decrement_call_chn_cnt(line);
}
else {
FIM_DEBUG(get_debug_string(GSM_DBG_PTR), "lsm not decremented", call_chn->call_id, fname,
"call_chn", call_chn);
}
}
static fim_icb_t *
fim_get_call_chn_by_call_id (callid_t call_id)
{
static const char fname[] = "fim_get_call_chn_by_call_id";
fim_icb_t *call_chn = NULL;
fim_icb_t *icb = NULL;
for (icb = fim_icbs; icb != NULL; icb = icb->next_chn) {
if (icb->call_id == call_id) {
call_chn = icb;
break;
}
}
FIM_DEBUG(get_debug_string(GSM_DBG_PTR), "FIM", call_id, fname, "chn",
call_chn);
return call_chn;
}
static fim_icb_t *
fim_get_new_call_chn (callid_t call_id)
{
static const char fname[] = "fim_get_new_call_chn";
fim_icb_t *call_chn = NULL;
fim_icb_t *icb = NULL;
/*
* Verify that this call_id is not already used.
*/
call_chn = fim_get_call_chn_by_call_id(call_id);
if (call_chn != NULL) {
FIM_DEBUG(get_debug_string(GSM_DBG1), "FIM", call_id, fname,
"call_id in use");
return NULL;
}
/*
* Construct a new call chain.
*/
call_chn = fim_get_call_chn_by_call_id(CC_NO_CALL_ID);
if (call_chn == NULL) {
/*
* No more free call_chns are available.
*/
FIM_DEBUG(get_debug_string(GSM_DBG1), "FIM", call_id, fname,
"no free call_chns");
return NULL;
}
call_chn->call_id = call_id;
call_chn->ui_locked = FALSE;
/*
* Set the control blocks for the icbs.
*/
for (icb = call_chn; icb != NULL; icb = icb->next_icb) {
FIM_DEBUG(get_debug_string(GSM_DBG1), "FIM", call_id, fname,
fsm_type_name(icb->scb->type));
if (icb->scb->get_cb) {
icb->scb->get_cb(icb, call_id);
icb->call_id = call_id;
icb->ui_locked = FALSE;
}
}
FIM_DEBUG(get_debug_string(GSM_DBG_PTR), "FIM", call_chn->call_id, fname,
"call_chn", call_chn);
return call_chn;
}
void
fim_free_event (void *data)
{
cc_msg_t *msg = (cc_msg_t *) data;
/* Free CCAPI msg. data that are not consumed */
cc_free_msg_data(msg);
}
static void
fim_process_options_msg (void *data)
{
static const char fname[] = "fim_process_options_msg";
cc_sdp_t *local_sdp_p = NULL;
cc_causes_t cause = CC_CAUSE_MIN;
cc_msgbody_info_t msg_body;
cc_options_sdp_req_t *options_msg = (cc_options_sdp_req_t *) data;
gsmsdp_create_options_sdp(&local_sdp_p);
cause = gsmsdp_encode_sdp(local_sdp_p, &msg_body);
if (cause != CC_CAUSE_OK) {
FIM_DEBUG(get_debug_string(GSM_DBG1), "FIM", options_msg->call_id,
fname, "Unable to build SDP\n");
} else {
cc_int_options_sdp_ack(CC_SRC_GSM, CC_SRC_SIP,
options_msg->call_id,
options_msg->line,
options_msg->pMessage, &msg_body);
}
sipsdp_src_dest_free(CCSIP_SRC_SDP_BIT, &local_sdp_p);
}
void
fim_lock_ui (callid_t call_id)
{
fim_icb_t *call_chn = fim_get_call_chn_by_call_id(call_id);
if (call_chn == NULL) {
FIM_DEBUG(DEB_F_PREFIX"unknown call id\n", DEB_F_PREFIX_ARGS(FIM, "fim_lock_ui"));
return;
}
call_chn->ui_locked = TRUE;
}
void
fim_unlock_ui (callid_t call_id)
{
fim_icb_t *call_chn = fim_get_call_chn_by_call_id(call_id);
if (call_chn == NULL) {
FIM_DEBUG(DEB_F_PREFIX"unknown call id\n", DEB_F_PREFIX_ARGS(FIM, "fim_unlock_ui"));
return;
}
call_chn->ui_locked = FALSE;
}
static boolean
fsm_event_filtered_by_ui_lock (int event_id, cc_features_t feature_id)
{
/*
* If UI has been locked by GSM due to pending states, we want
* to filter the events that come in from the UI. We will still
* allow all GSM and SIP stack originated events through to the
* GSM sm.
*/
if (event_id == CC_MSG_ONHOOK) {
return FALSE;
}
if ((event_id == CC_MSG_FEATURE) &&
(feature_id == CC_FEATURE_END_CALL ||
feature_id == CC_FEATURE_REQ_PEND_TIMER_EXP ||
feature_id == CC_FEATURE_RESUME ||
feature_id == CC_FEATURE_HOLD)) {
return FALSE;
}
return TRUE;
}
/**
*
* Check if the feature event is generic feature
*
* @param cc_features_t feature_id
*
* @return TRUE if the feature is generic or else FALSE
*
* @pre None
*/
boolean fim_is_app_generic_features (cc_features_t feat_id)
{
return(FALSE);
}
/**
*
* Check if the message is the feature event that may requires a new
* call chain.
*
* @param msg - pointer to cc_setup_t.
*
* @return TRUE if the feature that may requires a new call chain.
*
* @pre (msg != NULL)
*/
static boolean
fim_check_feature_event (cc_setup_t *msg)
{
if ((((cc_feature_t *)msg)->feature_id == CC_FEATURE_SELECT) ||
(((cc_feature_t *)msg)->feature_id == CC_FEATURE_DIRTRXFR) ||
(((cc_feature_t *)msg)->feature_id == CC_FEATURE_B2BCONF) ||
(((cc_feature_t *)msg)->feature_id == CC_FEATURE_CFWD_ALL)) {
return (TRUE);
}
return (FALSE);
}
/*
* fim_feature_need_outgoing_call_context
*
* Description:
* This function determines whther a new feature invokation needs
* an early outgoing call context created in the FIM main module or not.
*
* Parameters:
msg - pointer to the cc_setup_t.
*
* Returns:
* TRUE - the feature requires outgoing call context to be created.
* FALSE - the feature does not requries outgoing call context to
* be created during initial FIM displacthing.
*/
static boolean
fim_feature_need_outgoing_call_context (cc_setup_t *msg)
{
if (((cc_feature_t *)msg)->feature_id == CC_FEATURE_NOTIFY) {
/*
* these features do not need outgoing context to be created early
*/
return (FALSE);
}
return (TRUE);
}
/**
*
* Process events received for GSM
*
* @param void event data
* cac_passed indicate if the event passed cac request.
* Usually set to true once the cac response is
* received so that it won't do another request.
*
* @return true if message memory has to be released by caller
* false if the message memory is not to be released by
* caller.
*
* @pre (data not_eq NULL)
* @pre (CC_MSG_MIN < msg->msg_id <CC_MSG_MAX)
*/
boolean
fim_process_event (void *data, boolean cac_passed)
{
static const char fname[] = "fim_process_event";
cc_setup_t *msg = (cc_setup_t *) data;
cc_msgs_t msg_id = msg->msg_id;
callid_t call_id = msg->call_id;
line_t line = msg->line;
int event_id = msg_id;
sm_event_t event;
fim_icb_t *call_chn = NULL;
fim_icb_t *icb = NULL;
boolean done = FALSE;
sm_rcs_t rc = SM_RC_ERROR;
fim_cb_hdr_t *cb_hdr = NULL;
fsm_fcb_t *fcb = NULL;
cc_feature_t *feat_msg = (cc_feature_t *) data;
cc_feature_data_t * feat_data = &(feat_msg->data);
boolean update_call_cnt = TRUE;
uint32_t no_of_session = 1;
callid_t bw_call_id;
FIM_DEBUG(DEB_L_C_F_PREFIX"Msg name = %s\n", DEB_L_C_F_PREFIX_ARGS(FIM, line, call_id, fname),
cc_msg_name(msg_id));
/*
* Validate the incoming event.
*/
if ((event_id <= CC_MSG_MIN) || (event_id >= CC_MSG_MAX)) {
cc_call_state(call_id, line, CC_STATE_UNKNOWN, NULL);
return(TRUE);
}
/* Make sure to process the device events
*/
if (dcsm_process_event(data, event_id) == SM_RC_END) {
/* Keep the message in the dcsm state handler */
return(FALSE);
}
/*
* Grab non-call control events and hand them off to other functions that
* are not implemented by the fsms.
*/
if (msg_id == CC_MSG_MWI) {
fim_mwi((cc_mwi_t *) msg);
}
if (event_id == CC_MSG_OPTIONS) {
fim_process_options_msg(data);
return(TRUE);
}
if (platWlanISActive() && cac_passed == FALSE) {
/* The WLAN will request for bandwidth only for the events received from
* UI or other external entity. For internal events there is allocated bandwidth
* and do not need to request again.
*/
if ((msg->src_id != CC_SRC_GSM) &&
((event_id == CC_MSG_SETUP) ||
(event_id == CC_MSG_OFFHOOK) ||
(event_id == CC_MSG_DIALSTRING) ||
(event_id == CC_MSG_LINE) ||
((event_id == CC_MSG_FEATURE) &&
((((cc_feature_t *) msg)->feature_id == CC_FEATURE_NEW_CALL))))) {
bw_call_id = call_id;
if ((event_id == CC_MSG_SETUP) &&
((((cc_setup_t *)msg)->call_info.type == CC_FEAT_MONITOR))) {
no_of_session = 2;
bw_call_id = msg->call_info.data.join.join_call_id;
}
if (fsm_cac_call_bandwidth_req (bw_call_id, no_of_session, msg) != CC_CAUSE_OK) {
return(TRUE);
}
/* Do not release the msg once it returns from the call
*/
return(FALSE);
}
}
if ((event_id == CC_MSG_FEATURE) && ((call_id == CC_NO_CALL_ID) ||
fim_is_app_generic_features(((cc_feature_t *) msg)->feature_id))) {
if (((cc_feature_t *)msg)->feature_id == CC_FEATURE_BLF_ALERT_TONE) {
(void)cc_call_action(0, 0, CC_ACTION_PLAY_BLF_ALERTING_TONE, NULL);
} else if (((cc_feature_t *)msg)->feature_id == CC_FEATURE_UPD_MEDIA_CAP) {
fsmdef_update_media_cap_feature_event(feat_msg);
}
return(TRUE);
}
/*
* Throw away any messages with call_id < 1 (which means they are invalid).
*
* The GSM will send messages back to itself with a call_id < 1 because
* it uses the NULL_DCB. The fsmxfr will use a NULL_DCB that has invalid
* data in it, but the DCB points to a valid DCB. This way, the fsmxfr does
* not have to keep checking for the NULL.
*/
if (call_id < 1) {
return(TRUE);
}
/*
* Get the call chain associated with this call_id.
*/
call_chn = fim_get_call_chn_by_call_id(call_id);
if (call_chn == NULL) {
/*
* No call chain, so get a new call chain,
* but only if the event is a call establishment event.
*/
if ((event_id == CC_MSG_SETUP) ||
(event_id == CC_MSG_OFFHOOK) ||
(event_id == CC_MSG_DIALSTRING) ||
(event_id == CC_MSG_LINE) ||
(event_id == CC_MSG_CREATEOFFER) ||
(event_id == CC_MSG_CREATEANSWER) ||
(event_id == CC_MSG_SETLOCALDESC) ||
(event_id == CC_MSG_SETREMOTEDESC) ||
(event_id == CC_MSG_SETPEERCONNECTION) ||
(event_id == CC_MSG_ADDSTREAM) ||
(event_id == CC_MSG_REMOVESTREAM) ||
(event_id == CC_MSG_ADDCANDIDATE) ||
((event_id == CC_MSG_FEATURE) &&
((((cc_feature_t *) msg)->feature_id == CC_FEATURE_NEW_CALL)))) {
call_chn = fim_get_new_call_chn(call_id);
/*
* Make sure we got a new call chain.
*/
if (call_chn == NULL) {
FIM_DEBUG(get_debug_string(GSM_DBG1), "FIM", call_id, fname,
"no call_chn");
fsm_display_no_free_lines();
cc_call_state(call_id, line, CC_STATE_UNKNOWN, NULL);
return(TRUE);
}
} else if ((event_id == CC_MSG_FEATURE) &&
(fim_check_feature_event(msg))) {
call_chn = fim_get_new_call_chn(call_id);
/*
* Make sure we got a new call chain.
*/
if (call_chn == NULL) {
FIM_DEBUG(get_debug_string(GSM_DBG1), "FIM", call_id, fname,
"no call_chn");
fsm_display_no_free_lines();
cc_call_state(call_id, line, CC_STATE_UNKNOWN, NULL);
return(TRUE);
}
if (fim_feature_need_outgoing_call_context(msg)) {
/* Get new outgoing call context using DEF state m/c fsm */
if (fsm_get_new_outgoing_call_context(call_id, line,
fsm_get_fcb_by_call_id_and_type(call_id, FSM_TYPE_DEF),
FALSE) != CC_CAUSE_OK) {
cc_call_state(call_id, line, CC_STATE_UNKNOWN, NULL);
return(TRUE);
}
}
} else {
FIM_DEBUG(get_debug_string(GSM_DBG1), "FIM", call_id, fname,
"not a call establishment event\n");
if( feat_msg->feature_id == CC_FEATURE_UPD_SESSION_MEDIA_CAP) {
FIM_DEBUG(DEB_L_C_F_PREFIX"set_next_sess_video_pref = %d\n",
DEB_L_C_F_PREFIX_ARGS(FIM, line, call_id, fname),
feat_data->caps.support_direction);
set_next_sess_video_pref(feat_data->caps.support_direction);
}
return(TRUE);
}
if (event_id != CC_MSG_SETUP) {
/*
* Increment the call chain cnt for this line
* For incoming calls, we don't know the line number
* when SETUP msg is received, so it's an exception, it'll be
* added in fsmdef_idle_setup
*/
lsm_increment_call_chn_cnt(line);
}
} /* if (call_chn == NULL) */
/*
* Pass the event to the call chain unless UI lock has been enabled.
*/
if (call_chn->ui_locked && ((cc_feature_t *) msg)->src_id == CC_SRC_UI) {
if (fsm_event_filtered_by_ui_lock(event_id,
((cc_feature_t *)msg)->feature_id)) {
FIM_DEBUG(DEB_L_C_F_PREFIX" %s filtered by UI lock\n",
DEB_L_C_F_PREFIX_ARGS(FIM, line, call_id, fname),
cc_feature_name(((cc_feature_t *)msg)->feature_id));
return(TRUE);
}
}
/*
* Skip the head.
*/
icb = call_chn->next_icb;
PR_ASSERT(icb);
while (icb && !done) {
/*
* Set the required event data so the entity can process the event.
*/
cb_hdr = (fim_cb_hdr_t *) (icb->cb);
PR_ASSERT(cb_hdr);
if (!cb_hdr) {
done = TRUE;
break;
}
event.data = cb_hdr;
event.state = cb_hdr->state;
event.event = event_id;
event.msg = data;
fcb = (fsm_fcb_t *) icb->cb;
/*
* For example, if we receive INVITE and CANCEL messages back to back,
* we may be decrementing the call count of wrong line because SIP stack did
* not get the correct value for line yet.
* so set the line value to correct value from DCB.
* For RIU calls, there will be no DCB. However, line value cames from SIP stack
* correctly.
*/
if ((fcb->fsm_type == FSM_TYPE_DEF) && (event_id == CC_MSG_RELEASE)) {
if (fcb->dcb != NULL) {
line = fcb->dcb->line;
}
}
/*
* This update_call_cnt is only used when RC_CLEANUP is returned.
*/
update_call_cnt = TRUE; // by default, always update call count
if (fcb->dcb != NULL) {
update_call_cnt = (fcb->dcb->call_not_counted_in_mnc_bt) ? FALSE: TRUE;
}
FIM_DEBUG(DEB_L_C_F_PREFIX" %s(%s:%s)\n",
DEB_L_C_F_PREFIX_ARGS(FIM, line, call_id, fname), fsm_type_name(icb->scb->type),
fsm_state_name(fcb->fsm_type, event.state),
cc_msg_name((cc_msgs_t) (event.event)));
rc = sm_process_event(icb->scb->sm, &event);
switch (rc) {
case SM_RC_CONT:
case SM_RC_DEF_CONT:
icb = icb->next_icb;
break;
case SM_RC_END:
done = TRUE;
break;
case SM_RC_ERROR:
FIM_DEBUG(DEB_L_C_F_PREFIX" fsm sm error(%d:%d)\n",
DEB_L_C_F_PREFIX_ARGS(FIM, line, call_id, fname), event.state, event.event);
done = TRUE;
cc_call_state(call_id, line, CC_STATE_UNKNOWN, NULL);
break;
case SM_RC_CLEANUP:
done = TRUE;
cc_call_state(call_id, line, CC_STATE_UNKNOWN, NULL);
break;
default:
done = TRUE;
cc_call_state(call_id, line, CC_STATE_UNKNOWN, NULL);
break;
} /* switch (rc) */
if ((rc == SM_RC_END) && (fcb->fsm_type == FSM_TYPE_DEF) &&
(event_id == CC_MSG_FEATURE))
{
if ( ((cc_feature_t *) msg)->feature_id == CC_FEATURE_CFWD_ALL){
lsm_decrement_call_chn_cnt(line);
}
}
if (icb == NULL) {
done = TRUE;
FIM_DEBUG("icb is null and get here!");
}
} /* while (done != TRUE) { */
if (rc == SM_RC_CLEANUP) {
fim_free_call_chn(call_chn, line, update_call_cnt);
}
return(TRUE);
}
cc_int32_t
fim_show_cmd (cc_int32_t argc, const char *argv[])
{
fim_icb_t *icb = NULL;
fim_scb_t *scb = NULL;
int i = 0;
/*
* Check if need help.
*/
if ((argc == 2) && (argv[1][0] == '?')) {
debugif_printf("%s", "show fim\n");
}
/*
* Print the icbs.
*/
debugif_printf("%s", "\n---------------------------------- FIM icbs -----------------------------------");
debugif_printf("%s", "\ni call_id type icb next_chn next_icb cb scb");
debugif_printf("%s", "\n-------------------------------------------------------------------------------\n");
FSM_FOR_ALL_CBS(icb, fim_icbs, FIM_MAX_ICBS) {
debugif_printf("%-3d %-7d %-6s 0x%8p 0x%8p 0x%8p 0x%8p 0x%8p\n",
i++, icb->call_id, fsm_type_name(icb->scb->type),
icb, icb->next_chn, icb->next_icb, icb->cb, icb->scb);
}
/*
* Print the scbs.
*/
i = 0;
debugif_printf
("%s", "\n------------------------ FIM scbs ------------------------");
debugif_printf("%s", "\ni type scb sm get_cb free_cb");
debugif_printf
("%s", "\n----------------------------------------------------------\n");
FSM_FOR_ALL_CBS(scb, fim_scbs, FIM_MAX_SCBS) {
debugif_printf("%-2d %-6s 0x%8p 0x%8p 0x%8p 0x%8p\n",
i++, fsm_type_name(scb->type), scb,
scb->sm, scb->get_cb, scb->free_cb);
}
return (0);
}
void
fim_init (void)
{
fim_init_call_chns();
}
void
fim_shutdown (void)
{
cpr_free(fim_scbs);
cpr_free(fim_icbs);
fim_scbs = NULL;
fim_icbs = NULL;
}