/* Point-to-Point (PP) Short Message Service (SMS).
 * Support on Mobile Radio Interface
 * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0. */
/*
 * (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
 * (C) 2009 by Harald Welte <laforge@gnumonks.org>
 * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
 * (C) 2010 by On-Waves
 * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
 *
 * All Rights Reserved
 *
 * SPDX-License-Identifier: GPL-2.0+
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/* Notes on msg:
 *
 * Messages from lower layer are freed by lower layer.
 *
 * Messages to upper layer are freed after upper layer call returns, so upper
 * layer cannot use data after returning. Upper layer must not free the msg.
 *
 * This implies: Lower layer messages can be forwarded to upper layer.
 *
 * Upper layer messages are freed by lower layer, so they must not be freed
 * after calling lower layer.
 *
 *
 * Notes on release:
 *
 * Whenever the process returns to IDLE, the MM connection is released using
 * MMSMS-REL-REQ. It is allowed to destroy this process while processing
 * this message.
 *
 * There is an exception, if MMSMS-REL-IND is received from lower layer, the
 * process returns to IDLE without sending MMSMS-REL-REQ.
 *
 */

#include <sys/types.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/timer.h>

#include <osmocom/gsm/gsm0411_utils.h>
#include <osmocom/gsm/gsm0411_smc.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>

/*! \addtogroup sms
 *  @{
 *  \file gsm0411_smc.c
 *  Point-to-Point (PP) Short Message Service (SMS) as per TS 04.11
 */

static void cp_timer_expired(void *data);

#define MAX_SMS_RETRY 2

#define SMC_LOG_STR "SMC(%" PRIu64 ") "

/* init a new instance */
void gsm411_smc_init(struct gsm411_smc_inst *inst, uint64_t id, int network,
	int (*mn_recv) (struct gsm411_smc_inst *inst, int msg_type,
			struct msgb *msg),
	int (*mm_send) (struct gsm411_smc_inst *inst, int msg_type,
			struct msgb *msg, int cp_msg_type))
{
	memset(inst, 0, sizeof(*inst));
	inst->id = id;
	inst->network = network;
	inst->cp_max_retr = MAX_SMS_RETRY;
	inst->cp_tc1 = GSM411_TMR_TC1A_SEC / (inst->cp_max_retr + 1);
	inst->cp_state = GSM411_CPS_IDLE;
	inst->mn_recv = mn_recv;
	inst->mm_send = mm_send;

	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "instance created for %s\n",
		inst->id, inst->network ? "network" : "mobile");
}

/* clear instance */
void gsm411_smc_clear(struct gsm411_smc_inst *inst)
{
	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "clearing instance\n", inst->id);

	osmo_timer_del(&inst->cp_timer);

	/* free stored msg */
	if (inst->cp_msg) {
		LOGP(DLSMS, LOGL_INFO,
			SMC_LOG_STR "dropping pending message\n", inst->id);
		msgb_free(inst->cp_msg);
		inst->cp_msg = NULL;
	}
}

const char *smc_state_names[] = {
	"IDLE",
	"MM_CONN_PENDING",
	"WAIT_CP_ACK",
	"MM_ESTABLISHED",
};

const struct value_string gsm411_cp_cause_strs[] = {
	{ GSM411_CP_CAUSE_NET_FAIL,	"Network Failure" },
	{ GSM411_CP_CAUSE_CONGESTION,	"Congestion" },
	{ GSM411_CP_CAUSE_INV_TRANS_ID,	"Invalid Transaction ID" },
	{ GSM411_CP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
	{ GSM411_CP_CAUSE_INV_MAND_INF,	"Invalid Mandatory Information" },
	{ GSM411_CP_CAUSE_MSGTYPE_NOTEXIST, "Message Type doesn't exist" },
	{ GSM411_CP_CAUSE_MSG_INCOMP_STATE,
				"Message incompatible with protocol state" },
	{ GSM411_CP_CAUSE_IE_NOTEXIST,	"IE does not exist" },
	{ GSM411_CP_CAUSE_PROTOCOL_ERR,	"Protocol Error" },
	{ 0, 0 }
};

static void new_cp_state(struct gsm411_smc_inst *inst,
	enum gsm411_cp_state state)
{
	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "new CP state %s -> %s\n", inst->id,
		smc_state_names[inst->cp_state], smc_state_names[state]);
	inst->cp_state = state;
}

