/*  Bluetooth Mesh */

/*
 * Copyright (c) 2017 Intel Corporation
 * Copyright (c) 2021 Lingao Meng
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>

#include <zephyr/net_buf.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/mesh.h>

#include <zephyr/bluetooth/hci.h>

#include "common/bt_str.h"

#include "mesh.h"
#include "net.h"
#include "rpl.h"
#include "transport.h"
#include "prov.h"
#include "beacon.h"
#include "foundation.h"
#include "access.h"
#include "proxy.h"
#include "proxy_msg.h"

#define LOG_LEVEL CONFIG_BT_MESH_PROXY_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_proxy);

#define PDU_SAR(data)      (data[0] >> 6)

/* MshPRTv1.1: 6.3.2.2:
 * "The timeout for the SAR transfer is 20 seconds. When the timeout
 *  expires, the Proxy Server shall disconnect."
 */
#define PROXY_SAR_TIMEOUT  K_SECONDS(20)

#define SAR_COMPLETE       0x00
#define SAR_FIRST          0x01
#define SAR_CONT           0x02
#define SAR_LAST           0x03

#define PDU_HDR(sar, type) (sar << 6 | (type & BIT_MASK(6)))

static uint8_t __noinit bufs[CONFIG_BT_MAX_CONN * CONFIG_BT_MESH_PROXY_MSG_LEN];

static struct bt_mesh_proxy_role roles[CONFIG_BT_MAX_CONN];

static int conn_count;

static void proxy_queue_put(struct bt_mesh_proxy_role *role, struct bt_mesh_adv *adv)
{
	k_fifo_put(&role->pending, &(adv->gatt_bearer[bt_conn_index(role->conn)]));
}

static struct bt_mesh_adv *proxy_queue_get(struct bt_mesh_proxy_role *role)
{
	void *gatt_bearer;

	gatt_bearer = k_fifo_get(&role->pending, K_NO_WAIT);
	if (!gatt_bearer) {
		return NULL;
	}

	return CONTAINER_OF(gatt_bearer, struct bt_mesh_adv,
			    gatt_bearer[bt_conn_index(role->conn)]);
}

static void proxy_sar_timeout(struct k_work *work)
{
	struct bt_mesh_proxy_role *role;
	struct k_work_delayable *dwork = k_work_delayable_from_work(work);

	LOG_WRN("Proxy SAR timeout");

	role = CONTAINER_OF(dwork, struct bt_mesh_proxy_role, sar_timer);

	while (!k_fifo_is_empty(&role->pending)) {
		struct bt_mesh_adv *adv = proxy_queue_get(role);

		__ASSERT_NO_MSG(adv);

		bt_mesh_adv_unref(adv);
	}

	if (role->conn) {
		bt_conn_disconnect(role->conn,
				   BT_HCI_ERR_REMOTE_USER_TERM_CONN);
	}
}

ssize_t bt_mesh_proxy_msg_recv(struct bt_conn *conn,
			       const void *buf, uint16_t len)
{
	const uint8_t *data = buf;
	struct bt_mesh_proxy_role *role = &roles[bt_conn_index(conn)];

	if (net_buf_simple_tailroom(&role->buf) < len - 1) {
		LOG_WRN("Proxy role buffer overflow");
		return -EINVAL;
	}

	switch (PDU_SAR(data)) {
	case SAR_COMPLETE:
		if (role->buf.len) {
			LOG_WRN("Complete PDU while a pending incomplete one");
			return -EINVAL;
		}

		role->msg_type = PDU_TYPE(data);
		net_buf_simple_add_mem(&role->buf, data + 1, len - 1);
		role->cb.recv(role);
		net_buf_simple_reset(&role->buf);
		break;

	case SAR_FIRST:
		if (role->buf.len) {
			LOG_WRN("First PDU while a pending incomplete one");
			return -EINVAL;
		}

		k_work_reschedule(&role->sar_timer, PROXY_SAR_TIMEOUT);
		role->msg_type = PDU_TYPE(data);
		net_buf_simple_add_mem(&role->buf, data + 1, len - 1);
		break;

	case SAR_CONT:
		if (!role->buf.len) {
			LOG_WRN("Continuation with no prior data");
			return -EINVAL;
		}

		if (role->msg_type != PDU_TYPE(data)) {
			LOG_WRN("Unexpected message type in continuation");
			return -EINVAL;
		}

		k_work_reschedule(&role->sar_timer, PROXY_SAR_TIMEOUT);
		net_buf_simple_add_mem(&role->buf, data + 1, len - 1);
		break;

	case SAR_LAST:
		if (!role->buf.len) {
			LOG_WRN("Last SAR PDU with no prior data");
			return -EINVAL;
		}

		if (role->msg_type != PDU_TYPE(data)) {
			LOG_WRN("Unexpected message type in last SAR PDU");
			return -EINVAL;
		}

		/* If this fails, the work handler exits early, as there's no
		 * active SAR buffer.
		 */
		(void)k_work_cancel_delayable(&role->sar_timer);
		net_buf_simple_add_mem(&role->buf, data + 1, len - 1);
		role->cb.recv(role);
		net_buf_simple_reset(&role->buf);
		break;
	}

	return len;
}

