/*
 * OsmocomBB <-> SDR connection bridge
 * TDMA scheduler: primitive management
 *
 * (C) 2017-2022 by Vadim Yanitskiy <axilirator@gmail.com>
 * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 *
 * All Rights Reserved
 *
 * 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.
 *
 */

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <talloc.h>

#include <osmocom/core/msgb.h>
#include <osmocom/core/prim.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/linuxlist.h>

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

#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>

#define L1SCHED_PRIM_HEADROOM	64
#define L1SCHED_PRIM_TAILROOM	512

osmo_static_assert(sizeof(struct l1sched_prim) <= L1SCHED_PRIM_HEADROOM, l1sched_prim_size);

const struct value_string l1sched_prim_type_names[] = {
	{ L1SCHED_PRIM_T_DATA,			"DATA" },
	{ L1SCHED_PRIM_T_RACH,			"RACH" },
	{ L1SCHED_PRIM_T_SCH,			"SCH" },
	{ L1SCHED_PRIM_T_PCHAN_COMB,		"PCHAN_COMB" },
	{ 0, NULL },
};

void l1sched_prim_init(struct msgb *msg,
		       enum l1sched_prim_type type,
		       enum osmo_prim_operation op)
{
	struct l1sched_prim *prim;

	msg->l2h = msg->data;
	msg->l1h = msgb_push(msg, sizeof(*prim));

	prim = l1sched_prim_from_msgb(msg);
	osmo_prim_init(&prim->oph, 0, type, op, msg);
}

struct msgb *l1sched_prim_alloc(enum l1sched_prim_type type,
				enum osmo_prim_operation op)
{
	struct msgb *msg;

	msg = msgb_alloc_headroom(L1SCHED_PRIM_HEADROOM + L1SCHED_PRIM_TAILROOM,
				  L1SCHED_PRIM_HEADROOM, "l1sched_prim");
	if (msg == NULL)
		return NULL;

	l1sched_prim_init(msg, type, op);

	return msg;
}

/**
 * Composes a new primitive from cached RR Measurement Report.
 *
 * @param  lchan lchan to assign a primitive
 * @return       SACCH primitive to be transmitted
 */
static struct msgb *prim_compose_mr(struct l1sched_lchan_state *lchan)
{
	struct l1sched_prim *prim;
	struct msgb *msg;

	/* Allocate a new primitive */
	msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST);
	OSMO_ASSERT(msg != NULL);

	prim = l1sched_prim_from_msgb(msg);
	prim->data_req = (struct l1sched_prim_chdr) {
		.chan_nr = l1sched_lchan_desc[lchan->type].chan_nr | lchan->ts->index,
		.link_id = L1SCHED_CH_LID_SACCH,
	};

	/* Compose a new Measurement Report primitive */
	memcpy(msgb_put(msg, GSM_MACBLOCK_LEN),
	       &lchan->sacch.mr_cache[0],
	       GSM_MACBLOCK_LEN);

	/* Inform about the cache usage count */
	if (++lchan->sacch.mr_cache_usage > 5) {
		LOGP_LCHAND(lchan, LOGL_NOTICE,
			    "SACCH MR cache usage count=%u > 5 "
			    "=> ancient measurements, please fix!\n",
			    lchan->sacch.mr_cache_usage);
	}

	LOGP_LCHAND(lchan, LOGL_NOTICE, "Using cached Measurement Report\n");

	return msg;
}

/**
 * Dequeues a SACCH primitive from transmit queue, if present.
 * Otherwise dequeues a cached Measurement Report (the last
 * received one). Finally, if the cache is empty, a "dummy"
 * measurement report is used.
 *
 * According to 3GPP TS 04.08, section 3.4.1, SACCH channel
 * accompanies either a traffic or a signaling channel. It
 * has the particularity that continuous transmission must
 * occur in both directions, so on the Uplink direction
 * measurement result messages are sent at each possible
 * occasion when nothing else has to be sent. The LAPDm
 * fill frames (0x01, 0x03, 0x01, 0x2b, ...) are not
 * applicable on SACCH channels!
 *
 * Unfortunately, 3GPP TS 04.08 doesn't clearly state
 * which "else messages" besides Measurement Reports
 * can be send by the MS on SACCH channels. However,
 * in sub-clause 3.4.1 it's stated that the interval
 * between two successive measurement result messages
 * shall not exceed one L2 frame.
 *
 * @param  lchan lchan to assign a primitive
 * @return       SACCH primitive to be transmitted
 */
struct msgb *l1sched_lchan_prim_dequeue_sacch(struct l1sched_lchan_state *lchan)
{
	struct msgb *msg_nmr = NULL;
	struct msgb *msg_mr = NULL;
	struct msgb *msg;
	bool mr_now;

