/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2017  Intel Corporation. All rights reserved.
 *
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

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

#include <errno.h>
#include <fcntl.h>
#include <glib.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <json-c/json.h>
#include <sys/stat.h>

#include <glib.h>

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

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

#define CHECK_KEY_IDX_RANGE(x) (((x) >= 0) && ((x) <= 4095))

static const char *prov_filename;
static const char *local_filename;

static char* prov_file_read(const char *filename)
{
	int fd;
	char *str;
	struct stat st;
	ssize_t sz;

	if (!filename)
		return NULL;

	fd = open(filename,O_RDONLY);
	if (!fd)
		return NULL;

	if (fstat(fd, &st) == -1) {
		close(fd);
		return NULL;
	}

	str = (char *) g_malloc0(st.st_size + 1);
	if (!str) {
		close(fd);
		return NULL;
	}

	sz = read(fd, str, st.st_size);
	if (sz != st.st_size)
		bt_shell_printf("Incomplete read: %d vs %d\n", (int)sz,
							(int)(st.st_size));

	close(fd);

	return str;
}

static void prov_file_write(json_object *jmain, bool local)
{
	FILE *outfile;
	const char *out_str;
	const char *out_filename;

	if (local)
		out_filename = local_filename;
	else
		out_filename = prov_filename;

	outfile = fopen(out_filename, "wr");
	if (!outfile) {
		bt_shell_printf("Failed to open file %s for writing\n", out_filename);
		return;
	}

	out_str = json_object_to_json_string_ext(jmain,
						JSON_C_TO_STRING_PRETTY);

	fwrite(out_str, sizeof(char), strlen(out_str), outfile);
	fclose(outfile);
}

static void put_uint16(json_object *jobject, const char *desc, uint16_t value)
{
	json_object *jstring;
	char buf[5];

	snprintf(buf, 5, "%4.4x", value);
	jstring = json_object_new_string(buf);
	json_object_object_add(jobject, desc, jstring);
}

static void put_uint32(json_object *jobject, const char *desc, uint32_t value)
{
	json_object *jstring;
	char buf[9];

	snprintf(buf, 9, "%8.8x", value);
	jstring = json_object_new_string(buf);
	json_object_object_add(jobject, desc, jstring);
}

static void put_uint16_array_entry(json_object *jarray, uint16_t value)
{
	json_object *jstring;
	char buf[5];

	snprintf(buf, 5, "%4.4x", value);
	jstring = json_object_new_string(buf);
	json_object_array_add(jarray, jstring);
}

static void put_uint32_array_entry(json_object *jarray, uint32_t value)
{
	json_object *jstring;
	char buf[9];

	snprintf(buf, 9, "%8.8x", value);
	jstring = json_object_new_string(buf);
	json_object_array_add(jarray, jstring);
}

static void put_uint16_list(json_object *jarray, GList *list)
{
	GList *l;

	if (!list)
		return;

	for (l = list; l; l = l->next) {
		uint32_t ivalue = GPOINTER_TO_UINT(l->data);
		put_uint16_array_entry(jarray, ivalue);
	}
}

static void add_node_idxs(json_object *jnode, const char *desc,
				GList *idxs)
{
	json_object *jarray;

	jarray = json_object_new_array();

	put_uint16_list(jarray, idxs);

	json_object_object_add(jnode, desc, jarray);
}

static bool parse_unicast_range(json_object *jobject)
{
	int cnt;
	int i;

	cnt = json_object_array_length(jobject);

	for (i = 0; i < cnt; ++i) {
		json_object *jrange;
		json_object *jvalue;
		uint16_t low, high;
		char *str;

		jrange = json_object_array_get_idx(jobject, i);
		json_object_object_get_ex(jrange, "lowAddress", &jvalue);
		str = (char *)json_object_get_string(jvalue);
		if (sscanf(str, "%04hx", &low) != 1)
			return false;

		json_object_object_get_ex(jrange, "highAddress", &jvalue);
		str = (char *)json_object_get_string(jvalue);
		if (sscanf(str, "%04hx", &high) != 1)
			return false;

		if(high < low)
			return false;

		net_add_address_pool(low, high);
	}
	return true;
}

static int parse_node_keys(struct mesh_node *node, json_object *jidxs,
				bool is_app_key)
{
	int idx_cnt;
	int i;

	idx_cnt = json_object_array_length(jidxs);
	for (i = 0; i < idx_cnt; ++i) {
		int idx;
		json_object *jvalue;

		jvalue = json_object_array_get_idx(jidxs, i);
		if (!jvalue)
			break;
		idx = json_object_get_int(jvalue);
		if (!CHECK_KEY_IDX_RANGE(idx))
			break;

		if (is_app_key)
			node_app_key_add(node, idx);
		else
			node_net_key_add(node, idx);
	}

	return i;
}

