/*  Bluetooth Mesh */

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

#include "syscfg/syscfg.h"

#define BLE_NPL_LOG_MODULE BLE_MESH_PROXY_LOG
#include <nimble/nimble_npl_log.h>

#if MYNEWT_VAL(BLE_MESH_PROXY)

#include "mesh/mesh.h"
#include "host/ble_att.h"
#include "services/gatt/ble_svc_gatt.h"
#include "../../host/src/ble_hs_priv.h"

#include "mesh_priv.h"
#include "adv.h"
#include "net.h"
#include "rpl.h"
#include "prov.h"
#include "beacon.h"
#include "foundation.h"
#include "access.h"
#include "proxy.h"
#include "proxy_msg.h"

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

#define BT_UUID_16_ENCODE(w16)  \
	(((w16) >>  0) & 0xFF), \
	(((w16) >>  8) & 0xFF)
/* Mesh Profile 1.0 Section 6.6:
 * "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 bufs[CONFIG_BT_MAX_CONN * CONFIG_BT_MESH_PROXY_MSG_LEN];

static struct bt_mesh_proxy_role roles[CONFIG_BT_MAX_CONN];

static void proxy_sar_timeout(struct ble_npl_event *work)
{
	struct bt_mesh_proxy_role *role;
	int rc;
	role = ble_npl_event_get_arg(work);


	BT_WARN("Proxy SAR timeout");

	if (role->conn_handle != BLE_HS_CONN_HANDLE_NONE) {
		rc = ble_gap_terminate(role->conn_handle,
				       BLE_ERR_REM_USER_CONN_TERM);
		assert(rc == 0);
	}
}

int bt_mesh_proxy_msg_recv(struct bt_mesh_proxy_role *role,
			       const void *buf, uint16_t len)
{
	const uint8_t *data = buf;

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

	switch (PDU_SAR(data)) {
	case SAR_COMPLETE:
		if (role->buf->om_len) {
			BT_WARN("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->om_len) {
			BT_WARN("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->om_len) {
			BT_WARN("Continuation with no prior data");
			return -EINVAL;
		}

		if (role->msg_type != PDU_TYPE(data)) {
			BT_WARN("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->om_len) {
			BT_WARN("Last SAR PDU with no prior data");
			return -EINVAL;
		}

		if (role->msg_type != PDU_TYPE(data)) {
			BT_WARN("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_mesh_proxy_role *role, uint8_t type,
			   struct os_mbuf *msg)
{
	int err;
	uint16_t mtu;
	uint16_t conn_handle = role->conn_handle;

	BT_DBG("conn_handle %d type 0x%02x len %u: %s", conn_handle, type, msg->om_len,
	       bt_hex(msg->om_data, msg->om_len));

	/* ATT_MTU - OpCode (1 byte) - Handle (2 bytes) */
	mtu = ble_att_mtu(conn_handle) - 3;
	if (mtu > msg->om_len) {
		net_buf_simple_push_u8(msg, PDU_HDR(SAR_COMPLETE, type));
		return role->cb.send(conn_handle, msg->om_data, msg->om_len);
	}

	net_buf_simple_push_u8(msg, PDU_HDR(SAR_FIRST, type));
	err = role->cb.send(conn_handle, msg->om_data, mtu);
	if (err) {
		return err;
	}
	net_buf_simple_pull_mem(msg, mtu);

	while (msg->om_len) {
		if (msg->om_len + 1 < mtu) {
			net_buf_simple_push_u8(msg, PDU_HDR(SAR_LAST, type));
			err = role->cb.send(conn_handle, msg->om_data, msg->om_len);
			if (err) {
				return err;
			}
			break;
		}

		net_buf_simple_push_u8(msg, PDU_HDR(SAR_CONT, type));
		err = role->cb.send(conn_handle, msg->om_data, mtu);
		if (err) {
			return err;
		}
		net_buf_simple_pull_mem(msg, mtu);
	}

	return 0;
}

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 != NULL) {
		net_buf_simple_reset(role->buf);
		return;
	}

	role->buf = NET_BUF_SIMPLE(CONFIG_BT_MESH_PROXY_MSG_LEN);
	net_buf_simple_init_with_data(role->buf,
				      &bufs[role->index *
				      CONFIG_BT_MESH_PROXY_MSG_LEN],
				      CONFIG_BT_MESH_PROXY_MSG_LEN);

	net_buf_simple_reset(role->buf);

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

struct bt_mesh_proxy_role *bt_mesh_proxy_role_find_with_buf(const struct os_mbuf *buf)
{
    unsigned int i;

    for (i = 0; i < CONFIG_BT_MAX_CONN; i++) {
        if (roles[i].buf == buf) {
            return &roles[i];
        }
    }

    return NULL;
}

struct bt_mesh_proxy_role *get_role(uint16_t conn_handle)
{
    unsigned int i;

    for (i = 0; i < CONFIG_BT_MAX_CONN; i++) {
        if (roles[i].conn_handle == BLE_HS_CONN_HANDLE_NONE) {
            roles[i].conn_handle = conn_handle;
            return &roles[i];
        }
    }

    return NULL;
}

struct bt_mesh_proxy_role *bt_mesh_proxy_role_setup(uint16_t conn_handle,
	proxy_send_cb_t send,
	proxy_recv_cb_t recv)
{
	struct bt_mesh_proxy_role *role;

	role = get_role(conn_handle);
	assert(role);

	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);

	role->conn_handle = BLE_HS_CONN_HANDLE_NONE;

	bt_mesh_adv_update();
}

void bt_mesh_proxy_msg_init(void)
{
    unsigned int i;

    for (i = 0; i < MYNEWT_VAL(BLE_MAX_CONNECTIONS); i++) {
        roles[i].index = i;
        roles[i].conn_handle = 0xffff;
    }
}

#endif /* MYNEWT_VAL(BLE_MESH_PROXY) */