	/* Shall we transmit MR now? */
	mr_now = !lchan->sacch.mr_tx_last;

#define PRIM_MSGB_IS_MR(msg) \
	(l1sched_prim_data_from_msgb(msg)[5] == GSM48_PDISC_RR && \
	 l1sched_prim_data_from_msgb(msg)[6] == GSM48_MT_RR_MEAS_REP)

	/* Iterate over all primitives in the queue */
	llist_for_each_entry(msg, &lchan->tx_prims, list) {
		/* Look for a Measurement Report */
		if (!msg_mr && PRIM_MSGB_IS_MR(msg))
			msg_mr = msg;

		/* Look for anything else */
		if (!msg_nmr && !PRIM_MSGB_IS_MR(msg))
			msg_nmr = msg;

		/* Should we look further? */
		if (mr_now && msg_mr)
			break; /* MR was found */
		else if (!mr_now && msg_nmr)
			break; /* something else was found */
	}

	LOGP_LCHAND(lchan, LOGL_DEBUG,
		    "SACCH MR selection: mr_tx_last=%d msg_mr=%p msg_nmr=%p\n",
		    lchan->sacch.mr_tx_last, msg_mr, msg_nmr);

	/* Prioritize non-MR prim if possible */
	if (mr_now && msg_mr)
		msg = msg_mr;
	else if (!mr_now && msg_nmr)
		msg = msg_nmr;
	else if (!mr_now && msg_mr)
		msg = msg_mr;
	else /* Nothing was found */
		msg = NULL;

	/* Have we found what we were looking for? */
	if (msg) /* Dequeue if so */
		llist_del(&msg->list);
	else /* Otherwise compose a new MR */
		msg = prim_compose_mr(lchan);

	/* Update the cached report */
	if (msg == msg_mr) {
		memcpy(lchan->sacch.mr_cache, msgb_l2(msg), GSM_MACBLOCK_LEN);
		lchan->sacch.mr_cache_usage = 0;

		LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH MR cache has been updated\n");
	}

	/* Update the MR transmission state */
	lchan->sacch.mr_tx_last = PRIM_MSGB_IS_MR(msg);

	LOGP_LCHAND(lchan, LOGL_DEBUG, "SACCH decision: %s\n",
		    PRIM_MSGB_IS_MR(msg) ? "Measurement Report" : "data frame");

	return msg;
}

/**
 * Dequeues either a FACCH, or a speech TCH primitive
 * of a given channel type (Lm or Bm).
 *
 * @param  lchan      logical channel state
 * @param  facch      FACCH (true) or speech (false) prim?
 * @return            either a FACCH, or a TCH primitive if found,
 *                    otherwise NULL
 */
struct msgb *l1sched_lchan_prim_dequeue_tch(struct l1sched_lchan_state *lchan, bool facch)
{
	struct msgb *msg;

	/**
	 * There is no need to use the 'safe' list iteration here
	 * as an item removal is immediately followed by return.
	 */
	llist_for_each_entry(msg, &lchan->tx_prims, list) {
		bool is_facch = msgb_l2len(msg) == GSM_MACBLOCK_LEN;
		if (is_facch != facch)
			continue;

		llist_del(&msg->list);
		return msg;
	}

	return NULL;
}

/**
 * Allocate a DATA.req with dummy LAPDm func=UI frame for the given logical channel.
 * To be used when no suitable DATA.req is present in the Tx queue.
 *
 * @param  lchan lchan to allocate a dummy primitive for
 * @return       an msgb with DATA.req primitive, or NULL
 */
struct msgb *l1sched_lchan_prim_dummy_lapdm(const struct l1sched_lchan_state *lchan)
{
	struct l1sched_prim *prim;
	struct msgb *msg;
	uint8_t *ptr;

	/* LAPDm func=UI is not applicable for SACCH */
	OSMO_ASSERT(!L1SCHED_CHAN_IS_SACCH(lchan->type));

	msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST);
	OSMO_ASSERT(msg != NULL);

	prim = l1sched_prim_from_msgb(msg);
	prim->data_req = (struct l1sched_prim_chdr) {
		.chan_nr = l1sched_lchan_desc[lchan->type].chan_nr | lchan->ts->index,
		.link_id = l1sched_lchan_desc[lchan->type].link_id,
	};

	ptr = msgb_put(msg, GSM_MACBLOCK_LEN);

	/**
	 * TS 144.006, section 8.4.2.3 "Fill frames"
	 * A fill frame is a UI command frame for SAPI 0, P=0
	 * and with an information field of 0 octet length.
	 */
	*(ptr++) = 0x01;
	*(ptr++) = 0x03;
	*(ptr++) = 0x01;

	/**
	 * TS 144.006, section 5.2 "Frame delimitation and fill bits"
	 * Except for the first octet containing fill bits which shall
	 * be set to the binary value "00101011", each fill bit should
	 * be set to a random value when sent by the network.
	 */
	*(ptr++) = 0x2b;
	while (ptr < msg->tail)
		*(ptr++) = (uint8_t)rand();

	return msg;
}