static bool parse_composition_models(struct mesh_node *node, int index,
					json_object *jmodels)
{
	int model_cnt;
	int i;

	model_cnt = json_object_array_length(jmodels);

	for (i = 0; i < model_cnt; ++i) {
		json_object *jmodel;
		char *str;
		uint32_t model_id;
		int len;

		jmodel = json_object_array_get_idx(jmodels, i);
		str = (char *)json_object_get_string(jmodel);
		len = strlen(str);

		if (len != 4 && len != 8)
			return false;

		if (sscanf(str, "%08x", &model_id) != 1)
			return false;
		if (len == 4)
			model_id += 0xffff0000;

		node_set_model(node, index, model_id);
	}

	return true;
}

static bool parse_composition_elements(struct mesh_node *node,
					json_object *jelements)
{
	int el_cnt;
	int i;

	el_cnt = json_object_array_length(jelements);
	node_set_num_elements(node, el_cnt);

	for (i = 0; i < el_cnt; ++i) {
		json_object *jelement;
		json_object *jmodels;
		json_object *jvalue;
		int index;

		jelement = json_object_array_get_idx(jelements, i);
		json_object_object_get_ex(jelement, "elementIndex", &jvalue);
		if (jvalue) {
			index = json_object_get_int(jvalue);
			if (index >= el_cnt) {
				return false;
			}
		} else
			return false;

		if (!node_set_element(node, index))
			return false;

		json_object_object_get_ex(jelement, "models", &jmodels);
		if (!jmodels)
			continue;

		if(!parse_composition_models(node, index, jmodels))
			return false;
	}
	return true;
}

static bool parse_model_pub(struct mesh_node *node, int ele_idx,
				uint32_t model_id, json_object *jpub)
{
	json_object *jvalue;
	struct mesh_publication pub;
	char *str;

	memset(&pub, 0, sizeof(struct mesh_publication));

	/* Read only required fields */
	json_object_object_get_ex(jpub, "address", &jvalue);
	if (!jvalue)
		return false;

	str = (char *)json_object_get_string(jvalue);
	if (sscanf(str, "%04hx", &pub.u.addr16) != 1)
		return false;

	json_object_object_get_ex(jpub, "index", &jvalue);
	if (!jvalue)
		return false;

	str = (char *)json_object_get_string(jvalue);
	if (sscanf(str, "%04hx", &pub.app_idx) != 1)
		return false;


	json_object_object_get_ex(jpub, "ttl", &jvalue);
	pub.ttl = json_object_get_int(jvalue);

	if (!node_model_pub_set(node, ele_idx, model_id, &pub))
			return false;

	return true;
}

static bool parse_bindings(struct mesh_node *node, int ele_idx,
				uint32_t model_id, json_object *jbindings)
{
	int cnt;
	int i;

	cnt = json_object_array_length(jbindings);

	for (i = 0; i < cnt; ++i) {
		int key_idx;
		json_object *jvalue;

		jvalue = json_object_array_get_idx(jbindings, i);
		if (!jvalue)
			return true;

		key_idx = json_object_get_int(jvalue);
		if (!CHECK_KEY_IDX_RANGE(key_idx))
			return false;

		if (!node_add_binding(node, ele_idx, model_id, key_idx))
			return false;
	}

	return true;
}

static json_object* find_configured_model(struct mesh_node *node, int ele_idx,
				       json_object *jmodels, uint32_t target_id)
{
	int model_cnt;
	int i;

	model_cnt = json_object_array_length(jmodels);

	for (i = 0; i < model_cnt; ++i) {
		json_object *jmodel;
		json_object *jvalue;
		char *str;
		int len;
		uint32_t model_id;

		jmodel = json_object_array_get_idx(jmodels, i);

		json_object_object_get_ex(jmodel, "modelId", &jvalue);
		str = (char *)json_object_get_string(jvalue);

		len = strlen(str);

		if (len != 4 && len != 8)
			return NULL;

		if (sscanf(str, "%08x", &model_id) != 1)
			return NULL;

		if (len == 4)
			model_id += 0xffff0000;

		if (model_id == target_id)
			return jmodel;
	}

	return NULL;
}

static bool parse_subscriptions(struct mesh_node *node, int ele_idx,
				uint32_t model_id, json_object *jsubscriptions)

{
	int cnt;
	int i;
	int addr;

	cnt = json_object_array_length(jsubscriptions);

	for (i = 0; i < cnt; ++i) {
		char *str;
		json_object *jsubscription;

		jsubscription = json_object_array_get_idx(jsubscriptions, i);
		if (!jsubscription)
			return false;

		str = (char *)json_object_get_string(jsubscription);

		if (sscanf(str, "%04x", &addr) != 1)
			return false;

		if (!node_add_subscription(node, ele_idx, model_id, addr))
			return false;
	}

	return true;
}