static int gsm411_tx_cp_error(struct gsm411_smc_inst *inst, uint8_t cause)
{
	struct msgb *nmsg = gsm411_msgb_alloc();
	uint8_t *causep;

	LOGP(DLSMS, LOGL_NOTICE,
		SMC_LOG_STR "TX CP-ERROR, cause %d (%s)\n", inst->id, cause,
		get_value_string(gsm411_cp_cause_strs, cause));

	causep = msgb_put(nmsg, 1);
	*causep = cause;

	return inst->mm_send(inst, GSM411_MMSMS_DATA_REQ, nmsg,
		GSM411_MT_CP_ERROR);
}

/* establish SMC connection */
static int gsm411_mnsms_est_req(struct gsm411_smc_inst *inst, struct msgb *msg)
{
	struct msgb *nmsg;

	if (inst->cp_msg) {
		LOGP(DLSMS, LOGL_FATAL,
			SMC_LOG_STR "EST REQ, but we already have an "
			"cp_msg. This should never happen, please fix!\n",
			inst->id);
		msgb_free(inst->cp_msg);
	}

	inst->cp_msg = msg;
	new_cp_state(inst, GSM411_CPS_MM_CONN_PENDING);
	/* clear stored release flag */
	inst->cp_rel = 0;
	/* send MMSMS_EST_REQ */
	nmsg = gsm411_msgb_alloc();
	return inst->mm_send(inst, GSM411_MMSMS_EST_REQ, nmsg, 0);
}

static int gsm411_mmsms_send_msg(struct gsm411_smc_inst *inst)
{
	struct msgb *nmsg;

	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "send CP data\n", inst->id);
	/* reset retry counter */
	if (inst->cp_state != GSM411_CPS_WAIT_CP_ACK)
		inst->cp_retx = 0;
	/* 5.2.3.1.2: enter MO-wait for CP-ACK */
	/* 5.2.3.2.3: enter MT-wait for CP-ACK */
	new_cp_state(inst, GSM411_CPS_WAIT_CP_ACK);
	osmo_timer_setup(&inst->cp_timer, cp_timer_expired, inst);
	/* 5.3.2.1: Set Timer TC1A */
	osmo_timer_schedule(&inst->cp_timer, inst->cp_tc1, 0);
	/* clone cp_msg */
	nmsg = gsm411_msgb_alloc();
	memcpy(msgb_put(nmsg, inst->cp_msg->len), inst->cp_msg->data,
		inst->cp_msg->len);
	/* send MMSMS_DATA_REQ with CP-DATA */
	return inst->mm_send(inst, GSM411_MMSMS_DATA_REQ, nmsg,
				GSM411_MT_CP_DATA);
}

static int gsm411_mmsms_est_cnf(struct gsm411_smc_inst *inst, struct msgb *msg)
{
	if (!inst->cp_msg) {
		LOGP(DLSMS, LOGL_FATAL,
			SMC_LOG_STR "EST CNF, but we have no cp_msg. This "
			"should never happen, please fix!\n",
			inst->id);
		return -EINVAL;
	}

	return gsm411_mmsms_send_msg(inst);
}

/* SMC TC1* is expired */
static void cp_timer_expired(void *data)
{
	struct gsm411_smc_inst *inst = data;
	struct msgb *nmsg;

	if (inst->cp_retx == inst->cp_max_retr) {

		LOGP(DLSMS, LOGL_INFO,
			SMC_LOG_STR "TC1* timeout, no more retries.\n",
			inst->id);
		/* 5.3.2.1: enter idle state */
		new_cp_state(inst, GSM411_CPS_IDLE);
		/* indicate error */
		nmsg = gsm411_msgb_alloc();
		inst->mn_recv(inst, GSM411_MNSMS_ERROR_IND, nmsg);
		msgb_free(nmsg);
		/* free pending stored msg */
		if (inst->cp_msg) {
			msgb_free(inst->cp_msg);
			inst->cp_msg = NULL;
		}
		/* release MM connection */
		nmsg = gsm411_msgb_alloc();
		inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
		return;
	}

	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "TC1* timeout, retrying...\n", inst->id);
	inst->cp_retx++;
	gsm411_mmsms_est_cnf(inst, NULL);
}

