Skip to content
Snippets Groups Projects
sig_pri.c 295 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			default:
    				ast_verb(2, "core_id:%d %s CC request unknown status %d\n",
    					monitor->core_id, sig_pri_cc_type_name,
    					subcmd->u.cc_request_rsp.status);
    				ast_cc_monitor_failed(monitor->core_id, monitor->name,
    					"%s CC request unknown status", sig_pri_cc_type_name);
    				break;
    			}
    			ao2_ref(monitor, -1);
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_REMOTE_USER_FREE:
    			monitor = sig_pri_find_cc_monitor_by_cc_id(pri,
    				subcmd->u.cc_remote_user_free.cc_id);
    			if (!monitor) {
    				pri_cc_cancel(pri->pri, subcmd->u.cc_remote_user_free.cc_id);
    				break;
    			}
    			ast_cc_monitor_callee_available(monitor->core_id,
    				"%s callee has become available", sig_pri_cc_type_name);
    			ao2_ref(monitor, -1);
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_B_FREE:
    			monitor = sig_pri_find_cc_monitor_by_cc_id(pri,
    				subcmd->u.cc_b_free.cc_id);
    			if (!monitor) {
    				pri_cc_cancel(pri->pri, subcmd->u.cc_b_free.cc_id);
    				break;
    			}
    			ast_cc_monitor_party_b_free(monitor->core_id);
    			ao2_ref(monitor, -1);
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_STATUS_REQ:
    			monitor = sig_pri_find_cc_monitor_by_cc_id(pri,
    				subcmd->u.cc_status_req.cc_id);
    			if (!monitor) {
    				pri_cc_cancel(pri->pri, subcmd->u.cc_status_req.cc_id);
    				break;
    			}
    			ast_cc_monitor_status_request(monitor->core_id);
    			ao2_ref(monitor, -1);
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_STATUS_REQ_RSP:
    			agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_status_req_rsp.cc_id);
    			if (!agent) {
    				pri_cc_cancel(pri->pri, subcmd->u.cc_status_req_rsp.cc_id);
    				break;
    			}
    			ast_cc_agent_status_response(agent->core_id,
    				subcmd->u.cc_status_req_rsp.status ? AST_DEVICE_INUSE
    				: AST_DEVICE_NOT_INUSE);
    			ao2_ref(agent, -1);
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_STATUS:
    			agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_status.cc_id);
    			if (!agent) {
    				pri_cc_cancel(pri->pri, subcmd->u.cc_status.cc_id);
    				break;
    			}
    			if (subcmd->u.cc_status.status) {
    				ast_cc_agent_caller_busy(agent->core_id, "%s agent caller is busy",
    					sig_pri_cc_type_name);
    			} else {
    				ast_cc_agent_caller_available(agent->core_id,
    					"%s agent caller is available", sig_pri_cc_type_name);
    			}
    			ao2_ref(agent, -1);
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_CANCEL:
    			sig_pri_cc_link_canceled(pri, subcmd->u.cc_cancel.cc_id,
    				subcmd->u.cc_cancel.is_agent);
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_STOP_ALERTING:
    			monitor = sig_pri_find_cc_monitor_by_cc_id(pri,
    				subcmd->u.cc_stop_alerting.cc_id);
    			if (!monitor) {
    				pri_cc_cancel(pri->pri, subcmd->u.cc_stop_alerting.cc_id);
    				break;
    			}
    			ast_cc_monitor_stop_ringing(monitor->core_id);
    			ao2_ref(monitor, -1);
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    
    #if defined(HAVE_PRI_AOC_EVENTS)
    		case PRI_SUBCMD_AOC_E:
    
    			/* Queue AST_CONTROL_AOC frame */
    			sig_pri_aoc_e_from_pri(&subcmd->u.aoc_e, NULL, 0);
    
    			break;
    #endif	/* defined(HAVE_PRI_AOC_EVENTS) */
    
    			ast_debug(2, "Span %d: Unknown CIS subcommand(%d) in %s event.\n", pri->span,
    				subcmd->cmd, pri_event2str(event_id));
    
    #if defined(HAVE_PRI_AOC_EVENTS)
    /*!
     * \internal
     * \brief detect if AOC-S subcmd is present.
     * \since 1.8
     *
     * \param subcmds Subcommands to process if any. (Could be NULL).
     *
     * \note Knowing whether or not an AOC-E subcmd is present on certain
     * PRI hangup events is necessary to determine what method to use to hangup
     * the ast_channel.  If an AOC-E subcmd just came in, then a new AOC-E was queued
     * on the ast_channel.  If a soft hangup is used, the AOC-E msg will never make it
     * across the bridge, but if a AST_CONTROL_HANGUP frame is queued behind it
     * we can ensure the AOC-E frame makes it to it's destination before the hangup
     * frame is read.
     *
     *
     * \retval 0 AOC-E is not present in subcmd list
     * \retval 1 AOC-E is present in subcmd list
     */
    static int detect_aoc_e_subcmd(const struct pri_subcommands *subcmds)
    {
    	int i;
    
    	if (!subcmds) {
    		return 0;
    	}
    	for (i = 0; i < subcmds->counter_subcmd; ++i) {
    		const struct pri_subcommand *subcmd = &subcmds->subcmd[i];
    		if (subcmd->cmd == PRI_SUBCMD_AOC_E) {
    			return 1;
    		}
    	}
    	return 0;
    }
    #endif	/* defined(HAVE_PRI_AOC_EVENTS) */
    
    
    /*!
     * \internal
     * \brief Handle the call associated PRI subcommand events.
     * \since 1.8
     *
    
     * \param pri PRI span control structure.
    
     * \param chanpos Channel position in the span.
     * \param event_id PRI event id
     * \param subcmds Subcommands to process if any. (Could be NULL).
     * \param call_rsp libpri opaque call structure to send any responses toward.
     * Could be NULL either because it is not available or the call is for the
     * dummy call reference.  However, this should not be NULL in the cases that
     * need to use the pointer to send a response message back.
     *
     * \note Assumes the pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained.
     *
     * \return Nothing
     */
    
    static void sig_pri_handle_subcmds(struct sig_pri_span *pri, int chanpos, int event_id,
    
    	const struct pri_subcommands *subcmds, q931_call *call_rsp)
    
    {
    	int index;
    	struct ast_channel *owner;
    	struct ast_party_redirecting ast_redirecting;
    
    #if defined(HAVE_PRI_TRANSFER)
    	struct xfer_rsp_data xfer_rsp;
    #endif	/* defined(HAVE_PRI_TRANSFER) */
    
    
    	if (!subcmds) {
    		return;
    	}
    	for (index = 0; index < subcmds->counter_subcmd; ++index) {
    		const struct pri_subcommand *subcmd = &subcmds->subcmd[index];
    
    		switch (subcmd->cmd) {
    		case PRI_SUBCMD_CONNECTED_LINE:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    			if (owner) {
    				struct ast_party_connected_line ast_connected;
    				int caller_id_update;
    
    				/* Extract the connected line information */
    				ast_party_connected_line_init(&ast_connected);
    				sig_pri_party_id_convert(&ast_connected.id, &subcmd->u.connected_line.id,
    					pri);
    
    				ast_connected.id.tag = ast_strdup(pri->pvts[chanpos]->user_tag);
    
    
    				caller_id_update = 0;
    
    				if (ast_connected.id.name.str) {
    
    					/* Save name for Caller-ID update */
    					ast_copy_string(pri->pvts[chanpos]->cid_name,
    
    						ast_connected.id.name.str, sizeof(pri->pvts[chanpos]->cid_name));
    
    					caller_id_update = 1;
    				}
    
    				if (ast_connected.id.number.str) {
    
    					/* Save number for Caller-ID update */
    
    					ast_copy_string(pri->pvts[chanpos]->cid_num,
    						ast_connected.id.number.str, sizeof(pri->pvts[chanpos]->cid_num));
    					pri->pvts[chanpos]->cid_ton = ast_connected.id.number.plan;
    
    					caller_id_update = 1;
    				}
    				ast_connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
    
    				pri->pvts[chanpos]->cid_subaddr[0] = '\0';
    #if defined(HAVE_PRI_SUBADDR)
    				if (ast_connected.id.subaddress.valid) {
    
    					ast_party_subaddress_set(&ast_channel_caller(owner)->id.subaddress,
    
    						&ast_connected.id.subaddress);
    					if (ast_connected.id.subaddress.str) {
    						ast_copy_string(pri->pvts[chanpos]->cid_subaddr,
    							ast_connected.id.subaddress.str,
    							sizeof(pri->pvts[chanpos]->cid_subaddr));
    					}
    				}
    #endif	/* defined(HAVE_PRI_SUBADDR) */
    				if (caller_id_update) {
    
    					struct ast_party_caller ast_caller;
    
    
    					pri->pvts[chanpos]->callingpres =
    
    						ast_party_id_presentation(&ast_connected.id);
    
    					sig_pri_set_caller_id(pri->pvts[chanpos]);
    
    					ast_party_caller_set_init(&ast_caller, ast_channel_caller(owner));
    
    					ast_caller.id = ast_connected.id;
    					ast_caller.ani = ast_connected.id;
    					ast_channel_set_caller_event(owner, &ast_caller, NULL);
    
    				}
    
    				/* Update the connected line information on the other channel */
    				if (event_id != PRI_EVENT_RING) {
    					/* This connected_line update was not from a SETUP message. */
    
    					ast_channel_queue_connected_line_update(owner, &ast_connected, NULL);
    
    				}
    
    				ast_party_connected_line_free(&ast_connected);
    				ast_channel_unlock(owner);
    			}
    			break;
    		case PRI_SUBCMD_REDIRECTING:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    			if (owner) {
    				sig_pri_redirecting_convert(&ast_redirecting, &subcmd->u.redirecting,
    
    					ast_channel_redirecting(owner), pri);
    
    				ast_redirecting.orig.tag = ast_strdup(pri->pvts[chanpos]->user_tag);
    
    				ast_redirecting.from.tag = ast_strdup(pri->pvts[chanpos]->user_tag);
    				ast_redirecting.to.tag = ast_strdup(pri->pvts[chanpos]->user_tag);
    
    				ast_channel_set_redirecting(owner, &ast_redirecting, NULL);
    
    				if (event_id != PRI_EVENT_RING) {
    					/* This redirection was not from a SETUP message. */
    
    
    					/* Invalidate any earlier private redirecting id representations */
    					ast_party_id_invalidate(&ast_redirecting.priv_orig);
    					ast_party_id_invalidate(&ast_redirecting.priv_from);
    					ast_party_id_invalidate(&ast_redirecting.priv_to);
    
    
    					ast_channel_queue_redirecting_update(owner, &ast_redirecting, NULL);
    
    				}
    				ast_party_redirecting_free(&ast_redirecting);
    
    				ast_channel_unlock(owner);
    			}
    			break;
    #if defined(HAVE_PRI_CALL_REROUTING)
    		case PRI_SUBCMD_REROUTING:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    			if (owner) {
    				struct pri_party_redirecting pri_deflection;
    
    				if (!call_rsp) {
    
    					ast_log(LOG_WARNING,
    						"Span %d: %s tried CallRerouting/CallDeflection to '%s' without call!\n",
    
    						pri->span, ast_channel_name(owner), subcmd->u.rerouting.deflection.to.number.str);
    
    					ast_channel_unlock(owner);
    
    					break;
    				}
    				if (ast_strlen_zero(subcmd->u.rerouting.deflection.to.number.str)) {
    
    					ast_log(LOG_WARNING,
    
    						"Span %d: %s tried CallRerouting/CallDeflection to empty number!\n",
    
    						pri->span, ast_channel_name(owner));
    
    					pri_rerouting_rsp(pri->pri, call_rsp, subcmd->u.rerouting.invoke_id,
    						PRI_REROUTING_RSP_INVALID_NUMBER);
    					ast_channel_unlock(owner);
    
    				ast_verb(3, "Span %d: %s is CallRerouting/CallDeflection to '%s'.\n",
    
    					pri->span, ast_channel_name(owner), subcmd->u.rerouting.deflection.to.number.str);
    
    				/*
    				 * Send back positive ACK to CallRerouting/CallDeflection.
    				 *
    				 * Note:  This call will be hungup by the core when it processes
    				 * the call_forward string.
    				 */
    				pri_rerouting_rsp(pri->pri, call_rsp, subcmd->u.rerouting.invoke_id,
    					PRI_REROUTING_RSP_OK_CLEAR);
    
    				pri_deflection = subcmd->u.rerouting.deflection;
    
    
    				/* Adjust the deflecting to number based upon the subscription option. */
    				switch (subcmd->u.rerouting.subscription_option) {
    				case 0:	/* noNotification */
    				case 1:	/* notificationWithoutDivertedToNr */
    					/* Delete the number because the far end is not supposed to see it. */
    					pri_deflection.to.number.presentation =
    						PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_UNSCREENED;
    					pri_deflection.to.number.plan =
    						(PRI_TON_UNKNOWN << 4) | PRI_NPI_E163_E164;
    					pri_deflection.to.number.str[0] = '\0';
    					break;
    				case 2:	/* notificationWithDivertedToNr */
    					break;
    				case 3:	/* notApplicable */
    				default:
    					break;
    				}
    				sig_pri_redirecting_convert(&ast_redirecting, &pri_deflection,
    
    					ast_channel_redirecting(owner), pri);
    
    				ast_redirecting.orig.tag = ast_strdup(pri->pvts[chanpos]->user_tag);
    
    				ast_redirecting.from.tag = ast_strdup(pri->pvts[chanpos]->user_tag);
    				ast_redirecting.to.tag = ast_strdup(pri->pvts[chanpos]->user_tag);
    
    				ast_channel_set_redirecting(owner, &ast_redirecting, NULL);
    
    				ast_party_redirecting_free(&ast_redirecting);
    
    
    				/* Request the core to forward to the new number. */
    
    				ast_channel_call_forward_set(owner, subcmd->u.rerouting.deflection.to.number.str);
    
    				/* Wake up the channel. */
    				ast_queue_frame(owner, &ast_null_frame);
    
    
    				ast_channel_unlock(owner);
    			}
    			break;
    #endif	/* defined(HAVE_PRI_CALL_REROUTING) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_AVAILABLE:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    			if (owner) {
    				enum ast_cc_service_type service;
    
    				switch (event_id) {
    				case PRI_EVENT_RINGING:
    					service = AST_CC_CCNR;
    					break;
    				case PRI_EVENT_HANGUP_REQ:
    					/* We will assume that the cause was busy/congestion. */
    					service = AST_CC_CCBS;
    					break;
    				default:
    					service = AST_CC_NONE;
    					break;
    				}
    				if (service == AST_CC_NONE
    					|| sig_pri_cc_available(pri, chanpos, subcmd->u.cc_available.cc_id,
    					service)) {
    					pri_cc_cancel(pri->pri, subcmd->u.cc_available.cc_id);
    				}
    				ast_channel_unlock(owner);
    			} else {
    				/* No asterisk channel. */
    				pri_cc_cancel(pri->pri, subcmd->u.cc_available.cc_id);
    			}
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_CALL:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    			if (owner) {
    				struct ast_cc_agent *agent;
    
    				agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_call.cc_id);
    				if (agent) {
    					ast_setup_cc_recall_datastore(owner, agent->core_id);
    					ast_cc_agent_set_interfaces_chanvar(owner);
    					ast_cc_agent_recalling(agent->core_id,
    						"%s caller is attempting recall", sig_pri_cc_type_name);
    					ao2_ref(agent, -1);
    				}
    
    				ast_channel_unlock(owner);
    			}
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    #if defined(HAVE_PRI_CCSS)
    		case PRI_SUBCMD_CC_CANCEL:
    			sig_pri_cc_link_canceled(pri, subcmd->u.cc_cancel.cc_id,
    				subcmd->u.cc_cancel.is_agent);
    			break;
    #endif	/* defined(HAVE_PRI_CCSS) */
    
    #if defined(HAVE_PRI_TRANSFER)
    		case PRI_SUBCMD_TRANSFER_CALL:
    			if (!call_rsp) {
    				/* Should never happen. */
    				ast_log(LOG_ERROR,
    					"Call transfer subcommand without call to send response!\n");
    				break;
    			}
    
    			sig_pri_unlock_private(pri->pvts[chanpos]);
    
    			xfer_rsp.pri = pri;
    			xfer_rsp.call = call_rsp;
    			xfer_rsp.invoke_id = subcmd->u.transfer.invoke_id;
    			sig_pri_attempt_transfer(pri,
    				subcmd->u.transfer.call_1, subcmd->u.transfer.is_call_1_held,
    				subcmd->u.transfer.call_2, subcmd->u.transfer.is_call_2_held,
    				sig_pri_transfer_rsp, &xfer_rsp);
    
    			sig_pri_lock_private(pri->pvts[chanpos]);
    			break;
    #endif	/* defined(HAVE_PRI_TRANSFER) */
    
    #if defined(HAVE_PRI_AOC_EVENTS)
    		case PRI_SUBCMD_AOC_S:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    			if (owner) {
    
    				sig_pri_aoc_s_from_pri(&subcmd->u.aoc_s, owner,
    					(pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S));
    
    				ast_channel_unlock(owner);
    			}
    			break;
    #endif	/* defined(HAVE_PRI_AOC_EVENTS) */
    #if defined(HAVE_PRI_AOC_EVENTS)
    		case PRI_SUBCMD_AOC_D:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    			if (owner) {
    
    				/* Queue AST_CONTROL_AOC frame on channel */
    
    				sig_pri_aoc_d_from_pri(&subcmd->u.aoc_d, owner,
    					(pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_D));
    
    				ast_channel_unlock(owner);
    			}
    			break;
    #endif	/* defined(HAVE_PRI_AOC_EVENTS) */
    #if defined(HAVE_PRI_AOC_EVENTS)
    		case PRI_SUBCMD_AOC_E:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    
    			/* Queue AST_CONTROL_AOC frame */
    
    			sig_pri_aoc_e_from_pri(&subcmd->u.aoc_e, owner,
    				(pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_E));
    
    			if (owner) {
    				ast_channel_unlock(owner);
    			}
    			break;
    
    #endif	/* defined(HAVE_PRI_AOC_EVENTS) */
    #if defined(HAVE_PRI_AOC_EVENTS)
    		case PRI_SUBCMD_AOC_CHARGING_REQ:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    			if (owner) {
    
    				sig_pri_aoc_request_from_pri(&subcmd->u.aoc_request, pri->pvts[chanpos],
    					call_rsp);
    
    				ast_channel_unlock(owner);
    			}
    			break;
    #endif	/* defined(HAVE_PRI_AOC_EVENTS) */
    #if defined(HAVE_PRI_AOC_EVENTS)
    		case PRI_SUBCMD_AOC_CHARGING_REQ_RSP:
    
    			/*
    			 * An AOC request response may contain an AOC-S rate list.
    			 * If this is the case handle this just like we
    			 * would an incoming AOC-S msg.
    			 */
    
    			if (subcmd->u.aoc_request_response.valid_aoc_s) {
    				sig_pri_lock_owner(pri, chanpos);
    				owner = pri->pvts[chanpos]->owner;
    				if (owner) {
    
    					sig_pri_aoc_s_from_pri(&subcmd->u.aoc_request_response.aoc_s, owner,
    						(pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_S));
    
    					ast_channel_unlock(owner);
    				}
    			}
    			break;
    
    #endif	/* defined(HAVE_PRI_AOC_EVENTS) */
    
    #if defined(HAVE_PRI_MCID)
    		case PRI_SUBCMD_MCID_REQ:
    			sig_pri_lock_owner(pri, chanpos);
    			owner = pri->pvts[chanpos]->owner;
    			sig_pri_mcid_event(pri, &subcmd->u.mcid_req, owner);
    			if (owner) {
    				ast_channel_unlock(owner);
    			}
    			break;
    #endif	/* defined(HAVE_PRI_MCID) */
    #if defined(HAVE_PRI_MCID)
    		case PRI_SUBCMD_MCID_RSP:
    			/* Ignore for now. */
    			break;
    #endif	/* defined(HAVE_PRI_MCID) */
    
    #if defined(HAVE_PRI_DISPLAY_TEXT)
    		case PRI_SUBCMD_DISPLAY_TEXT:
    			if (event_id != PRI_EVENT_RING) {
    				/*
    				 * This display text was not from a SETUP message.  We can do
    				 * something with this display text string.
    				 */
    				sig_pri_lock_owner(pri, chanpos);
    				owner = pri->pvts[chanpos]->owner;
    				if (owner) {
    					struct ast_frame f;
    
    					/* Pass the display text to the peer channel. */
    					memset(&f, 0, sizeof(f));
    					f.frametype = AST_FRAME_TEXT;
    					f.subclass.integer = 0;
    					f.offset = 0;
    					f.data.ptr = &subcmd->u.display.text;
    					f.datalen = subcmd->u.display.length + 1;
    					ast_queue_frame(owner, &f);
    					ast_channel_unlock(owner);
    				}
    			}
    			break;
    #endif	/* defined(HAVE_PRI_DISPLAY_TEXT) */
    
    			ast_debug(2, "Span %d: Unknown call subcommand(%d) in %s event.\n",
    				pri->span, subcmd->cmd, pri_event2str(event_id));
    
    /*!
     * \internal
     * \brief Convert the MOH state to string.
    
     *
     * \param state MOH state to process.
     *
     * \return String version of MOH state.
     */
    static const char *sig_pri_moh_state_str(enum sig_pri_moh_state state)
    {
    	const char *str;
    
    	str = "Unknown";
    	switch (state) {
    	case SIG_PRI_MOH_STATE_IDLE:
    		str = "SIG_PRI_MOH_STATE_IDLE";
    		break;
    	case SIG_PRI_MOH_STATE_NOTIFY:
    		str = "SIG_PRI_MOH_STATE_NOTIFY";
    		break;
    	case SIG_PRI_MOH_STATE_MOH:
    		str = "SIG_PRI_MOH_STATE_MOH";
    		break;
    #if defined(HAVE_PRI_CALL_HOLD)
    	case SIG_PRI_MOH_STATE_HOLD_REQ:
    		str = "SIG_PRI_MOH_STATE_HOLD_REQ";
    		break;
    	case SIG_PRI_MOH_STATE_PEND_UNHOLD:
    		str = "SIG_PRI_MOH_STATE_PEND_UNHOLD";
    		break;
    	case SIG_PRI_MOH_STATE_HOLD:
    		str = "SIG_PRI_MOH_STATE_HOLD";
    		break;
    	case SIG_PRI_MOH_STATE_RETRIEVE_REQ:
    		str = "SIG_PRI_MOH_STATE_RETRIEVE_REQ";
    		break;
    	case SIG_PRI_MOH_STATE_PEND_HOLD:
    		str = "SIG_PRI_MOH_STATE_PEND_HOLD";
    		break;
    	case SIG_PRI_MOH_STATE_RETRIEVE_FAIL:
    		str = "SIG_PRI_MOH_STATE_RETRIEVE_FAIL";
    		break;
    #endif	/* defined(HAVE_PRI_CALL_HOLD) */
    	case SIG_PRI_MOH_STATE_NUM:
    		/* Not a real state. */
    		break;
    	}
    	return str;
    }
    
    /*!
     * \internal
     * \brief Convert the MOH event to string.
    
     *
     * \param event MOH event to process.
     *
     * \return String version of MOH event.
     */
    static const char *sig_pri_moh_event_str(enum sig_pri_moh_event event)
    {
    	const char *str;
    
    	str = "Unknown";
    	switch (event) {
    	case SIG_PRI_MOH_EVENT_RESET:
    		str = "SIG_PRI_MOH_EVENT_RESET";
    		break;
    	case SIG_PRI_MOH_EVENT_HOLD:
    		str = "SIG_PRI_MOH_EVENT_HOLD";
    		break;
    	case SIG_PRI_MOH_EVENT_UNHOLD:
    		str = "SIG_PRI_MOH_EVENT_UNHOLD";
    		break;
    #if defined(HAVE_PRI_CALL_HOLD)
    	case SIG_PRI_MOH_EVENT_HOLD_ACK:
    		str = "SIG_PRI_MOH_EVENT_HOLD_ACK";
    		break;
    	case SIG_PRI_MOH_EVENT_HOLD_REJ:
    		str = "SIG_PRI_MOH_EVENT_HOLD_REJ";
    		break;
    	case SIG_PRI_MOH_EVENT_RETRIEVE_ACK:
    		str = "SIG_PRI_MOH_EVENT_RETRIEVE_ACK";
    		break;
    	case SIG_PRI_MOH_EVENT_RETRIEVE_REJ:
    		str = "SIG_PRI_MOH_EVENT_RETRIEVE_REJ";
    		break;
    	case SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK:
    		str = "SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK";
    		break;
    #endif	/* defined(HAVE_PRI_CALL_HOLD) */
    	case SIG_PRI_MOH_EVENT_NUM:
    		/* Not a real event. */
    		break;
    	}
    	return str;
    }
    
    #if defined(HAVE_PRI_CALL_HOLD)
    /*!
     * \internal
     * \brief Retrieve a call that was placed on hold by the HOLD message.
    
     *
     * \param pvt Channel private control structure.
     * 
     * \note Assumes the pvt->pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pvt) is already obtained.
     *
     * \return Next MOH state
     */
    static enum sig_pri_moh_state sig_pri_moh_retrieve_call(struct sig_pri_chan *pvt)
    {
    	int chanpos;
    	int channel;
    
    	if (pvt->pri->nodetype == PRI_NETWORK) {
    		/* Find an available channel to propose */
    		chanpos = pri_find_empty_chan(pvt->pri, 1);
    		if (chanpos < 0) {
    			/* No channels available. */
    			return SIG_PRI_MOH_STATE_RETRIEVE_FAIL;
    		}
    		channel = PVT_TO_CHANNEL(pvt->pri->pvts[chanpos]);
    
    		/*
    		 * We cannot occupy or reserve this channel at this time because
    		 * the retrieve may fail or we could have a RETRIEVE collision.
    		 */
    	} else {
    		/* Let the network pick the channel. */
    		channel = 0;
    	}
    
    	if (pri_retrieve(pvt->pri->pri, pvt->call, channel)) {
    		return SIG_PRI_MOH_STATE_RETRIEVE_FAIL;
    	}
    	return SIG_PRI_MOH_STATE_RETRIEVE_REQ;
    }
    #endif	/* defined(HAVE_PRI_CALL_HOLD) */
    
    /*!
     * \internal
     * \brief MOH FSM state idle.
    
     *
     * \param chan Channel to post event to (Usually pvt->owner)
     * \param pvt Channel private control structure.
     * \param event MOH event to process.
     * 
     * \note Assumes the pvt->pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pvt) is already obtained.
     *
     * \return Next MOH state
     */
    static enum sig_pri_moh_state sig_pri_moh_fsm_idle(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event)
    {
    	enum sig_pri_moh_state next_state;
    
    	next_state = pvt->moh_state;
    	switch (event) {
    	case SIG_PRI_MOH_EVENT_HOLD:
    		if (!strcasecmp(pvt->mohinterpret, "passthrough")) {
    			/*
    			 * This config setting is deprecated.
    			 * The old way did not send MOH just in case the notification was ignored.
    			 */
    			pri_notify(pvt->pri->pri, pvt->call, pvt->prioffset, PRI_NOTIFY_REMOTE_HOLD);
    			next_state = SIG_PRI_MOH_STATE_NOTIFY;
    			break;
    		}
    
    		switch (pvt->pri->moh_signaling) {
    		default:
    		case SIG_PRI_MOH_SIGNALING_MOH:
    			ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret);
    			next_state = SIG_PRI_MOH_STATE_MOH;
    			break;
    		case SIG_PRI_MOH_SIGNALING_NOTIFY:
    			/* Send MOH anyway in case the far end does not interpret the notification. */
    			ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret);
    
    			pri_notify(pvt->pri->pri, pvt->call, pvt->prioffset, PRI_NOTIFY_REMOTE_HOLD);
    			next_state = SIG_PRI_MOH_STATE_NOTIFY;
    			break;
    #if defined(HAVE_PRI_CALL_HOLD)
    		case SIG_PRI_MOH_SIGNALING_HOLD:
    			if (pri_hold(pvt->pri->pri, pvt->call)) {
    				/* Fall back to MOH instead */
    				ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret);
    				next_state = SIG_PRI_MOH_STATE_MOH;
    			} else {
    				next_state = SIG_PRI_MOH_STATE_HOLD_REQ;
    			}
    			break;
    #endif	/* defined(HAVE_PRI_CALL_HOLD) */
    		}
    		break;
    	default:
    		break;
    	}
    	pvt->moh_state = next_state;
    	return next_state;
    }
    
    /*!
     * \internal
     * \brief MOH FSM state notify remote party.
    
     *
     * \param chan Channel to post event to (Usually pvt->owner)
     * \param pvt Channel private control structure.
     * \param event MOH event to process.
     * 
     * \note Assumes the pvt->pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pvt) is already obtained.
     *
     * \return Next MOH state
     */
    static enum sig_pri_moh_state sig_pri_moh_fsm_notify(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event)
    {
    	enum sig_pri_moh_state next_state;
    
    	next_state = pvt->moh_state;
    	switch (event) {
    
    	case SIG_PRI_MOH_EVENT_HOLD:
    		if (strcasecmp(pvt->mohinterpret, "passthrough")) {
    			/* Restart MOH in case it was stopped by other means. */
    			ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret);
    		}
    		break;
    
    	case SIG_PRI_MOH_EVENT_UNHOLD:
    		pri_notify(pvt->pri->pri, pvt->call, pvt->prioffset, PRI_NOTIFY_REMOTE_RETRIEVAL);
    		/* Fall through */
    	case SIG_PRI_MOH_EVENT_RESET:
    		ast_moh_stop(chan);
    		next_state = SIG_PRI_MOH_STATE_IDLE;
    		break;
    	default:
    		break;
    	}
    	pvt->moh_state = next_state;
    	return next_state;
    }
    
    /*!
     * \internal
     * \brief MOH FSM state generate moh.
    
     *
     * \param chan Channel to post event to (Usually pvt->owner)
     * \param pvt Channel private control structure.
     * \param event MOH event to process.
     * 
     * \note Assumes the pvt->pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pvt) is already obtained.
     *
     * \return Next MOH state
     */
    static enum sig_pri_moh_state sig_pri_moh_fsm_moh(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event)
    {
    	enum sig_pri_moh_state next_state;
    
    	next_state = pvt->moh_state;
    	switch (event) {
    
    	case SIG_PRI_MOH_EVENT_HOLD:
    		/* Restart MOH in case it was stopped by other means. */
    		ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret);
    		break;
    
    	case SIG_PRI_MOH_EVENT_RESET:
    	case SIG_PRI_MOH_EVENT_UNHOLD:
    		ast_moh_stop(chan);
    		next_state = SIG_PRI_MOH_STATE_IDLE;
    		break;
    	default:
    		break;
    	}
    	pvt->moh_state = next_state;
    	return next_state;
    }
    
    #if defined(HAVE_PRI_CALL_HOLD)
    /*!
     * \internal
     * \brief MOH FSM state hold requested.
    
     *
     * \param chan Channel to post event to (Usually pvt->owner)
     * \param pvt Channel private control structure.
     * \param event MOH event to process.
     * 
     * \note Assumes the pvt->pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pvt) is already obtained.
     *
     * \return Next MOH state
     */
    static enum sig_pri_moh_state sig_pri_moh_fsm_hold_req(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event)
    {
    	enum sig_pri_moh_state next_state;
    
    	next_state = pvt->moh_state;
    	switch (event) {
    	case SIG_PRI_MOH_EVENT_RESET:
    		next_state = SIG_PRI_MOH_STATE_IDLE;
    		break;
    	case SIG_PRI_MOH_EVENT_UNHOLD:
    		next_state = SIG_PRI_MOH_STATE_PEND_UNHOLD;
    		break;
    	case SIG_PRI_MOH_EVENT_HOLD_REJ:
    		/* Fall back to MOH */
    		if (chan) {
    			ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret);
    		}
    		next_state = SIG_PRI_MOH_STATE_MOH;
    		break;
    	case SIG_PRI_MOH_EVENT_HOLD_ACK:
    		next_state = SIG_PRI_MOH_STATE_HOLD;
    		break;
    	default:
    		break;
    	}
    	pvt->moh_state = next_state;
    	return next_state;
    }
    #endif	/* defined(HAVE_PRI_CALL_HOLD) */
    
    #if defined(HAVE_PRI_CALL_HOLD)
    /*!
     * \internal
     * \brief MOH FSM state hold requested with pending unhold.
    
     *
     * \param chan Channel to post event to (Usually pvt->owner)
     * \param pvt Channel private control structure.
     * \param event MOH event to process.
     * 
     * \note Assumes the pvt->pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pvt) is already obtained.
     *
     * \return Next MOH state
     */
    static enum sig_pri_moh_state sig_pri_moh_fsm_pend_unhold(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event)
    {
    	enum sig_pri_moh_state next_state;
    
    	next_state = pvt->moh_state;
    	switch (event) {
    	case SIG_PRI_MOH_EVENT_RESET:
    		next_state = SIG_PRI_MOH_STATE_IDLE;
    		break;
    	case SIG_PRI_MOH_EVENT_HOLD:
    		next_state = SIG_PRI_MOH_STATE_HOLD_REQ;
    		break;
    	case SIG_PRI_MOH_EVENT_HOLD_REJ:
    		next_state = SIG_PRI_MOH_STATE_IDLE;
    		break;
    	case SIG_PRI_MOH_EVENT_HOLD_ACK:
    		next_state = sig_pri_moh_retrieve_call(pvt);
    		break;
    	default:
    		break;
    	}
    	pvt->moh_state = next_state;
    	return next_state;
    }
    #endif	/* defined(HAVE_PRI_CALL_HOLD) */
    
    #if defined(HAVE_PRI_CALL_HOLD)
    /*!
     * \internal
     * \brief MOH FSM state hold.
    
     *
     * \param chan Channel to post event to (Usually pvt->owner)
     * \param pvt Channel private control structure.
     * \param event MOH event to process.
     * 
     * \note Assumes the pvt->pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pvt) is already obtained.
     *
     * \return Next MOH state
     */
    static enum sig_pri_moh_state sig_pri_moh_fsm_hold(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event)
    {
    	enum sig_pri_moh_state next_state;
    
    	next_state = pvt->moh_state;
    	switch (event) {
    	case SIG_PRI_MOH_EVENT_RESET:
    		next_state = SIG_PRI_MOH_STATE_IDLE;
    		break;
    	case SIG_PRI_MOH_EVENT_UNHOLD:
    		next_state = sig_pri_moh_retrieve_call(pvt);
    		break;
    	case SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK:
    		/* Fall back to MOH */
    		if (chan) {
    			ast_moh_start(chan, pvt->moh_suggested, pvt->mohinterpret);
    		}
    		next_state = SIG_PRI_MOH_STATE_MOH;
    		break;
    	default:
    		break;
    	}
    	pvt->moh_state = next_state;
    	return next_state;
    }
    #endif	/* defined(HAVE_PRI_CALL_HOLD) */
    
    #if defined(HAVE_PRI_CALL_HOLD)
    /*!
     * \internal
     * \brief MOH FSM state retrieve requested.
    
     *
     * \param chan Channel to post event to (Usually pvt->owner)
     * \param pvt Channel private control structure.
     * \param event MOH event to process.
     * 
     * \note Assumes the pvt->pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pvt) is already obtained.
     *
     * \return Next MOH state
     */
    static enum sig_pri_moh_state sig_pri_moh_fsm_retrieve_req(struct ast_channel *chan, struct sig_pri_chan *pvt, enum sig_pri_moh_event event)
    {
    	enum sig_pri_moh_state next_state;
    
    	next_state = pvt->moh_state;
    	switch (event) {
    	case SIG_PRI_MOH_EVENT_RESET:
    		next_state = SIG_PRI_MOH_STATE_IDLE;
    		break;
    	case SIG_PRI_MOH_EVENT_HOLD:
    		next_state = SIG_PRI_MOH_STATE_PEND_HOLD;
    		break;
    	case SIG_PRI_MOH_EVENT_RETRIEVE_ACK:
    	case SIG_PRI_MOH_EVENT_REMOTE_RETRIEVE_ACK:
    		next_state = SIG_PRI_MOH_STATE_IDLE;
    		break;
    	case SIG_PRI_MOH_EVENT_RETRIEVE_REJ:
    		next_state = SIG_PRI_MOH_STATE_RETRIEVE_FAIL;
    		break;
    	default:
    		break;
    	}
    	pvt->moh_state = next_state;
    	return next_state;
    }
    #endif	/* defined(HAVE_PRI_CALL_HOLD) */
    
    #if defined(HAVE_PRI_CALL_HOLD)
    /*!
     * \internal
     * \brief MOH FSM state retrieve requested with pending hold.
    
     *
     * \param chan Channel to post event to (Usually pvt->owner)
     * \param pvt Channel private control structure.
     * \param event MOH event to process.
     * 
     * \note Assumes the pvt->pri->lock is already obtained.
     * \note Assumes the sig_pri_lock_private(pvt) is already obtained.
     *
     * \return Next MOH state