static bool parse_configuration_models(struct mesh_node *node, int ele_idx,
							json_object *jmodels)
{
	int model_cnt;
	int i;

	model_cnt = json_object_array_length(jmodels);

	for (i = 0; i < model_cnt; ++i) {
		json_object *jmodel;
		json_object *jvalue;
		json_object *jarray;
		char *str;
		int len;
		uint32_t model_id;

		jmodel = json_object_array_get_idx(jmodels, i);

		json_object_object_get_ex(jmodel, "modelId", &jvalue);
		str = (char *)json_object_get_string(jvalue);

		len = strlen(str);

		if (len != 4 && len != 8)
			return false;

		if (sscanf(str, "%08x", &model_id) != 1)
			return false;
		if (len == 4)
			model_id += 0xffff0000;

		json_object_object_get_ex(jmodel, "bind", &jarray);

		if (jarray && !parse_bindings(node, ele_idx, model_id, jarray))
			return false;

		json_object_object_get_ex(jmodel, "subscribe", &jarray);
		if (jarray && !parse_subscriptions(node, ele_idx, model_id, jarray))
			return false;

		json_object_object_get_ex(jmodel, "publish", &jvalue);
		if (jvalue && !parse_model_pub(node, ele_idx, model_id, jvalue))
			return false;
	}

	return true;
}

static bool parse_configuration_elements(struct mesh_node *node,
				json_object *jelements, bool local)
{
	int el_cnt;
	int i;

	el_cnt = json_object_array_length(jelements);
	node_set_num_elements(node, el_cnt);

	for (i = 0; i < el_cnt; ++i) {
		json_object *jelement;
		json_object *jmodels;
		json_object *jvalue;
		int index;
		uint16_t addr;

		jelement = json_object_array_get_idx(jelements, i);
		json_object_object_get_ex(jelement, "elementIndex", &jvalue);
		if (jvalue) {
			index = json_object_get_int(jvalue);
			if (index >= el_cnt) {
				return false;
			}
		} else
			return false;

		if (index == 0) {
			char *str;

			json_object_object_get_ex(jelement, "unicastAddress",
							&jvalue);
			str = (char *)json_object_get_string(jvalue);
			if (sscanf(str, "%04hx", &addr) != 1)
				return false;

			if (!local && !net_reserve_address_range(addr, el_cnt))
				return false;

			node_set_primary(node, addr);
		}

		json_object_object_get_ex(jelement, "models", &jmodels);
		if (!jmodels)
			continue;

		if(!parse_configuration_models(node, index, jmodels))
			return false;
	}
	return true;
}

static void add_key(json_object *jobject, const char *desc, uint8_t* key)
{
	json_object *jstring;
	char hexstr[33];

	hex2str(key, 16, hexstr, 33);
	jstring = json_object_new_string(hexstr);
	json_object_object_add(jobject, desc, jstring);
}

static json_object *find_node_by_primary(json_object *jmain, uint16_t primary)
{
	json_object *jarray;
	int i, len;

	json_object_object_get_ex(jmain, "nodes", &jarray);

	if (!jarray)
		return NULL;
	len = json_object_array_length(jarray);

	for (i = 0; i < len; ++i) {
		json_object *jnode;
		json_object *jconfig;
		json_object *jelements;
		json_object *jelement;
		json_object *jvalue;
		char *str;
		uint16_t addr;

		jnode = json_object_array_get_idx(jarray, i);
		if (!jnode)
			return NULL;

		json_object_object_get_ex(jnode, "configuration", &jconfig);
		if (!jconfig)
			return NULL;

		json_object_object_get_ex(jconfig, "elements", &jelements);
		if (!jelements)
			return NULL;

		jelement = json_object_array_get_idx(jelements, 0);
		if (!jelement)
			return NULL;

		json_object_object_get_ex(jelement, "unicastAddress",
								&jvalue);
		str = (char *)json_object_get_string(jvalue);
		if (sscanf(str, "%04hx", &addr) != 1)
				return NULL;

		if (addr == primary)
			return jnode;
	}

	return NULL;

}

void prov_db_print_node_composition(struct mesh_node *node)
{
	char *in_str;
	const char *comp_str;
	json_object *jmain;
	json_object *jnode;
	json_object *jcomp;
	uint16_t primary = node_get_primary(node);
	const char *filename;
	bool res = false;

	if (!node || !node_get_composition(node))
		return;

	if (node == node_get_local_node())
		filename = local_filename;
	else
		filename = prov_filename;

	in_str = prov_file_read(filename);
	if (!in_str)
		return;

	jmain = json_tokener_parse(in_str);
	if (!jmain)
		goto done;

	jnode = find_node_by_primary(jmain, primary);
	if (!jnode)
		goto done;

	json_object_object_get_ex(jnode, "composition", &jcomp);
	if (!jcomp)
		goto done;

	comp_str = json_object_to_json_string_ext(jcomp,
						JSON_C_TO_STRING_PRETTY);

	res = true;

done:
	if (res)
		bt_shell_printf("\tComposition data for node %4.4x %s\n",
							primary, comp_str);
	else
		bt_shell_printf("\tComposition data for node %4.4x not present\n",
								primary);
	g_free(in_str);

	if (jmain)
		json_object_put(jmain);
}

