// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2017  Intel Corporation. All rights reserved.
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/uio.h>
#include <wordexp.h>
#include <inttypes.h>

#include <glib.h>

#include "src/shared/util.h"
#include "src/shared/shell.h"
#include "gdbus/gdbus.h"

#include "tools/mesh/config-model.h"

#include "tools/mesh-gatt/mesh-net.h"
#include "tools/mesh-gatt/node.h"
#include "tools/mesh-gatt/keys.h"
#include "tools/mesh-gatt/gatt.h"
#include "tools/mesh-gatt/net.h"
#include "tools/mesh-gatt/prov-db.h"
#include "tools/mesh-gatt/util.h"

struct mesh_model {
	struct mesh_model_ops cbs;
	void *user_data;
	GList *bindings;
	GList *subscriptions;
	uint32_t id;
	struct mesh_publication *pub;
};

struct mesh_element {
	GList *models;
	uint16_t loc;
	uint8_t index;
};

struct mesh_node {
	const char *name;
	GList *net_keys;
	GList *app_keys;
	void *prov;
	GList *elements;
	uint32_t iv_index;
	uint32_t seq_number;
	uint16_t primary_net_idx;
	uint16_t primary;
	uint16_t oob;
	uint16_t features;
	uint8_t dev_uuid[16];
	uint8_t dev_key[16];
	uint8_t num_ele;
	uint8_t ttl;
	bool provisioner;
	struct mesh_node_composition *comp;
};

static GList *nodes;

static struct mesh_node *local_node;

static int match_node_unicast(const void *a, const void *b)
{
	const struct mesh_node *node = a;
	uint16_t dst = GPOINTER_TO_UINT(b);

	if (dst >= node->primary &&
				dst <= (node->primary + node->num_ele - 1))
		return 0;

	return -1;
}

static int match_device_uuid(const void *a, const void *b)
{
	const struct mesh_node *node = a;
	const uint8_t *uuid = b;

	return memcmp(node->dev_uuid, uuid, 16);
}

static int match_element_idx(const void *a, const void *b)
{
	const struct mesh_element *element = a;
	uint32_t index = GPOINTER_TO_UINT(b);

	return (element->index == index) ? 0 : -1;
}

static int match_model_id(const void *a, const void *b)
{
	const struct mesh_model *model = a;
	uint32_t id = GPOINTER_TO_UINT(b);

	return (model->id == id) ? 0 : -1;
}

struct mesh_node *node_find_by_addr(uint16_t addr)
{
	GList *l;

	if (!IS_UNICAST(addr))
		return NULL;

	l = g_list_find_custom(nodes, GUINT_TO_POINTER(addr),
			match_node_unicast);

	if (l)
		return l->data;
	else
		return NULL;
}

struct mesh_node *node_find_by_uuid(uint8_t uuid[16])
{
	GList *l;

	l = g_list_find_custom(nodes, uuid, match_device_uuid);

	if (l)
		return l->data;
	else
		return NULL;
}

struct mesh_node *node_create_new(struct prov_svc_data *prov)
{
	struct mesh_node *node;

	if (node_find_by_uuid(prov->dev_uuid))
		return NULL;

	node = g_malloc0(sizeof(struct mesh_node));
	if (!node)
		return NULL;

	memcpy(node->dev_uuid, prov->dev_uuid, 16);
	node->oob = prov->oob;
	nodes = g_list_append(nodes, node);

	return node;
}

struct mesh_node *node_new(void)
{
	struct mesh_node *node;

	node = g_malloc0(sizeof(struct mesh_node));
	if (!node)
		return NULL;

	nodes = g_list_append(nodes, node);

	return node;
}

static void model_free(void *data)
{
	struct mesh_model *model = data;

	g_list_free(model->bindings);
	g_list_free(model->subscriptions);
	g_free(model->pub);
	g_free(model);
}

static void element_free(void *data)
{
	struct mesh_element *element = data;

	g_list_free_full(element->models, model_free);
	g_free(element);
}

static void free_node_resources(void *data)
{
	struct mesh_node *node = data;
	g_list_free(node->net_keys);
	g_list_free(node->app_keys);

	g_list_free_full(node->elements, element_free);

	if(node->comp)
		g_free(node->comp);

	g_free(node);
}

