view sip-in/mncc_handle.c @ 165:9419fe55824f

themwi-sip-out complete to the point of compiling and linking
author Mychaela Falconia <falcon@freecalypso.org>
date Wed, 12 Oct 2022 17:05:45 -0800
parents 99fd4ae573ae
children
line wrap: on
line source

/*
 * In this module we implement our handling of call control messages
 * from OsmoMSC relayed to us via themwi-mncc.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include "../include/mncc.h"
#include "../include/gsm48_const.h"
#include "call.h"

extern struct call *find_call_by_mncc_callref();

static struct gsm_mncc_cause default_cause = {
	.coding		= GSM48_CAUSE_CODING_GSM,
	.location	= GSM48_CAUSE_LOC_PRN_S_LU,
	.value		= GSM48_CC_CAUSE_NORMAL_UNSPEC,
};

static void
send_rtp_connect(call)
	struct call *call;
{
	struct gsm_mncc_rtp rtp;

	bzero(&rtp, sizeof(struct gsm_mncc_rtp));
	rtp.msg_type = MNCC_RTP_CONNECT;
	rtp.callref = call->mncc_callref;
	bcopy(&call->gsm_rtp_tmgw, &rtp.addr, sizeof(struct sockaddr_storage));
	send_mncc_to_gsm(&rtp, sizeof(struct gsm_mncc_rtp));
}

static void
handle_alerting(call, msg)
	struct call *call;
	struct gsm_mncc *msg;
{
	if (call->mncc_state != MNCC_STATE_STARTED) {
		syslog(LOG_ERR, "MNCC_ALERT_IND in wrong MNCC state 0x%x",
			call->mncc_state);
		return;
	}
	call->mncc_state = MNCC_STATE_ALERTING;
	call->overall_state = OVERALL_STATE_ALERTING;
	signal_invite_ringing(call);
}

static void
handle_answer(call, msg)
	struct call *call;
	struct gsm_mncc *msg;
{
	if (call->mncc_state != MNCC_STATE_STARTED &&
	    call->mncc_state != MNCC_STATE_ALERTING) {
		syslog(LOG_ERR, "MNCC_SETUP_CNF in wrong MNCC state 0x%x",
			call->mncc_state);
		return;
	}
	call->mncc_state = MNCC_STATE_ANSWERED;
	call->overall_state = OVERALL_STATE_ANSWERED;
	/* right now we require MNCC_RTP_CREATE to have come first */
	if (!call->gsm_payload_msg_type) {
		call->overall_state = OVERALL_STATE_TEARDOWN;
		disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_PROTO_ERR);
		disconnect_tmgw(call);
		strcpy(call->invite_fail, "502 Internal protocol error");
		signal_invite_error(call);
		return;
	}
	send_rtp_connect(call);
	tmgw_send_mdcx_connect(call);
}

static void
handle_disconnect_ind(call, msg)
	struct call *call;
	struct gsm_mncc *msg;
{
	struct gsm_mncc_cause *cause;

	/* release back to MNCC */
	msg->msg_type = MNCC_REL_REQ;
	send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
	call->mncc_state = MNCC_STATE_RELEASE;
	/* signal disconnect to SIP */
	call->overall_state = OVERALL_STATE_TEARDOWN;
	if (msg->fields & MNCC_F_CAUSE)
		cause = &msg->cause;
	else
		cause = &default_cause;
	disconnect_sip(call, cause);
	disconnect_tmgw(call);
}

static void
handle_final_release(call, msg)
	struct call *call;
	struct gsm_mncc *msg;
{
	struct gsm_mncc_cause *cause;

	/* MNCC call leg is gone */
	call->mncc_state = MNCC_STATE_NO_EXIST;
	/* signal disconnect to SIP */
	call->overall_state = OVERALL_STATE_TEARDOWN;
	if (msg->fields & MNCC_F_CAUSE)
		cause = &msg->cause;
	else
		cause = &default_cause;
	disconnect_sip(call, cause);
	disconnect_tmgw(call);
	transition_dead_sip(call);
}

static void
handle_dtmf_start(call, msg)
	struct call *call;
	struct gsm_mncc *msg;
{
	if (!(msg->fields & MNCC_F_KEYPAD) ||
	    !is_valid_dtmf_digit(msg->keypad)) {
		msg->msg_type = MNCC_START_DTMF_REJ;
		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_INVAL_MAND_INF);
		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
		return;
	}
	if (call->overall_state != OVERALL_STATE_CONNECTED ||
	    call->mgw_state != MGW_STATE_COMPLETE) {
		msg->msg_type = MNCC_START_DTMF_REJ;
		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_MSGTYPE_INCOMPAT);
		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
		return;
	}
	call->dtmf_digit = msg->keypad;
	tmgw_send_dtmf_start(call);
}

static void
handle_dtmf_stop(call, msg)
	struct call *call;
	struct gsm_mncc *msg;
{
	if (call->overall_state != OVERALL_STATE_CONNECTED)
		return;
	if (call->mgw_state == MGW_STATE_COMPLETE)
		tmgw_send_dtmf_stop(call);
	else if (call->mgw_state == MGW_STATE_DTMF_OP)
		call->dtmf_pending_stop = 1;
	else {
		/* dummy OK response */
		msg->msg_type = MNCC_STOP_DTMF_RSP;
		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
	}
}

