/*
 * Copyright (c) 2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 *
 * SLIP driver using uart_pipe. This is meant for network connectivity between
 * host and qemu. The host will need to run tunslip process.
 */

#define LOG_MODULE_NAME slip
#define LOG_LEVEL CONFIG_SLIP_LOG_LEVEL

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME);

#include <stdio.h>

#include <zephyr/kernel.h>

#include <errno.h>
#include <stddef.h>
#include <zephyr/sys/util.h>
#include <zephyr/net/ethernet.h>
#include <zephyr/net/net_core.h>
#include <zephyr/net/dummy.h>
#include <zephyr/drivers/uart_pipe.h>
#include <zephyr/random/random.h>

#include "slip.h"

#define SLIP_END     0300
#define SLIP_ESC     0333
#define SLIP_ESC_END 0334
#define SLIP_ESC_ESC 0335

enum slip_state {
	STATE_GARBAGE,
	STATE_OK,
	STATE_ESC,
};

#if defined(CONFIG_NET_BUF_FIXED_DATA_SIZE)
#define SLIP_FRAG_LEN CONFIG_NET_BUF_DATA_SIZE
#else
#define SLIP_FRAG_LEN _SLIP_MTU
#endif /* CONFIG_NET_BUF_FIXED_DATA_SIZE */

static inline void slip_writeb(unsigned char c)
{
	uint8_t buf[1] = { c };

	uart_pipe_send(&buf[0], 1);
}

/**
 *  @brief Write byte to SLIP, escape if it is END or ESC character
 *
 *  @param c  a byte to write
 */
static void slip_writeb_esc(unsigned char c)
{
	switch (c) {
	case SLIP_END:
		/* If it's the same code as an END character,
		 * we send a special two character code so as
		 * not to make the receiver think we sent
		 * an END.
		 */
		slip_writeb(SLIP_ESC);
		slip_writeb(SLIP_ESC_END);
		break;
	case SLIP_ESC:
		/* If it's the same code as an ESC character,
		 * we send a special two character code so as
		 * not to make the receiver think we sent
		 * an ESC.
		 */
		slip_writeb(SLIP_ESC);
		slip_writeb(SLIP_ESC_ESC);
		break;
	default:
		slip_writeb(c);
	}
}

int slip_send(const struct device *dev, struct net_pkt *pkt)
{
	struct net_buf *buf;
	uint8_t *ptr;
	uint16_t i;
	uint8_t c;

	ARG_UNUSED(dev);

	if (!pkt->buffer) {
		/* No data? */
		return -ENODATA;
	}

	slip_writeb(SLIP_END);

	for (buf = pkt->buffer; buf; buf = buf->frags) {
		ptr = buf->data;
		for (i = 0U; i < buf->len; ++i) {
			c = *ptr++;
			slip_writeb_esc(c);
		}

		if (LOG_LEVEL >= LOG_LEVEL_DBG) {
			LOG_DBG("sent data %d bytes", buf->len);

			if (buf->len) {
				LOG_HEXDUMP_DBG(buf->data,
						buf->len, "<slip ");
			}
		}
	}

	slip_writeb(SLIP_END);

	return 0;
}

static struct net_pkt *slip_poll_handler(struct slip_context *slip)
{
	if (slip->last && slip->last->len) {
		return slip->rx;
	}

	return NULL;
}

static inline struct net_if *get_iface(struct slip_context *context,
				       uint16_t vlan_tag)
{
#if defined(CONFIG_NET_VLAN)
	struct net_if *iface;

	iface = net_eth_get_vlan_iface(context->iface, vlan_tag);
	if (!iface) {
		return context->iface;
	}

	return iface;
#else
	ARG_UNUSED(vlan_tag);

	return context->iface;
#endif
}

static void process_msg(struct slip_context *slip)
{
	uint16_t vlan_tag = NET_VLAN_TAG_UNSPEC;
	struct net_pkt *pkt;

	pkt = slip_poll_handler(slip);
	if (!pkt || !pkt->buffer) {
		return;
	}

#if defined(CONFIG_NET_VLAN)
	{
		struct net_eth_hdr *hdr = NET_ETH_HDR(pkt);

		if (ntohs(hdr->type) == NET_ETH_PTYPE_VLAN) {
			struct net_eth_vlan_hdr *hdr_vlan =
				(struct net_eth_vlan_hdr *)NET_ETH_HDR(pkt);

			net_pkt_set_vlan_tci(pkt, ntohs(hdr_vlan->vlan.tci));
			vlan_tag = net_pkt_vlan_tag(pkt);
		}
	}
#endif

	if (net_recv_data(get_iface(slip, vlan_tag), pkt) < 0) {
		net_pkt_unref(pkt);
	}

	slip->rx = NULL;
	slip->last = NULL;
}

