/* 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 "cpr_types.h" #include "cpr_stdlib.h" #include "cpr_string.h" #include "cpr_stdio.h" #include "fsm.h" #include "fim.h" #include "lsm.h" #include "sm.h" #include "ccapi.h" #include "phone_debug.h" #include "text_strings.h" #include "config.h" #include "debug.h" #include "gsm_sdp.h" #include "regmgrapi.h" #include "platform_api.h" static fsmcnf_ccb_t *fsmcnf_ccbs; static int softkey_mask_list[MAX_SOFT_KEYS]; #define FSMCNF_CONNECTED_SET "CONNECTED" typedef enum { FSMCNF_S_MIN = -1, FSMCNF_S_IDLE, FSMCNF_S_CNFING, FSMCNF_S_CNFED, FSMCNF_S_MAX } fsmcnf_states_t; static const char *fsmcnf_state_names[] = { "IDLE", "CNFING", "CNFED" }; static sm_rcs_t fsmcnf_ev_idle_setup(sm_event_t *event); static sm_rcs_t fsmcnf_ev_idle_feature(sm_event_t *event); static sm_rcs_t fsmcnf_ev_cnfing_release(sm_event_t *event); static sm_rcs_t fsmcnf_ev_cnfing_feature(sm_event_t *event); static sm_rcs_t fsmcnf_ev_cnfing_onhook(sm_event_t *event); static sm_rcs_t fsmcnf_ev_cnfed_release(sm_event_t *event); static sm_rcs_t fsmcnf_ev_cnfed_feature(sm_event_t *event); static sm_rcs_t fsmcnf_ev_cnfed_feature_ack(sm_event_t *event); static sm_rcs_t fsmcnf_ev_cnfed_onhook(sm_event_t *event); static sm_function_t fsmcnf_function_table[FSMCNF_S_MAX][CC_MSG_MAX] = { /* FSMCNF_S_IDLE ------------------------------------------------------------ */ { /* FSMCNF_E_SETUP */ fsmcnf_ev_idle_setup, /* FSMCNF_E_SETUP_ACK */ NULL, /* FSMCNF_E_PROCEEDING */ NULL, /* FSMCNF_E_ALERTING */ NULL, /* FSMCNF_E_CONNECTED */ NULL, /* FSMCNF_E_CONNECTED_ACK */ NULL, /* FSMCNF_E_RELEASE */ NULL, /* FSMCNF_E_RELEASE_COMPLETE */ NULL, /* FSMCNF_E_FEATURE */ fsmcnf_ev_idle_feature, /* FSMCNF_E_FEATURE_ACK */ NULL, /* FSMCNF_E_OFFHOOK */ NULL, /* FSMCNF_E_ONHOOK */ NULL, /* FSMCNF_E_LINE */ NULL, /* FSMCNF_E_DIGIT_BEGIN */ NULL, /* FSMCNF_E_DIGIT */ NULL, /* FSMCNF_E_DIALSTRING */ NULL, /* FSMCNF_E_MWI */ NULL, /* FSMCNF_E_SESSION_AUDIT */ NULL }, /* FSMCNF_S_CNFING --------------------------------------------------- */ { /* FSMCNF_E_SETUP */ NULL, /* FSMCNF_E_SETUP_ACK */ NULL, /* FSMCNF_E_PROCEEDING */ NULL, /* FSMCNF_E_ALERTING */ NULL, /* FSMCNF_E_CONNECTED */ NULL, /* FSMCNF_E_CONNECTED_ACK */ NULL, /* FSMCNF_E_RELEASE */ fsmcnf_ev_cnfing_release, /* FSMCNF_E_RELEASE_COMPLETE */ NULL, /* FSMCNF_E_FEATURE */ fsmcnf_ev_cnfing_feature, /* FSMCNF_E_FEATURE_ACK */ NULL, /* FSMCNF_E_OFFHOOK */ NULL, /* FSMCNF_E_ONHOOK */ fsmcnf_ev_cnfing_onhook, /* FSMCNF_E_LINE */ NULL, /* FSMCNF_E_DIGIT_BEGIN */ NULL, /* FSMCNF_E_DIGIT */ NULL, /* FSMCNF_E_DIALSTRING */ NULL, /* FSMCNF_E_MWI */ NULL, /* FSMCNF_E_SESSION_AUDIT */ NULL }, /* FSMCNF_S_CNFED ---------------------------------------------------- */ { /* FSMCNF_E_SETUP */ NULL, /* FSMCNF_E_SETUP_ACK */ NULL, /* FSMCNF_E_PROCEEDING */ NULL, /* FSMCNF_E_ALERTING */ NULL, /* FSMCNF_E_CONNECTED */ NULL, /* FSMCNF_E_CONNECTED_ACK */ NULL, /* FSMCNF_E_RELEASE */ fsmcnf_ev_cnfed_release, /* FSMCNF_E_RELEASE_COMPLETE */ NULL, /* FSMCNF_E_FEATURE */ fsmcnf_ev_cnfed_feature, /* FSMCNF_E_FEATURE_ACK */ fsmcnf_ev_cnfed_feature_ack, /* FSMCNF_E_OFFHOOK */ NULL, /* FSMCNF_E_ONHOOK */ fsmcnf_ev_cnfed_onhook, /* FSMCNF_E_LINE */ NULL, /* FSMCNF_E_DIGIT_BEGIN */ NULL, /* FSMCNF_E_DIGIT */ NULL, /* FSMCNF_E_DIALSTRING */ NULL, /* FSMCNF_E_MWI */ NULL, /* FSMCNF_E_SESSION_AUDIT */ NULL } }; static sm_table_t fsmcnf_sm_table; sm_table_t *pfsmcnf_sm_table = &fsmcnf_sm_table; const char * fsmcnf_state_name (int state) { if ((state <= FSMCNF_S_MIN) || (state >= FSMCNF_S_MAX)) { return (get_debug_string(GSM_UNDEFINED)); } return (fsmcnf_state_names[state]); } static int fsmcnf_get_new_cnf_id (void) { static int cnf_id = 0; if (++cnf_id < 0) { cnf_id = 1; } return (cnf_id); } static void fsmcnf_init_ccb (fsmcnf_ccb_t *ccb) { if (ccb != NULL) { ccb->cnf_id = FSM_NO_ID; ccb->cnf_call_id = CC_NO_CALL_ID; ccb->cns_call_id = CC_NO_CALL_ID; ccb->cnf_line = CC_NO_LINE; ccb->cns_line = CC_NO_LINE; ccb->bridged = FALSE; ccb->active = FALSE; ccb->flags = 0; ccb->cnf_ftr_ack = FALSE; } } static fsmcnf_ccb_t * fsmcnf_get_ccb_by_cnf_id (int cnf_id) { fsmcnf_ccb_t *ccb; fsmcnf_ccb_t *ccb_found = NULL; FSM_FOR_ALL_CBS(ccb, fsmcnf_ccbs, FSMCNF_MAX_CCBS) { if (ccb->cnf_id == cnf_id) { ccb_found = ccb; break; } } return (ccb_found); } /* * Function: fsmcnf_get_new_cnf_context * * Parameters: * cnf_call_id: call_id for the call initiating the conference * * Description: This function creates a new conference context by: * - getting a free ccb * - creating new cnf_id and cns_call_id * * Returns: ccb * */ static fsmcnf_ccb_t * fsmcnf_get_new_cnf_context (callid_t cnf_call_id) { static const char fname[] = "fsmcnf_get_new_cnf_context"; fsmcnf_ccb_t *ccb; ccb = fsmcnf_get_ccb_by_cnf_id(FSM_NO_ID); if (ccb != NULL) { ccb->cnf_id = fsmcnf_get_new_cnf_id(); ccb->cnf_call_id = cnf_call_id; ccb->cns_call_id = cc_get_new_call_id(); FSM_DEBUG_SM(get_debug_string(FSMCNF_DBG_PTR), ccb->cnf_id, ccb->cnf_call_id, ccb->cns_call_id, fname, ccb); } else { GSM_DEBUG_ERROR(GSM_F_PREFIX"Failed to get new ccb.\n", fname); } return (ccb); } fsmcnf_ccb_t * fsmcnf_get_ccb_by_call_id (callid_t call_id) { fsmcnf_ccb_t *ccb; fsmcnf_ccb_t *ccb_found = NULL; FSM_FOR_ALL_CBS(ccb, fsmcnf_ccbs, FSMCNF_MAX_CCBS) { if ((ccb->cnf_call_id == call_id) || (ccb->cns_call_id == call_id)) { ccb_found = ccb; break; } } return (ccb_found); } static void fsmcnf_update_cnf_context (fsmcnf_ccb_t *ccb, callid_t old_call_id, callid_t new_call_id) { static const char fname[] = "fsmcnf_update_cnf_context"; if (ccb != NULL) { if (old_call_id == ccb->cnf_call_id) { ccb->cnf_call_id = new_call_id; } else if (old_call_id == ccb->cns_call_id) { ccb->cns_call_id = new_call_id; } FSM_DEBUG_SM(get_debug_string(FSMCNF_DBG_PTR), ccb->cnf_id, ccb->cnf_call_id, ccb->cns_call_id, fname, ccb); } } callid_t fsmcnf_get_other_call_id (fsmcnf_ccb_t *ccb, callid_t call_id) { callid_t other_call_id = CC_NO_CALL_ID; if (ccb != NULL) { if (ccb->cnf_call_id == call_id) { other_call_id = ccb->cns_call_id; } else if (ccb->cns_call_id == call_id) { other_call_id = ccb->cnf_call_id; } } return (other_call_id); } static void fsmcnf_cnf_xfer (fsmcnf_ccb_t *ccb) { fsmdef_dcb_t *dcb; cc_feature_data_t ftr_data; dcb = fsm_get_dcb(ccb->cnf_call_id); /* * Pretending attended transfer */ ftr_data.xfer.cause = CC_CAUSE_XFER_CNF; ftr_data.xfer.target_call_id = ccb->cns_call_id; cc_int_feature(CC_SRC_UI, CC_SRC_GSM, dcb->call_id, dcb->line, CC_FEATURE_XFER, &(ftr_data)); } /* * Function: fsmcnf_remove_fcb * * Parameters: * cnf_id: cnf_id for the conference * call_id: call_id that identifies the fcb to be removed * * Description: This function will remove the fcb identified by the given * call_id from the ccb. And the function will free the ccb * if both fcbs have been removed. * * Returns: none * * Note: This is a helper function for fsmcnf_cleanup. It allows fsmcnf_cleanup * to cleanup one fcb (one call involved in the conference) independent * of the other involved fcb. */ static void fsmcnf_remove_fcb (fsm_fcb_t *fcb, callid_t call_id) { fsmcnf_ccb_t *ccb = fcb->ccb; if (ccb != NULL) { fsmcnf_update_cnf_context(ccb, call_id, CC_NO_CALL_ID); /* * Free the ccb if both fcb references have been removed. */ if ((ccb->cnf_call_id == CC_NO_CALL_ID) && (ccb->cns_call_id == CC_NO_CALL_ID)) { fsmcnf_init_ccb(ccb); } } } static void fsmcnf_cleanup (fsm_fcb_t *fcb, int fname, boolean both) { fsmcnf_ccb_t *ccb; fsm_fcb_t *other_fcb, *fcb_def; callid_t call_id = fcb->call_id; callid_t other_call_id = CC_NO_CALL_ID; /* stop the channel mixing if we are currently doing it */ ccb = fsmcnf_get_ccb_by_call_id(call_id); other_call_id = fsmcnf_get_other_call_id(fcb->ccb, call_id); /* Set session to be primary */ fcb_def = fsm_get_fcb_by_call_id_and_type(call_id, FSM_TYPE_DEF); if (fcb->ccb && (call_id == fcb->ccb->cnf_call_id)) { if (other_call_id != CC_NO_CALL_ID) { /* * Not clearing consulation call, so change consultation * call attribute to display connected softkey set. * Do not change softkey set if it is a transfer o */ if (ccb == NULL) { GSM_DEBUG_ERROR(GSM_F_PREFIX"Failed to get CCB.\n", fname); } else { cc_call_attribute(other_call_id, ccb->cnf_line, NORMAL_CALL); } } } if (fcb_def && fcb_def->dcb) { fcb_def->dcb->session = PRIMARY; } /* * Check if the user wanted to cleanup the whole ccb. * If so, then we will grab the other fcb first and call this function * again with this other fcb. The whole ccb will be freed after this block * of code because both call_ids will be -1, which tells * fsmcnf_remove_fcb to free the ccb. */ if (both) { other_call_id = fsmcnf_get_other_call_id(fcb->ccb, call_id); if (other_call_id != CC_NO_CALL_ID) { other_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_CNF); if (other_fcb != NULL) { fsmcnf_cleanup(other_fcb, fname, FALSE); } } } /* * Remove the reference to this fcb from the ccb. */ fsmcnf_remove_fcb(fcb, fcb->call_id); /* * Move this fcb to the IDLE state */ fsm_change_state(fcb, fname, FSMCNF_S_IDLE); /* * Reset the data for this fcb. The fcb is still included in a call * so set the call_id and dcb values accordingly. */ fsm_init_fcb(fcb, fcb->call_id, fcb->dcb, FSM_TYPE_CNF); } void fsmcnf_free_cb (fim_icb_t *icb, callid_t call_id) { fsm_fcb_t *fcb = NULL; if (call_id != CC_NO_CALL_ID) { fcb = fsm_get_fcb_by_call_id_and_type(call_id, FSM_TYPE_CNF); if (fcb != NULL) { fsmcnf_cleanup(fcb, __LINE__, FALSE); fsm_init_fcb(fcb, CC_NO_CALL_ID, FSMDEF_NO_DCB, FSM_TYPE_NONE); } } } /** * * Cancel conference feature by sending cancel event to SIP stack. * This routine is used in roundtable phone. * * Copied and pasted from fsmb2bcnf_feature_cancel(). * See also fsmxfr_feature_cancel(). * * @param line, call_id, target_call_id, cause (implicit or explicit) * * @return void * * @pre (none) */ void fsmcnf_feature_cancel (fsmcnf_ccb_t *ccb, line_t line, callid_t call_id, callid_t target_call_id) { cc_feature_data_t data; fsm_fcb_t *fcb_def; fcb_def = fsm_get_fcb_by_call_id_and_type(call_id, FSM_TYPE_DEF); // 'cause' will always be CC_SK_EVT_TYPE_EXPLI for now since it's only // called by fsmcnf_ev_cnfing_feature in the case of CC_FEAURE_CANCEL. // Thus 'cause' is ignored (for now) until the whole SM is refactored. if (/*(cause == CC_SK_EVT_TYPE_EXPLI) && */ (fcb_def != NULL) && ((fcb_def->dcb->selected == FALSE) && ((fcb_def->state == FSMDEF_S_OUTGOING_ALERTING) || ((fcb_def->state == FSMDEF_S_CONNECTED) && (fcb_def->dcb->spoof_ringout_requested == TRUE) && (fcb_def->dcb->spoof_ringout_applied == TRUE))))) { cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, call_id, line, CC_FEATURE_END_CALL, NULL); } fcb_def = fsm_get_fcb_by_call_id_and_type(target_call_id, FSM_TYPE_DEF); if (/* (cause == CC_SK_EVT_TYPE_EXPLI) && */ (fcb_def != NULL) && ((fcb_def->dcb->selected == FALSE) && ((fcb_def->state == FSMDEF_S_OUTGOING_ALERTING) || ((fcb_def->state == FSMDEF_S_CONNECTED) && (fcb_def->dcb->spoof_ringout_requested == TRUE) && (fcb_def->dcb->spoof_ringout_applied == TRUE))))) { cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, target_call_id, line, CC_FEATURE_END_CALL, NULL); } data.cancel.target_call_id = target_call_id; data.cancel.call_id = call_id; data.cancel.cause = CC_SK_EVT_TYPE_EXPLI; cc_int_feature(CC_SRC_GSM, CC_SRC_SIP, call_id, line, CC_FEATURE_CANCEL, &data); } /******************************************************************* * event functions */ static sm_rcs_t fsmcnf_ev_idle_setup (sm_event_t *event) { fsm_fcb_t *fcb = (fsm_fcb_t *) event->data; cc_setup_t *msg = (cc_setup_t *) event->msg; callid_t call_id = msg->call_id; fsmcnf_ccb_t *ccb; if (!msg->replaces) { return (SM_RC_DEF_CONT); } /* * Check to see if this new setup call is a new call that replaces * one of the conferenced call i.e. the this new call replacing the * existing leg of a conferenced by XFER feature from SIP. The * call id of this setup should match call id of a conference. */ ccb = fsmcnf_get_ccb_by_call_id(call_id); if (ccb == NULL) { return (SM_RC_DEF_CONT); } /* This new call is part of a conference */ fcb->ccb = ccb; /* attach ccb to the new call chain */ fsm_change_state(fcb, __LINE__, FSMCNF_S_CNFING); return (SM_RC_CONT); } static sm_rcs_t fsmcnf_ev_idle_feature (sm_event_t *event) { static const char *fname = "fsmcnf_ev_idle_feature"; fsm_fcb_t *fcb = (fsm_fcb_t *) event->data; cc_feature_t *msg = (cc_feature_t *) event->msg; callid_t call_id = msg->call_id; line_t line = msg->line; cc_srcs_t src_id = msg->src_id; cc_features_t ftr_id = msg->feature_id; fsmdef_dcb_t *dcb = fcb->dcb; callid_t cns_call_id; sm_rcs_t sm_rc = SM_RC_CONT; fsmcnf_ccb_t *ccb; int free_lines; cc_feature_data_t data; cc_action_data_t action_data; fsmdef_dcb_t *other_dcb; fsm_fcb_t *other_fcb; fsm_fcb_t *fcb_def, *join_fcb_cnf; cc_feature_data_t ftr_data = msg->data; cc_feature_data_t *feat_data = &(msg->data); fsm_fcb_t *cns_fcb; callid_t other_call_id; fsmxfr_xcb_t *xcb; cc_causes_t cause; memset(&data, 0, sizeof(cc_feature_data_t)); fsm_sm_ftr(ftr_id, src_id); switch (src_id) { case CC_SRC_UI: switch (ftr_id) { case CC_FEATURE_CONF: /* Connect the existing call to active conference state * machine. If the UI generates the event with target * call_id in the data then terminate the existing consulatative * call and link that to another call. */ if (feat_data && msg->data_valid && (feat_data->cnf.target_call_id != CC_NO_CALL_ID) && (cns_fcb = fsm_get_fcb_by_call_id_and_type(feat_data->cnf.target_call_id, FSM_TYPE_CNF)) != NULL) { /* * Get a new ccb and new b2bcnf id - This is the handle that will * identify the b2bcnf. */ ccb = fsmcnf_get_new_cnf_context(feat_data->cnf.target_call_id); if (ccb == NULL || ccb->cnf_id == FSM_NO_ID) { return(SM_RC_END); } ccb->cns_call_id = call_id; fcb->ccb = ccb; cns_fcb->ccb = ccb; ccb->cnf_line = line; ccb->cns_line = line; fsm_change_state(fcb, __LINE__, FSMCNF_S_CNFING); fsm_change_state(cns_fcb, __LINE__, FSMCNF_S_CNFING); cc_int_feature(CC_SRC_UI, CC_SRC_GSM, ccb->cns_call_id, cns_fcb->dcb->line, CC_FEATURE_CONF, NULL); return(SM_RC_END); } /* * This call is the conference and we are initiating a local * conference. So: * 1. Make sure we have a free line to open a new call plane to * collect digits (and place call) for the consultation call, * 2. Create a new conference context, * 3. Place this call on hold, * 4. Send a newcall feature back to the GSM so that the * consultation call can be initiated. */ /* * Check for any other active features which may block * the conference. */ /* * The call must be in the connected state to initiate a conference. */ fcb_def = fsm_get_fcb_by_call_id_and_type(call_id, FSM_TYPE_DEF); if ((fcb_def != NULL) && (fcb_def->state != FSMDEF_S_CONNECTED)) { break; } /* * Make sure we have a free line to start the consultation call. */ //CSCsz38962 don't use expline for local conf call //free_lines = lsm_get_instances_available_cnt(line, TRUE); free_lines = lsm_get_instances_available_cnt(line, FALSE); if (free_lines <= 0) { /* * No free lines - let the user know and end this request. */ fsm_display_no_free_lines(); break; } /* * Get a new ccb and new cnf id - This is the handle that will * identify the cnf. */ ccb = fsmcnf_get_new_cnf_context(call_id); if (ccb == NULL || ccb->cnf_id == 0) { break; } fcb->ccb = ccb; ccb->cnf_line = line; ccb->cns_line = line; /* * This call needs to go on hold so we can start the consultation * call. Indicate feature indication should be send by setting * call info type to hold and feature reason to conference. */ data.hold.call_info.type = CC_FEAT_HOLD; data.hold.call_info.data.hold_resume_reason = CC_REASON_CONF; data.hold.msg_body.num_parts = 0; cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, dcb->call_id, dcb->line, CC_FEATURE_HOLD, &data); /* * Initiate the consultation call. */ data.newcall.cause = CC_CAUSE_CONF; cns_call_id = ccb->cns_call_id; cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, cns_call_id, line, CC_FEATURE_NEW_CALL, &data); FSM_DEBUG_SM(get_debug_string(FSMCNF_DBG_CNF_INITIATED), ccb->cnf_id, call_id, cns_call_id, __LINE__); fsm_change_state(fcb, __LINE__, FSMCNF_S_CNFING); sm_rc = SM_RC_END; break; case CC_FEATURE_JOIN: /* * The call must be in the connected state to initiate a conference */ fcb_def = fsm_get_fcb_by_call_id_and_type(call_id, FSM_TYPE_DEF); if ((fcb_def != NULL) && (fcb_def->state != FSMDEF_S_CONNECTED)) { break; } /* * Make sure that we have another active call on this line. */ other_dcb = fsmdef_get_other_dcb_by_line(call_id, dcb->line); if (other_dcb == NULL) { break; } /* get other calls FCB */ other_fcb = fsm_get_fcb_by_call_id_and_type(other_dcb->call_id, FSM_TYPE_DEF); if (other_fcb == NULL) { break; } if (other_fcb->state == FSMDEF_S_HOLDING) { /* * Get a new ccb and new cnf id - This is the handle that will * identify the cnf. */ ccb = fsmcnf_get_new_cnf_context(call_id); if (ccb == NULL) { break; } fcb->ccb = ccb; fsm_change_state(fcb, __LINE__, FSMCNF_S_CNFED); other_fcb = fsm_get_fcb_by_call_id_and_type(other_dcb->call_id, FSM_TYPE_CNF); if (other_fcb == NULL) { fsmcnf_cleanup(fcb, __LINE__, TRUE); break; } other_fcb->ccb = ccb; fsm_change_state(other_fcb, __LINE__, FSMCNF_S_CNFED); ccb->cnf_call_id = dcb->call_id; ccb->cns_call_id = other_dcb->call_id; ccb->bridged = TRUE; /* Build SDP for sending out */ cause = gsmsdp_encode_sdp_and_update_version(other_dcb, &data.resume.msg_body); if (cause != CC_CAUSE_OK) { FSM_DEBUG_SM(get_debug_string(FSM_DBG_SDP_BUILD_ERR)); sm_rc = SM_RC_END; break; } data.resume.cause = CC_CAUSE_CONF; cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, other_dcb->call_id, other_dcb->line, CC_FEATURE_RESUME, &data); /* * Update the UI for this call. */ action_data.update_ui.action = CC_UPDATE_CONF_ACTIVE; (void)cc_call_action(other_dcb->call_id, other_dcb->line, CC_ACTION_UPDATE_UI, &action_data); } else { fsm_display_feature_unavailable(); } break; default: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); sm_rc = SM_RC_DEF_CONT; break; } /* switch (ftr_id) */ break; case CC_SRC_GSM: switch (ftr_id) { case CC_FEATURE_NEW_CALL: /* * If this is the consultation call involved in a conference, * then set the rest of the data required to make the conference * happen. The data is the cnf_id in the fcb. The data is set now * because we did not have the fcb when the conference was * initiated. The fcb is created when a new_call event is * received by the FIM, not when a conference event is received. * * Or this could be the call that originated the conference and * the person he was talking to (the conference target) has * decided to conference the trasnferor to another party. */ /* * Ignore this event if this call is not involved in a conference. */ ccb = fsmcnf_get_ccb_by_call_id(call_id); if (ccb == NULL) { break; } fcb->ccb = ccb; /* * Determine what state this cnf should be in (cnfing or cnfed). * If the cnfrn key has only been hit once, then this call will * be in the cnfing state. If it has been hit the second time, * then this call should go to the cnfed state. The latter * case only happens when the calls are conferenced and one * of the remote ends has decided to transfer one leg of the cnf. * And it just so happens that the other call involved in the cnf * should match this call, so we can just use it's state to * assign the state to this call. */ other_call_id = fsmcnf_get_other_call_id(ccb, call_id); other_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_CNF); if(other_fcb == NULL) { GSM_DEBUG_ERROR(GSM_F_PREFIX"Failed to get FCB.\n", fname); } else { fsm_change_state(fcb, __LINE__, other_fcb->state); } break; case CC_FEATURE_JOIN: if (fsm_is_joining_call(ftr_data)) { /* * The join target call must be in the * connected state to initiate a conference. */ fcb_def = fsm_get_fcb_by_call_id_and_type(call_id, FSM_TYPE_DEF); if ((fcb_def == NULL) || ((fcb_def->state != FSMDEF_S_CONNECTED) && (fcb_def->state != FSMDEF_S_RESUME_PENDING))) { FSM_DEBUG_SM(DEB_L_C_F_PREFIX"Join target \ call is not at connected state\n", DEB_L_C_F_PREFIX_ARGS(FSM, line, call_id, fname)); break; } /* Get the joining call fcb */ join_fcb_cnf = fsm_get_fcb_by_call_id_and_type( ftr_data.newcall.join.join_call_id, FSM_TYPE_CNF); /* Create a conference context for the join target call */ ccb = fsmcnf_get_new_cnf_context(call_id); if (ccb == NULL || join_fcb_cnf == NULL) { FSM_DEBUG_SM(DEB_L_C_F_PREFIX"Could not \ find the conference context\n", DEB_L_C_F_PREFIX_ARGS(FSM, line, call_id, fname)); break; } fcb->ccb = ccb; join_fcb_cnf->ccb = ccb; ccb->cnf_call_id = fcb_def->dcb->call_id; /* Joining call is the consultative call of the conference */ ccb->cns_call_id = join_fcb_cnf->dcb->call_id; join_fcb_cnf->dcb->group_id = fcb_def->dcb->group_id; fsm_change_state(fcb, __LINE__, FSMCNF_S_CNFED); fsm_change_state(join_fcb_cnf, __LINE__, FSMCNF_S_CNFED); softkey_mask_list[0] = skConfrn; ui_select_feature_key_set(line, call_id, FSMCNF_CONNECTED_SET, softkey_mask_list, 1); ccb->flags |= JOINED; ccb->bridged = FALSE; } break; default: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); sm_rc = SM_RC_DEF_CONT; break; } /* switch (ftr_id) */ break; case CC_SRC_SIP: switch (ftr_id) { case CC_FEATURE_NOTIFY: if ((msg->data_valid == TRUE) && (msg->data.notify.subscription == CC_SUBSCRIPTIONS_XFER) && (msg->data.notify.method == CC_XFER_METHOD_REFER)) { xcb = fsmxfr_get_xcb_by_call_id(call_id); if ((xcb != NULL) && (call_id == xcb->cns_call_id)) { /* * One leg of the conference has decided to transfer us. * We need to * 1. remove the transferred call from the conference * context and associate the new call * with the context, * 2. cleanup this transferred fcb. */ ccb = fsmcnf_get_ccb_by_call_id(xcb->xfr_call_id); if (ccb == NULL) { break; } other_fcb = fsm_get_fcb_by_call_id_and_type(xcb->xfr_call_id, FSM_TYPE_CNF); if (other_fcb == NULL) { break; } fcb->ccb = ccb; fsmcnf_update_cnf_context(ccb, xcb->xfr_call_id, xcb->cns_call_id); fsmcnf_cleanup(other_fcb, __LINE__, FALSE); other_call_id = fsmcnf_get_other_call_id(ccb, call_id); other_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_CNF); fsm_change_state(fcb, __LINE__, other_fcb->state); if (other_fcb->state == FSMCNF_S_CNFED) { ccb->bridged = TRUE; /* * Resume the other leg of the conference that was * not transferred. */ other_dcb = fsm_get_dcb(other_call_id); /* Build SDP for sending out */ cause = gsmsdp_encode_sdp_and_update_version(other_dcb, &data.resume.msg_body); if (cause != CC_CAUSE_OK) { GSM_DEBUG_ERROR(get_debug_string(FSM_DBG_SDP_BUILD_ERR)); fsmcnf_cleanup(fcb, __LINE__, TRUE); sm_rc = SM_RC_END; break; } data.resume.cause = CC_CAUSE_CONF; cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, other_dcb->call_id, other_dcb->line, CC_FEATURE_RESUME, &data); } /* * Update the UI for the just transferred leg. */ action_data.update_ui.action = CC_UPDATE_CONF_ACTIVE; (void)cc_call_action(fcb->call_id, dcb->line, CC_ACTION_UPDATE_UI, &action_data); } } break; default: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); sm_rc = SM_RC_DEF_CONT; break; } /* switch (ftr_id) */ break; default: fsm_sm_ignore_src(fcb, __LINE__, src_id); sm_rc = SM_RC_DEF_CONT; break; } /* switch (src_id) */ return (sm_rc); } static void fsmcnf_update_release (sm_event_t *event) { fsm_fcb_t *fcb = (fsm_fcb_t *) event->data; callid_t other_call_id; cc_action_data_t action_data; fsm_fcb_t *other_fcb; /* * Update the UI for the other call. */ other_call_id = fsmcnf_get_other_call_id(fcb->ccb, fcb->call_id); if (other_call_id != CC_NO_CALL_ID) { action_data.update_ui.action = CC_UPDATE_CONF_RELEASE; (void)cc_call_action(other_call_id, fcb->dcb->line, CC_ACTION_UPDATE_UI, &action_data); /* Far end released its original call. Set the attribute of * other call so it can display connected softkey set. * * Check only for the consultation leg since only that leg * will need its attribute reset - the other leg does not * have an atribute set. */ if (fcb->ccb && (fcb->call_id == fcb->ccb->cnf_call_id)) { other_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_CNF); if (other_fcb != NULL) { fsm_fcb_t *b2bcnf_fcb, *xfr_fcb; b2bcnf_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_B2BCNF); xfr_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_XFR); if ((b2bcnf_fcb != NULL && b2bcnf_fcb->b2bccb == NULL) && (xfr_fcb != NULL && xfr_fcb->xcb == NULL)) { cc_call_attribute(other_call_id, other_fcb->dcb->line, NORMAL_CALL); } } } } fsmcnf_cleanup(fcb, __LINE__, TRUE); } static sm_rcs_t fsmcnf_ev_cnfing_release (sm_event_t *event) { fsm_fcb_t *fcb = (fsm_fcb_t *) event->data; cc_release_t *msg = (cc_release_t *) event->msg; callid_t call_id = msg->call_id; fsmcnf_ccb_t *ccb = fcb->ccb; fsmxfr_xcb_t *xcb; fsm_fcb_t *other_fcb; xcb = fsmxfr_get_xcb_by_call_id(call_id); if (xcb != NULL) { /* * One leg of the conference has decided to transfer us. * We need to * 1. remove this call from the conference context and * associate the new (transferred) call with the context, * 2. cleanup this fcb. */ fsmcnf_update_cnf_context(ccb, call_id, xcb->cns_call_id); fsmcnf_cleanup(fcb, __LINE__, FALSE); other_fcb = fsm_get_fcb_by_call_id_and_type(xcb->cns_call_id, FSM_TYPE_CNF); if (other_fcb != NULL) { other_fcb->ccb = ccb; fsm_change_state(other_fcb, __LINE__, FSMCNF_S_CNFING); } return (SM_RC_CONT); } fsmcnf_update_release(event); return (SM_RC_CONT); } static sm_rcs_t fsmcnf_ev_cnfing_feature (sm_event_t *event) { fsm_fcb_t *fcb = (fsm_fcb_t *) event->data; cc_feature_t *msg = (cc_feature_t *) event->msg; callid_t call_id = msg->call_id; fsmdef_dcb_t *dcb = fcb->dcb; fsmcnf_ccb_t *ccb = fcb->ccb; cc_srcs_t src_id = msg->src_id; cc_features_t ftr_id = msg->feature_id; static const char fname[] = "fsmcnf_ev_cnfing_feature"; cc_feature_data_t *feat_data = &(msg->data); sm_rcs_t sm_rc = SM_RC_CONT; callid_t other_call_id; fsmdef_dcb_t *other_dcb; fsm_fcb_t *other_fcb; cc_action_data_t action_data; cc_feature_data_t data; cc_causes_t cause; fsm_sm_ftr(ftr_id, src_id); switch (src_id) { case CC_SRC_UI: switch (ftr_id) { case CC_FEATURE_CANCEL: sm_rc = SM_RC_END; fsmcnf_feature_cancel(ccb, ccb->cnf_line, ccb->cnf_call_id, ccb->cns_call_id /*, CC_SK_EVT_TYPE_EXPLI */); fsmcnf_cleanup(fcb, __LINE__, TRUE); break; case CC_FEATURE_ANSWER: other_call_id = fsmcnf_get_other_call_id(ccb, call_id); if (other_call_id == CC_NO_CALL_ID) { fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); return (sm_rc); } other_dcb = fsm_get_dcb(other_call_id); if (other_dcb == NULL) { fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); return (sm_rc); } /* * The answer to the a setup with replaced we have received. * The setup with replaced is automatically answered * by xfer state machine as UI source (simulate user pressing * the answer key). * * Set the group ID to the new call so that the voice can be * mixed with the new call. */ dcb->group_id = other_dcb->group_id; fsm_change_state(fcb, __LINE__, FSMCNF_S_CNFED); return (sm_rc); default: break; } /* Fall through */ case CC_SRC_GSM: switch (ftr_id) { case CC_FEATURE_CONF: /* * This is the second conference event for a local * attended conference with consultation. * * The user is attempting to complete the conference, so * resume the other leg of the conference call. */ ccb->bridged = TRUE; ccb->active = TRUE; ccb->flags |= LCL_CNF; other_call_id = fsmcnf_get_other_call_id(ccb, call_id); other_dcb = fsm_get_dcb(other_call_id); /* * Since we are resuming the other leg, allocate the src_sdp * because src_sdp was freed and set to null when this other-leg * was put on hold. */ gsmsdp_update_local_sdp_media_capability(other_dcb, TRUE, FALSE); /* Build SDP for sending out */ cause = gsmsdp_encode_sdp_and_update_version(other_dcb, &data.resume.msg_body); if (cause != CC_CAUSE_OK) { GSM_DEBUG_ERROR(get_debug_string(FSM_DBG_SDP_BUILD_ERR)); sm_rc = SM_RC_END; break; } data.resume.cause = CC_CAUSE_CONF; other_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_DEF); if (other_fcb && other_fcb->state == FSMDEF_S_HOLDING) { other_dcb->session = LOCAL_CONF; cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, other_dcb->call_id, other_dcb->line, CC_FEATURE_RESUME, &data); } else { /* Other call must be on hold */ dcb->session = LOCAL_CONF; cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, call_id, ccb->cnf_line, CC_FEATURE_RESUME, &data); } /* * Update the UI for this call. */ action_data.update_ui.action = CC_UPDATE_CONF_ACTIVE; (void)cc_call_action(dcb->call_id, dcb->line, CC_ACTION_UPDATE_UI, &action_data); other_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_CNF); fsm_change_state(other_fcb, __LINE__, FSMCNF_S_CNFED); fsm_change_state(fcb, __LINE__, FSMCNF_S_CNFED); sm_rc = SM_RC_END; break; case CC_FEATURE_END_CALL: fsmcnf_update_release(event); break; case CC_FEATURE_HOLD: if ((msg->data_valid) && (feat_data->hold.call_info.data.hold_resume_reason != CC_REASON_SWAP && feat_data->hold.call_info.data.hold_resume_reason != CC_REASON_CONF && feat_data->hold.call_info.data.hold_resume_reason != CC_REASON_INTERNAL)) { sm_rc = SM_RC_END; DEF_DEBUG(DEB_F_PREFIX"Invoke hold call_id = %d t_call_id=%d\n", DEB_F_PREFIX_ARGS(GSM, fname), ccb->cnf_call_id, ccb->cns_call_id); //Actual hold to this call, so break the feature layer. ui_terminate_feature(dcb->line, ccb->cnf_call_id, ccb->cns_call_id); fsmcnf_feature_cancel(ccb, ccb->cnf_line, ccb->cnf_call_id, ccb->cns_call_id /*, CC_SK_EVT_TYPE_EXPLI */); fsmcnf_cleanup(fcb, __LINE__, TRUE); } break; default: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; } /* switch (ftr_id) */ break; case CC_SRC_SIP: switch (ftr_id) { default: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; } /* switch (ftr_id) */ break; default: fsm_sm_ignore_src(fcb, __LINE__, src_id); break; } /* switch (src_id) */ return (sm_rc); } static sm_rcs_t fsmcnf_ev_cnfing_onhook (sm_event_t *event) { fsmcnf_update_release(event); return (SM_RC_CONT); } static sm_rcs_t fsmcnf_ev_cnfed_release (sm_event_t *event) { fsm_fcb_t *fcb = (fsm_fcb_t *) event->data; cc_release_t *msg = (cc_release_t *) event->msg; callid_t call_id = msg->call_id; fsmdef_dcb_t *dcb = fcb->dcb; fsmcnf_ccb_t *ccb = fcb->ccb; callid_t other_call_id; fsmdef_dcb_t *other_dcb; cc_feature_data_t data; cc_action_data_t action_data; fsmxfr_xcb_t *xcb; fsm_fcb_t *other_fcb; cc_causes_t cause; /* Conference is not active any more, clear that flag */ ccb->active = FALSE; if( ccb->flags & JOINED ){ other_call_id = fsmcnf_get_other_call_id(ccb, call_id); if(other_call_id != CC_NO_CALL_ID ){ fsm_fcb_t *b2bcnf_fcb, *xfr_fcb; b2bcnf_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_B2BCNF); xfr_fcb = fsm_get_fcb_by_call_id_and_type(other_call_id, FSM_TYPE_XFR); if ((b2bcnf_fcb != NULL && b2bcnf_fcb->b2bccb == NULL) && (xfr_fcb != NULL && xfr_fcb->xcb == NULL)) { cc_call_attribute(other_call_id, dcb->line, NORMAL_CALL); } } } xcb = fsmxfr_get_xcb_by_call_id(call_id); if ((xcb != NULL) && (!(ccb->flags & JOINED))) { /* * One leg of the conference has decided to transfer us. * We need to: * 1. remove this call from the conference context and * associate the new (transferred) call with the context, * 2. Resume the other leg of the conference that was not transferred. * More than likely this leg was put on hold when the transfer * was intitated if this was a REFER transfer. * 3. */ fsmcnf_update_cnf_context(ccb, call_id, xcb->cns_call_id); fsmcnf_cleanup(fcb, __LINE__, FALSE); /* NOTE: * Normally, we would reset the ccb->bridged flag to indicate * that the bridge is not active. We do not want to do that in * this case because we want the conference to bridge as soon as * the target we are transferred to answers. */ ccb->bridged = TRUE; /* * Resume the other leg of the conference that was not transferred. */ other_call_id = fsmcnf_get_other_call_id(ccb, xcb->cns_call_id); other_dcb = fsm_get_dcb(other_call_id); /* Build SDP for sending out */ cause = gsmsdp_encode_sdp_and_update_version(other_dcb, &data.resume.msg_body); if (cause != CC_CAUSE_OK) { GSM_DEBUG_ERROR(get_debug_string(FSM_DBG_SDP_BUILD_ERR)); return (SM_RC_END); } data.resume.cause = CC_CAUSE_CONF; cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, other_dcb->call_id, other_dcb->line, CC_FEATURE_RESUME, &data); /* * Update the UI for the just transferred leg. */ other_fcb = fsm_get_fcb_by_call_id_and_type(xcb->cns_call_id, FSM_TYPE_CNF); if (other_fcb != NULL) { other_fcb->ccb = ccb; fsm_change_state(other_fcb, __LINE__, FSMCNF_S_CNFED); action_data.update_ui.action = CC_UPDATE_CONF_ACTIVE; cc_call_action(other_fcb->call_id, dcb->line, CC_ACTION_UPDATE_UI, &action_data); return (SM_RC_CONT); } } fsmcnf_update_release(event); return (SM_RC_CONT); } static void fsmcnf_other_feature (fsm_fcb_t *fcb, cc_features_t ftr_id) { callid_t other_call_id; other_call_id = fsmcnf_get_other_call_id(fcb->ccb, fcb->call_id); if (other_call_id != CC_NO_CALL_ID) { cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, other_call_id, fcb->dcb->line, ftr_id, NULL); } } static sm_rcs_t fsmcnf_ev_cnfed_feature (sm_event_t *event) { fsm_fcb_t *fcb = (fsm_fcb_t *) event->data; cc_feature_t *msg = (cc_feature_t *) event->msg; callid_t call_id = msg->call_id; cc_srcs_t src_id = msg->src_id; cc_features_t ftr_id = msg->feature_id; sm_rcs_t sm_rc = SM_RC_CONT; fsmcnf_ccb_t *ccb = fcb->ccb; fsmxfr_xcb_t *xcb; int join = 1; fsm_sm_ftr(ftr_id, src_id); switch (src_id) { case CC_SRC_UI: case CC_SRC_GSM: switch (ftr_id) { case CC_FEATURE_CANCEL: sm_rc = SM_RC_END; fsmcnf_cleanup(fcb, __LINE__, TRUE); break; case CC_FEATURE_END_CALL: if ((ccb->flags & JOINED) && (ccb->bridged == FALSE)) { /* Something went wrong during the barge/monitor call setup, clean up */ fsmcnf_cleanup(fcb, __LINE__, TRUE); return (sm_rc); } /* * If bridge of conference call ends the call, other 2 * users should still be able to talk to each other. * So we simulate attended transfer when bridge tries * to end the call. */ config_get_value(CFGID_CNF_JOIN_ENABLE, &join, sizeof(join)); if (((ccb->bridged == TRUE) && (join) && !(ccb->flags & JOINED)) || ((ccb->bridged == TRUE) && (ccb->flags & XFER))) { fsmcnf_cnf_xfer(ccb); sm_rc = SM_RC_END; } else { /* * The user is attempting to clear the call, therefore * we need to also clear the other leg of this * conference if it is active. */ fsmcnf_other_feature(fcb, ftr_id); } fsmcnf_cleanup(fcb, __LINE__, TRUE); break; case CC_FEATURE_HOLD: /* Don't process the Hold if we are still waiting * on an ack from a previous Resume. */ if ((ccb->cnf_ftr_ack) && (src_id == CC_SRC_UI)) { return (SM_RC_END); } /* * The user is attempting to hold the call, therefore we need * to also hold the other leg of this conference. Only update * the other leg if the conference is bridged and not involved * in a transfer. */ if (ccb->bridged == TRUE) { if ((ccb->flags & XFER) && (src_id == CC_SRC_GSM)) { fsmcnf_cleanup(fcb, __LINE__, TRUE); return (sm_rc); } xcb = fsmxfr_get_xcb_by_call_id(call_id); if ((xcb == NULL) || (!(ccb->flags & XFER))) { fsmcnf_other_feature(fcb, ftr_id); ccb->cnf_ftr_ack = TRUE; ccb->bridged = FALSE; } } break; case CC_FEATURE_RESUME: /* Don't process the Resume if we are still waiting * on an ack from a previous Hold. */ if ((ccb->cnf_ftr_ack) && (src_id == CC_SRC_UI)) { return (SM_RC_END); } /* * The user is attempting to resume the call, therefore we need * to also resume the other leg of this conference. Only do this * if the conference is not bridged. */ if (ccb->bridged == FALSE) { fsmcnf_other_feature(fcb, ftr_id); ccb->cnf_ftr_ack = TRUE; ccb->bridged = TRUE; } break; default: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; } /* switch (ftr_id) */ break; case CC_SRC_SIP: switch (ftr_id) { case CC_FEATURE_CALLINFO: if ((ccb->flags & JOINED) && (call_id == ccb->cns_call_id)) { /* * Call is already joined into, eat this call * info event that came for the virtual bubble */ return (SM_RC_END); } fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; case CC_FEATURE_XFER: if (msg->data_valid == FALSE) { fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; } switch (msg->data.xfer.method) { case CC_XFER_METHOD_BYE: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; case CC_XFER_METHOD_REFER: if ((msg->data.xfer.cause == CC_CAUSE_XFER_REMOTE) && (msg->data.xfer.target_call_id != CC_NO_CALL_ID)) { /* * This operation is replacing this call leg with the * new leg. This call is a target of a transfer. * Replace the call id of this call with the new * replacing call id. */ fsmcnf_update_cnf_context(ccb, call_id, msg->data.xfer.target_call_id); /* Drop this call from conferenced */ fsmcnf_cleanup(fcb, __LINE__, FALSE); } else { fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); } break; default: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; } break; default: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; } break; default: fsm_sm_ignore_src(fcb, __LINE__, src_id); break; } /* switch (src_id) */ return (sm_rc); } static sm_rcs_t fsmcnf_ev_cnfed_feature_ack (sm_event_t *event) { fsm_fcb_t *fcb = (fsm_fcb_t *) event->data; cc_feature_ack_t *msg = (cc_feature_ack_t *) event->msg; fsmdef_dcb_t *dcb = fcb->dcb; fsmcnf_ccb_t *ccb = fcb->ccb; cc_srcs_t src_id = msg->src_id; cc_features_t ftr_id = msg->feature_id; sm_rcs_t sm_rc = SM_RC_CONT; cc_feature_data_t ftr_data; cc_causes_t cause; char tmp_str[STATUS_LINE_MAX_LEN]; fsm_sm_ftr(ftr_id, src_id); switch (src_id) { case CC_SRC_SIP: switch (ftr_id) { case CC_FEATURE_HOLD: /* * HOLD request is pending. Let this event drop through * to the default sm for handling. */ if (msg->cause == CC_CAUSE_REQUEST_PENDING) { fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); } else { ccb->cnf_ftr_ack = FALSE; } break; case CC_FEATURE_RESUME: /* * Resume request is pending. Let this event drop through * to the default sm for handling. */ if (msg->cause == CC_CAUSE_REQUEST_PENDING) { fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; } ccb->cnf_ftr_ack = FALSE; /* * Check the status of the cause code */ if (msg->cause != CC_CAUSE_NORMAL) { if ((platGetPhraseText(STR_INDEX_CNFR_FAIL_NOCODEC, (char *) tmp_str, STATUS_LINE_MAX_LEN - 1)) == CPR_SUCCESS) { lsm_ui_display_notify(tmp_str, NO_FREE_LINES_TIMEOUT); } cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, ccb->cns_call_id, fcb->dcb->line, CC_FEATURE_END_CALL, NULL); dcb = fsmdef_get_dcb_by_call_id(ccb->cnf_call_id); if (dcb != NULL) { /* Build SDP for sending out */ cause = gsmsdp_encode_sdp_and_update_version(dcb, &ftr_data.resume.msg_body); if (cause != CC_CAUSE_OK) { GSM_DEBUG_ERROR(get_debug_string(FSM_DBG_SDP_BUILD_ERR)); return (SM_RC_END); } ftr_data.resume.cause = CC_CAUSE_OK; cc_int_feature(CC_SRC_GSM, CC_SRC_GSM, dcb->call_id, dcb->line, CC_FEATURE_RESUME, &ftr_data); } fsmcnf_cleanup(fcb, __LINE__, TRUE); } break; default: fsm_sm_ignore_ftr(fcb, __LINE__, ftr_id); break; } /* switch (ftr_id) */ break; default: fsm_sm_ignore_src(fcb, __LINE__, src_id); break; } /* switch (src_id) */ return (sm_rc); } static sm_rcs_t fsmcnf_ev_cnfed_onhook (sm_event_t *event) { fsm_fcb_t *fcb = (fsm_fcb_t *) event->data; sm_rcs_t sm_rc = SM_RC_CONT; fsmcnf_ccb_t *ccb = fcb->ccb; int join = 1; fsmdef_dcb_t *other_dcb; boolean conf_id_valid = FALSE; /* If call_id of the ONHOOK received is of cnf_call_id, * flag to dcb of cns_call_id to know of the event, * and vice versa. We do not want to process more than * one onhook for calls in a conf call. Note that this is * needed because when in Speaker mode, onhook event is * sent to each call_id in active state. */ if (fcb->call_id == ccb->cnf_call_id) { other_dcb = fsm_get_dcb(ccb->cns_call_id); } else { other_dcb = fsm_get_dcb(ccb->cnf_call_id); } other_dcb->onhook_received = TRUE; // CSCtc04202, when conf_id_valid is FALSE,which mean at least one call is dropped // Need release the whole conference session. conf_id_valid = fsmcnd_conf_call_id_valid(ccb); config_get_value(CFGID_CNF_JOIN_ENABLE, &join, sizeof(join)); if (((ccb->bridged == TRUE) && (join) && !(ccb->flags & JOINED) && (conf_id_valid)) || ((ccb->bridged == TRUE) && (ccb->flags & XFER) && (conf_id_valid))) { /* * If bridge of conference call ends the call other 2 * users should still be able to talk to each other * So we simulate attended transfer when bridge tries * to end the call */ fsmcnf_cnf_xfer(ccb); sm_rc = SM_RC_END; } else { /* * The user is attempting to clear the call, therefore * we need to also clear the other leg of this * conference if it is active. */ fsmcnf_other_feature(fcb, CC_FEATURE_END_CALL); } fsmcnf_cleanup(fcb, __LINE__, TRUE); return (sm_rc); } cc_int32_t fsmcnf_show_cmd (cc_int32_t argc, const char *argv[]) { fsmcnf_ccb_t *ccb; int i = 0; PR_ASSERT(i == 0); /* * check if need help */ if ((argc == 2) && (argv[1][0] == '?')) { debugif_printf("%s", "show fsmcnf\n"); return (0); } debugif_printf("%s", "\n-------------------------- FSMCNF ccbs --------------------------"); debugif_printf("%s", "\ni cnf_id ccb cnf_call_id cns_call_id active bridged"); debugif_printf("%s", "\n-----------------------------------------------------------------" "\n"); FSM_FOR_ALL_CBS(ccb, fsmcnf_ccbs, FSMCNF_MAX_CCBS) { debugif_printf("%-2d %-6d 0x%8p %-11d %-11d %-6d %-7d\n", i++, ccb->cnf_id, ccb, ccb->cnf_call_id, ccb->cns_call_id, ccb->active, ccb->bridged); } return (0); } void fsmcnf_init (void) { fsmcnf_ccb_t *ccb; static const char *fname = "fsmcnf_init"; /* * Initialize the ccbs. */ fsmcnf_ccbs = (fsmcnf_ccb_t *) cpr_calloc(FSMCNF_MAX_CCBS, sizeof(fsmcnf_ccb_t)); if (fsmcnf_ccbs == NULL) { GSM_DEBUG_ERROR(GSM_F_PREFIX"Failed to allocate memory for " \ "cnf ccbs.\n", fname); return; } FSM_FOR_ALL_CBS(ccb, fsmcnf_ccbs, FSMCNF_MAX_CCBS) { fsmcnf_init_ccb(ccb); } /* * Initialize the state/event table. */ fsmcnf_sm_table.min_state = FSMCNF_S_MIN; fsmcnf_sm_table.max_state = FSMCNF_S_MAX; fsmcnf_sm_table.min_event = CC_MSG_MIN; fsmcnf_sm_table.max_event = CC_MSG_MAX; fsmcnf_sm_table.table = (&(fsmcnf_function_table[0][0])); } int cc_is_cnf_call (callid_t call_id) { if (call_id == CC_NO_CALL_ID) { return FALSE; } return fsmutil_is_cnf_leg(call_id, fsmcnf_ccbs, FSMCNF_MAX_CCBS); } void fsmcnf_shutdown (void) { cpr_free(fsmcnf_ccbs); fsmcnf_ccbs = NULL; } int fsmutil_is_cnf_consult_call (callid_t call_id) { return fsmutil_is_cnf_consult_leg(call_id, fsmcnf_ccbs, FSMCNF_MAX_CCBS); } boolean fsmcnd_conf_call_id_valid(fsmcnf_ccb_t *ccb){ static const char fname[] = "fsmcnd_conf_call_id_valid"; if( ccb != NULL){ FSM_DEBUG_SM(get_debug_string(FSMCNF_DBG_PTR), ccb->cnf_id, ccb->cnf_call_id, ccb->cns_call_id, fname, ccb); if((ccb->cnf_call_id != CC_NO_CALL_ID) && (ccb->cns_call_id != CC_NO_CALL_ID) ){ return TRUE; } } return FALSE; }