// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2021 MediaTek Inc.
 */

#include "atsc30_log.h"
#include "alp_adaption.h"
#include "alp_rdt.h"
#include "rohc_parser.h"
#include "rohc_build.h"
#include "atsc30_utils.h"

//-------------------------------------------------------------------------------------------------
//  Debug Constants and Macros
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Constants
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Macros
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Structs and Enums
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Functions Prototype
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Variables
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Internal Functions
//-------------------------------------------------------------------------------------------------
static inline bool alp_get_rohc_packet_payload(
		net_buf_t *const payload,
		const u8 rohc_header_len,
		const net_buf_t rohc_pkt)
{
	if (ATSC30_NULL_PTR_CHECK(payload))
		return false;

	*payload = net_buf_init_empty(NULL, 0);

	if (rohc_pkt.len >= rohc_header_len) {
		u32 payload_len = rohc_pkt.len - rohc_header_len;
		u8 *payload_start =
			net_buf_get_ptr(rohc_pkt, rohc_header_len);

		*payload = net_buf_init_full(payload_start, payload_len);

		return true;
	}

	return false;
}

static inline bool is_random_ip_id(const u8 * const dyn_chain)
{
	bool rnd = false;

	if (dyn_chain != NULL)
		rnd = (dyn_chain[4] & 0x40);

	return rnd;
}

static inline bool is_udp_checksum_enabled(const u8 * const dyn_chain)
{
	u16 udp_csum = 0;

	if (dyn_chain != NULL)
		udp_csum = *(u16 *)(&dyn_chain[5]);

	return (udp_csum != 0);
}

static bool _alp_adapt_build_rohc_context(
		rohc_parser_t *const parser,
		rohc_packet_info_t  *const pkt_info,
		rdt_rohc_context_info_t *const rdt_ctx,
		const net_buf_t rohc_pkt)
{
	if (ATSC30_NULL_PTR_CHECK(parser))
		return false;
	if (ATSC30_NULL_PTR_CHECK(pkt_info))
		return false;
	if (ATSC30_NULL_PTR_CHECK(rdt_ctx))
		return false;

	if (rdt_ctx->rohc_ctx.d_chain_len >= ROHC_DYNAMIC_CHAIN_LEN) {
		rohc_dynamic_flags_t dyn_flags;

		dyn_flags.rnd =
			is_random_ip_id(rdt_ctx->rohc_ctx.d_chain);
		dyn_flags.en_udp_csum =
			is_udp_checksum_enabled(rdt_ctx->rohc_ctx.d_chain);

		ALP_PARSER_LOG_DEBUG(
			"set rohc parser flags: rnd %d, csum%d\n",
			dyn_flags.rnd,
			dyn_flags.en_udp_csum);
		rohc_parser_set_dynamic_flags(parser, dyn_flags);
	}

	if (!rohc_parser_parse_packet(parser, pkt_info, rohc_pkt)) {
		ALP_PARSER_LOG_ERROR(
			"%s rohc_parse_packet fail\n",
			__func__);

		return false;
	}

	if (pkt_info->ctx.d_chain_len >= ROHC_DYNAMIC_CHAIN_LEN) {
		if (rdt_ctx->rohc_ctx.s_chain_len == ROHC_STATIC_CHAIN_LEN) {
			pkt_info->packet_type = ROHC_PACKET_IR;
			ALP_PARSER_LOG_DEBUG(
				"rdt got static context, de-adapt to IR-packet\n");

			memcpy(pkt_info->ctx.s_chain,
				rdt_ctx->rohc_ctx.s_chain,
				rdt_ctx->rohc_ctx.s_chain_len);
			pkt_info->ctx.s_chain_len =
				rdt_ctx->rohc_ctx.s_chain_len;
		} else {
			ALP_PARSER_LOG_WARN(
				"rdt got static but no dyn conetxt, unable to deadapt to IR-packet\n");

			return false;
		}
	} else {
		if ((rdt_ctx->rohc_ctx.s_chain_len == ROHC_STATIC_CHAIN_LEN)
			&& (rdt_ctx->rohc_ctx.d_chain_len >= ROHC_DYNAMIC_CHAIN_LEN)) {
			pkt_info->packet_type = ROHC_PACKET_IR;

			ALP_PARSER_LOG_DEBUG(
				"rdt got dynamic and static context, de-adapt to IR-packet\n");
			memcpy(&pkt_info->ctx,
				&rdt_ctx->rohc_ctx,
				sizeof(rohc_packet_decomp_decomp_context_t));
		} else if (rdt_ctx->rohc_ctx.d_chain_len >= ROHC_DYNAMIC_CHAIN_LEN) {
			pkt_info->packet_type = ROHC_PACKET_IR_DYN;

			ALP_PARSER_LOG_DEBUG(
				"rdt got dynamic and static context, de-adapt to IR-DYN-packet\n");
			memcpy(pkt_info->ctx.d_chain,
				rdt_ctx->rohc_ctx.d_chain,
				rdt_ctx->rohc_ctx.d_chain_len);
			pkt_info->ctx.d_chain_len = rdt_ctx->rohc_ctx.d_chain_len;
		} else {
			ALP_PARSER_LOG_ERROR(
				"%s, no chain update, unable to do deadaption\n",
				__func__);

			return false;
		}
	}

	if (rdt_ctx->rohc_ctx.d_chain_len >= ROHC_DYNAMIC_CHAIN_LEN) {
		// Dynamic part can only be used once,
		// and static part can be re-used.
		rdt_ctx->rohc_ctx.d_chain_len = 0;
	}

	return true;
}