static int gsm411_mmsms_cp_ack(struct gsm411_smc_inst *inst, struct msgb *msg)
{
	/* free stored msg */
	if (inst->cp_msg) {
		msgb_free(inst->cp_msg);
		inst->cp_msg = NULL;
	}

	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "received CP-ACK\n", inst->id);
	/* 5.3.2.1 enter MM Connection established */
	new_cp_state(inst, GSM411_CPS_MM_ESTABLISHED);
	/* 5.3.2.1: Reset Timer TC1* */
	osmo_timer_del(&inst->cp_timer);

	/* pending release? */
	if (inst->cp_rel) {
		struct msgb *nmsg;

		LOGP(DLSMS, LOGL_INFO,
			SMC_LOG_STR "we have pending release.\n",
			inst->id);
		new_cp_state(inst, GSM411_CPS_IDLE);
		/* release MM connection */
		nmsg = gsm411_msgb_alloc();
		return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
	}

	return 0;
}

static int gsm411_mmsms_cp_data(struct gsm411_smc_inst *inst, struct msgb *msg)
{
	struct msgb *nmsg;
	int mt = GSM411_MNSMS_DATA_IND;

	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "received CP-DATA\n", inst->id);
	/* 5.3.1 enter MM Connection established (if idle) */
	if (inst->cp_state == GSM411_CPS_IDLE) {
		new_cp_state(inst, GSM411_CPS_MM_ESTABLISHED);
		mt = GSM411_MNSMS_EST_IND;
		/* clear stored release flag */
		inst->cp_rel = 0;
	}
	/* send MMSMS_DATA_REQ (CP ACK) */
	nmsg = gsm411_msgb_alloc();
	inst->mm_send(inst, GSM411_MMSMS_DATA_REQ, nmsg, GSM411_MT_CP_ACK);
	/* indicate data */
	inst->mn_recv(inst, mt, msg);

	return 0;
}

/* send CP DATA */
static int gsm411_mnsms_data_req(struct gsm411_smc_inst *inst, struct msgb *msg)
{
	if (inst->cp_msg) {
		LOGP(DLSMS, LOGL_FATAL,
			SMC_LOG_STR "DATA REQ, but we already have an "
			"cp_msg. This should never happen, please fix!\n",
			inst->id);
		msgb_free(inst->cp_msg);
	}

	/* store and send */
	inst->cp_msg = msg;
	return gsm411_mmsms_send_msg(inst);
}

/* release SMC connection */
static int gsm411_mnsms_rel_req(struct gsm411_smc_inst *inst, struct msgb *msg)
{
	struct msgb *nmsg;

	msgb_free(msg);

	/* discard silently */
	if (inst->cp_state == GSM411_CPS_IDLE)
		return 0;

	/* store release, until established or released */
	if (inst->cp_state != GSM411_CPS_MM_ESTABLISHED) {
		LOGP(DLSMS, LOGL_NOTICE,
			SMC_LOG_STR "cannot release yet current state: %s\n",
			inst->id, smc_state_names[inst->cp_state]);
		inst->cp_rel = 1;
		return 0;
	}

	/* free stored msg */
	if (inst->cp_msg) {
		msgb_free(inst->cp_msg);
		inst->cp_msg = NULL;
	}

	new_cp_state(inst, GSM411_CPS_IDLE);
	/* release MM connection */
	nmsg = gsm411_msgb_alloc();
	return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
}

static int gsm411_mmsms_cp_error(struct gsm411_smc_inst *inst, struct msgb *msg)
{
	struct msgb *nmsg;

	/* free stored msg */
	if (inst->cp_msg) {
		msgb_free(inst->cp_msg);
		inst->cp_msg = NULL;
	}

	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "received CP-ERROR\n", inst->id);
	/* 5.3.4 enter idle */
	new_cp_state(inst, GSM411_CPS_IDLE);
	/* indicate error */
	inst->mn_recv(inst, GSM411_MNSMS_ERROR_IND, msg);
	/* release MM connection */
	nmsg = gsm411_msgb_alloc();
	return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
}

