view sip-in/mgw_ops.c @ 141:e499e8db8b82

sip-in: handle call hold and retrieve
author Mychaela Falconia <falcon@freecalypso.org>
date Sat, 08 Oct 2022 13:28:30 -0800
parents 5685412bd6aa
children 0ecbc3dc8f93
line wrap: on
line source

/*
 * In this module we implement all transactions from themwi-sip-in
 * toward themwi-mgw.
 */

#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/gsm48_const.h"
#include "../include/mncc.h"
#include "../include/tmgw_ctrl.h"
#include "../include/tmgw_const.h"
#include "call.h"

extern struct call *call_list;

struct call *
find_call_with_mgw_xact(xact_id)
	uint32_t xact_id;
{
	struct call *call;

	for (call = call_list; call; call = call->next)
		if (call->mgw_xact && call->mgw_xact_id == xact_id)
			return call;
	return 0;
}

uint32_t
get_new_tmgw_xact_id()
{
	static uint32_t next_xact_id;

	for (;;) {
		next_xact_id++;
		if (!find_call_with_mgw_xact(next_xact_id))
			return next_xact_id;
	}
}

void
tmgw_send_crcx(call)
	struct call *call;
{
	struct tmgw_ctrl_req req;

	bzero(&req, sizeof req);
	req.opcode = TMGW_CTRL_OP_CRCX;
	req.transact_ref = get_new_tmgw_xact_id();
	req.ep_id = TMGW_EP_TYPE_GATEWAY;
	req.setup_mask = TMGW_CTRL_MASK_PSTN_CONN;
	bcopy(&call->pstn_rtp_remote, &req.pstn_addr,
		sizeof(struct sockaddr_in));
	req.pstn_payload_type =
		call->use_pcma ? PSTN_CODEC_PCMA : PSTN_CODEC_PCMU;
	send_req_to_tmgw(&req);
	call->mgw_xact = TMGW_CTRL_OP_CRCX;
	call->mgw_xact_id = req.transact_ref;
}

void
tmgw_send_mdcx_connect(call)
	struct call *call;
{
	struct tmgw_ctrl_req req;

	bzero(&req, sizeof req);
	req.opcode = TMGW_CTRL_OP_MDCX;
	req.transact_ref = get_new_tmgw_xact_id();
	req.ep_id = call->mgw_ep_id;
	req.setup_mask = TMGW_CTRL_MASK_GSM_CONN | TMGW_CTRL_MASK_FWD_MODE;
	bcopy(&call->gsm_rtp_osmo, &req.gsm_addr,
		sizeof(struct sockaddr_storage));
	req.gsm_payload_type = call->gsm_payload_type;
	req.gsm_payload_msg_type = call->gsm_payload_msg_type;
	req.fwd_mode = TMGW_FWD_MODE_SENDRECV;
	send_req_to_tmgw(&req);
	call->mgw_state = MGW_STATE_CONNECTING;
	call->mgw_xact = TMGW_CTRL_OP_MDCX;
	call->mgw_xact_id = req.transact_ref;
}

void
tmgw_send_mdcx_hold(call)
	struct call *call;
{
	struct tmgw_ctrl_req req;

	bzero(&req, sizeof req);
	req.opcode = TMGW_CTRL_OP_MDCX;
	req.transact_ref = get_new_tmgw_xact_id();
	req.ep_id = call->mgw_ep_id;
	req.setup_mask = TMGW_CTRL_MASK_FWD_MODE;
	req.fwd_mode = TMGW_FWD_MODE_INACTIVE;
	send_req_to_tmgw(&req);
	call->mgw_state = MGW_STATE_HOLD_OP;
	call->mgw_xact = TMGW_CTRL_OP_MDCX;
	call->mgw_xact_id = req.transact_ref;
}

void
tmgw_send_mdcx_retrieve(call)
	struct call *call;
{
	struct tmgw_ctrl_req req;

	bzero(&req, sizeof req);
	req.opcode = TMGW_CTRL_OP_MDCX;
	req.transact_ref = get_new_tmgw_xact_id();
	req.ep_id = call->mgw_ep_id;
	req.setup_mask = TMGW_CTRL_MASK_FWD_MODE;
	req.fwd_mode = TMGW_FWD_MODE_SENDRECV;
	send_req_to_tmgw(&req);
	call->mgw_state = MGW_STATE_RETRIEVE_OP;
	call->mgw_xact = TMGW_CTRL_OP_MDCX;
	call->mgw_xact_id = req.transact_ref;
}