static void
handle_call_hold(call, msg)
	struct call *call;
	struct gsm_mncc *msg;
{
	if (call->overall_state != OVERALL_STATE_CONNECTED ||
	    call->mgw_state != MGW_STATE_COMPLETE) {
		msg->msg_type = MNCC_HOLD_REJ;
		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_MSGTYPE_INCOMPAT);
		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
		return;
	}
	tmgw_send_mdcx_hold(call);
}

static void
handle_call_retrieve(call, msg)
	struct call *call;
	struct gsm_mncc *msg;
{
	if (call->overall_state != OVERALL_STATE_CONNECTED ||
	    call->mgw_state != MGW_STATE_HELD) {
		msg->msg_type = MNCC_RETRIEVE_REJ;
		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_MSGTYPE_INCOMPAT);
		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
		return;
	}
	send_rtp_connect(call);
	tmgw_send_mdcx_retrieve(call);
}

static void
handle_signaling_msg(msg, msglen)
	struct gsm_mncc *msg;
	unsigned msglen;
{
	struct call *call;

	if (msglen != sizeof(struct gsm_mncc)) {
		syslog(LOG_CRIT,
			"FATAL: Rx MNCC message type 0x%x has wrong length",
			msg->msg_type);
		exit(1);
	}
	call = find_call_by_mncc_callref(msg->callref);
	if (!call) {
		syslog(LOG_CRIT,
		"error: Rx MNCC message type 0x%x has invalid callref 0x%x",
			msg->msg_type, msg->callref);
		exit(1);
	}
	switch (msg->msg_type) {
	case MNCC_SETUP_CNF:
		handle_answer(call, msg);
		return;
	case MNCC_ALERT_IND:
		handle_alerting(call, msg);
		return;
	case MNCC_DISC_IND:
		handle_disconnect_ind(call, msg);
		return;
	case MNCC_REL_IND:
	case MNCC_REL_CNF:
	case MNCC_REJ_IND:
		handle_final_release(call, msg);
		return;
	case MNCC_START_DTMF_IND:
		handle_dtmf_start(call, msg);
		return;
	case MNCC_STOP_DTMF_IND:
		handle_dtmf_stop(call, msg);
		return;
	case MNCC_MODIFY_IND:
		msg->msg_type = MNCC_MODIFY_REJ;
		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
		return;
	case MNCC_HOLD_IND:
		handle_call_hold(call, msg);
		return;
	case MNCC_RETRIEVE_IND:
		handle_call_retrieve(call, msg);
		return;
	}
}

static void
handle_rtp_create(msg, msglen)
	struct gsm_mncc_rtp *msg;
	unsigned msglen;
{
	struct call *call;

	if (msglen != sizeof(struct gsm_mncc_rtp)) {
		syslog(LOG_CRIT,
			"FATAL: Rx MNCC message type 0x%x has wrong length",
			msg->msg_type);
		exit(1);
	}
	call = find_call_by_mncc_callref(msg->callref);
	if (!call) {
		syslog(LOG_CRIT,
		"error: Rx MNCC message type 0x%x has invalid callref 0x%x",
			msg->msg_type, msg->callref);
		exit(1);
	}
	/* save Osmocom network RTP information */
	bcopy(&msg->addr, &call->gsm_rtp_osmo, sizeof(struct sockaddr_storage));
	call->gsm_payload_type = msg->payload_type;
	call->gsm_payload_msg_type = msg->payload_msg_type;
}

void
msg_from_mncc(msg, msglen)
	union mncc_msg *msg;
	unsigned msglen;
{
	switch (msg->msg_type) {
	case MNCC_SETUP_CNF:
	case MNCC_CALL_CONF_IND:
	case MNCC_ALERT_IND:
	case MNCC_NOTIFY_IND:
	case MNCC_DISC_IND:
	case MNCC_FACILITY_IND:
	case MNCC_START_DTMF_IND:
	case MNCC_STOP_DTMF_IND:
	case MNCC_MODIFY_IND:
	case MNCC_HOLD_IND:
	case MNCC_RETRIEVE_IND:
	case MNCC_USERINFO_IND:
	case MNCC_REL_IND:
	case MNCC_REL_CNF:
	case MNCC_REJ_IND:
		handle_signaling_msg(msg, msglen);
		return;
	case MNCC_RTP_CREATE:
		handle_rtp_create(msg, msglen);
		return;
	case MNCC_RTP_CONNECT:
		syslog(LOG_ERR, "MNCC_RTP_CONNECT error from OsmoMSC");
		return;
	case MNCC_RTP_FREE:
		syslog(LOG_ERR, "MNCC_RTP_FREE bogon from OsmoMSC");
		return;
	default:
		syslog(LOG_CRIT,
			"FATAL: received unexpected MNCC message type 0x%x",
			msg->msg_type);
		exit(1);
	}
}