static bool _alp_adapt_need_deadaption(
		const adaption_mode_t adpt_mode,
		rdt_rohc_context_info_t * const rdt_ctx)
{
	bool de_adapt = false;

	if (ATSC30_NULL_PTR_CHECK(rdt_ctx))
		return false;

	if (rdt_ctx->new_update) {
		switch (adpt_mode) {
		case adapt_mode2:
			ALP_PARSER_LOG_DEBUG(
				"Adaption mode2, rdt update, need deadaption\n");
			de_adapt = true;
			break;

		case adapt_mode3:
			ALP_PARSER_LOG_DEBUG(
				"Adaption mode 3, rdt update, need deadaption\n");
			de_adapt = true;
			break;

		default:
			return false;
		}
	}

	rdt_ctx->new_update = false; //reset rdt update flag

	return de_adapt;
}

//-------------------------------------------------------------------------------------------------
//  Public Functions
//-------------------------------------------------------------------------------------------------
bool alp_adaption_check(
		alp_per_plp_context_t *const ppx,
		net_buf_t *const dst_decomp_input_pkt,
		net_buf_t src_rohc_pkt)
{
	u8 cid = MAX_CTX_NUM_PER_PLP;

	if (ATSC30_NULL_PTR_CHECK(ppx))
		return false;

	if (!rohc_parser_parse_cid(
		src_rohc_pkt,
		ppx->parser.cid_type,
		&cid)) {
		ALP_PARSER_LOG_ERROR(
			"%s rohc_parse_cid fail\n",
			__func__);

		return false;
	}

	if (ALP_PARSER_VERIFY(cid >= MAX_CTX_NUM_PER_PLP))
		return false;

	if (_alp_adapt_need_deadaption(ppx->rdt.adpt_mode, &ppx->rdt.ctx[cid])) {
		rohc_packet_info_t rohc_pkt_info;
		net_buf_t payload;

		memset(&rohc_pkt_info, 0, sizeof(rohc_packet_info_t));

		if (!_alp_adapt_build_rohc_context(
			&ppx->parser,
			&rohc_pkt_info,
			&ppx->rdt.ctx[cid],
			src_rohc_pkt)) {
			ALP_PARSER_LOG_ERROR(
				"%s fail to build deadapt packet info\n",
				__func__);

			return false;
		}

		if (!alp_get_rohc_packet_payload(
			&payload,
			rohc_pkt_info.tot_header_len,
			src_rohc_pkt)) {
			ALP_PARSER_LOG_ERROR(
				"%s fail to get rohc packet payload\n",
				__func__);

			return false;
		}

		if (!rohc_build_IR_X_packet(
			&ppx->buf[PER_PLP_ADAPT_BUF],
			&rohc_pkt_info, payload)) {
			ALP_PARSER_LOG_DEBUG(
				"deadaption change to  %s done\n",
				rohc_packet_type_2_str(rohc_pkt_info.packet_type));

			return false;
		}

		*dst_decomp_input_pkt = ppx->buf[PER_PLP_ADAPT_BUF];
	} else {
		*dst_decomp_input_pkt = src_rohc_pkt;
	}

	return true;
}