static inline int slip_input_byte(struct slip_context *slip,
				  unsigned char c)
{
	switch (slip->state) {
	case STATE_GARBAGE:
		if (c == SLIP_END) {
			slip->state = STATE_OK;
		}

		return 0;
	case STATE_ESC:
		if (c == SLIP_ESC_END) {
			c = SLIP_END;
		} else if (c == SLIP_ESC_ESC) {
			c = SLIP_ESC;
		} else {
			slip->state = STATE_GARBAGE;
			SLIP_STATS(slip->garbage++);
			return 0;
		}

		slip->state = STATE_OK;

		break;
	case STATE_OK:
		if (c == SLIP_ESC) {
			slip->state = STATE_ESC;
			return 0;
		}

		if (c == SLIP_END) {
			slip->state = STATE_OK;
			slip->first = false;

			if (slip->rx) {
				return 1;
			}

			return 0;
		}

		if (slip->first && !slip->rx) {
			/* Must have missed buffer allocation on first byte. */
			return 0;
		}

		if (!slip->first) {
			slip->first = true;

			slip->rx = net_pkt_rx_alloc_on_iface(slip->iface,
							     K_NO_WAIT);
			if (!slip->rx) {
				LOG_ERR("[%p] cannot allocate pkt", slip);
				return 0;
			}

			slip->last = net_pkt_get_frag(slip->rx, SLIP_FRAG_LEN,
						      K_NO_WAIT);
			if (!slip->last) {
				LOG_ERR("[%p] cannot allocate 1st data buffer",
					slip);
				net_pkt_unref(slip->rx);
				slip->rx = NULL;
				return 0;
			}

			net_pkt_append_buffer(slip->rx, slip->last);
			slip->ptr = net_pkt_ip_data(slip->rx);
		}

		break;
	}

	/* It is possible that slip->last is not set during the startup
	 * of the device. If this happens do not continue and overwrite
	 * some random memory.
	 */
	if (!slip->last) {
		return 0;
	}

	if (!net_buf_tailroom(slip->last)) {
		/* We need to allocate a new buffer */
		struct net_buf *buf;

		buf = net_pkt_get_reserve_rx_data(SLIP_FRAG_LEN, K_NO_WAIT);
		if (!buf) {
			LOG_ERR("[%p] cannot allocate next data buf", slip);
			net_pkt_unref(slip->rx);
			slip->rx = NULL;
			slip->last = NULL;

			return 0;
		}

		net_buf_frag_insert(slip->last, buf);
		slip->last = buf;
		slip->ptr = slip->last->data;
	}

	/* The net_buf_add_u8() cannot add data to ll header so we need
	 * a way to do it.
	 */
	if (slip->ptr < slip->last->data) {
		*slip->ptr = c;
	} else {
		slip->ptr = net_buf_add_u8(slip->last, c);
	}

	slip->ptr++;

	return 0;
}

static uint8_t *recv_cb(uint8_t *buf, size_t *off)
{
	struct slip_context *slip =
		CONTAINER_OF(buf, struct slip_context, buf[0]);
	size_t i;

	if (!slip->init_done) {
		*off = 0;
		return buf;
	}

	for (i = 0; i < *off; i++) {
		if (slip_input_byte(slip, buf[i])) {

			if (LOG_LEVEL >= LOG_LEVEL_DBG) {
				struct net_buf *rx_buf = slip->rx->buffer;
				int bytes = net_buf_frags_len(rx_buf);
				int count = 0;

				while (bytes && rx_buf) {
					char msg[6 + 10 + 1];

					snprintk(msg, sizeof(msg),
						 ">slip %2d", count);

					LOG_HEXDUMP_DBG(rx_buf->data, rx_buf->len,
							msg);

					rx_buf = rx_buf->frags;
					count++;
				}

				LOG_DBG("[%p] received data %d bytes", slip,
					bytes);
			}

			process_msg(slip);
			break;
		}
	}

	*off = 0;

	return buf;
}

int slip_init(const struct device *dev)
{
	struct slip_context *slip = dev->data;

	LOG_DBG("[%p] dev %p", slip, dev);

	slip->state = STATE_OK;
	slip->rx = NULL;
	slip->first = false;

#if defined(CONFIG_SLIP_TAP) && defined(CONFIG_NET_IPV4)
	LOG_DBG("ARP enabled");
#endif

	uart_pipe_register(slip->buf, sizeof(slip->buf), recv_cb);

	return 0;
}

static inline struct net_linkaddr *slip_get_mac(struct slip_context *slip)
{
	slip->ll_addr.addr = slip->mac_addr;
	slip->ll_addr.len = sizeof(slip->mac_addr);

	return &slip->ll_addr;
}

void slip_iface_init(struct net_if *iface)
{
	struct slip_context *slip = net_if_get_device(iface)->data;
	struct net_linkaddr *ll_addr;

#if defined(CONFIG_SLIP_TAP) && defined(CONFIG_NET_L2_ETHERNET)
	ethernet_init(iface);
#endif

#if defined(CONFIG_NET_LLDP)
	net_lldp_set_lldpdu(iface);
#endif

	if (slip->init_done) {
		return;
	}

	ll_addr = slip_get_mac(slip);

	slip->init_done = true;
	slip->iface = iface;

	if (CONFIG_SLIP_MAC_ADDR[0] != 0) {
		if (net_bytes_from_str(slip->mac_addr, sizeof(slip->mac_addr),
				       CONFIG_SLIP_MAC_ADDR) < 0) {
			goto use_random_mac;
		}
	} else {
use_random_mac:
		/* 00-00-5E-00-53-xx Documentation RFC 7042 */
		slip->mac_addr[0] = 0x00;
		slip->mac_addr[1] = 0x00;
		slip->mac_addr[2] = 0x5E;
		slip->mac_addr[3] = 0x00;
		slip->mac_addr[4] = 0x53;
		slip->mac_addr[5] = sys_rand32_get();
	}
	net_if_set_link_addr(iface, ll_addr->addr, ll_addr->len,
			     NET_LINK_ETHERNET);
}


#if !defined(CONFIG_SLIP_TAP)
static struct slip_context slip_context_data;

static const struct dummy_api slip_if_api = {
	.iface_api.init = slip_iface_init,

	.send = slip_send,
};

#define _SLIP_L2_LAYER DUMMY_L2
#define _SLIP_L2_CTX_TYPE NET_L2_GET_CTX_TYPE(DUMMY_L2)

NET_DEVICE_INIT(slip, CONFIG_SLIP_DRV_NAME, slip_init, NULL,
		&slip_context_data, NULL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
		&slip_if_api, _SLIP_L2_LAYER, _SLIP_L2_CTX_TYPE, _SLIP_MTU);
#endif
