/*
 * Copyright (C) 2020-2023, IOPSYS Software Solutions AB.
 *
 * Author: Vivek Dutta <vivek.dutta@iopsys.eu>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stdbool.h>

#include "vendor_uspd.h"
#include "msg_handler.h"
#include "os_utils.h"
#include "common_defs.h"
#include "str_vector.h"
#include "text_utils.h"
#include "uptime.h"
#include "usp_err.h"
#include "database.h"
#include "dm_access.h"

#include "vendor_ubus_thread.h"
#include "vendor_utils.h"
#include "vendor_datamodel_ext.h"

#include <libubox/blobmsg_json.h>

#ifndef DM_SECURE
#define DM_SECURE       0x00000800      // secure parameter
#endif

// Timeout in milliseconds
#define NUMENTRY "NumberOfEntries"
#define MAX_CACHE_TIME (10)
#define MAX_GROUP_SEP 2

#define OBUSPA_BOOT_MARKER "/etc/obuspa/.boot"

#define VENDOR_SE_PERM_FILE "/etc/obuspa/search_query_params.json"

extern bool is_running_cli_local_command;

static char *CONST_DM[] = {
	"Device.DeviceInfo.SoftwareVersion",
	"Device.DeviceInfo.ProductClass",
	"Device.DeviceInfo.Manufacturer",
	"Device.DeviceInfo.ModelName",
	"Device.DeviceInfo.HardwareVersion",
	"Device.DeviceInfo.ManufacturerOUI",
	"Device.DeviceInfo.SerialNumber",
};

static const char *CORE_DM[] = {
	"Device.LocalAgent.",
	"Device.BulkData.",
	"Device.MQTT.Client.",
	"Device.MQTT.Capabilities.",
	"Device.STOMP.",
	"Device.UnixDomainSockets.",
	"Device.USPServices."
};

struct uspd_global {
	int ipc_timeout;
	int refresh_expiry_timeout;
	int cached_index;
	bool hold_commit;
	str_vector_t group_vec;
	str_vector_t dm_caching_exclude;
	str_vector_t cached_instances;
	kv_vector_t cached_dm;
};

static struct uspd_global g_uspd;

// core functions
extern int CountPathSeparator(char *path);

// Forward declarations
static void register_operates_events(struct blob_attr *param_blob, dmt_type_t bbf_type);
static char *TEXT_UTILS_SplitPathAtSeparator(char *path, char *buf, int len, int separator_split);
static int uspd_tran_start();
static int uspd_tran_commit();

// Private functions
static int uspd_operate_async(dm_req_t *req, kv_vector_t *input_args, int instance)
{
	int fault;
	vendor_data_t arg;
	char param[MAX_DM_PATH] = {0};

	fault = USP_SIGNAL_OperationStatus(instance, "Active");
	if (fault != USP_ERR_OK) {
		USP_LOG_Error("Failed to set operation status %d", instance);
		return fault;
	}

	memset(&arg, 0, sizeof(vendor_data_t));
	arg.cmd = CMD_OPERATE_ASYNC;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	arg.path = req->path;
	arg.kv = input_args;
	arg.inst = instance;
	_get_controller_info(&arg);

	USP_SNPRINTF(param, sizeof(param), "%s.%d.CommandKey", device_req_root, instance);
	DATA_MODEL_GetParameterValue(param, arg.command_key, MAX_DM_PATH, 0);

	ubus_enqueue_cmd(&arg);

	return arg.fault;
}

static int uspd_operate_sync(dm_req_t *req, char *command_key,
		      kv_vector_t *input_args, kv_vector_t *output_args)
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));
	arg.cmd = CMD_OPERATE_SYNC;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	arg.path = req->path;
	arg.p_value = command_key;
	arg.kv = input_args;
	arg.kv_out = output_args;
	_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);

	return arg.fault;
}

static int uspd_stop_service(void)
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));
	arg.cmd = CMD_STOP;
	arg.ipc_timeout = g_uspd.ipc_timeout;

	ubus_enqueue_cmd(&arg);

	return arg.fault;
}

void register_objects_uniq_params(char *obj_path, str_vector_t *uniq_param_vec)
{
	int i;
	size_t obj_len;
	str_vector_t uniq_params;

	obj_len = strlen(obj_path);
	STR_VECTOR_Init(&uniq_params);

	for (i = 0; i < uniq_param_vec->num_entries; i++) {
		size_t cur_len;
		char *tmp = uniq_param_vec->vector[i];

		// Skip is object len is greater than parameter len
		cur_len = strlen(tmp);
		if (cur_len < obj_len + 1) {
			continue;
		}

		// skip uniq params which does not belong to this object
		if (strstr(tmp, obj_path) == NULL) {
			continue;
		}

		// check if its a leaf parameter
		if (strrchr(tmp + obj_len, '.') == NULL) {
			STR_VECTOR_Add(&uniq_params, tmp+obj_len);
		}
	}

	if (uniq_params.num_entries) {
		// USP_LOG_Debug("Uniq key Reg[%s], num_uniq_params [%d]", obj_path, uniq_params.num_entries);
		USP_REGISTER_Object_UniqueKey(obj_path, uniq_params.vector, uniq_params.num_entries);
	} else {
		USP_LOG_Info("Unique keys not defined for [%s]", obj_path);
	}
	STR_VECTOR_Destroy(&uniq_params);
}

static int get_path_from_group_schema(char *group_name, char *schema)
{
	size_t i, len, j;

	j = 0;
	len = strlen(schema);
	group_name[0] = '\0';

	for (i = 0; i < len; i++) {
		if (schema[i] == '{') {
			group_name[j++] = '*';
			i += 2;
		} else {
			group_name[j++] = schema[i];
		}
	}

	// remove the last *
	if (group_name[j - 2] == '*')
		group_name[j - 2] = '\0';

	return 0;
}

// DM Caching algorithm
// ret =>
//    false: data not valid, get fresh data
//    true: data present in cached and updated in params_kv
static bool check_cached_db(kv_vector_t *params_kv)
{
	kv_vector_t *kv;
	int i;
	bool all_found = false;

	kv = &g_uspd.cached_dm;
	if (kv->num_entries == 0) {
		USP_LOG_Debug("No cached database");
		return false;
	}

	for (i = 0; i < params_kv->num_entries; i++) {
		kv_pair_t *pair = &params_kv->vector[i];

		g_uspd.cached_index = KV_VECTOR_FindKey(kv, pair->key, g_uspd.cached_index);
		if (g_uspd.cached_index != INVALID) {
			USP_SAFE_FREE(pair->value);
			pair->value = USP_STRDUP(kv->vector[g_uspd.cached_index].value);
			all_found = true;
		} else {
			USP_LOG_Debug("[%s] Param [%s], not found in db", __func__, pair->key);
			all_found = false;
			break;
		}
	}

	return all_found;
}

static int group_get(int group_id, kv_vector_t *params)
{
	char group_path[MAX_DM_PATH] = {0};
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));

	// If all parameters not found in cached data, clean cached data and get it from uspd
	bool ret = check_cached_db(params);
	if (ret == false && g_uspd.cached_dm.num_entries == 0) {
		USP_LOG_Info("[%s] Cache db lookup failed, groupid %d", __func__, group_id);
		get_path_from_group_schema(group_path, g_uspd.group_vec.vector[group_id]);
		arg.cmd = CMD_GROUP_GET;
		arg.ipc_timeout = g_uspd.ipc_timeout;
		arg.path = group_path;
		arg.kv = params;
		_get_controller_info(&arg);
		ubus_enqueue_cmd(&arg);
	}
	USP_LOG_Debug("Group %s, cached index %d, is_cached %d, fault %d",g_uspd.group_vec.vector[group_id], g_uspd.cached_dm.num_entries, ret, arg.fault);

	return arg.fault;
}

static int uspd_set_value(dm_req_t *req, char *buf)
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));
	arg.cmd = CMD_SET;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	arg.path = req->path;
	arg.p_value = buf;
	arg.len = strlen(buf);
	_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);

	// Set the specific error message from bbfdm if available
	if (arg.fault != USP_ERR_OK && strlen(arg.fault_msg) > 0) {
		USP_ERR_SetMessage("%s", arg.fault_msg);
	}

	return arg.fault;
}

static int group_set(int group_id, kv_vector_t *params, unsigned *param_types, int *failure_index)
{
	vendor_data_t arg;
	char path[MAX_DM_PATH] = {0};

	memset(&arg, 0, sizeof(vendor_data_t));

	USP_SNPRINTF(path, MAX_DM_PATH, "%s", g_uspd.group_vec.vector[group_id]);

	char *tmp = strrchr(path, '{');
	if (tmp == NULL) {
		USP_ERR_SetMessage("%s: %s is not a multi-instance object", __FUNCTION__, g_uspd.group_vec.vector[group_id]);
		return USP_ERR_INVALID_PATH_SYNTAX;
	}

	*tmp = '\0';
	*failure_index = INVALID;
	arg.path = path;
	arg.cmd = CMD_GROUP_SET;
	arg.ipc_timeout = (g_uspd.ipc_timeout < 60000) ? 60000 : g_uspd.ipc_timeout;
	arg.kv = params;
	arg.p_failure_index = failure_index;
	_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);

	// Set the specific error message from bbfdm if available
	if (arg.fault != USP_ERR_OK && strlen(arg.fault_msg) > 0) {
		USP_ERR_SetMessage("%s", arg.fault_msg);
	}

	return arg.fault;
}

static int group_add(int group_id, char *path, int *instance)
{
	vendor_data_t arg;
	char temp[MAX_DM_PATH] = {0};
	size_t len;

	USP_STRNCPY(temp, path, MAX_DM_PATH);
	len = strlen(temp);
	if (temp[len - 1] != '.') {
		strcat(temp, ".");
	}

	memset(&arg, 0, sizeof(vendor_data_t));
	arg.cmd = CMD_ADD;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	arg.path = temp;
	arg.p_instance = instance;
	_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);

	if (arg.fault != 0)
		return USP_ERR_CREATION_FAILURE;

	return 0;
}

static int group_del(int group_id, char *path)
{
	vendor_data_t arg;
	char temp[MAX_DM_PATH] = {0};
	size_t len;

	USP_STRNCPY(temp, path, MAX_DM_PATH);
	len = strlen(temp);
	if (temp[len - 1] != '.') {
		strcat(temp, ".");
	}

	memset(&arg, 0, sizeof(vendor_data_t));
	arg.cmd = CMD_DEL;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	arg.path = temp;
	_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);

	return arg.fault;
}

static unsigned int get_cached_time(char *path)
{
	int i;

	for (i = 0; i < g_uspd.dm_caching_exclude.num_entries; i++) {
		size_t len = strlen(g_uspd.dm_caching_exclude.vector[i]);

		if (strncmp(path, g_uspd.dm_caching_exclude.vector[i], len) == 0) {
			return 2;
		}
	}

	return g_uspd.refresh_expiry_timeout;
}

static bool get_instances_cached_db(int gid, char *path, str_vector_t *inst_vec)
{
	int i;
	char cached_group_name[MAX_DM_PATH] = {0};
	char group_name[MAX_DM_PATH] = {0};
	bool is_group_found = false;
	size_t len;

	if (g_uspd.cached_instances.num_entries == 0) {
		return false;
	}

	for(i = 0; i < g_uspd.cached_instances.num_entries; i++) {
		if (strncmp(g_uspd.cached_instances.vector[i], path, strlen(path)) == 0) {
			STR_VECTOR_Add(inst_vec, g_uspd.cached_instances.vector[i]);
			is_group_found = true;
		}

		// Check if cached db and requested instance are from same group, if not drop the cached instances
		if (is_group_found == false) {
			TEXT_UTILS_SplitPathAtSeparator(g_uspd.cached_instances.vector[i], cached_group_name, MAX_DM_PATH, MAX_GROUP_SEP);
			len = strlen(cached_group_name);
			if (strncmp(g_uspd.group_vec.vector[gid], cached_group_name, len) == 0) {
				is_group_found = true;
			}
		}
	}

	if (is_group_found == false) {
		TEXT_UTILS_SplitPathAtSeparator(path, group_name, MAX_DM_PATH, MAX_GROUP_SEP);
		len = strlen(group_name);
		for (i = 0; i < g_uspd.cached_dm.num_entries; i++) {
			if (strncmp(g_uspd.cached_dm.vector[i].key, group_name, len) == 0) {
				USP_LOG_Debug("Instance not found for %s, but available in Cached DM", path)
				is_group_found = true;
				break;
			}
		}
	}

	return is_group_found;
}

static int refresh_instances(int group_id, char *path, int *expiry_period)
{
	int i;
	bool ret;
	vendor_data_t arg;
	str_vector_t inst_vec;

	STR_VECTOR_Init(&inst_vec);

	*expiry_period = get_cached_time(path);
	USP_LOG_Debug("Refresh instance cb for %s, group %d", path, group_id);
	// First try to get the instances from cached db
	ret = get_instances_cached_db(group_id, path, &inst_vec);
	if (ret ==  false && inst_vec.num_entries == 0) {
		USP_LOG_Debug("No instances[%s] in cached db, probe lowerlayer", path);
		memset(&arg, 0, sizeof(vendor_data_t));
		arg.cmd = CMD_INSTANCES;
		arg.ipc_timeout = g_uspd.ipc_timeout;
		arg.path = path;
		arg.vec = &inst_vec;
		arg.ceid[0] = '\0'; // instance update is an internal process
		ubus_enqueue_cmd(&arg);
	}

	for (i = 0; i < inst_vec.num_entries; i++) {
		USP_DM_RefreshInstance(inst_vec.vector[i]);
	}

	STR_VECTOR_Destroy(&inst_vec);

	return USP_ERR_OK;
}

static char *TEXT_UTILS_SplitPathAtSeparator(char *path, char *buf, int len, int separator_split)
{
    char *p;
    int size;

    // If no split is specified, then just split on the last object in the path
    // NOTE: This maybe the case if the path was fully specified and did not require any resolution
    if (separator_split == 0)
    {
        return TEXT_UTILS_SplitPath(path, buf, len);
    }

    // Skip to split point by counting separators
    p = path;
    while (separator_split > 0)
    {
        // Skip to the next separator
        p = strchr(p, '.');

        // If the number of separators to skip is greater than the number in the path string, then just split on the last object in the path
        // NOTE: This should never occur (and does not in automated tests), however leaving code in for safety's sake
        if (p == NULL)
        {
            return TEXT_UTILS_SplitPath(path, buf, len);
        }

        p++;    // Skip the '.' itself
        separator_split--;
    }

    // If the code gets here, p points to the point at which we want to split the string

    // Copy the left-hand-side into the return buffer
    size = p - path;
    size = MIN(size, len-1);
    memcpy(buf, path, size);
    buf[size] = '\0';

    // Return a pointer to the right-hand-side
    return p;
}

static bool get_bool_from_str(char *bool_str)
{
	bool ret = false;

	if (bool_str == NULL)
		return false;

	if (TEXT_UTILS_StringToBool(bool_str, &ret) != USP_ERR_OK) {
		USP_LOG_Info("Decode perm of some param failed");
		return false;
	}

	return ret;
}

static int get_associated_group(const char *spath)
{
	int i, group = INVALID;

	for (i = g_uspd.group_vec.num_entries - 1; i >= 0; i--) {
		char *p = g_uspd.group_vec.vector[i];
		size_t len = strlen(p);

		if (strncmp(spath, p, len) == 0) {
			group = i;
			break;
		}
	}

	return group;
}

static int create_dm_group(char *spath, char *dm_write)
{
	int index, fault;
	char group_name[MAX_DM_PATH];
	char *tmp;
	bool is_writable;

	tmp = strchr(spath, '}');
	if (tmp == NULL) {
		USP_LOG_Error("Single instance object[%s]", spath);
		return -1;
	}

	is_writable = get_bool_from_str(dm_write);
	index = get_associated_group(spath);
	if (index != INVALID) {
		USP_LOG_Info("Reg %s obj with existing group %d", spath, index);
		fault = USP_REGISTER_GroupedObject(index, spath, is_writable);
		return fault;
	}

	USP_SNPRINTF(group_name, abs(tmp-spath)+3, "%s", spath);
	index = g_uspd.group_vec.num_entries;

	USP_LOG_Debug("# ADD GROUP [%d => %s] #", index, group_name);
	fault = USP_REGISTER_GroupedObject(index, group_name, is_writable);
	if (fault == USP_ERR_OK) {
		USP_REGISTER_GroupVendorHooks(index, group_get, group_set, group_add, group_del);
		USP_REGISTER_Object_RefreshInstances(group_name, refresh_instances);
		STR_VECTOR_Add(&g_uspd.group_vec, group_name);
	}

	return fault;
}


static bool skip_core_dm(char *spath)
{
	bool ret = false;
	int i;

	for (i = 0; i < ARRAY_SIZE(CORE_DM); i++) {
		size_t len;

		len = strlen(CORE_DM[i]);
		if (strncmp(spath, CORE_DM[i], len) == 0) {
			ret = true;
			break;
		}
	}

	return ret;
}

static bool skip_core_hooks(char *spath)
{
	if ((strncmp(spath, "Device.Reboot()", 15) == 0) ||
	    (strncmp(spath, "Device.FactoryReset()", 21) == 0))
		return true;

	return false;
}

static int initiate_data_caching(str_vector_t *path_vec, bool commit)
{
	int i;
	vendor_data_t arg;

	if (path_vec == NULL) {
		USP_LOG_Error("Invalid inputs");
		return USP_ERR_INTERNAL_ERROR;
	}

	if (commit) {
		uspd_tran_start();
	}
	for (i = 0; i < path_vec->num_entries; i++) {
		// Caching with datamodel kv
		memset(&arg, 0, sizeof(vendor_data_t));
		arg.cmd = CMD_GROUP_GET;
		arg.ipc_timeout = 10 * g_uspd.ipc_timeout;
		arg.path = path_vec->vector[i];
		arg.kv_out = &g_uspd.cached_dm;
		arg.vec = &g_uspd.cached_instances;
		_get_controller_info(&arg);
		ubus_enqueue_cmd(&arg);
		USP_LOG_Debug("Caching dm/inst for [%s], params[%d], inst[%d]", path_vec->vector[i], g_uspd.cached_dm.num_entries, g_uspd.cached_instances.num_entries);
	}
	if (commit) {
		uspd_tran_commit();
	}

	return 0;
}

static int uspd_get_value(dm_req_t *req, char *buf, int len)
{
	kv_vector_t kv_param;
	vendor_data_t arg;
	int fault = USP_ERR_OK;

	USP_ARG_Init(&kv_param);
	USP_ARG_Add(&kv_param, req->path, NULL);

	if (check_cached_db(&kv_param) == false) {
		USP_LOG_Info("[%s] Cache db lookup failed for [%s]", __func__, req->path);
		memset(&arg, 0, sizeof(vendor_data_t));
		arg.cmd = CMD_GET;
		arg.ipc_timeout = g_uspd.ipc_timeout;
		arg.path = req->path;
		arg.p_value = buf;
		arg.len = len;
		_get_controller_info(&arg);
		ubus_enqueue_cmd(&arg);
	} else {
		USP_STRNCPY(buf, kv_param.vector[0].value, len)
	}

	USP_ARG_Destroy(&kv_param);

	return fault;
}

static bool register_num_entries(kv_vector_t *numentry_kv)
{
       int i, fault = USP_ERR_OK;

       // check if the dm entry is a NumberOfEntries parameter
       for (i = 0; i < numentry_kv->num_entries; i++) {
               char *spath = numentry_kv->vector[i].key;
               char *pobj = numentry_kv->vector[i].value;

               // Exit if this is not an object
               dm_node_t *node = dm_node_present(pobj);
               if (node!=NULL && IsObject(node)) {
		       //USP_LOG_Debug("NumberEntries spath[%s], Parent[%s]", spath, pobj);
		       fault = USP_REGISTER_Param_NumEntries(spath, pobj);
               } else {
		       fault = USP_REGISTER_VendorParam_ReadOnly(spath, uspd_get_value, DM_UINT);
		       USP_LOG_Info("pobj %s not registered, reg %s as leaf", pobj, spath);
	       }
       }

       return fault;
}


static bool handle_num_entries_reg(char *spath, kv_vector_t *numentry_kv)
{
	char *p;
	int len = 0, num_len;
	char obj_path[MAX_DM_PATH] = {0};
	char buffer[MAX_DM_PATH] = {0};

	len = strlen(spath);
	num_len = strlen(NUMENTRY);
	// check if the dm entry is a NumberOfEntries parameter
	if (len < num_len || len >= MAX_DM_PATH)
		return false;

	p = spath + len - num_len;
	if (strcmp(p, NUMENTRY) == 0) {
		// exception handling for the object, which does not follow the pattern
		if (strcmp(spath, "Device.DNS.Relay.ForwardNumberOfEntries") == 0) {
			USP_SNPRINTF(obj_path, MAX_DM_PATH, "Device.DNS.Relay.Forwarding.{i}.");
		} else {
			memcpy(buffer, spath, len - num_len);
			buffer[len-num_len] = '\0';
			USP_SNPRINTF(obj_path, MAX_DM_PATH, "%s.{i}.", buffer);
		}
		KV_VECTOR_Add(numentry_kv, spath, obj_path);
		return true;
	}

	return false;
}

static void uspd_register_leaf(char *spath, dmt_type_t bbf_dmt, bool is_writable, str_vector_t *flags_vec)
{
	int group;
	int type;

	type = convert_dmt_to_dmtype(bbf_dmt);

	group = get_associated_group(spath);
	if (group == INVALID) {
		USP_LOG_Debug("LEAF without group (%s), write %d", spath, is_writable);
		if (is_writable)
			USP_REGISTER_VendorParam_ReadWrite(spath, uspd_get_value, uspd_set_value, NULL, type);
		else
			USP_REGISTER_VendorParam_ReadOnly(spath, uspd_get_value, type);
	} else {
		// USP_LOG_Debug("LEAF (%d => %s), write %d", group, spath, is_writable);
		if (is_writable)
			USP_REGISTER_GroupedVendorParam_ReadWrite(group, spath, type);
		else
			USP_REGISTER_GroupedVendorParam_ReadOnly(group, spath, type);
	}

}

static bool handle_prereg_dm(kv_vector_t *cc_param, char *spath, dmt_type_t bbf_type, kv_vector_t *num_entries_kv)
{
	char *val;

	if (skip_core_dm(spath))
		return true;

	if (handle_num_entries_reg(spath, num_entries_kv))
		return true;

	val = KV_VECTOR_Get(cc_param, spath, NULL, 0);
	if (val) {
		return true;
	}

	return false;
}

static int cache_const_dm(kv_vector_t *cached_kv)
{
	int i, fault = 0;
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));

	for (i = 0; i < ARRAY_SIZE(CONST_DM); i++) {
		USP_ARG_Add(cached_kv, CONST_DM[i], "");
	}

	arg.kv = cached_kv;
	arg.cmd = CMD_GROUP_GET;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	arg.path = "Device.DeviceInfo.";
	// there is no controller at this point
	//_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);
	for (i = 0; i < cached_kv->num_entries; i++) {
		kv_pair_t *vptr = &cached_kv->vector[i];

		if (strlen(vptr->value) == 0) {
			char *ptr = NULL;
			char val[MAX_DM_PATH] = {0};

			ptr = strrchr(vptr->key, '.');
			if (ptr == NULL) {
				USP_LOG_Error("Invalid param [%s] in cached dm", vptr->key);
				continue;
			}
			ptr = ptr + 1;
			fault = get_deviceinfo_from_db(ptr, val, MAX_DM_PATH);
			if (fault != USP_ERR_OK) {
				USP_LOG_Error("Empty const %s value", vptr->key);
			}
			USP_FREE(vptr->value);
			vptr->value = USP_STRDUP(val);
		}
		USP_REGISTER_Param_Constant(vptr->key, vptr->value, DM_STRING);
	}
	return 0;
}

static void register_schema_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	size_t rem = 0, i;
	int ret;
	struct blob_attr *schema_blob = NULL;
	struct blob_attr *params;
	struct blob_attr *cur;
	str_vector_t unique_params_vec;
	str_vector_t multi_inst_vec;
	kv_vector_t num_entries_kv;
	kv_vector_t cached_kv;
	vendor_data_t *arg = req->priv;

	if (!msg) {
		arg->fault = USP_ERR_INTERNAL_ERROR;
		return;
	}

	schema_blob = blob_memdup(msg);
	if (schema_blob == NULL) {
		arg->fault = USP_ERR_INTERNAL_ERROR;
		return;
	}

	STR_VECTOR_Init(&unique_params_vec);
	STR_VECTOR_Init(&multi_inst_vec);
	KV_VECTOR_Init(&num_entries_kv);
	KV_VECTOR_Init(&cached_kv);

	params = get_parameters(schema_blob, "results");
	if (params == NULL) {
		arg->fault = USP_ERR_INTERNAL_ERROR;
		goto end;
	}

	// Get const datamodel value
	cache_const_dm(&cached_kv);
	blobmsg_for_each_attr(cur, params, rem) {
		char dm_spath[MAX_DM_PATH] = {0};
		char dm_stype[MAX_DM_PATH] = {0};
		char dm_write[MAX_DM_VALUE_LEN] = {0};
		str_vector_t flags_vec;
		dmt_type_t bbf_dmt;

		STR_VECTOR_Init(&flags_vec);
		ret = get_details_from_blob(cur, dm_spath, dm_write, dm_stype, NULL, &flags_vec);
		if (ret != USP_ERR_OK) {
			STR_VECTOR_Destroy(&flags_vec);
			USP_LOG_Error("Empty parameter blob");
			continue;
		}

		bbf_dmt = get_dmt_type(dm_stype);
		switch(bbf_dmt) {
			case DMT_COMMAND: // Register commands
			case DMT_EVENT: // Register events
			{
				register_operates_events(cur, bbf_dmt);
				break;
			}
			case DMT_OBJECT:
			{
				create_dm_group(dm_spath, dm_write);
				STR_VECTOR_Add_IfNotExist(&multi_inst_vec, dm_spath);
				break;
			}
			default: // Register leaf
			{
				bool is_writable = get_bool_from_str(dm_write);

				if (handle_prereg_dm(&cached_kv, dm_spath, bbf_dmt, &num_entries_kv) == false) {
					uspd_register_leaf(dm_spath, bbf_dmt, is_writable, &flags_vec);
				}

				if (STR_VECTOR_Find(&flags_vec, DM_FLAG_UNIQUE) != INVALID) {
					STR_VECTOR_Add(&unique_params_vec, dm_spath);
				}
			}

		}
		STR_VECTOR_Destroy(&flags_vec);
	}

	STR_VECTOR_Sort(&unique_params_vec);
	STR_VECTOR_Sort(&multi_inst_vec);

	// register uniq parameters
	for (i = 0; i < multi_inst_vec.num_entries; i++) {
		register_objects_uniq_params(multi_inst_vec.vector[i], &unique_params_vec);
	}

	register_num_entries(&num_entries_kv);

end:
	KV_VECTOR_Destroy(&cached_kv);
	KV_VECTOR_Destroy(&num_entries_kv);
	STR_VECTOR_Destroy(&unique_params_vec);
	STR_VECTOR_Destroy(&multi_inst_vec);
	USP_FREE(schema_blob);
}

static int register_event_dm(char *epath, struct blob_attr *in_attr)
{
	int in_nr = 0, fault;
	char **in = NULL;

	if (in_attr == NULL)
		return INVALID;

	// USP_LOG_Debug("Registering event [%s]", epath);

	fault = USP_REGISTER_Event(epath);
	if (fault != USP_ERR_OK) {
		return fault;
	}

	in = blob_array_str_list(in_attr, &in_nr);
	if (in_nr != INVALID) {
		fault = USP_REGISTER_EventArguments(epath, in, in_nr);
	}

	free_str_list(in_nr, in);
	return fault;
}

static int register_operate_dm(char *path, const char *op_type,
		struct blob_attr *in_attr, struct blob_attr *out_attr)
{
	int fault = USP_ERR_OK;
	char **in = NULL, **out = NULL;
	int in_nr = 0, out_nr = 0;

	if (path == NULL || op_type == NULL) {
		USP_LOG_Error("Type info not present");
		return USP_ERR_INTERNAL_ERROR;
	}

	// Skip core hooks
	if (skip_core_hooks(path)) {
		// USP_LOG_Debug("Skipping Registering operate [%s => %s]", path, op_type);
		return USP_ERR_OK;
	}

	// USP_LOG_Debug("Registering operate [%s => %s]", path, op_type);

	if (dm_node_present(path) != NULL) {
		USP_LOG_Debug("Operation (%s) already registered", path);
		return USP_ERR_OK;
	}
	if (strncmp(op_type, "sync", 4) == 0) {
		fault = USP_REGISTER_SyncOperation(path, uspd_operate_sync);
	} else {
		fault = USP_REGISTER_AsyncOperation(path, uspd_operate_async, NULL);
		fault |= USP_REGISTER_AsyncOperation_MaxConcurrency(path, 1);
	}

	if (fault != USP_ERR_OK) {
		return fault;
	}

	if (in_attr != NULL)
		in = blob_array_str_list(in_attr, &in_nr);

	if (out_attr != NULL)
		out = blob_array_str_list(out_attr, &out_nr);

	fault = USP_REGISTER_OperationArguments(path, in, in_nr, out, out_nr);

	free_str_list(in_nr, in);
	free_str_list(out_nr, out);

	return fault;
}

static void register_operates_events(struct blob_attr *param_blob, dmt_type_t bbf_type)
{
	char path[MAX_DM_PATH] = {0}, data[MAX_DM_VALUE_LEN] = {0};
	struct blob_attr *input, *output;

	get_details_from_blob(param_blob, path, data, NULL, NULL, NULL);
	input = get_parameters(param_blob, "input");
	output = get_parameters(param_blob, "output");
	if (bbf_type == DMT_EVENT) {
		register_event_dm(path, input);
	} else if (bbf_type == DMT_COMMAND) {
		register_operate_dm(path, data, input, output);
	} else {
		USP_LOG_Error("Invalid type for registration");
	}
}

int Validate_SecuredRoles(dm_req_t *req, char *value)
{
	char *role_path;
	char *saveptr;
	char *str;
	char temp[MAX_DM_PATH];
	int role_instance;
	int err;

	// Empty string is valid
	if (*value == '\0')
	{
		return USP_ERR_OK;
	}

	// Copy the value as strtok_r modifies the string
	USP_STRNCPY(temp, value, sizeof(temp));

	// Iterate through comma-separated list
	str = temp;
	role_path = strtok_r(str, ",", &saveptr);
	while (role_path != NULL)
	{
		// Trim whitespace
		role_path = TEXT_UTILS_TrimBuffer(role_path);

		// Verify that this path exists in the Role table using DM_ACCESS_ValidateReference
		err = DM_ACCESS_ValidateReference(role_path, "Device.LocalAgent.ControllerTrust.Role.{i}", &role_instance);
		if (err != USP_ERR_OK)
		{
			USP_ERR_SetMessage("%s: Role path '%s' does not exist in Device.LocalAgent.ControllerTrust.Role table", __FUNCTION__, role_path);
			return USP_ERR_INVALID_VALUE;
		}

		role_path = strtok_r(NULL, ",", &saveptr);
	}

	return USP_ERR_OK;
}

int register_securedrole()
{
	// Register Device.LocalAgent.ControllerTrust.SecuredRoles parameter
	return USP_REGISTER_DBParam_ReadWrite("Device.LocalAgent.ControllerTrust.SecuredRoles", "", Validate_SecuredRoles, NULL, DM_STRING);
}

static int uspd_register_schema()
{
	struct blob_buf bb = {};
	int fault = USP_ERR_OK;
	vendor_data_t arg;

	// register mandatory parameters
	memset(&arg, 0, sizeof(vendor_data_t));
	arg.ipc_timeout = g_uspd.ipc_timeout;

	// Invoke Ubus to get data from uspd
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "path", ROOT_PATH);
	blobmsg_add_u8(&bb, "first_level", false);
	blobmsg_add_u8(&bb, "commands", true);
	blobmsg_add_u8(&bb, "events", true);
	blobmsg_add_u8(&bb, "params", true);
	blob_add_optinal_params(&bb);

	fault = uspd_call(NULL, DM_SCHEMA_METHOD, &bb, register_schema_cb, &arg);
	blob_buf_free(&bb);

	return fault;
}

static int factory_reset(void)
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));

	arg.cmd = CMD_OPERATE_SYNC;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	arg.path = "Device.FactoryReset()";

	ubus_enqueue_cmd(&arg);
	uspd_stop_service();
	return arg.fault;
}

static int reboot(void)
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));

	arg.cmd = CMD_OPERATE_SYNC;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	arg.path = "Device.Reboot()";

	ubus_enqueue_cmd(&arg);
	uspd_stop_service();
	return arg.fault;
}

static int uspd_tran_start()
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));
	arg.cmd = CMD_TRAN_START;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);

	return arg.fault;
}

static int uspd_tran_commit()
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));
	if (g_uspd.hold_commit == false) {
		arg.cmd = CMD_TRAN_COMMIT;
		arg.ipc_timeout = g_uspd.ipc_timeout;
		_get_controller_info(&arg);
		ubus_enqueue_cmd(&arg);
	}

	return arg.fault;
}

static int uspd_tran_abort()
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));
	arg.cmd = CMD_TRAN_ABORT;
	arg.ipc_timeout = g_uspd.ipc_timeout;
	_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);

	return arg.fault;
}

void uspd_firmware_upgrade_marker(bool *is_firmware_updated)
{
    struct stat info;
    int err;
    bool missing = false;

    err = stat(OBUSPA_BOOT_MARKER, &info);
    if (err != 0) {
	    missing = true;
    }
    // Boot marker will be missing in case of firmware upgrade
    *is_firmware_updated = missing;
    USP_LOG_Debug("Firmware upgrade status: %d", missing);
}

static int vendor_hook_init(void)
{
	vendor_hook_cb_t callbacks;

	memset(&callbacks, 0, sizeof(callbacks));
	callbacks.reboot_cb = reboot;
	callbacks.factory_reset_cb = factory_reset;
	callbacks.start_trans_cb = uspd_tran_start;
	callbacks.commit_trans_cb = uspd_tran_commit;
	callbacks.abort_trans_cb = uspd_tran_abort;
	callbacks.modify_firmware_updated_cb = uspd_firmware_upgrade_marker;

	return USP_REGISTER_CoreVendorHooks(&callbacks);
}

static void dont_cached_params(str_vector_t *cached_vec)
{
	char cached_json_file[256] = {0};
	const char uci_cpath[] = "obuspa.global.dm_caching_exclude";
	struct blob_buf bb;
	struct blob_attr *cur;
	int rem;
	enum {
		CACHED_PARAMS,
		__CACHED_MAX
	};
	struct blob_attr *f_attr[__CACHED_MAX] = {NULL};
	const struct blobmsg_policy fp[__CACHED_MAX] = {
		{ "dmcaching_exclude", BLOBMSG_TYPE_ARRAY }
	};

	memset(&bb, 0, sizeof(struct blob_buf));
	blob_buf_init(&bb, 0);
	STR_VECTOR_Init(cached_vec);

	if (get_value_from_uci(uci_cpath, cached_json_file, 256) != 0)
		goto exit;

	if (blobmsg_add_json_from_file(&bb, cached_json_file) == false)
		goto exit;

	blobmsg_parse(fp, __CACHED_MAX, f_attr, blobmsg_data(bb.head), blobmsg_len(bb.head));
	if (!f_attr[CACHED_PARAMS])
		goto exit;

	blobmsg_for_each_attr(cur, f_attr[CACHED_PARAMS], rem) {
		char *str = NULL;
		size_t len = 0;

		if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
			continue;

		str = blobmsg_get_string(cur);
		len = strlen(str);
		if (strncmp(str, ROOT_PATH, 7) == 0 && str[len - 1] == '.') // Safety check
			STR_VECTOR_Add(cached_vec, str);
		else
			USP_LOG_Error("[%s:%d] Invalid entry[%s] in Cached json", __func__, __LINE__, str);
	}

exit:
	blob_buf_free(&bb);
}

static void global_structure_init()
{
	int val;

	memset(&g_uspd, 0, sizeof(struct uspd_global));
	g_uspd.cached_index = 0;
	STR_VECTOR_Init(&g_uspd.group_vec);
	STR_VECTOR_Init(&g_uspd.dm_caching_exclude);
	STR_VECTOR_Init(&g_uspd.cached_instances);
	KV_VECTOR_Init(&g_uspd.cached_dm);

	if (is_running_cli_local_command == false) {
		val = get_int_value_from_uci("obuspa.global.ipc_timeout");
		g_uspd.ipc_timeout = (val) ? val : DEFAULT_IPC_TIMEOUT;

		val = get_int_value_from_uci("obuspa.global.max_cache_timeout");
		g_uspd.refresh_expiry_timeout = (val) ? val : MAX_CACHE_TIME;
	}
}

static void global_structure_free()
{
	STR_VECTOR_Destroy(&g_uspd.dm_caching_exclude);
	STR_VECTOR_Destroy(&g_uspd.cached_instances);
	KV_VECTOR_Destroy(&g_uspd.cached_dm);
	// This needs to be manually freed as the memory
	// monitoring stars with start function, so any memory allocated in
	// init needs to be freed manually.
	manual_vec_free(&g_uspd.group_vec);
}

bool DEVICE_CTRUST_IsControllerSecured()
{
	char secured_roles[MAX_DM_PATH] = {0};
	char role[MAX_DM_PATH] = {0};
	char temp[MAX_DM_PATH] = {0};
	int err;
	int controller_instance;

	// Exit if unable to get the secured roles
	err = DATA_MODEL_GetParameterValue("Device.LocalAgent.ControllerTrust.SecuredRoles", secured_roles, sizeof(secured_roles), 0);
	if (err != USP_ERR_OK)
	{
		return false;
	}

	// Empty string means no secured roles
	if (*secured_roles == '\0')
	{
		return false;
	}

	controller_instance = MSG_HANDLER_GetMsgControllerInstance();
	USP_SNPRINTF(temp, sizeof(temp), "Device.LocalAgent.Controller.%d.InheritedRole", controller_instance);

	err = DATA_MODEL_GetParameterValue(temp, role, sizeof(role), 0);
	if ((err != USP_ERR_OK) || (strlen(role) == 0))
	{
		return false;
	}

	if (strstr(secured_roles, role) != NULL) {
		return true;
	}

	USP_SNPRINTF(temp, sizeof(temp), "Device.LocalAgent.Controller.%d.AssignedRole", controller_instance);
	err = DATA_MODEL_GetParameterValue(temp, role, sizeof(role), 0);
	if ((err != USP_ERR_OK) || (strlen(role) == 0))
	{
		return false;
	}

	if (strstr(secured_roles, role) != NULL) {
		return true;
	}

	return false;
}

int _get_controller_info(vendor_data_t *argp)
{
	const char *endpoint_id = NULL;

	if (!argp)
		return INVALID;

	argp->ceid[0] = '\0'; // init with the empty value
	endpoint_id = MSG_HANDLER_GetMsgControllerEndpointId();
	if (endpoint_id && (strlen(endpoint_id) != 0)) {
		USP_STRNCPY(argp->ceid, endpoint_id, MAX_DM_PATH); // Only copy if its a valid string
	}

	if (DEVICE_CTRUST_IsControllerSecured()) {
		USP_LOG_Debug("Controller [%s] is secured", argp->ceid);
		argp->is_secured = true;
	}

	return USP_ERR_OK;
}

// Public methods
int vendor_create_dm_cache(char *paths[], int num_paths)
{
	int i;
	str_vector_t path_vec;

	STR_VECTOR_Destroy(&g_uspd.cached_instances);
	KV_VECTOR_Destroy(&g_uspd.cached_dm);

	// No paths to cache
	if (num_paths == 0) {
		USP_LOG_Debug("Cache dropped, returning no num_paths");
		return 0;
	}

	STR_VECTOR_Init(&path_vec);

	for (i = 0; i < num_paths; i++) {
		char buf[MAX_DM_PATH] = {0};
		char *temp;
		size_t len;

		temp = paths[i];
		if (temp == NULL) {
			continue;
		}

		len = strlen(temp);
		if (len == 0) {
			continue;
		}

		if (skip_core_dm(temp)) {
			continue;
		}

		if (temp[len-1] == '.') {
			TEXT_UTILS_SplitPathAtSeparator(temp, buf, MAX_DM_PATH, MAX_GROUP_SEP);
			STR_VECTOR_Add_IfNotExist(&path_vec, buf);
		}
	}

	if (path_vec.num_entries) {
		initiate_data_caching(&path_vec, false);
	}

	STR_VECTOR_Destroy(&path_vec);
	USP_LOG_Info("Cache done on [%d] paths, cached status[%d => %d](dm => inst), ", num_paths, g_uspd.cached_dm.num_entries, g_uspd.cached_instances.num_entries);

	return 0;
}

int vendor_hold_commits(bool hold_commit)
{
	g_uspd.hold_commit = hold_commit;
	if (hold_commit == false) {
		uspd_tran_commit();
	}
	return 0;
}

int vendor_uspd_init(void)
{
	int fault = USP_ERR_OK;

	global_structure_init();
	if (is_running_cli_local_command == false) {
		fault = ubus_thread_init();
		fault |= uspd_register_schema();
		fault |= vendor_hook_init();
		DATABASE_force_reset_file();
		USP_LOG_Info("Value Change poll period is [%d]", VALUE_CHANGE_POLL_PERIOD);
	}

	fault |= register_securedrole();
	fault |= register_localagent_extn();

	if (is_running_cli_local_command == false) {
		ubus_thread_start();

		USP_LOG_Info("MAX CONTROLLERs Supported %d", MAX_CONTROLLERS);
		dont_cached_params(&g_uspd.dm_caching_exclude);
	}

	return fault;
}

int vendor_uspd_start(void)
{
	int fault = USP_ERR_OK;
	FILE *fd;
	char *path[2] = {ROOT_PATH, 0};

	// Create the boot marker file, if not present
	fd = fopen(OBUSPA_BOOT_MARKER, "a"); // cppcheck-suppress cert-MSC24-C
	if (fd) {
		fclose(fd);
	} else {
		USP_LOG_Error("Failed to create %s boot marker", OBUSPA_BOOT_MARKER);
		fault = USP_ERR_INTERNAL_ERROR;
	}

	vendor_create_dm_cache(path, 1);

	return fault;
}

int vendor_uspd_stop(void)
{
	global_structure_free();
	ubus_thread_cleanup();
	return USP_ERR_OK;
}

/*********************************************************************//**
**
** VendorAppendSEParamsFromJsonFile
**
** Reads parameter names from /etc/obuspa/search_query_param.json and
** appends them to the given string vector, skipping duplicates.
** The file is optional; if absent, this function returns silently.
**
** Expected JSON format: { "params": ["ParamName1", "ParamName2"] }
**
** \param   sv - string vector to append parameter names to
**
** \return  None
**
**************************************************************************/
void VendorAppendSEParamsFromJsonFile(str_vector_t *sv)
{
    struct blob_buf bb;
    struct blob_attr *cur;
    int rem;
    enum {
        SE_PARAMS,
        __SE_MAX
    };
    struct blob_attr *f_attr[__SE_MAX] = {NULL};
    const struct blobmsg_policy fp[__SE_MAX] = {
        { "params", BLOBMSG_TYPE_ARRAY }
    };

    if (sv == NULL)
        return;

    memset(&bb, 0, sizeof(struct blob_buf));
    blob_buf_init(&bb, 0);

    if (blobmsg_add_json_from_file(&bb, VENDOR_SE_PERM_FILE) == false)
        goto exit;

    blobmsg_parse(fp, __SE_MAX, f_attr, blobmsg_data(bb.head), blobmsg_len(bb.head));
    if (!f_attr[SE_PARAMS])
        goto exit;

    blobmsg_for_each_attr(cur, f_attr[SE_PARAMS], rem) {
        char *str;

        if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
            continue;

        str = blobmsg_get_string(cur);
	STR_VECTOR_Add_IfNotExist(sv, str);
    }

exit:
    blob_buf_free(&bb);
}