bool prov_db_add_node_composition(struct mesh_node *node, uint8_t *data,
								uint16_t len)
{
	char *in_str;
	json_object *jmain;
	json_object *jnode;
	json_object *jcomp;
	json_object *jbool;
	json_object *jfeatures;
	json_object *jelements;
	struct mesh_node_composition *comp;
	uint8_t num_ele;
	int i;
	uint16_t primary = node_get_primary(node);
	bool res = NULL;

	comp = node_get_composition(node);
	if (!comp)
		return false;

	in_str = prov_file_read(prov_filename);
	if (!in_str)
		return false;

	jmain = json_tokener_parse(in_str);
	if (!jmain)
		goto done;

	jnode = find_node_by_primary(jmain, primary);
	if (!jnode)
		goto done;

	jcomp = json_object_new_object();

	put_uint16(jcomp, "cid", comp->cid);
	put_uint16(jcomp, "pid", comp->pid);
	put_uint16(jcomp, "vid", comp->vid);
	put_uint16(jcomp, "crpl", comp->crpl);

	jfeatures = json_object_new_object();
	jbool = json_object_new_boolean(comp->relay);
	json_object_object_add(jfeatures, "relay", jbool);
	jbool = json_object_new_boolean(comp->proxy);
	json_object_object_add(jfeatures, "proxy", jbool);
	jbool = json_object_new_boolean(comp->friend);
	json_object_object_add(jfeatures, "friend", jbool);
	jbool = json_object_new_boolean(comp->lpn);
	json_object_object_add(jfeatures, "lpn", jbool);
	json_object_object_add(jcomp, "features", jfeatures);

	data += 11;
	len -= 11;

	num_ele =  node_get_num_elements(node);

	jelements = json_object_new_array();

	for (i = 0; i < num_ele; ++i) {
		json_object *jelement;
		json_object *jmodels;
		json_object *jint;
		uint32_t mod_id;
		uint16_t vendor_id;
		uint8_t m, v;

		jelement = json_object_new_object();

		/* Element Index */
		jint = json_object_new_int(i);
		json_object_object_add(jelement, "elementIndex", jint);

		/* Location */
		put_uint16(jelement, "location", get_le16(data));
		data += 2;
		m = *data++;
		v = *data++;
		len -= 4;

		/* Models */
		jmodels = json_object_new_array();
		while (len >= 2 && m--) {
			mod_id = get_le16(data);
			data += 2;
			len -= 2;
			put_uint16_array_entry(jmodels, (uint16_t) mod_id);
		}

		while (len >= 4 && v--) {
			mod_id = get_le16(data + 2);
			vendor_id = get_le16(data);
			mod_id |= (vendor_id << 16);
			data += 4;
			len -= 4;
			put_uint32_array_entry(jmodels, mod_id);
		}

		json_object_object_add(jelement, "models", jmodels);
		json_object_array_add(jelements, jelement);
	}

	json_object_object_add(jcomp, "elements", jelements);

	json_object_object_add(jnode, "composition", jcomp);

	prov_file_write(jmain, false);

	res = true;;
done:

	g_free(in_str);

	if(jmain)
		json_object_put(jmain);

	return res;
}

bool prov_db_node_set_ttl(struct mesh_node *node, uint8_t ttl)
{
	char *in_str;
	json_object *jmain;
	json_object *jnode;
	json_object *jconfig;
	json_object *jvalue;
	uint16_t primary = node_get_primary(node);
	const char *filename;
	bool local = node == node_get_local_node();
	bool res = false;

	if (local)
		filename = local_filename;
	else
		filename = prov_filename;

	in_str = prov_file_read(filename);
	if (!in_str)
		return false;

	jmain = json_tokener_parse(in_str);
	if (!jmain)
		goto done;

	if (local)
		json_object_object_get_ex(jmain, "node", &jnode);
	else
		jnode = find_node_by_primary(jmain, primary);

	if (!jnode)
		goto done;

	json_object_object_get_ex(jnode, "configuration", &jconfig);
	if (!jconfig)
		goto done;

	json_object_object_del(jconfig, "defaultTTL");

	jvalue = json_object_new_int(ttl);
	json_object_object_add(jconfig, "defaultTTL", jvalue);

	prov_file_write(jmain, local);

	res = true;
done:

	g_free(in_str);

	if(jmain)
		json_object_put(jmain);

	return res;

}

static void set_local_iv_index(json_object *jobj, uint32_t idx, bool update)
{
	json_object *jvalue;

	json_object_object_del(jobj, "IVindex");
	jvalue = json_object_new_int(idx);
	json_object_object_add(jobj, "IVindex", jvalue);

	json_object_object_del(jobj, "IVupdate");
	jvalue = json_object_new_int((update) ? 1 : 0);
	json_object_object_add(jobj, "IVupdate", jvalue);

}

bool prov_db_local_set_iv_index(uint32_t iv_index, bool update, bool prov)
{
	char *in_str;
	json_object *jmain;
	json_object *jnode;
	bool res = false;

	in_str = prov_file_read(local_filename);
	if (!in_str)
		return false;

	jmain = json_tokener_parse(in_str);
	if (!jmain)
		goto done;

	json_object_object_get_ex(jmain, "node", &jnode);
	set_local_iv_index(jnode, iv_index, update);
	prov_file_write(jmain, true);

	g_free(in_str);
	json_object_put(jmain);

	/* If provisioner, save to global DB as well */
	if (prov) {
		in_str = prov_file_read(prov_filename);
		if (!in_str)
			return false;

		jmain = json_tokener_parse(in_str);
		if (!jmain)
			goto done;

		set_local_iv_index(jmain, iv_index, update);
		prov_file_write(jmain, false);
	}

	res = true;
done:

	g_free(in_str);

	if(jmain)
		json_object_put(jmain);

	return res;

}