static int gsm411_mmsms_rel_ind(struct gsm411_smc_inst *inst, struct msgb *msg)
{
	struct msgb *nmsg;

	/* free stored msg */
	if (inst->cp_msg) {
		msgb_free(inst->cp_msg);
		inst->cp_msg = NULL;
	}

	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "MM layer is released\n", inst->id);
	/* 5.3.4 enter idle */
	new_cp_state(inst, GSM411_CPS_IDLE);
	/* indicate error */
	nmsg = gsm411_msgb_alloc();
	inst->mn_recv(inst, GSM411_MNSMS_ERROR_IND, nmsg);
	msgb_free(nmsg);

	return 0;
}

/* abort SMC connection */
static int gsm411_mnsms_abort_req(struct gsm411_smc_inst *inst,
	struct msgb *msg)
{
	struct msgb *nmsg;

	/* free stored msg */
	if (inst->cp_msg) {
		msgb_free(inst->cp_msg);
		inst->cp_msg = NULL;
	}

	/* 5.3.4 go idle */
	new_cp_state(inst, GSM411_CPS_IDLE);
	/* send MMSMS_DATA_REQ with CP-ERROR */
	inst->mm_send(inst, GSM411_MMSMS_DATA_REQ, msg, GSM411_MT_CP_ERROR);
	/* release MM connection */
	nmsg = gsm411_msgb_alloc();
	return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg, 0);
}

/* statefull handling for MNSMS SAP messages */
static const struct smcdownstate {
	uint32_t	states;
	int		type;
	const char 	*name;
	int		(*rout) (struct gsm411_smc_inst *inst,
					struct msgb *msg);
} smcdownstatelist[] = {
	/* establish request */
	{SBIT(GSM411_CPS_IDLE),
	GSM411_MNSMS_EST_REQ,
	 "MNSMS-EST-REQ", gsm411_mnsms_est_req},

	/* release request */
	{ALL_STATES,
	GSM411_MNSMS_REL_REQ,
	 "MNSMS-REL-REQ", gsm411_mnsms_rel_req},

	/* data request */
	{SBIT(GSM411_CPS_MM_ESTABLISHED),
	GSM411_MNSMS_DATA_REQ,
	 "MNSMS-DATA-REQ", gsm411_mnsms_data_req},

	/* abort request */
	{ALL_STATES - SBIT(GSM411_CPS_IDLE),
	GSM411_MNSMS_ABORT_REQ,
	 "MNSMS-ABORT-REQ", gsm411_mnsms_abort_req},
};

#define SMCDOWNSLLEN \
	(sizeof(smcdownstatelist) / sizeof(struct smcdownstate))

/* message from upper layer */
int gsm411_smc_send(struct gsm411_smc_inst *inst, int msg_type,
	struct msgb *msg)
{
	int i, rc;

	/* find function for current state and message */
	for (i = 0; i < SMCDOWNSLLEN; i++) {
		if ((msg_type == smcdownstatelist[i].type)
		  && (SBIT(inst->cp_state) & smcdownstatelist[i].states))
				break;
	}
	if (i == SMCDOWNSLLEN) {
		LOGP(DLSMS, LOGL_NOTICE,
			SMC_LOG_STR "message %u unhandled at this state %s.\n",
			inst->id, msg_type, smc_state_names[inst->cp_state]);
		msgb_free(msg);
		return 0;
	}

	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "message %s received in state %s\n", inst->id,
		smcdownstatelist[i].name, smc_state_names[inst->cp_state]);

	rc = smcdownstatelist[i].rout(inst, msg);

	return rc;
}

