// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2008-2009, 2012-2014, 2016-2018, The Linux Foundation.
 * All rights reserved.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/ratelimit.h>
#include <linux/crc-ccitt.h>
#include "diagchar_hdlc.h"
#include "diagchar.h"


MODULE_LICENSE("GPL v2");

#define CRC_16_L_SEED           0xFFFF

#define CRC_16_L_STEP(xx_crc, xx_c) \
	crc_ccitt_byte(xx_crc, xx_c)

void diag_hdlc_encode(struct diag_send_desc_type *src_desc,
		      struct diag_hdlc_dest_type *enc)
{
	uint8_t *dest;
	uint8_t *dest_last;
	const uint8_t *src;
	const uint8_t *src_last;
	uint16_t crc;
	unsigned char src_byte = 0;
	enum diag_send_state_enum_type state;
	unsigned int used = 0;

	if (!src_desc || !enc)
		return;

	/* Copy parts to local variables. */
	src = src_desc->pkt;
	src_last = src_desc->last;
	state = src_desc->state;
	dest = enc->dest;
	dest_last = enc->dest_last;

	if (state == DIAG_STATE_START) {
		crc = CRC_16_L_SEED;
		state++;
	} else {
		/* Get a local copy of the CRC */
		crc = enc->crc;
	}

	/* dest or dest_last may be NULL to trigger a
	 * state transition only.
	 */
	if (dest && dest_last) {
		/* This condition needs to include the possibility
		 * of 2 dest bytes for an escaped byte
		 */
		while (src <= src_last && dest <= dest_last) {

			src_byte = *src++;
			if ((src_byte == CONTROL_CHAR) ||
			    (src_byte == ESC_CHAR)) {
				/* If the escape character is not the
				 * last byte
				 */
				if (dest != dest_last) {
					crc = CRC_16_L_STEP(crc, src_byte);
					*dest++ = ESC_CHAR;
					used++;
					*dest++ = src_byte ^ ESC_MASK;
					used++;
				} else {
					src--;
					break;
				}
			} else {
				crc = CRC_16_L_STEP(crc, src_byte);
				*dest++ = src_byte;
				used++;
			}
		}

		if (src > src_last) {
			if (state == DIAG_STATE_BUSY) {
				if (src_desc->terminate) {
					crc = ~crc;
					state++;
				} else {
					/* Done with fragment */
					state = DIAG_STATE_COMPLETE;
				}
			}

			while (dest <= dest_last && state >= DIAG_STATE_CRC1
					&& state < DIAG_STATE_TERM) {
				/* Encode a byte of the CRC next */
				src_byte = crc & 0xFF;

				if ((src_byte == CONTROL_CHAR)
				    || (src_byte == ESC_CHAR)) {

					if (dest != dest_last) {
						*dest++ = ESC_CHAR;
						used++;
						*dest++ = src_byte ^ ESC_MASK;
						used++;
						crc >>= 8;
					} else
						break;
				} else {

					crc >>= 8;
					*dest++ = src_byte;
					used++;
				}
				state++;
			}

			if (state == DIAG_STATE_TERM) {
				if (dest_last >= dest) {
					*dest++ = CONTROL_CHAR;
					used++;
					state++;	/* Complete */
				}
			}
		}
	}

	/* Copy local variables back into the encode structure. */
	enc->dest = dest;
	enc->dest_last = dest_last;
	enc->crc = crc;
	src_desc->pkt = src;
	src_desc->last = src_last;
	src_desc->state = state;
}


int diag_hdlc_decode(struct diag_hdlc_decode_type *hdlc)
{
	uint8_t *src_ptr = NULL, *dest_ptr = NULL;
	unsigned int src_length = 0, dest_length = 0;
	unsigned int len = 0;
	unsigned int i;
	uint8_t src_byte;

	int pkt_bnd = HDLC_INCOMPLETE;
	int msg_start;

	if (hdlc && hdlc->src_ptr && hdlc->dest_ptr &&
	    (hdlc->src_size > hdlc->src_idx) &&
	    (hdlc->dest_size > hdlc->dest_idx)) {

		msg_start = (hdlc->src_idx == 0) ? 1 : 0;

		src_ptr = hdlc->src_ptr;
		src_ptr = &src_ptr[hdlc->src_idx];
		src_length = hdlc->src_size - hdlc->src_idx;

		dest_ptr = hdlc->dest_ptr;
		dest_ptr = &dest_ptr[hdlc->dest_idx];
		dest_length = hdlc->dest_size - hdlc->dest_idx;

		for (i = 0; i < src_length && len < dest_length; i++) {
			src_byte = src_ptr[i];

			if (hdlc->escaping) {
				dest_ptr[len++] = src_byte ^ ESC_MASK;
				hdlc->escaping = 0;
				continue;
			}
			if (src_byte == ESC_CHAR) {
				if (i == (src_length - 1)) {
					hdlc->escaping = 1;
					i++;
					break;
				}
				dest_ptr[len++] = src_ptr[++i] ^ ESC_MASK;
				continue;
			}
			if (src_byte == CONTROL_CHAR) {
				if (msg_start && i == 0 && src_length > 1)
					continue;
				/* Byte 0x7E will be considered as end of
				 * packet
				 */
				dest_ptr[len++] = src_byte;
				i++;
				pkt_bnd = HDLC_COMPLETE;
				break;
			}
			dest_ptr[len++] = src_byte;
		}
		hdlc->src_idx += i;
		hdlc->dest_idx += len;
	}

	return pkt_bnd;
}

int crc_check(uint8_t *buf, uint16_t len)
{
	uint16_t crc = CRC_16_L_SEED;
	uint8_t sent_crc[2] = {0, 0};

	/*
	 * The minimum length of a valid incoming packet is 4. 1 byte
	 * of data and 3 bytes for CRC
	 */
	if (!buf || len < 4) {
		pr_err_ratelimited("diag: In %s, invalid packet or length, buf: 0x%p, len: %d",
				   __func__, buf, len);
		return -EIO;
	}

	/*
	 * Run CRC check for the original input. Skip the last 3 CRC
	 * bytes
	 */
	crc = crc_ccitt(crc, buf, len-3);
	crc ^= CRC_16_L_SEED;

	/* Check the computed CRC against the original CRC bytes. */
	sent_crc[0] = buf[len-3];
	sent_crc[1] = buf[len-2];
	if (crc != *((uint16_t *)sent_crc)) {
		pr_debug("diag: In %s, crc mismatch. expected: %x, sent %x.\n",
				__func__, crc, *((uint16_t *)sent_crc));
		return -EIO;
	}

	return 0;
}