bool prov_db_local_set_seq_num(uint32_t seq_num)
{
	char *in_str;
	json_object *jmain;
	json_object *jnode;
	json_object *jvalue;
	bool res = false;

	in_str = prov_file_read(local_filename);
	if (!in_str)
		return false;

	jmain = json_tokener_parse(in_str);
	if (!jmain)
		goto done;

	json_object_object_get_ex(jmain, "node", &jnode);

	json_object_object_del(jnode, "sequenceNumber");
	jvalue = json_object_new_int(seq_num);
	json_object_object_add(jnode, "sequenceNumber", jvalue);

	prov_file_write(jmain, true);

	res = true;
done:

	g_free(in_str);

	if(jmain)
		json_object_put(jmain);

	return res;
}

bool prov_db_node_set_iv_seq(struct mesh_node *node, uint32_t iv, uint32_t seq)
{
	char *in_str;
	json_object *jmain;
	json_object *jnode;
	json_object *jvalue;
	uint16_t primary = node_get_primary(node);
	bool res = false;

	in_str = prov_file_read(prov_filename);
	if (!in_str)
		return false;

	jmain = json_tokener_parse(in_str);
	if (!jmain)
		goto done;

	jnode = find_node_by_primary(jmain, primary);
	if (!jnode)
		goto done;

	json_object_object_del(jnode, "IVindex");

	jvalue = json_object_new_int(iv);
	json_object_object_add(jnode, "IVindex", jvalue);

	json_object_object_del(jnode, "sequenceNumber");

	jvalue = json_object_new_int(seq);
	json_object_object_add(jnode, "sequenceNumber", jvalue);

	prov_file_write(jmain, false);

	res = true;
done:

	g_free(in_str);

	if(jmain)
		json_object_put(jmain);

	return res;

}

bool prov_db_node_keys(struct mesh_node *node, GList *idxs, const char *desc)
{
	char *in_str;
	json_object *jmain;
	json_object *jnode;
	json_object *jconfig;
	json_object *jidxs;
	uint16_t primary = node_get_primary(node);
	const char *filename;
	bool local = (node == node_get_local_node());
	bool res = false;

	if (local)
		filename = local_filename;
	else
		filename = prov_filename;

	in_str = prov_file_read(filename);
	if (!in_str)
		return false;

	jmain = json_tokener_parse(in_str);
	if (!jmain)
		goto done;

	jnode = find_node_by_primary(jmain, primary);
	if (!jnode)
		goto done;

	json_object_object_get_ex(jnode, "configuration", &jconfig);
	if (!jconfig)
		goto done;

	json_object_object_del(jconfig, desc);

	if (idxs) {
		jidxs = json_object_new_array();
		put_uint16_list(jidxs, idxs);
		json_object_object_add(jconfig, desc, jidxs);
	}

	prov_file_write(jmain, local);

	res = true;
done:

	g_free(in_str);

	if(jmain)
		json_object_put(jmain);

	return res;

}

static json_object *get_jmodel_obj(struct mesh_node *node, uint8_t ele_idx,
					uint32_t model_id, json_object **jmain)
{
	char *in_str;
	json_object *jnode;
	json_object *jconfig;
	json_object *jelements, *jelement;
	json_object *jmodels, *jmodel = NULL;
	uint16_t primary = node_get_primary(node);
	const char *filename;
	bool local = (node == node_get_local_node());

	if (local)
		filename = local_filename;
	else
		filename = prov_filename;

	in_str = prov_file_read(filename);
	if (!in_str)
		return NULL;

	*jmain = json_tokener_parse(in_str);
	if (!(*jmain))
		goto done;

	if (local)
		json_object_object_get_ex(*jmain, "node", &jnode);
	else
		jnode = find_node_by_primary(*jmain, primary);

	if (!jnode)
		goto done;

	/* Configuration is mandatory for nodes in provisioning database */
	json_object_object_get_ex(jnode, "configuration", &jconfig);
	if (!jconfig)
		goto done;

	json_object_object_get_ex(jconfig, "elements", &jelements);
	if (!jelements) {
		goto done;
	}

	jelement = json_object_array_get_idx(jelements, ele_idx);
	if (!jelement) {
		goto done;
	}

	json_object_object_get_ex(jelement, "models", &jmodels);

	if (!jmodels)  {
		jmodels = json_object_new_array();
		json_object_object_add(jelement, "models", jmodels);
	} else {
		jmodel = find_configured_model(node, ele_idx, jmodels,
								model_id);
	}

	if (!jmodel) {
		jmodel = json_object_new_object();

		if ((model_id & 0xffff0000) == 0xffff0000)
			put_uint16(jmodel, "modelId", model_id & 0xffff);
		else
			put_uint32(jmodel, "modelId", model_id);

		json_object_array_add(jmodels, jmodel);
	}

done:

	g_free(in_str);

	if(!jmodel && *jmain)
		json_object_put(*jmain);

	return jmodel;

}