void node_free(struct mesh_node *node)
{
	if (!node)
		return;
	nodes = g_list_remove(nodes, node);
	free_node_resources(node);
}

void node_cleanup(void)
{
	g_list_free_full(nodes, free_node_resources);
	local_node = NULL;
}

bool node_is_provisioned(struct mesh_node *node)
{
	return (!IS_UNASSIGNED(node->primary));
}

void *node_get_prov(struct mesh_node *node)
{
	return node->prov;
}

void node_set_prov(struct mesh_node *node, void *prov)
{
	node->prov = prov;
}

bool node_app_key_add(struct mesh_node *node, uint16_t idx)
{
	uint32_t index;
	uint16_t net_idx;

	if (!node)
		return false;

	net_idx = keys_app_key_get_bound(idx);
	if (net_idx == NET_IDX_INVALID)
		return false;

	if (!g_list_find(node->net_keys, GUINT_TO_POINTER(net_idx)))
		return false;

	index = (net_idx << 16) + idx;

	if (g_list_find(node->app_keys, GUINT_TO_POINTER(index)))
		return false;

	node->app_keys = g_list_append(node->app_keys, GUINT_TO_POINTER(index));

	return true;
}

bool node_net_key_add(struct mesh_node *node, uint16_t index)
{
	if(!node)
		return false;

	if (g_list_find(node->net_keys, GUINT_TO_POINTER(index)))
		return false;

	node->net_keys = g_list_append(node->net_keys, GUINT_TO_POINTER(index));
	return true;
}

bool node_net_key_delete(struct mesh_node *node, uint16_t index)
{
	GList *l;

	if(!node)
		return false;

	l = g_list_find(node->net_keys, GUINT_TO_POINTER(index));
	if (!l)
		return false;

	node->net_keys = g_list_remove(node->net_keys,
						GUINT_TO_POINTER(index));
	/* TODO: remove all associated app keys and bindings */
	return true;
}

bool node_app_key_delete(struct mesh_node *node, uint16_t net_idx,
				uint16_t idx)
{
	GList *l;
	uint32_t index;

	if(!node)
		return false;

	index = (net_idx << 16) + idx;

	l = g_list_find(node->app_keys, GUINT_TO_POINTER(index));
	if (!l)
		return false;

	node->app_keys = g_list_remove(node->app_keys,
					GUINT_TO_POINTER(index));
	/* TODO: remove all associated bindings */
	return true;
}

void node_set_primary(struct mesh_node *node, uint16_t unicast)
{
	node->primary = unicast;
}

uint16_t node_get_primary(struct mesh_node *node)
{
	if (!node)
		return UNASSIGNED_ADDRESS;
	else
		return node->primary;
}

void node_set_device_key(struct mesh_node *node, uint8_t *key)

{
	if (!node || !key)
		return;

	memcpy(node->dev_key, key, 16);
}

uint8_t *node_get_device_key(struct mesh_node *node)
{
	if (!node)
		return NULL;
	else
		return node->dev_key;
}

void node_set_num_elements(struct mesh_node *node, uint8_t num_ele)
{
	node->num_ele = num_ele;
}

uint8_t node_get_num_elements(struct mesh_node *node)
{
	return node->num_ele;
}

GList *node_get_net_keys(struct mesh_node *node)
{
	if (!node)
		return NULL;
	else
		return node->net_keys;
}

GList *node_get_app_keys(struct mesh_node *node)
{
	if (!node)
		return NULL;
	else
		return node->app_keys;
}