void
tmgw_send_dlcx(call)
	struct call *call;
{
	struct tmgw_ctrl_req req;

	bzero(&req, sizeof req);
	req.opcode = TMGW_CTRL_OP_DLCX;
	req.transact_ref = get_new_tmgw_xact_id();
	req.ep_id = call->mgw_ep_id;
	send_req_to_tmgw(&req);
	call->mgw_state = MGW_STATE_DELETING;
	call->mgw_xact = TMGW_CTRL_OP_DLCX;
	call->mgw_xact_id = req.transact_ref;
}

void
tmgw_send_dtmf_start(call)
	struct call *call;
{
	struct tmgw_ctrl_req req;

	bzero(&req, sizeof req);
	req.opcode = TMGW_CTRL_OP_DTMF_START;
	req.transact_ref = get_new_tmgw_xact_id();
	req.ep_id = call->mgw_ep_id;
	req.fwd_mode = call->dtmf_digit;
	send_req_to_tmgw(&req);
	call->mgw_state = MGW_STATE_DTMF_OP;
	call->mgw_xact = TMGW_CTRL_OP_DTMF_START;
	call->mgw_xact_id = req.transact_ref;
}

void
tmgw_send_dtmf_stop(call)
	struct call *call;
{
	struct tmgw_ctrl_req req;

	bzero(&req, sizeof req);
	req.opcode = TMGW_CTRL_OP_DTMF_STOP;
	req.transact_ref = get_new_tmgw_xact_id();
	req.ep_id = call->mgw_ep_id;
	send_req_to_tmgw(&req);
	call->mgw_state = MGW_STATE_DTMF_OP;
	call->mgw_xact = TMGW_CTRL_OP_DTMF_STOP;
	call->mgw_xact_id = req.transact_ref;
}

static void
handle_crcx_fail(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	call->overall_state = OVERALL_STATE_TEARDOWN;
	strcpy(call->invite_fail, "503 Gateway resource allocation failure");
	signal_invite_error(call);
}

static void
crcx_response(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	if (msg->res == TMGW_RESP_OK) {
		call->mgw_state = MGW_STATE_ALLOCATED;
		call->mgw_ep_id = msg->ep_id;
		bcopy(&msg->gsm_addr, &call->gsm_rtp_tmgw,
			sizeof(struct sockaddr_storage));
		bcopy(&msg->pstn_addr, &call->pstn_rtp_local,
			sizeof(struct sockaddr_in));
		switch (call->overall_state) {
		case OVERALL_STATE_CRCX:
			proceed_with_call_setup(call);
			return;
		case OVERALL_STATE_TEARDOWN:
			tmgw_send_dlcx(call);
			return;
		default:
		bad_state:
			syslog(LOG_CRIT,
			"FATAL: invalid overall state 0x%x on CRCX response",
				call->overall_state);
			exit(1);
		}
	} else {
		switch (call->overall_state) {
		case OVERALL_STATE_CRCX:
			handle_crcx_fail(call, msg);
			return;
		case OVERALL_STATE_TEARDOWN:
			transition_dead_sip(call);
			return;
		default:
			goto bad_state;
		}
	}
}

static void
handle_mdcx_connect_fail(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	call->overall_state = OVERALL_STATE_TEARDOWN;
	switch (msg->res) {
	case TMGW_RESP_ERR_RSRC:
		disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
		strcpy(call->invite_fail,
			"503 Gateway resource allocation failure");
		break;
	case TMGW_RESP_ERR_NOTSUP:
		disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
		strcpy(call->invite_fail, "502 Gateway internal error");
		break;
	default:
		disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_PROTO_ERR);
		strcpy(call->invite_fail, "502 Gateway internal error");
	}
	signal_invite_error(call);
}

static void
mdcx_connect_response(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	if (msg->res == TMGW_RESP_OK) {
		call->mgw_state = MGW_STATE_COMPLETE;
		switch (call->overall_state) {
		case OVERALL_STATE_ANSWERED:
			signal_invite_200(call);
			return;
		case OVERALL_STATE_TEARDOWN:
			tmgw_send_dlcx(call);
			return;
		default:
		bad_state:
			syslog(LOG_CRIT,
			"FATAL: invalid overall state 0x%x on MDCX response",
				call->overall_state);
			exit(1);
		}
	} else {
		tmgw_send_dlcx(call);
		switch (call->overall_state) {
		case OVERALL_STATE_ANSWERED:
			handle_mdcx_connect_fail(call, msg);
			return;
		case OVERALL_STATE_TEARDOWN:
			return;
		default:
			goto bad_state;
		}
	}
}