bool prov_db_add_binding(struct mesh_node *node, uint8_t ele_idx,
			uint32_t model_id, uint16_t app_idx)
{
	bool local = (node == node_get_local_node());
	json_object *jbindings = NULL;
	json_object *jmodel;
	json_object *jvalue;
	json_object *jmain;

	jmodel = get_jmodel_obj(node, ele_idx, model_id, &jmain);
	if (!jmodel)
		return false;

	json_object_object_get_ex(jmodel, "bind", &jbindings);

	if (!jbindings) {
		jbindings = json_object_new_array();
		json_object_object_add(jmodel, "bind", jbindings);
	}

	jvalue = json_object_new_int(app_idx);
	json_object_array_add(jbindings, jvalue);

	prov_file_write(jmain, local);

	json_object_put(jmain);

	return true;
}

bool prov_db_add_subscription(struct mesh_node *node, uint8_t ele_idx,
			      uint32_t model_id, uint16_t addr)
{
	bool local = (node == node_get_local_node());
	json_object *jsubscriptions = NULL;
	json_object *jmodel;
	json_object *jmain;

	jmodel = get_jmodel_obj(node, ele_idx, model_id, &jmain);
	if (!jmodel)
		return false;

	json_object_object_get_ex(jmodel, "subscribe", &jsubscriptions);

	if (!jsubscriptions) {
		jsubscriptions = json_object_new_array();
		json_object_object_add(jmodel, "subscribe", jsubscriptions);
	}

	put_uint16_array_entry(jsubscriptions, addr);

	prov_file_write(jmain, local);

	json_object_put(jmain);

	return true;
}

bool prov_db_node_set_model_pub(struct mesh_node *node, uint8_t ele_idx,
							uint32_t model_id,
						struct mesh_publication *pub)
{
	json_object *jmain;
	json_object *jmodel;
	json_object *jpub;
	json_object *jvalue;
	bool local = (node == node_get_local_node());

	jmodel = get_jmodel_obj(node, ele_idx, model_id, &jmain);

	if (!jmodel)
		return false;

	json_object_object_del(jmodel, "publish");
	if (!pub)
		goto done;

	jpub = json_object_new_object();

	/* Save only required fields */
	put_uint16(jpub, "address", pub->u.addr16);
	put_uint16(jpub, "index", pub->app_idx);
	jvalue = json_object_new_int(pub->ttl);
	json_object_object_add(jpub, "ttl", jvalue);

	json_object_object_add(jmodel, "publish", jpub);

done:
	prov_file_write(jmain, local);

	json_object_put(jmain);

	return true;
}

bool prov_db_add_new_node(struct mesh_node *node)
{
	char *in_str;
	json_object *jmain;
	json_object *jarray;
	json_object *jnode;
	json_object *jconfig;
	json_object *jelements;
	uint8_t num_ele;
	uint16_t primary;
	int i;
	bool first_node;
	bool res = false;

	in_str = prov_file_read(prov_filename);
	if (!in_str)
		return false;

	jmain = json_tokener_parse(in_str);
	if (!jmain)
		goto done;
	json_object_object_get_ex(jmain, "nodes", &jarray);

	if (!jarray) {
		jarray = json_object_new_array();
		first_node = true;
	} else
		first_node = false;

	jnode = json_object_new_object();

	/* Device key */
	add_key(jnode, "deviceKey", node_get_device_key(node));

	/* Net key */
	jconfig = json_object_new_object();
	add_node_idxs(jconfig, "netKeys", node_get_net_keys(node));

	num_ele = node_get_num_elements(node);
	if (num_ele == 0)
		goto done;

	jelements = json_object_new_array();

	primary = node_get_primary(node);
	if (IS_UNASSIGNED(primary))
		goto done;

	for (i = 0; i < num_ele; ++i) {
		json_object *jelement;
		json_object *jint;

		jelement = json_object_new_object();

		/* Element Index */
		jint = json_object_new_int(i);
		json_object_object_add(jelement, "elementIndex", jint);

		/* Unicast */
		put_uint16(jelement, "unicastAddress", primary + i);

		json_object_array_add(jelements, jelement);
	}

	json_object_object_add(jconfig, "elements", jelements);

	json_object_object_add(jnode, "configuration", jconfig);

	json_object_array_add(jarray, jnode);

	if (first_node)
		json_object_object_add(jmain, "nodes", jarray);

	prov_file_write(jmain, false);

	res = true;
done:

	g_free(in_str);

	if (jmain)
		json_object_put(jmain);

	return res;
}