/* statefull handling for MMSMS SAP messages */
static const struct smcdatastate {
	uint32_t	states;
	int		type, cp_type;
	const char 	*name;
	int		(*rout) (struct gsm411_smc_inst *inst,
					struct msgb *msg);
} smcdatastatelist[] = {
	/* establish confirm */
	{SBIT(GSM411_CPS_MM_CONN_PENDING),
	 GSM411_MMSMS_EST_CNF, 0,
	 "MMSMS-EST-CNF", gsm411_mmsms_est_cnf},

	/* establish indication (CP DATA) */
	{SBIT(GSM411_CPS_IDLE),
	 GSM411_MMSMS_EST_IND, GSM411_MT_CP_DATA,
	 "MMSMS-EST-IND (CP DATA)", gsm411_mmsms_cp_data},

	/* data indication (CP DATA) */
	{SBIT(GSM411_CPS_MM_ESTABLISHED),
	 GSM411_MMSMS_DATA_IND, GSM411_MT_CP_DATA,
	 "MMSMS-DATA-IND (CP DATA)", gsm411_mmsms_cp_data},

	/* data indication (CP ACK) */
	{SBIT(GSM411_CPS_WAIT_CP_ACK),
	 GSM411_MMSMS_DATA_IND, GSM411_MT_CP_ACK,
	 "MMSMS-DATA-IND (CP ACK)", gsm411_mmsms_cp_ack},

	/* data indication (CP ERROR) */
	{ALL_STATES,
	 GSM411_MMSMS_DATA_IND, GSM411_MT_CP_ERROR,
	 "MMSMS-DATA-IND (CP_ERROR)", gsm411_mmsms_cp_error},

	/* release indication */
	{ALL_STATES - SBIT(GSM411_CPS_IDLE),
	 GSM411_MMSMS_REL_IND, 0,
	 "MMSMS-REL-IND", gsm411_mmsms_rel_ind},

};

#define SMCDATASLLEN \
	(sizeof(smcdatastatelist) / sizeof(struct smcdatastate))

/* message from lower layer
 * WARNING: We must not free msg, since it will be performed by the
 * lower layer. */
int gsm411_smc_recv(struct gsm411_smc_inst *inst, int msg_type,
	struct msgb *msg, int cp_msg_type)
{
	int i, rc;

	/* find function for current state and message */
	for (i = 0; i < SMCDATASLLEN; i++) {
		/* state must match, MM message must match
		 * CP msg must match only in case of MMSMS_DATA_IND
		 */
		if ((msg_type == smcdatastatelist[i].type)
		  && (SBIT(inst->cp_state) & smcdatastatelist[i].states)
		  && (msg_type != GSM411_MMSMS_DATA_IND
		   || cp_msg_type == smcdatastatelist[i].cp_type))
				break;
	}
	if (i == SMCDATASLLEN) {
		LOGP(DLSMS, LOGL_NOTICE,
			SMC_LOG_STR "message 0x%x/%u unhandled at this "
			"state %s.\n", inst->id, msg_type, cp_msg_type,
			smc_state_names[inst->cp_state]);
		if (msg_type == GSM411_MMSMS_EST_IND
		 || msg_type == GSM411_MMSMS_DATA_IND) {
			struct msgb *nmsg;

			LOGP(DLSMS, LOGL_NOTICE,
				SMC_LOG_STR "RX Unimplemented CP "
				"msg_type: 0x%02x\n", inst->id, msg_type);
			/* 5.3.4 enter idle */
			new_cp_state(inst, GSM411_CPS_IDLE);
			/* indicate error */
			gsm411_tx_cp_error(inst,
				GSM411_CP_CAUSE_MSGTYPE_NOTEXIST);
			/* send error indication to upper layer */
			nmsg = gsm411_msgb_alloc();
			inst->mn_recv(inst, GSM411_MNSMS_ERROR_IND, nmsg);
			msgb_free(nmsg);
			/* release MM connection */
			nmsg = gsm411_msgb_alloc();
			return inst->mm_send(inst, GSM411_MMSMS_REL_REQ, nmsg,
						0);
		}
		return 0;
	}

	LOGP(DLSMS, LOGL_INFO,
		SMC_LOG_STR "message %s received in state %s\n", inst->id,
		smcdatastatelist[i].name, smc_state_names[inst->cp_state]);

	rc = smcdatastatelist[i].rout(inst, msg);

	return rc;
}

const struct value_string gsm411_cp_state_names[] = {
	{ GSM411_CPS_IDLE,		"IDLE" },
	{ GSM411_CPS_MM_CONN_PENDING,	"MM_CONN_PENDING" },
	{ GSM411_CPS_WAIT_CP_ACK,	"WAIT_CP_ACK" },
	{ GSM411_CPS_MM_ESTABLISHED,	"ESTABLISHD" },
	{ 0, NULL }
};
/*! @} */