bool node_parse_composition(struct mesh_node *node, uint8_t *data, uint16_t len)
{
	struct mesh_node_composition *comp;
	uint16_t features;
	int i;

	comp = g_malloc0(sizeof(struct mesh_node_composition));
	if (!comp)
		return false;

	/* skip page -- We only support Page Zero */
	data++;
	len--;

	comp->cid = get_le16(&data[0]);
	comp->pid = get_le16(&data[2]);
	comp->vid = get_le16(&data[4]);
	comp->crpl = get_le16(&data[6]);
	features = get_le16(&data[8]);
	data += 10;
	len -= 10;

	comp->relay = !!(features & MESH_FEATURE_RELAY);
	comp->proxy = !!(features & MESH_FEATURE_PROXY);
	comp->friend = !!(features & MESH_FEATURE_FRIEND);
	comp->lpn =  !!(features & MESH_FEATURE_LPN);

	for (i = 0;  i< node->num_ele; i++) {
		uint8_t m, v;
		uint32_t mod_id;
		uint16_t vendor_id;
		struct mesh_element *ele;
		ele = g_malloc0(sizeof(struct mesh_element));
		if (!ele) {
			g_free(comp);
			return false;
		}

		ele->index = i;
		ele->loc = get_le16(data);
		data += 2;
		node->elements = g_list_append(node->elements, ele);

		m = *data++;
		v = *data++;
		len -= 2;

		while (len >= 2 && m--) {
			mod_id = get_le16(data);
			/* initialize uppper 16 bits to 0xffff for SIG models */
			mod_id |= 0xffff0000;
			if (!node_set_model(node, ele->index, mod_id)) {
				g_free(comp);
				return false;
			}
			data += 2;
			len -= 2;
		}
		while (len >= 4 && v--) {
			mod_id = get_le16(data + 2);
			vendor_id = get_le16(data);
			mod_id |= (vendor_id << 16);
			if (!node_set_model(node, ele->index, mod_id)) {
				g_free(comp);
				return false;
			}
			data += 4;
			len -= 4;
		}

	}

	node->comp = comp;
	return true;
}

bool node_set_local_node(struct mesh_node *node)
{
	if (local_node) {
		bt_shell_printf("Local node already registered\n");
		return false;
	}
	net_register_unicast(node->primary, node->num_ele);

	local_node = node;
	local_node->provisioner = true;

	return true;
}

struct mesh_node *node_get_local_node()
{
	return local_node;
}

uint16_t node_get_primary_net_idx(struct mesh_node *node)
{
	if (node == NULL)
		return NET_IDX_INVALID;

	return node->primary_net_idx;
}

static bool deliver_model_data(struct mesh_element* element, uint16_t src,
				uint16_t app_idx, uint8_t *data, uint16_t len)
{
	GList *l;

	for(l = element->models; l; l = l->next) {
		struct mesh_model *model = l->data;

		if (!g_list_find(model->bindings, GUINT_TO_POINTER(app_idx)))
			continue;

		if (model->cbs.recv &&
			model->cbs.recv(src, data, len, model->user_data))
			return true;
	}

	return false;
}

void node_local_data_handler(uint16_t src, uint32_t dst,
		uint32_t iv_index, uint32_t seq_num,
		uint16_t app_idx, uint8_t *data, uint16_t len)
{
	GList *l;
	bool res;
	uint64_t iv_seq;
	uint64_t iv_seq_remote;
	uint8_t ele_idx;
	struct mesh_element *element;
	struct mesh_node *remote;
	bool loopback;

	if (!local_node || seq_num > 0xffffff)
		return;

	iv_seq = iv_index << 24;
	iv_seq |= seq_num;

	remote = node_find_by_addr(src);

	if (!remote) {
		if (local_node->provisioner) {
			bt_shell_printf("Remote node unknown (%4.4x)\n", src);
			return;
		}

		remote = g_new0(struct mesh_node, 1);
		if (!remote)
			return;

		/* Not Provisioner; Assume all SRC elements stand alone */
		remote->primary = src;
		remote->num_ele = 1;
		nodes = g_list_append(nodes, remote);
	}

	loopback = (src < (local_node->primary + local_node->num_ele) &&
						src >= local_node->primary);

	if (!loopback) {
		iv_seq_remote = remote->iv_index << 24;
		iv_seq |= remote->seq_number;

		if (iv_seq_remote >= iv_seq) {
			bt_shell_printf("Replayed message detected "
					"(%016" PRIx64 " >= %016" PRIx64 ")\n",
							iv_seq_remote, iv_seq);
			return;
		}
	}

	if (IS_GROUP(dst) || IS_VIRTUAL(dst)) {
		/* TODO: if subscription address, deliver to subscribers */
		return;
	}

	if (IS_ALL_NODES(dst)) {
		ele_idx = 0;
	} else {
		if (dst >= (local_node->primary + local_node->num_ele) ||
			dst < local_node->primary)
				return;

		ele_idx = dst - local_node->primary;
	}

	l = g_list_find_custom(local_node->elements,
				GUINT_TO_POINTER(ele_idx), match_element_idx);

	/* This should not happen */
	if (!l)
		return;

	element = l->data;
	res = deliver_model_data(element, src, app_idx, data, len);

	if (res && !loopback) {
		/* TODO: Save remote in Replay Protection db */
		remote->iv_index = iv_index;
		remote->seq_number = seq_num;
		prov_db_node_set_iv_seq(remote, iv_index, seq_num);
	}
}