static bool parse_node_composition(struct mesh_node *node, json_object *jcomp)
{
	json_object *jvalue;
	json_object *jelements;
	json_object *jfeatures;
	json_bool enable;
	char *str;
	struct mesh_node_composition comp;

	json_object_object_get_ex(jcomp, "cid", &jvalue);
	if (!jvalue)
		return false;

	str = (char *)json_object_get_string(jvalue);

	if (sscanf(str, "%04hx", &comp.cid) != 1)
		return false;

	json_object_object_get_ex(jcomp, "pid", &jvalue);
	if (!jvalue)
		return false;

	str = (char *)json_object_get_string(jvalue);

	if (sscanf(str, "%04hx", &comp.pid) != 1)
		return false;

	json_object_object_get_ex(jcomp, "vid", &jvalue);
	if (!jvalue)
		return false;

	str = (char *)json_object_get_string(jvalue);

	if (sscanf(str, "%04hx", &comp.vid) != 1)
		return false;

	json_object_object_get_ex(jcomp, "crpl", &jvalue);
	if (!jvalue)
		return false;

	str = (char *)json_object_get_string(jvalue);

	if (sscanf(str, "%04hx", &comp.crpl) != 1)
		return false;

	/* Extract features */

	json_object_object_get_ex(jcomp, "features", &jfeatures);
	if (!jfeatures)
		return false;

	json_object_object_get_ex(jfeatures, "relay", &jvalue);
	enable = json_object_get_boolean(jvalue);
	comp.relay = (enable) ? true : false;

	json_object_object_get_ex(jfeatures, "proxy", &jvalue);
	enable = json_object_get_boolean(jvalue);
	comp.proxy = (enable) ? true : false;

	json_object_object_get_ex(jfeatures, "friend", &jvalue);
	enable = json_object_get_boolean(jvalue);
	comp.friend = (enable) ? true : false;

	json_object_object_get_ex(jfeatures, "lowPower", &jvalue);
	enable = json_object_get_boolean(jvalue);
	comp.lpn = (enable) ? true : false;

	if (!node_set_composition(node, &comp))
		return false;

	json_object_object_get_ex(jcomp, "elements", &jelements);
	if (!jelements)
		return false;

	return parse_composition_elements(node, jelements);
}

static bool parse_node(json_object *jnode, bool local)
{
	json_object *jconfig;
	json_object *jelements;
	json_object *jidxs;
	json_object *jvalue;
	json_object *jint;
	uint8_t key[16];
	char *value_str;
	uint32_t idx;
	struct mesh_node *node;

	/* Device key */
	if (!json_object_object_get_ex(jnode, "deviceKey", &jvalue) ||
								!jvalue) {
		if (!mesh_get_random_bytes(key, 16))
			return false;

		add_key(jnode, "deviceKey", key);
	} else {
		value_str = (char *)json_object_get_string(jvalue);
		if (!str2hex(value_str, strlen(value_str), key, 16))
			return false;;
	}

	node = node_new();

	if (!node)
		return false;

	node_set_device_key(node, key);

	json_object_object_get_ex(jnode, "IVindex", &jint);
	if (jint)
		idx = json_object_get_int(jint);
	else
		idx = 0;

	node_set_iv_index(node, idx);
	if (local) {
		bool update = false;
		json_object_object_get_ex(jnode, "IVupdate", &jint);
		if (jint)
			update = json_object_get_int(jint) ? true : false;
		net_set_iv_index(idx, update);
	}

	if (json_object_object_get_ex(jnode, "sequenceNumber", &jint) &&
									jint) {
		int seq = json_object_get_int(jint);
		node_set_sequence_number(node, seq);
	}

	/* Composition is mandatory for local node */
	json_object_object_get_ex(jnode, "composition", &jconfig);
	if ((jconfig && !parse_node_composition(node, jconfig)) ||
							(!jconfig && local)) {
		node_free(node);
		return false;
	}

	/* Configuration is mandatory for nodes in provisioning database */
	json_object_object_get_ex(jnode, "configuration", &jconfig);
	if (!jconfig) {
		if (local) {
			/* This is an unprovisioned local device */
			goto done;
		} else {
			node_free(node);
			return false;
		}
	}

	json_object_object_get_ex(jconfig, "elements", &jelements);
	if (!jelements) {
		node_free(node);
		return false;
	}

	if (!parse_configuration_elements(node, jelements, local)) {
		node_free(node);
		return false;;
	}

	json_object_object_get_ex(jconfig, "netKeys", &jidxs);
	if (!jidxs || (parse_node_keys(node, jidxs, false) == 0)) {
		node_free(node);
		return false;
	}

	json_object_object_get_ex(jconfig, "appKeys", &jidxs);
	if (jidxs)
		parse_node_keys(node, jidxs, true);

	json_object_object_get_ex(jconfig, "defaultTTL", &jvalue);
	if (jvalue) {
		int ttl = json_object_get_int(jvalue);
		node_set_default_ttl(node, ttl &TTL_MASK);
	}

done:
	if (local && !node_set_local_node(node)) {
		node_free(node);
		return false;
	}

	return true;
}

bool prov_db_show(const char *filename)
{
	char *str;

	str = prov_file_read(filename);
	if (!str)
		return false;

	bt_shell_printf("%s\n", str);
	g_free(str);
	return true;
}