int l1sched_lchan_emit_data_ind(struct l1sched_lchan_state *lchan,
				const uint8_t *data, size_t data_len,
				int n_errors, int n_bits_total,
				bool traffic)
{
	const struct l1sched_meas_set *meas = &lchan->meas_avg;
	const struct l1sched_lchan_desc *lchan_desc;
	struct l1sched_prim *prim;
	struct msgb *msg;

	lchan_desc = &l1sched_lchan_desc[lchan->type];

	msg = l1sched_prim_alloc(L1SCHED_PRIM_T_DATA, PRIM_OP_INDICATION);
	OSMO_ASSERT(msg != NULL);

	prim = l1sched_prim_from_msgb(msg);
	prim->data_ind = (struct l1sched_prim_data_ind) {
		.chdr = {
			.frame_nr = meas->fn,
			.chan_nr = lchan_desc->chan_nr | lchan->ts->index,
			.link_id = lchan_desc->link_id,
			.traffic = traffic,
		},
		.toa256 = meas->toa256,
		.rssi = meas->rssi,
		.n_errors = n_errors,
		.n_bits_total = n_bits_total,
	};

	if (data_len > 0)
		memcpy(msgb_put(msg, data_len), data, data_len);

	return l1sched_prim_to_user(lchan->ts->sched, msg);
}

int l1sched_lchan_emit_data_cnf(struct l1sched_lchan_state *lchan,
				struct msgb *msg, uint32_t fn)
{
	struct l1sched_prim *prim;

	if (msg == NULL)
		return -ENODEV;

	/* convert from DATA.req to DATA.cnf */
	prim = l1sched_prim_from_msgb(msg);
	prim->oph.operation = PRIM_OP_CONFIRM;

	switch (prim->oph.primitive) {
	case L1SCHED_PRIM_T_DATA:
		prim->data_cnf.frame_nr = fn;
		break;
	case L1SCHED_PRIM_T_RACH:
		prim->rach_cnf.chdr.frame_nr = fn;
		break;
	default:
		/* shall not happen */
		OSMO_ASSERT(0);
	}

	return l1sched_prim_to_user(lchan->ts->sched, msg);
}

static int prim_enqeue(struct l1sched_state *sched, struct msgb *msg,
		       const struct l1sched_prim_chdr *chdr)
{
	const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg);
	struct l1sched_lchan_state *lchan;

	lchan = l1sched_find_lchan_by_chan_nr(sched, chdr->chan_nr, chdr->link_id);
	if (OSMO_UNLIKELY(lchan == NULL || !lchan->active)) {
		LOGP_SCHEDD(sched, LOGL_ERROR,
			    "No [active] lchan for primitive " L1SCHED_PRIM_STR_FMT " "
			    "(fn=%u, chan_nr=0x%02x, link_id=0x%02x, len=%u): %s\n",
			    L1SCHED_PRIM_STR_ARGS(prim),
			    chdr->frame_nr, chdr->chan_nr, chdr->link_id,
			    msgb_l2len(msg), msgb_hexdump_l2(msg));
		msgb_free(msg);
		return -ENODEV;
	}

	LOGP_LCHAND(lchan, LOGL_DEBUG,
		    "Enqueue primitive " L1SCHED_PRIM_STR_FMT " "
		    "(fn=%u, chan_nr=0x%02x, link_id=0x%02x, len=%u): %s\n",
		    L1SCHED_PRIM_STR_ARGS(prim),
		    chdr->frame_nr, chdr->chan_nr, chdr->link_id,
		    msgb_l2len(msg), msgb_hexdump_l2(msg));

	msgb_enqueue(&lchan->tx_prims, msg);
	return 0;
}

int l1sched_prim_from_user(struct l1sched_state *sched, struct msgb *msg)
{
	const struct l1sched_prim *prim = l1sched_prim_from_msgb(msg);

	LOGP_SCHEDD(sched, LOGL_DEBUG,
		    "%s(): Rx " L1SCHED_PRIM_STR_FMT "\n",
		    __func__, L1SCHED_PRIM_STR_ARGS(prim));

	switch (OSMO_PRIM_HDR(&prim->oph)) {
	case OSMO_PRIM(L1SCHED_PRIM_T_DATA, PRIM_OP_REQUEST):
		return prim_enqeue(sched, msg, &prim->data_req);
	case OSMO_PRIM(L1SCHED_PRIM_T_RACH, PRIM_OP_REQUEST):
		return prim_enqeue(sched, msg, &prim->rach_req.chdr);
	default:
		LOGP_SCHEDD(sched, LOGL_ERROR,
			 "%s(): Unhandled primitive " L1SCHED_PRIM_STR_FMT "\n",
			 __func__, L1SCHED_PRIM_STR_ARGS(prim));
		msgb_free(msg);
		return -ENOTSUP;
	}
}