static gboolean restore_model_state(gpointer data)
{
	struct mesh_model *model = data;
	GList *l;
	struct mesh_model_ops *ops;

	ops = &model->cbs;

	if (model->bindings && ops->bind) {
		for (l = model->bindings; l; l = l->next) {
			if (ops->bind(GPOINTER_TO_UINT(l->data), ACTION_ADD) !=
				MESH_STATUS_SUCCESS)
				break;
		}
	}

	if (model->pub && ops->pub)
		ops->pub(model->pub);

	g_idle_remove_by_data(data);

	return true;

}

bool node_local_model_register(uint8_t ele_idx, uint16_t model_id,
				struct mesh_model_ops *ops, void *user_data)
{
	uint32_t id = 0xffff0000 | model_id;

	return node_local_vendor_model_register(ele_idx, id, ops, user_data);
}

bool node_local_vendor_model_register(uint8_t ele_idx, uint32_t model_id,
				struct mesh_model_ops *ops, void *user_data)
{
	struct mesh_element *ele;
	struct mesh_model *model;
	GList *l;

	if (!local_node)
		return false;

	l = g_list_find_custom(local_node->elements, GUINT_TO_POINTER(ele_idx),
				match_element_idx);
	if (!l)
		return false;

	ele = l->data;

	l = g_list_find_custom(ele->models, GUINT_TO_POINTER(model_id),
				match_model_id);
	if (!l)
		return false;

	model = l->data;
	model->cbs = *ops;
	model->user_data = user_data;

	if (model_id >= 0xffff0000)
		model_id = model_id & 0xffff;

	/* Silently assign device key binding to configuration models */
	if (model_id == CONFIG_SERVER_MODEL_ID ||
					model_id == CONFIG_CLIENT_MODEL_ID) {
		model->bindings = g_list_append(model->bindings,
						GUINT_TO_POINTER(APP_IDX_DEV));
	} else {
		g_idle_add(restore_model_state, model);
	}

	return true;
}

bool node_set_element(struct mesh_node *node, uint8_t ele_idx)
{
	struct mesh_element *ele;
	GList *l;

	if (!node)
		return false;

	l = g_list_find_custom(node->elements, GUINT_TO_POINTER(ele_idx),
				match_element_idx);
	if (l)
		return false;

	ele = g_malloc0(sizeof(struct mesh_element));
	if (!ele)
		return false;

	ele->index = ele_idx;
	node->elements = g_list_append(node->elements, ele);

	return true;
}

bool node_set_model(struct mesh_node *node, uint8_t ele_idx, uint32_t id)
{
	struct mesh_element *ele;
	struct mesh_model *model;
	GList *l;

	if (!node)
		return false;

	l = g_list_find_custom(node->elements, GUINT_TO_POINTER(ele_idx),
				match_element_idx);
	if (!l)
		return false;

	ele = l->data;

	l = g_list_find_custom(ele->models, GUINT_TO_POINTER(id),
				match_model_id);
	if (l)
		return true;

	model = g_malloc0(sizeof(struct mesh_model));
	if (!model)
		return false;

	model->id = id;
	ele->models = g_list_append(ele->models, model);

	return true;
}

bool node_set_composition(struct mesh_node *node,
				struct mesh_node_composition *comp)
{
	if (!node || !comp || node->comp)
		return false;