static bool read_json_db(const char *filename, bool provisioner, bool local)
{
	char *str;
	json_object *jmain;
	json_object *jarray;
	json_object *jprov;
	json_object *jvalue;
	json_object *jtemp;
	uint8_t key[16];
	int value_int;
	char *value_str;
	int len;
	int i;
	uint32_t index;
	bool refresh = false;
	bool res = false;

	str = prov_file_read(filename);
	if (!str) return false;

	jmain = json_tokener_parse(str);
	if (!jmain)
		goto done;

	if (local) {
		json_object *jnode;
		bool result;

		json_object_object_get_ex(jmain, "node", &jnode);
		if (!jnode) {
			bt_shell_printf("Cannot find \"node\" object");
			goto done;
		} else
			result = parse_node(jnode, true);

		/*
		* If local node is provisioner, the rest of mesh settings
		* are read from provisioning database.
		*/
		if (provisioner) {
			res = result;
			goto done;
		}
	}

	/* IV index */
	json_object_object_get_ex(jmain, "IVindex", &jvalue);
	if (!jvalue)
		goto done;

	index = json_object_get_int(jvalue);

	json_object_object_get_ex(jmain, "IVupdate", &jvalue);
	if (!jvalue)
		goto done;

	value_int = json_object_get_int(jvalue);

	net_set_iv_index(index, value_int);

	/* Network key(s) */
	json_object_object_get_ex(jmain, "netKeys", &jarray);
	if (!jarray)
		goto done;

	len = json_object_array_length(jarray);
	bt_shell_printf("# netkeys = %d\n", len);

	for (i = 0; i < len; ++i) {
		uint32_t idx;

		jtemp = json_object_array_get_idx(jarray, i);
		json_object_object_get_ex(jtemp, "index", &jvalue);
		if (!jvalue)
			goto done;
		idx = json_object_get_int(jvalue);

		json_object_object_get_ex(jtemp, "key", &jvalue);
		if (!jvalue) {
			if (!mesh_get_random_bytes(key, 16))
				goto done;
			add_key(jtemp, "key", key);
			refresh = true;
		} else {
			value_str = (char *)json_object_get_string(jvalue);
			if (!str2hex(value_str, strlen(value_str), key, 16)) {
				goto done;
			}
		}

		if (!keys_net_key_add(idx, key, false))
			goto done;

		json_object_object_get_ex(jtemp, "keyRefresh", &jvalue);
		if (!jvalue)
			goto done;

		keys_set_kr_phase(idx, (uint8_t) json_object_get_int(jvalue));
	}

	/* App keys */
	json_object_object_get_ex(jmain, "appKeys", &jarray);
	if (jarray) {
		len = json_object_array_length(jarray);
		bt_shell_printf("# appkeys = %d\n", len);

		for (i = 0; i < len; ++i) {
			int app_idx;
			int net_idx;

			jtemp = json_object_array_get_idx(jarray, i);
			json_object_object_get_ex(jtemp, "index",
						&jvalue);
			if (!jvalue)
				goto done;

			app_idx = json_object_get_int(jvalue);
			if (!CHECK_KEY_IDX_RANGE(app_idx))
				goto done;

			json_object_object_get_ex(jtemp, "key", &jvalue);
			if (!jvalue) {
				if (!mesh_get_random_bytes(key, 16))
					goto done;
				add_key(jtemp, "key", key);
				refresh = true;
			} else {
				value_str =
					(char *)json_object_get_string(jvalue);
				str2hex(value_str, strlen(value_str), key, 16);
			}

			json_object_object_get_ex(jtemp, "boundNetKey",
							&jvalue);
			if (!jvalue)
				goto done;

			net_idx = json_object_get_int(jvalue);
			if (!CHECK_KEY_IDX_RANGE(net_idx))
				goto done;

			keys_app_key_add(net_idx, app_idx, key, false);
		}
	}

	/* Provisioner info */
	json_object_object_get_ex(jmain, "provisioners", &jarray);
	if (!jarray)
		goto done;

	len = json_object_array_length(jarray);
	bt_shell_printf("# provisioners = %d\n", len);

	for (i = 0; i < len; ++i) {

		jprov = json_object_array_get_idx(jarray, i);

		/* Allocated unicast range */
		json_object_object_get_ex(jprov, "allocatedUnicastRange",
						&jtemp);
		if (!jtemp) {
			goto done;
		}

		if (!parse_unicast_range(jtemp)) {
			bt_shell_printf("Doneed to parse unicast range\n");
			goto done;
		}
	}

	json_object_object_get_ex(jmain, "nodes", &jarray);
	if (!jarray) {
		res = true;
		goto done;
	}

	len = json_object_array_length(jarray);

	bt_shell_printf("# provisioned nodes = %d\n", len);
	for (i = 0; i < len; ++i) {
		json_object *jnode;
		jnode = json_object_array_get_idx(jarray, i);

		if (!jnode || !parse_node(jnode, false))
			goto done;
	}

	res = true;
done:

	g_free(str);

	if (res && refresh)
		prov_file_write(jmain, false);

	if (jmain)
		json_object_put(jmain);

	return res;
}

bool prov_db_read(const char *filename)
{
	prov_filename = filename;
	return read_json_db(filename, true, false);
}

bool prov_db_read_local_node(const char *filename, bool provisioner)
{
	local_filename = filename;
	return read_json_db(filename, provisioner, true);
}