static struct gsm_mncc_cause mgw_hold_retrieve_error = {
	.coding		= GSM48_CAUSE_CODING_GSM,
	.location	= GSM48_CAUSE_LOC_PRN_S_LU,
	.value		= GSM48_CC_CAUSE_NETWORK_OOO,
};

static void
mdcx_hold_response(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	if (call->overall_state == OVERALL_STATE_TEARDOWN) {
		tmgw_send_dlcx(call);
		return;
	}
	if (msg->res == TMGW_RESP_OK) {
		call->mgw_state = MGW_STATE_HELD;
		mncc_send_hold_ack(call);
	} else {
		call->overall_state = OVERALL_STATE_TEARDOWN;
		tmgw_send_dlcx(call);
		disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_NETWORK_OOO);
		disconnect_sip(call, &mgw_hold_retrieve_error);
	}
}

static void
mdcx_retrieve_response(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	if (call->overall_state == OVERALL_STATE_TEARDOWN) {
		tmgw_send_dlcx(call);
		return;
	}
	if (msg->res == TMGW_RESP_OK) {
		call->mgw_state = MGW_STATE_COMPLETE;
		mncc_send_retrieve_ack(call);
	} else {
		call->overall_state = OVERALL_STATE_TEARDOWN;
		tmgw_send_dlcx(call);
		disconnect_mncc(call, GSM48_CAUSE_LOC_PRN_S_LU,
				GSM48_CC_CAUSE_NETWORK_OOO);
		disconnect_sip(call, &mgw_hold_retrieve_error);
	}
}

static void
mdcx_response(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	switch (call->mgw_state) {
	case MGW_STATE_CONNECTING:
		mdcx_connect_response(call, msg);
		return;
	case MGW_STATE_HOLD_OP:
		mdcx_hold_response(call, msg);
		return;
	case MGW_STATE_RETRIEVE_OP:
		mdcx_retrieve_response(call, msg);
		return;
	default:
		syslog(LOG_CRIT,
			"FATAL: invalid MGW state 0x%x on MDCX response",
			call->mgw_state);
		exit(1);
	}
}

static void
dlcx_response(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	if (msg->res != TMGW_RESP_OK) {
		syslog(LOG_CRIT, "FATAL: TMGW DLCX failed with code 0x%x",
			msg->res);
		exit(1);
	}
	call->mgw_state = MGW_STATE_NO_EXIST;
	transition_dead_sip(call);
}

static void
dtmf_start_response(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	if (call->overall_state == OVERALL_STATE_TEARDOWN) {
		tmgw_send_dlcx(call);
		return;
	}
	if (msg->res == TMGW_RESP_OK)
		mncc_dtmf_start_ok(call);
	else
		mncc_dtmf_start_err(call);
	if (call->dtmf_pending_stop)
		tmgw_send_dtmf_stop(call);
	else
		call->mgw_state = MGW_STATE_COMPLETE;
}

static void
dtmf_stop_response(call, msg)
	struct call *call;
	struct tmgw_ctrl_resp *msg;
{
	if (call->overall_state == OVERALL_STATE_TEARDOWN) {
		tmgw_send_dlcx(call);
		return;
	}
	mncc_dtmf_stop_ok(call);
	call->mgw_state = MGW_STATE_COMPLETE;
	call->dtmf_pending_stop = 0;
}

void
process_tmgw_response(msg)
	struct tmgw_ctrl_resp *msg;
{
	struct call *call;
	unsigned opc;

	call = find_call_with_mgw_xact(msg->transact_ref);
	if (!call) {
		syslog(LOG_CRIT,
		"FATAL: response from TMGW xact 0x%x does not match any call",
			msg->transact_ref);
		exit(1);
	}
	opc = call->mgw_xact;
	call->mgw_xact = 0;
	switch (opc) {
	case TMGW_CTRL_OP_CRCX:
		crcx_response(call, msg);
		return;
	case TMGW_CTRL_OP_MDCX:
		mdcx_response(call, msg);
		return;
	case TMGW_CTRL_OP_DLCX:
		dlcx_response(call, msg);
		return;
	case TMGW_CTRL_OP_DTMF_START:
		dtmf_start_response(call, msg);
		return;
	case TMGW_CTRL_OP_DTMF_STOP:
		dtmf_stop_response(call, msg);
		return;
	default:
		syslog(LOG_CRIT,
			"FATAL: invalid opcode 0x%x in call->msg_xact", opc);
		exit(1);
	}
}