	node->comp = g_malloc0(sizeof(struct mesh_node_composition));
	if (!node->comp)
		return false;

	*(node->comp) = *comp;
	return true;
}

struct mesh_node_composition *node_get_composition(struct mesh_node *node)
{
	if (!node)
		return NULL;

	return node->comp;
}

static struct mesh_model *get_model(struct mesh_node *node, uint8_t ele_idx,
							uint32_t model_id)
{
	struct mesh_element *ele;
	GList *l;

	if (!node)
		return NULL;

	l = g_list_find_custom(node->elements, GUINT_TO_POINTER(ele_idx),
				match_element_idx);
	if (!l)
		return NULL;

	ele = l->data;

	l = g_list_find_custom(ele->models, GUINT_TO_POINTER(model_id),
				match_model_id);
	if (!l)
		return NULL;

	return l->data;

}

bool node_add_binding(struct mesh_node *node, uint8_t ele_idx,
			uint32_t model_id, uint16_t app_idx)
{
	struct mesh_model *model;
	GList *l;

	model = get_model(node, ele_idx, model_id);
	if (!model)
		return false;

	l = g_list_find(model->bindings, GUINT_TO_POINTER(app_idx));
	if (l)
		return false;

	if ((node == local_node) && model->cbs.bind) {
		if (!model->cbs.bind(app_idx, ACTION_ADD))
			return false;
	}

	model->bindings = g_list_append(model->bindings,
					GUINT_TO_POINTER(app_idx));
	return true;
}

bool node_add_subscription(struct mesh_node *node, uint8_t ele_idx,
			   uint32_t model_id, uint16_t addr)
{
	struct mesh_model *model;
	GList *l;

	model = get_model(node, ele_idx, model_id);
	if (!model)
		return false;

	l = g_list_find(model->subscriptions, GUINT_TO_POINTER(addr));
	if (l)
		return false;

	model->subscriptions = g_list_append(model->subscriptions,
					     GUINT_TO_POINTER(addr));
	return true;
}

uint8_t node_get_default_ttl(struct mesh_node *node)
{
	if (!node)
		return DEFAULT_TTL;
	else if (node == local_node)
		return net_get_default_ttl();
	else
		return node->ttl;
}

bool node_set_default_ttl(struct mesh_node *node, uint8_t ttl)
{
	if (!node)
		return false;

	node->ttl = ttl;

	if (node == local_node || local_node == NULL)
		return net_set_default_ttl(ttl);

	return true;
}

bool node_set_sequence_number(struct mesh_node *node, uint32_t seq)
{
	if (!node)
		return false;

	node->seq_number = seq;

	if (node == local_node || local_node == NULL)
		return net_set_seq_num(seq);

	return true;
}

uint32_t node_get_sequence_number(struct mesh_node *node)
{
	if (!node)
		return 0xffffffff;
	else if (node == local_node)
		return net_get_seq_num();

	return node->seq_number;
}

bool node_set_iv_index(struct mesh_node *node, uint32_t iv_index)
{
	if (!node)
		return false;

	node->iv_index = iv_index;
	return true;
}

uint32_t node_get_iv_index(struct mesh_node *node)
{
	bool update;

	if (!node)
		return 0xffffffff;
	else if (node == local_node)
		return net_get_iv_index(&update);
	return node->iv_index;
}

bool node_model_pub_set(struct mesh_node *node, uint8_t ele, uint32_t model_id,
						struct mesh_publication *pub)
{
	struct mesh_model *model;

	model = get_model(node, ele, model_id);
	if(!model)
		return false;

	if (!model->pub)
		model->pub = g_malloc0(sizeof(struct mesh_publication));
	if (!model)
		return false;

	memcpy(model->pub, pub, (sizeof(struct mesh_publication)));

	if((node == local_node) && model->cbs.pub)
		model->cbs.pub(pub);
	return true;
}

struct mesh_publication *node_model_pub_get(struct mesh_node *node, uint8_t ele,
							uint32_t model_id)
{
	struct mesh_model *model;

	model = get_model(node, ele, model_id);
	if(!model)
		return NULL;
	else
		return model->pub;
}