int bt_mesh_proxy_msg_send(struct bt_conn *conn, uint8_t type,
			   struct net_buf_simple *msg,
			   bt_gatt_complete_func_t end, void *user_data)
{
	int err;
	uint16_t att_mtu = bt_gatt_get_mtu(conn);
	uint16_t mtu;
	struct bt_mesh_proxy_role *role = &roles[bt_conn_index(conn)];

	LOG_DBG("conn %p type 0x%02x len %u: %s", (void *)conn, type, msg->len,
		bt_hex(msg->data, msg->len));

	/* ATT_MTU - OpCode (1 byte) - Handle (2 bytes) */
	if (att_mtu < 3) {
		LOG_WRN("Invalid ATT MTU: %d", att_mtu);
		return -EINVAL;
	}

	mtu = att_mtu - 3;
	if (mtu > msg->len) {
		net_buf_simple_push_u8(msg, PDU_HDR(SAR_COMPLETE, type));
		return role->cb.send(conn, msg->data, msg->len, end, user_data);
	}

	net_buf_simple_push_u8(msg, PDU_HDR(SAR_FIRST, type));
	err = role->cb.send(conn, msg->data, mtu, NULL, NULL);
	if (err) {
		return err;
	}

	net_buf_simple_pull(msg, mtu);

	while (msg->len) {
		if (msg->len + 1 <= mtu) {
			net_buf_simple_push_u8(msg, PDU_HDR(SAR_LAST, type));
			err = role->cb.send(conn, msg->data, msg->len, end, user_data);
			if (err) {
				return err;
			}

			break;
		}

		net_buf_simple_push_u8(msg, PDU_HDR(SAR_CONT, type));
		err = role->cb.send(conn, msg->data, mtu, NULL, NULL);
		if (err) {
			return err;
		}

		net_buf_simple_pull(msg, mtu);
	}

	return 0;
}

static void buf_send_end(struct bt_conn *conn, void *user_data)
{
	struct bt_mesh_adv *adv = user_data;

	bt_mesh_adv_unref(adv);
}

static int proxy_relay_send(struct bt_conn *conn, struct bt_mesh_adv *adv)
{
	int err;

	NET_BUF_SIMPLE_DEFINE(msg, 1 + BT_MESH_NET_MAX_PDU_LEN);

	/* Proxy PDU sending modifies the original buffer,
	 * so we need to make a copy.
	 */
	net_buf_simple_reserve(&msg, 1);
	net_buf_simple_add_mem(&msg, adv->b.data, adv->b.len);

	err = bt_mesh_proxy_msg_send(conn, BT_MESH_PROXY_NET_PDU,
				     &msg, buf_send_end, bt_mesh_adv_ref(adv));

	bt_mesh_adv_send_start(0, err, &adv->ctx);
	if (err) {
		LOG_ERR("Failed to send proxy message (err %d)", err);

		/* If segment_and_send() fails the buf_send_end() callback will
		 * not be called, so we need to clear the user data (net_buf,
		 * which is just opaque data to segment_and send) reference given
		 * to segment_and_send() here.
		 */
		bt_mesh_adv_unref(adv);
	}

	return err;
}

int bt_mesh_proxy_relay_send(struct bt_conn *conn, struct bt_mesh_adv *adv)
{
	struct bt_mesh_proxy_role *role = &roles[bt_conn_index(conn)];

	proxy_queue_put(role, bt_mesh_adv_ref(adv));

	bt_mesh_wq_submit(&role->work);

	return 0;
}

static void proxy_msg_send_pending(struct k_work *work)
{
	struct bt_mesh_proxy_role *role = CONTAINER_OF(work, struct bt_mesh_proxy_role, work);
	struct bt_mesh_adv *adv;

	if (!role->conn) {
		return;
	}

	adv = proxy_queue_get(role);
	if (!adv) {
		return;
	}

	(void)proxy_relay_send(role->conn, adv);
	bt_mesh_adv_unref(adv);

	if (!k_fifo_is_empty(&role->pending)) {
		bt_mesh_wq_submit(&role->work);
	}
}

static void proxy_msg_init(struct bt_mesh_proxy_role *role)
{
	/* Check if buf has been allocated, in this way, we no longer need
	 * to repeat the operation.
	 */
	if (role->buf.__buf) {
		net_buf_simple_reset(&role->buf);
		return;
	}

	net_buf_simple_init_with_data(&role->buf,
				      &bufs[bt_conn_index(role->conn) *
					    CONFIG_BT_MESH_PROXY_MSG_LEN],
				      CONFIG_BT_MESH_PROXY_MSG_LEN);

	net_buf_simple_reset(&role->buf);

	k_fifo_init(&role->pending);
	k_work_init(&role->work, proxy_msg_send_pending);

	k_work_init_delayable(&role->sar_timer, proxy_sar_timeout);
}

struct bt_mesh_proxy_role *bt_mesh_proxy_role_setup(struct bt_conn *conn,
						    proxy_send_cb_t send,
						    proxy_recv_cb_t recv)
{
	struct bt_mesh_proxy_role *role;

	conn_count++;

	role = &roles[bt_conn_index(conn)];

	role->conn = bt_conn_ref(conn);
	proxy_msg_init(role);

	role->cb.recv = recv;
	role->cb.send = send;

	return role;
}

void bt_mesh_proxy_role_cleanup(struct bt_mesh_proxy_role *role)
{
	/* If this fails, the work handler exits early, as
	 * there's no active connection.
	 */
	(void)k_work_cancel_delayable(&role->sar_timer);
	bt_conn_unref(role->conn);
	role->conn = NULL;

	conn_count--;

	bt_mesh_adv_gatt_update();
}

bool bt_mesh_proxy_has_avail_conn(void)
{
	return conn_count < CONFIG_BT_MESH_MAX_CONN;
}
