/*
 * Copyright (C) 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 "os_utils.h"
#include "text_utils.h"
#include "msg_handler.h"

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

#define INTERNAL_CID "CLI_Utility"
#define NETWORK_UCI "/etc/config/network"

struct ubus_thread_global {
       bool is_critial_service_present;
       int mq_sockets[2];
       // cppcheck-suppress cert-API01-C
       struct ubus_event_handler ubus_notify_event; // ubus usp.event to notify about events
       // cppcheck-suppress cert-API01-C
       struct uloop_timeout session_abort_timer; // cppcheck-suppress cert-API01-C
       // cppcheck-suppress cert-API01-C
       struct uloop_timeout critical_service_apply_timer; // cppcheck-suppress cert-API01-C
       pthread_mutex_t ubus_lock; // cppcheck-suppress cert-API01-C
       pthread_cond_t ubus_cond; // cppcheck-suppress cert-API01-C
       str_vector_t updated_services; // cppcheck-suppress cert-API01-C
       struct ubus_context *ubus_ctx; // ubus context
       char session_ceid[MAX_DM_PATH];
};

static struct ubus_thread_global g_ubus_data;

#define MQ_RX_SOCKET  g_ubus_data.mq_sockets[0]
#define MQ_TX_SOCKET  g_ubus_data.mq_sockets[1]
#define STR(s) #s

#define DM_EVENT DM_OBJECT_NAME ".event"

static int _uspd_call(struct ubus_context *ctx, const char *object, const char *method,
		struct blob_buf *data, ubus_data_handler_t callback,
		vendor_data_t *arg);

static void _handle_update_service(bool is_commit);

static int ubus_data_init()
{
	memset(&g_ubus_data, 0, sizeof(struct ubus_thread_global));

	g_ubus_data.ubus_ctx = NULL;

	// Required in init
	STR_VECTOR_Init(&g_ubus_data.updated_services);

	g_ubus_data.is_critial_service_present = false;
	g_ubus_data.mq_sockets[0] = -1;
	g_ubus_data.mq_sockets[1] = -1;
	g_ubus_data.session_ceid[0] = '\0';

	pthread_cond_init(&g_ubus_data.ubus_cond, NULL);
	pthread_mutex_init(&g_ubus_data.ubus_lock, NULL);

	return USP_ERR_OK;
}

// Event handling
static void event_handler(struct ubus_context *ctx, struct ubus_event_handler *ev, const char *type, struct blob_attr *msg)
{
	char *ename;
	int rem, ret;
	kv_vector_t *eparam;
	dm_node_t *node;
	enum {
		P_NAME,
		P_INPUT,
		__P_MAX
	};
	const struct blobmsg_policy p[__P_MAX] = {
		{ "name", BLOBMSG_TYPE_STRING },
		{ "input", BLOBMSG_TYPE_ARRAY },
	};
	struct blob_attr *tb[__P_MAX] = {NULL}, *params, *cur;

	if (blobmsg_parse(p, __P_MAX, tb, blobmsg_data(msg), blobmsg_len(msg)) != UBUS_STATUS_OK) {
		USP_LOG_Warning("Failed to parse event data");
		return;
	}

	if (!tb[P_NAME]) {
		USP_LOG_Warning("Event name not present");
		return;
	}

	if (!tb[P_INPUT]) {
		USP_LOG_Warning("Event input not present in msg");
		return;
	}

	params = tb[P_INPUT];
	ename = blobmsg_get_string(tb[P_NAME]);
	USP_LOG_Info("Got event %s", ename);

	node = DM_PRIV_GetNodeFromPath(ename, NULL, NULL, DONT_LOG_ERRORS);
	if (node == NULL) {
		USP_LOG_Warning("Event/Param (%s) not registered", ename);
		return;
	}

	if (node->type != kDMNodeType_Event) {
		USP_LOG_Warning("Parameter (%s) not an event", ename);
		return;
	}

	eparam = USP_ARG_Create();
	if (eparam == NULL) {
		USP_LOG_Error("Failed to allocate memory for input event");
		return;
	}

	blobmsg_for_each_attr(cur, params, rem) {
		char path[MAX_DM_PATH], val[MAX_DM_VALUE_LEN];

		int fault = get_details_from_blob(cur, path, val, NULL, NULL, NULL);
		if (fault != USP_ERR_OK) {
			continue;
		}

		USP_ARG_Add(eparam, path, val);
	}

	// Dump parameters for cli validation
	if (USP_LOG_GetLogLevel() >= kLogLevel_Info) {
		KV_VECTOR_Dump(eparam);
	}

	ret = USP_SIGNAL_DataModelEvent(ename, eparam);
	USP_LOG_Debug("Event[%s] notified, status[%d]", ename, ret);
	if (ret != USP_ERR_OK) {
		USP_LOG_Warning("Failed to notify [%s], status[%d]", ename, ret);
	}
}

// Registration handling
static char *stringify_cmd(int cmd)
{
	switch(cmd) {
	case CMD_NULL:
		return STR(CMD_NULL);
	case CMD_GROUP_GET:
		return STR(CMD_GROUP_GET);
	case CMD_GROUP_SET:
		return STR(CMD_GROUP_SET);
	case CMD_ADD:
		return STR(CMD_ADD);
	case CMD_DEL:
		return STR(CMD_DEL);
	case CMD_GET:
		return STR(CMD_GET);
	case CMD_SET:
		return STR(CMD_SET);
	case CMD_OPERATE_SYNC:
		return STR(CMD_OPERATE_SYNC);
	case CMD_OPERATE_ASYNC:
		return STR(CMD_OPERATE_ASYNC);
	case CMD_INSTANCES:
		return STR(CMD_INSTANCES);
	case CMD_TRAN_START:
		return STR(CMD_TRAN_START);
	case CMD_TRAN_COMMIT:
		return STR(CMD_TRAN_COMMIT);
	case CMD_TRAN_ABORT:
		return STR(CMD_TRAN_ABORT);
	case CMD_SESSION_MGMT:
		return STR(CMD_SESSION_MGMT);
	case CMD_STOP:
		return STR(CMD_STOP);
	}

	return NULL;
}

static void _get_value_single_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	vendor_data_t *arg = req->priv;
	struct blob_attr *params;
	struct blob_attr *cur;
	size_t rem;

	if (arg == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return;
	}

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

	// No place holder to return value for the variable, returning
	if (arg->p_value == NULL) {
		USP_LOG_Debug("[%s:%d] Null p_value", __func__, __LINE__);
		arg->fault = USP_ERR_OK;
		return;
	}

	params = get_parameters(msg, "results");
	if (params == NULL) {
		arg->fault = USP_ERR_INTERNAL_ERROR;
		return;
	}

	blobmsg_for_each_attr(cur, params, rem) {
		char path[MAX_DM_PATH] = {0}, val[MAX_DM_VALUE_LEN] = {0};
		int fault;
		str_vector_t flags_vec;

		STR_VECTOR_Init(&flags_vec);
		fault = get_details_from_blob(cur, path, val, NULL, NULL, &flags_vec);
		if (fault != USP_ERR_OK) {
			arg->fault = fault;
		}

		if (strcmp(path, arg->path) == 0) {
			if (STR_VECTOR_Find(&flags_vec, DM_FLAG_SECURE) != INVALID) {
				if (arg->is_secured == false) {
					val[0]='\0';
				}
			}
			USP_STRNCPY(arg->p_value, val, arg->len);
			STR_VECTOR_Destroy(&flags_vec);
			break;
		}
		STR_VECTOR_Destroy(&flags_vec);
	}
}

static int _get_value_single(vendor_data_t *arg)
{
	struct blob_buf bb = { };

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

	blobmsg_add_string(&bb, "path", arg->path);
	blob_add_optinal_params(&bb);

	// Invoke Ubus to get data from uspd
	uspd_call(g_ubus_data.ubus_ctx, "get", &bb, _get_value_single_cb, arg);

	blob_buf_free(&bb);
	return arg->fault;
}

static void _check_updated_service_list(struct blob_attr *msg)
{
	struct blob_attr *modified_uci = NULL;
	struct blob_attr *item;
	size_t left;
	char *config;

	modified_uci = get_parameters(msg, "modified_uci");
	if (modified_uci == NULL)
		return;

	blobmsg_for_each_attr(item, modified_uci, left) {
		if (blobmsg_type(item) != BLOBMSG_TYPE_STRING)
			continue;

		config = blobmsg_get_string(item);
		if (strcmp(config, NETWORK_UCI) == 0) {
			g_ubus_data.is_critial_service_present = true;
		}
		STR_VECTOR_Add_IfNotExist(&g_ubus_data.updated_services, config);
		USP_LOG_Debug("Updated configs [%s]", config);
	}
}

static void _get_value_group_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	int index, i;
	vendor_data_t *arg = req->priv;
	struct blob_attr *params, *cur;
	size_t rem;
	kv_vector_t pv_set;

	if (arg == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return;
	}

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

	_check_updated_service_list(msg);

	params = get_parameters(msg, "results");
	if (params == NULL) {
		arg->fault = USP_ERR_INTERNAL_ERROR;
		return;
	}

	index = 0;
	blobmsg_for_each_attr(cur, params, rem) {
		char path[MAX_DM_PATH], val[MAX_DM_VALUE_LEN];
		int fault;
		str_vector_t flags_vec;

		STR_VECTOR_Init(&flags_vec);
		fault = get_details_from_blob(cur, path, val, NULL, NULL, &flags_vec);
		if (fault != USP_ERR_OK) {
			arg->fault = fault;
		}
		if (STR_VECTOR_Find(&flags_vec, DM_FLAG_SECURE) != INVALID) {
			if (arg->is_secured == false) {
				val[0]='\0';
			}
		}
		USP_ARG_Add(&pv_set, path, val);
		if (arg->kv_out) {
			USP_ARG_Add(arg->kv_out, path, val);
		}
		STR_VECTOR_Destroy(&flags_vec);
	}

	if (arg->kv) {
		for (i = 0, index = 0; i < arg->kv->num_entries; i++) {
			kv_pair_t *pair = &arg->kv->vector[i];

			USP_SAFE_FREE(pair->value);
			index = KV_VECTOR_FindKey(&pv_set, pair->key, index);
			if (index == INVALID) {
				USP_LOG_Error("Instance[%s] might have deleted in backend", pair->key);
				pair->value = USP_STRDUP("");
			} else {
				// Found a matching key, so replace its value
				pair->value = USP_STRDUP(pv_set.vector[index].value);
				index++;
			}
		}
	}
	if (arg->vec) {
		params = get_parameters(msg, "instances");
		if (params) {
			rem = 0;
			blobmsg_for_each_attr(cur, params, rem) {
				if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
					continue;

				STR_VECTOR_Add(arg->vec, blobmsg_get_string(cur));
			}
		}
	}

	USP_ARG_Destroy(&pv_set);
}

static int _get_value_group(vendor_data_t *arg)
{
	struct blob_buf bb = { };

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

	blobmsg_add_string(&bb, "path", arg->path);
	blob_add_optinal_params(&bb);

	uspd_call(g_ubus_data.ubus_ctx, "get", &bb, _get_value_group_cb, arg);

	blob_buf_free(&bb);
	return arg->fault;
}

static void _get_instances_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	vendor_data_t *arg = req->priv;
	struct blob_attr *cur, *params = NULL;
	size_t rem;

	if (arg == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return;
	}

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

	_check_updated_service_list(msg);

	params = get_parameters(msg, "results");
	if (params == NULL) {
		arg->fault = USP_ERR_INTERNAL_ERROR;
		return;
	}

	blobmsg_for_each_attr(cur, params, rem) {
		char path[MAX_DM_PATH];
		int fault;

		fault = get_details_from_blob(cur, path, NULL, NULL, NULL, NULL);
		if (fault != USP_ERR_OK) {
			arg->fault = fault;
		}

		STR_VECTOR_Add(arg->vec, path);
	}
}

static int _get_instances(vendor_data_t *arg)
{
	struct blob_buf bb = {};

	if (arg->path == NULL || arg->vec == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return USP_ERR_INTERNAL_ERROR;
	}

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

	blobmsg_add_string(&bb, "path", arg->path);
	blob_add_optinal_params(&bb);

	uspd_call(g_ubus_data.ubus_ctx, DM_INSTANCE_METHOD, &bb, _get_instances_cb, arg);

	blob_buf_free(&bb);

	return USP_ERR_OK;
}

static void _add_object_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	size_t rem;
	struct blob_attr *results, *cur;
	vendor_data_t *arg = req->priv;

	if (arg == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return;
	}

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

	_check_updated_service_list(msg);

	results = get_parameters(msg, "results");
	if (results == NULL) {
		arg->fault = USP_ERR_INTERNAL_ERROR;
		return;
	}

	blobmsg_for_each_attr(cur, results, rem) {
		char instance[MAX_DM_VALUE_LEN] = {0};

		arg->fault = get_details_from_blob(cur, NULL, instance, NULL, NULL, NULL);
		*arg->p_instance = strtol(instance, NULL, 10);
		break;
	}
}

static int _add_object(vendor_data_t *arg)
{
	struct blob_buf bb = {};

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

	blobmsg_add_string(&bb, "path", arg->path);
	blob_add_optinal_params(&bb);

	uspd_call(g_ubus_data.ubus_ctx, "add", &bb, _add_object_cb, arg);

	blob_buf_free(&bb);
	return arg->fault;
}

static void _set_value_single_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	size_t rem;
	struct blob_attr *results, *cur;
	vendor_data_t *arg = req->priv;

	if (arg == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return;
	}

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

	_check_updated_service_list(msg);

	results = get_parameters(msg, "results");
	if (results == NULL) {
		arg->fault = USP_ERR_INTERNAL_ERROR;
		return;
	}

	blobmsg_for_each_attr(cur, results, rem) {
		arg->fault = get_details_from_blob(cur, NULL, NULL, NULL, arg->fault_msg, NULL);
		break;
	}
}

static int _set_value_single(vendor_data_t *arg)
{
	struct blob_buf bb = {};

	if (arg->p_value == NULL || arg->path == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return USP_ERR_INTERNAL_ERROR;
	}

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

	blobmsg_add_string(&bb, "path", arg->path);
	blobmsg_add_string(&bb, "value", arg->p_value);
	blob_add_optinal_params(&bb);

	uspd_call(g_ubus_data.ubus_ctx, "set", &bb, _set_value_single_cb, arg);

	blob_buf_free(&bb);
	return USP_ERR_OK;
}

static void _set_value_group_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	struct blob_attr *params;
	struct blob_attr *cur, *modified_uci;
	size_t rem;
	vendor_data_t *arg = req->priv;

	if (arg == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return;
	}

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

	modified_uci = get_parameters(msg, "modified_uci");
	if (modified_uci) {
		struct blob_attr *item;
		size_t left;

		blobmsg_for_each_attr(item, modified_uci, left) {
			STR_VECTOR_Add_IfNotExist(&g_ubus_data.updated_services, blobmsg_get_string(item));
			USP_LOG_Debug("# Updated configs [%s]", blobmsg_get_string(item));
		}
	}

	params = get_parameters(msg, "results");
	if (params == NULL) {
		return;
	}

	blobmsg_for_each_attr(cur, params, rem) {
		char path[MAX_DM_PATH];

		arg->fault = get_details_from_blob(cur, path, NULL, NULL, arg->fault_msg, NULL);
		if ( arg->fault != USP_ERR_OK) {
			USP_LOG_Info("# Failed to set %s, fault: %s", path, arg->fault_msg);
			*arg->p_failure_index = KV_VECTOR_FindKey(arg->kv, path, 0);
			return;
		}
	}
}

static int _set_value_group(vendor_data_t *arg)
{
	struct blob_buf bb = {};
	void *table;
	int i;
	size_t path_len;

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

	path_len = strlen(arg->path);
	blobmsg_add_string(&bb, "path", arg->path);
	table = blobmsg_open_table(&bb, "obj_path");
	for (i = 0; i < arg->kv->num_entries; i++) {
		blobmsg_add_string(&bb, arg->kv->vector[i].key + path_len, arg->kv->vector[i].value);
	}
	blobmsg_close_table(&bb, table);

	blob_add_optinal_params(&bb);

	uspd_call(g_ubus_data.ubus_ctx, "set", &bb, _set_value_group_cb, arg);

	blob_buf_free(&bb);
	return USP_ERR_OK;
}

static void _del_object_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	struct blob_attr *params;
	struct blob_attr *cur;
	size_t rem;
	vendor_data_t *arg = req->priv;

	if (arg == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return;
	}

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

	_check_updated_service_list(msg);

	params = get_parameters(msg, "results");
	if (params == NULL) {
		arg->fault = USP_ERR_INTERNAL_ERROR;
		return;
	}

	blobmsg_for_each_attr(cur, params, rem) {
		arg->fault = get_details_from_blob(cur, NULL, NULL, NULL, NULL, NULL);
	}
}

static int _del_object(vendor_data_t *arg)
{
	struct blob_buf bb = {};

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

	blobmsg_add_string(&bb, "path", arg->path);
	blob_add_optinal_params(&bb);

	uspd_call(g_ubus_data.ubus_ctx, "del", &bb, _del_object_cb, arg);

	blob_buf_free(&bb);
	return arg->fault;
}

static void _sync_operate_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	vendor_data_t *arg = req->priv;
	struct blob_attr *results, *rtemp;
	kv_vector_t *kv_out;
	size_t rem;

	if (arg == NULL) {
		USP_LOG_Warning("[%s:%d] Null argument value", __func__, __LINE__);
		return;
	}

	if (!msg)
		return;

	kv_out = arg->kv_out;

	results = get_parameters(msg, "results");
	if (results == NULL)
		return;

	blobmsg_for_each_attr(rtemp, results, rem) {
		arg->fault = get_details_from_blob(rtemp, NULL, NULL, NULL, NULL, NULL);
		if (arg->fault != USP_ERR_OK)
			return;

		struct blob_attr *params = get_parameters(rtemp, "output");

		if (params == NULL)
			return;

		get_parameters_in_kv(params, kv_out);
	}
}

static int _operate_sync_handler(vendor_data_t *arg)
{
	struct blob_buf bb = {};

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

	if (arg->kv_out)
		KV_VECTOR_Init(arg->kv_out);

	blobmsg_add_string(&bb, "path", arg->path);
	if (arg->p_value) {
		blobmsg_add_string(&bb, "command_key", arg->p_value);
	}

	if (arg->kv != NULL) {
		void *table = blobmsg_open_table(&bb, "input");

		for (int i = 0; i < arg->kv->num_entries; ++i) {
			kv_pair_t *kv;

			kv = &arg->kv->vector[i];
			blobmsg_add_string(&bb, kv->key, kv->value);
		}
		blobmsg_close_table(&bb, table);
	}

	blob_add_optinal_params(&bb);

	uspd_call(g_ubus_data.ubus_ctx, "operate", &bb, _sync_operate_cb, arg);

	blob_buf_free(&bb);

	return arg->fault;
}

static void _operate_async_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	struct blob_attr *results, *rtemp;
	int *instance = (int *) req->priv;
	char err_log[MAX_DM_PATH] = {0};
	char *err_msg;
	int fault = USP_ERR_OK, ret;
	kv_vector_t *output_args = NULL;
	size_t rem;

	if (instance == NULL) {
		USP_LOG_Error("Empty instance number, can't proceed");
		return;
	}

	output_args = USP_ARG_Create();
	if (!msg) {
		fault = USP_ERR_INTERNAL_ERROR;
		goto exit;
	}

	results = get_parameters(msg, "results");
	if (results == NULL) {
		fault = USP_ERR_INTERNAL_ERROR;
		goto exit;
	}

	blobmsg_for_each_attr(rtemp, results, rem) {
		// First check if fault and fault_msg are at top level
		fault = get_details_from_blob(rtemp, NULL, NULL, NULL, err_log, NULL);
		if (fault != USP_ERR_OK)
			break;

		struct blob_attr *params = get_parameters(rtemp, "output");
		if (params == NULL)
			break;

		// Check for fault and fault_msg inside the output array (in case of error)
		if (blobmsg_len(params) > 0) {
			struct blob_attr *first_output = NULL;
			size_t output_rem;

			blobmsg_for_each_attr(first_output, params, output_rem) {
				// If the first item in output array has fault information
				int inner_fault = get_details_from_blob(first_output, NULL, NULL, NULL, err_log, NULL);
				if (inner_fault != USP_ERR_OK) {
					fault = inner_fault;
					break;
				}
				// We only need to check the first item
				break;
			}

			if (fault != USP_ERR_OK)
				break;
		}

		// If no fault found, process the output parameters
		get_parameters_in_kv(params, output_args);
	}

exit:
	if (USP_LOG_GetLogLevel() >= kLogLevel_Info && output_args) {
		USP_LOG_Info("# OperationComplete instance[%d] out args[%d]:", *instance, output_args->num_entries);
		KV_VECTOR_Dump(output_args);
	}

	err_msg = (fault != USP_ERR_OK) ? err_log : NULL;
	ret = USP_SIGNAL_OperationComplete(*instance, fault, err_msg, output_args);
	USP_LOG_Debug("Operation Complete inst[%d], err[%s], op_fault[%d], ret[%d]", *instance, (err_msg)?err_msg:"", fault, ret);

	USP_SAFE_FREE(instance);
	if (ret != USP_ERR_OK) {
		USP_LOG_Warning("Failed to send OperationComplete, status=%d", ret);
	}
}

static int _operate_async_handler(vendor_data_t *arg)
{
	struct blob_buf bb = {};
	int fault = USP_ERR_OK;
	int *instance;

	instance = (int *) USP_MALLOC(sizeof(int));
	if (instance == NULL)
		return USP_ERR_COMMAND_FAILURE;

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

	blobmsg_add_string(&bb, "path", arg->path);
	blob_add_optinal_params(&bb);

	if (arg->kv != NULL) {
		void *table = blobmsg_open_table(&bb, "input");

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

			blobmsg_add_string(&bb, kv->key, kv->value);
		}

		// add internal data
		blobmsg_add_string(&bb, "__BBF_CommandKey", arg->command_key);
		blobmsg_add_string(&bb, "__BBF_Requestor", arg->ceid);

		blobmsg_close_table(&bb, table);
	}

	*instance = arg->inst;
	fault = uspd_call_async("operate", &bb, _operate_async_cb, instance);
	if (fault != USP_ERR_OK) {
		USP_FREE(instance);
		arg->fault = fault;
	}

	blob_buf_free(&bb);
	return fault;
}

static void __handle_service_reload_sessions(uspd_cmd_t cmd)
{
	int64_t session_remaining;

	session_remaining = uloop_timeout_remaining64(&g_ubus_data.session_abort_timer) / 1000;
	USP_LOG_Debug("Session_id %s, rem_time %lld, cmd %s, num_services %d", g_ubus_data.session_ceid, (long long int)session_remaining, stringify_cmd(cmd), g_ubus_data.updated_services.num_entries);

	if ((session_remaining <= 0) && (g_ubus_data.updated_services.num_entries > 0)) {
		_handle_update_service(cmd == CMD_TRAN_COMMIT);
		// Free the service list here
		USP_LOG_Debug("# Freeing updated_services .....");
		STR_VECTOR_Destroy(&g_ubus_data.updated_services);
		g_ubus_data.is_critial_service_present = false;
	}
}

static void _handle_critical_service_apply(struct uloop_timeout *t)
{
	USP_LOG_Warning("Applying the changes for critical services");
	__handle_service_reload_sessions(CMD_TRAN_COMMIT);
}

static int _tran_exec(vendor_data_t *arg)
{
	unsigned session_remaining;

	if (arg == NULL) {
		USP_LOG_Warning("Empty input arg");
		return 0;
	}
	session_remaining = uloop_timeout_remaining64(&g_ubus_data.session_abort_timer) / 1000;

	USP_LOG_Debug("Transaction cmd %s, current controller %s, session controller %s, rem %u", stringify_cmd(arg->cmd), arg->ceid, g_ubus_data.session_ceid, session_remaining);
	// reject transaction if not started with same controller or if not an internal controller
	if ((session_remaining > 0) && (strcmp(g_ubus_data.session_ceid, arg->ceid) != 0)) {
		if (strcmp(arg->ceid, INTERNAL_CID) != 0) {
			USP_LOG_Warning("# Session[%s] on-going, reject[%s] transaction %s", g_ubus_data.session_ceid, arg->ceid, stringify_cmd(arg->cmd));
			arg->fault = USP_ERR_INTERNAL_ERROR;
			return USP_ERR_INTERNAL_ERROR;
		}
	}

	if (arg->cmd != CMD_TRAN_START) {
		if ((arg->cmd == CMD_TRAN_COMMIT) && (g_ubus_data.is_critial_service_present == true)) {
			USP_LOG_Warning("Critical service detected, starting timer to apply in 2 sec ...");
			g_ubus_data.critical_service_apply_timer.cb = _handle_critical_service_apply;
			uloop_timeout_set(&g_ubus_data.critical_service_apply_timer, 2000);
		} else {
			__handle_service_reload_sessions(arg->cmd);
		}
	}

	USP_LOG_Debug("Transaction cmd %s completed", stringify_cmd(arg->cmd));
	return USP_ERR_OK;
}

static int stop_service()
{
	struct blob_buf bb = { };

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

	// Invoke Ubus to get data from uspd
	blobmsg_add_string(&bb, "name", "obuspa");
	blobmsg_add_string(&bb, "instance", "obuspa");

	_uspd_call(g_ubus_data.ubus_ctx, "service", "delete", &bb, NULL, NULL);
	blob_buf_free(&bb);

	USP_LOG_Debug("Removed procd entry for the service");

	return USP_ERR_OK;
}

static void _handle_update_service(bool is_commit)
{
	struct blob_buf bb = {0};
	struct ubus_context *uctx;

	memset(&bb, 0, sizeof(struct blob_buf));
	uctx = ubus_connect(NULL);
	if (uctx == NULL) {
		USP_LOG_Error("Failed to create ubus context for %d", is_commit);
		return;
	}

	blob_buf_init(&bb, 0);

	void *array = blobmsg_open_array(&bb, "services");

	for (int i = 0; i < g_ubus_data.updated_services.num_entries; i++)
		blobmsg_add_string(&bb, NULL, g_ubus_data.updated_services.vector[i]);

	blobmsg_close_array(&bb, array);

	blobmsg_add_string(&bb, "proto", "usp");

	_uspd_call(uctx, DM_RELOAD_HANDLER, is_commit ? "commit" : "revert", &bb, NULL, NULL);

	blob_buf_free(&bb);
	ubus_free(uctx);
}

static void _handle_session_timeout(struct uloop_timeout *t)
{
	USP_LOG_Warning("Session [%s] timed out ...", g_ubus_data.session_ceid);
	g_ubus_data.session_ceid[0]='\0';
	__handle_service_reload_sessions(CMD_TRAN_ABORT);
}

static void _handle_sessions(vendor_data_t *arg)
{
	const char *sess_cmd = arg->path;
	unsigned session_remaining;

	session_remaining = uloop_timeout_remaining64(&g_ubus_data.session_abort_timer) / 1000;
	arg->fault = USP_ERR_OK;

	if (strcmp(sess_cmd, "status") == 0) {
			arg->len = (int)session_remaining;
			USP_STRNCPY(arg->ceid, g_ubus_data.session_ceid, MAX_DM_PATH);
			USP_LOG_Debug("# Status time %d, controller [%s] #", arg->len, arg->ceid);
			return;
	}

	// Check if session already on-going and started by different controller then reject
	if ((session_remaining > 0) && (strcmp(g_ubus_data.session_ceid, arg->ceid) != 0)) {
		USP_LOG_Error("Session with [%s] on-going, ignoring %s, command %s", g_ubus_data.session_ceid, arg->ceid, sess_cmd);
		arg->fault = USP_ERR_INTERNAL_ERROR;
		return;
	}

	if (strcmp(sess_cmd, "start") == 0) {
		if (session_remaining > 0) { // If session already started then ignore this request
			USP_LOG_Info("Session already on-going, remaining time %d", (int)session_remaining);
		} else {
			USP_LOG_Debug("Starting session with ceid [%s], timeout [%d]", g_ubus_data.session_ceid, arg->len);
			USP_STRNCPY(g_ubus_data.session_ceid, arg->ceid, MAX_DM_PATH);

			g_ubus_data.session_abort_timer.cb = _handle_session_timeout;
			uloop_timeout_set(&g_ubus_data.session_abort_timer, arg->len * 1000);
		}

	} else {
		if (strcmp(sess_cmd, "commit") == 0) {
			USP_LOG_Info("Commit session with ceid [%s]", g_ubus_data.session_ceid);
			g_ubus_data.session_ceid[0]='\0';
			uloop_timeout_cancel(&g_ubus_data.session_abort_timer);
			__handle_service_reload_sessions(CMD_TRAN_COMMIT);
		} else if (strcmp(sess_cmd, "abort") == 0) {
			USP_LOG_Info("Aborting session with ceid [%s]", g_ubus_data.session_ceid);
			g_ubus_data.session_ceid[0]='\0';
			uloop_timeout_cancel(&g_ubus_data.session_abort_timer);
			__handle_service_reload_sessions(CMD_TRAN_ABORT);
		} else {
			USP_LOG_Error("Unknown command [%s]", sess_cmd);
			arg->fault = USP_ERR_INTERNAL_ERROR;
		}
	}
}

static void read_cmd_callback(struct uloop_fd *u, unsigned int events)
{
	vendor_data_t *arg = NULL;
	int bytes_read;

	pthread_mutex_lock(&g_ubus_data.ubus_lock);
	bytes_read = recv(MQ_RX_SOCKET, &arg, sizeof(vendor_data_t *), 0);
	if (bytes_read != sizeof(vendor_data_t *)) {
		USP_LOG_Error("[UBUS] size mismatch %d", bytes_read);
		pthread_cond_signal(&g_ubus_data.ubus_cond);
		pthread_mutex_unlock(&g_ubus_data.ubus_lock);
		return;
	}

	// USP_LOG_Debug("Mutex Lock, processing %s", stringify_cmd(arg->cmd));
	switch (arg->cmd) {
	case CMD_GET:
		_get_value_single(arg);
		break;
	case CMD_GROUP_GET:
		_get_value_group(arg);
		break;
	case CMD_INSTANCES:
		_get_instances(arg);
		break;
	case CMD_ADD:
		_add_object(arg);
		break;
	case CMD_SET:
		_set_value_single(arg);
		break;
	case CMD_GROUP_SET:
		_set_value_group(arg);
		break;
	case CMD_DEL:
		_del_object(arg);
		break;
	case CMD_OPERATE_SYNC:
		_operate_sync_handler(arg);
		break;
	case CMD_OPERATE_ASYNC:
		_operate_async_handler(arg);
		break;
	case CMD_TRAN_START:
		_tran_exec(arg);
		break;
	case CMD_TRAN_COMMIT:
		_tran_exec(arg);
		break;
	case CMD_TRAN_ABORT:
		_tran_exec(arg);
		break;
	case CMD_SESSION_MGMT:
		_handle_sessions(arg);
		break;
	case CMD_STOP:
		stop_service();
		break;
	case CMD_NULL:
		USP_LOG_Info("Command not supported as of now");
		break;
	default:
		USP_LOG_Info("Command %d not supported as of now", arg->cmd);
	}

	// USP_LOG_Debug("Mutex free, done processing %s", stringify_cmd(arg->cmd));
	pthread_cond_signal(&g_ubus_data.ubus_cond);
	pthread_mutex_unlock(&g_ubus_data.ubus_lock);
}


static void *start_ubus_thread(void *arg)
{
	struct uloop_fd cmd_fd;

	pthread_mutex_lock(&g_ubus_data.ubus_lock);
	g_ubus_data.ubus_ctx = ubus_connect(NULL);
	if (g_ubus_data.ubus_ctx == NULL) {
		USP_LOG_Error("[%s:%d] ubus_connect failed %d", __func__, __LINE__, errno);
		pthread_cond_signal(&g_ubus_data.ubus_cond);
		pthread_mutex_unlock(&g_ubus_data.ubus_lock);
		return NULL;
	}

	USP_LOG_Debug("Mutex Lock, processing ubus thread init");
	uloop_init();
	ubus_add_uloop(g_ubus_data.ubus_ctx);

	memset(&cmd_fd, 0, sizeof(struct uloop_fd));
	cmd_fd.cb = read_cmd_callback;
	cmd_fd.fd = MQ_RX_SOCKET;
	uloop_fd_add(&cmd_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER);

	// register for ubus events
	g_ubus_data.ubus_notify_event.cb = event_handler;
	ubus_register_event_handler(g_ubus_data.ubus_ctx, &g_ubus_data.ubus_notify_event, DM_EVENT);

	pthread_cond_signal(&g_ubus_data.ubus_cond);
	pthread_mutex_unlock(&g_ubus_data.ubus_lock);
	USP_LOG_Debug("Mutex free, done ubus thread init");
	uloop_run();

	uloop_done();

	close(MQ_TX_SOCKET);
	close(MQ_RX_SOCKET);
	pthread_cond_destroy(&g_ubus_data.ubus_cond);
	pthread_mutex_destroy(&g_ubus_data.ubus_lock);

	g_ubus_data.mq_sockets[0] = -1;
	g_ubus_data.mq_sockets[1] = -1;

	if (g_ubus_data.ubus_ctx) {
		ubus_free(g_ubus_data.ubus_ctx);
		g_ubus_data.ubus_ctx = NULL;
	}

	USP_LOG_Debug("Exiting ubus thread !");

	return NULL;
}

static int spawn_ubus_thread(void)
{
	int fault = USP_ERR_OK;

	pthread_mutex_lock(&g_ubus_data.ubus_lock);
	// Exit if unable to initialize the unix domain socket pair used to implement a message queue
	fault = socketpair(AF_UNIX, SOCK_DGRAM, 0, g_ubus_data.mq_sockets);
	if (fault != 0) {
		USP_LOG_Error("Failed to create socketpair...");
		return USP_ERR_INTERNAL_ERROR;
	}

	fault = OS_UTILS_CreateThread("UBUS_Thread", start_ubus_thread, NULL);
	if (fault != 0) {
		USP_LOG_Error("Failed to create uloop thread ...");
		return USP_ERR_INTERNAL_ERROR;
	}
	pthread_cond_wait(&g_ubus_data.ubus_cond, &g_ubus_data.ubus_lock);
	pthread_mutex_unlock(&g_ubus_data.ubus_lock);

	return fault;
}

static void uspd_ubus_complete_cb(struct ubus_request *req, int ret)
{
    USP_FREE(req);
}

static int _uspd_call(struct ubus_context *ctx, const char *object, const char *method,
		struct blob_buf *data, ubus_data_handler_t callback,
		vendor_data_t *arg)
{
	uint32_t id;
	struct ubus_context *lctx = NULL;
	int fault = UBUS_STATUS_OK;
	uint32_t timeout;

	if (ctx == NULL) {
		lctx = ubus_connect(NULL);
		if (lctx == NULL) {
			USP_LOG_Error("[%s:%d] ubus_connect failed %d", __func__, __LINE__, errno);
			return USP_ERR_INTERNAL_ERROR;
		}
		ctx = lctx;
	}

	fault = ubus_lookup_id(ctx, object, &id);
	if (fault) {
		USP_LOG_Error("[%s:%d] %s not present (%s)",
			      __func__, __LINE__, object, ubus_strerror(fault));
		if (arg)
			arg->fault = USP_ERR_INTERNAL_ERROR;
		if (lctx)
			ubus_free(lctx);
		return USP_ERR_INTERNAL_ERROR;
	}

	timeout = (arg)?arg->ipc_timeout:DEFAULT_IPC_TIMEOUT;
	if (USP_LOG_GetLogLevel() > kLogLevel_Info) {
		char *ptr = blobmsg_format_json(data->head, true);
		USP_LOG_Debug("# ubus -t %dms call %s@%s => %s", timeout, object, method, ptr);
		USP_SAFE_FREE(ptr);
	}

	// Invoke Ubus to get data from uspd
	fault = ubus_invoke(ctx, id, method, data->head, callback, arg, timeout);
	if (fault) {
		USP_LOG_Error("[%s:%d] ubus call for (%s)method failed(%s)",
			      __func__, __LINE__, method, ubus_strerror(fault));
		if (arg)
			arg->fault = USP_ERR_INTERNAL_ERROR;
		if (lctx)
			ubus_free(lctx);
		return USP_ERR_INTERNAL_ERROR;
	}

	if (lctx)
		ubus_free(lctx);
	return USP_ERR_OK;
}

static void lookup_event_cb(struct ubus_context *ctx __attribute__((unused)),
		struct ubus_event_handler *ev __attribute__((unused)),
		const char *type, struct blob_attr *msg)
{
	int ret;
	const struct blobmsg_policy policy = {
		"path", BLOBMSG_TYPE_STRING
	};
	struct blob_attr *attr;
	const char *path = NULL;

	if (strcmp(type, "ubus.object.add") != 0)
		return;

	ret = blobmsg_parse(&policy, 1, &attr, blob_data(msg), blob_len(msg));
	if (ret!=0 || !attr) {
		USP_LOG_Warning("Failed to parse add event data");
		return;
	}

	path = blobmsg_data(attr);
	if ((path != NULL) && strcmp(path, DM_OBJECT_NAME) == 0) {
		USP_LOG_Info("Found %s ubus object", DM_OBJECT_NAME);
		uloop_end();
	} else {
		USP_LOG_Error("Waiting for %s ubus object", DM_OBJECT_NAME);
	}
}

static int wait_for_dm_object(void)
{
	struct ubus_context *uctx;
	struct ubus_event_handler add_event;
	int ret;
	uint32_t id;

	uctx = ubus_connect(NULL);
	if (uctx == NULL) {
		USP_LOG_Error("Can't create ubus context");
		return USP_ERR_INTERNAL_ERROR;
	}

	uloop_init();
	ubus_add_uloop(uctx);

	// register for add event
	memset(&add_event, 0, sizeof(struct ubus_event_handler));
	add_event.cb = lookup_event_cb;
	ubus_register_event_handler(uctx, &add_event, "ubus.object.add");

	// check if object already present
	ret = ubus_lookup_id(uctx, DM_OBJECT_NAME, &id);
	if (ret == 0) {
		goto end;
	}

	USP_LOG_Warning("Waiting for lower layer");
	uloop_run();
	uloop_done();

end:
	ret = ubus_lookup_id(uctx, DM_OBJECT_NAME, &id);
	ubus_free(uctx);

	return ret;
}

// Public methods
int uspd_call(struct ubus_context *ctx, const char *method,
		struct blob_buf *data, ubus_data_handler_t callback,
		vendor_data_t *arg)
{
	return _uspd_call(ctx, DM_OBJECT_NAME, method, data, callback, arg);
}

int uspd_call_async(const char *method, struct blob_buf *data, ubus_data_handler_t callback, void *priv)
{
	uint32_t id;
	int fault = UBUS_STATUS_OK;
	struct ubus_request *req;

	if (g_ubus_data.ubus_ctx == NULL) {
		USP_LOG_Error("[%s:%d] ubus_ctx not connected", __func__, __LINE__);
		return USP_ERR_INTERNAL_ERROR;
	}

	fault = ubus_lookup_id(g_ubus_data.ubus_ctx, DM_OBJECT_NAME, &id);
	if (fault) {
		USP_LOG_Error("[%s:%d] %s not present (%s)",
			      __func__, __LINE__, DM_OBJECT_NAME, ubus_strerror(fault));
		return USP_ERR_INTERNAL_ERROR;
	}

	req = USP_MALLOC(sizeof(struct ubus_request));
	if (req == NULL) {
		USP_LOG_Error("Out of memory!");
		return USP_ERR_RESOURCES_EXCEEDED;
	}

	memset(req, 0, sizeof(struct ubus_request));
	fault = ubus_invoke_async(g_ubus_data.ubus_ctx, id, method, data->head, req);
	if (fault) {
		USP_LOG_Error("[%s:%d] ubus async call failed(%s)",
			      __func__, __LINE__, ubus_strerror(fault));
		USP_FREE(req);
		return USP_ERR_INTERNAL_ERROR;
	}

	req->data_cb = callback;
	req->complete_cb = uspd_ubus_complete_cb;
	req->priv = priv;

	ubus_complete_request_async(g_ubus_data.ubus_ctx, req);

	return USP_ERR_OK;
}

int ubus_enqueue_cmd(vendor_data_t *arg)
{
	USP_LOG_Debug("CMD (%s) path(%s) Socket TX:RX(%d:%d)", stringify_cmd(arg->cmd), arg->path, MQ_TX_SOCKET, MQ_RX_SOCKET);

	if (MQ_TX_SOCKET != -1) {
		int bytes;

		pthread_mutex_lock(&g_ubus_data.ubus_lock);
		bytes = send(MQ_TX_SOCKET, &arg, sizeof(vendor_data_t *), 0);
		if (bytes == -1) {
			pthread_mutex_unlock(&g_ubus_data.ubus_lock);
			USP_LOG_Error("## Failed to send (%s), err (%s)", stringify_cmd(arg->cmd), strerror(errno));
			return USP_ERR_INTERNAL_ERROR;
		}

		if (bytes != sizeof(vendor_data_t *)) {
			pthread_mutex_unlock(&g_ubus_data.ubus_lock);
			USP_LOG_Error("[DMThread] send size miss-match (%d:%zd) to ubus thread", bytes, sizeof(vendor_data_t *));
			return USP_ERR_INTERNAL_ERROR;
		}
		pthread_cond_wait(&g_ubus_data.ubus_cond, &g_ubus_data.ubus_lock);
		pthread_mutex_unlock(&g_ubus_data.ubus_lock);

		USP_LOG_Debug("CMD (%s) path(%s) Done", stringify_cmd(arg->cmd), arg->path);
		return arg->fault;
	}

	switch (arg->cmd) {
	case CMD_GET:
		_get_value_single(arg);
		break;
	case CMD_GROUP_GET:
		_get_value_group(arg);
		break;
	case CMD_TRAN_START:
		USP_LOG_Debug("Ignoring transaction start");
		break;
	case CMD_TRAN_COMMIT:
		USP_LOG_Debug("Ignoring transaction commit");
		break;
	case CMD_TRAN_ABORT:
		USP_LOG_Debug("Ignoring transaction abort");
		break;
	case CMD_STOP:
		stop_service();
	case CMD_OPERATE_SYNC:
		_operate_sync_handler(arg);
		break;
	default:
		USP_LOG_Error("[%s:%d]: ubus thread not ready for %s cmd", __FUNCTION__, __LINE__, stringify_cmd(arg->cmd));
		arg->fault = USP_ERR_INTERNAL_ERROR;
		break;
	}

	USP_LOG_Debug("# noThread CMD (%s) path(%s) Done ##", stringify_cmd(arg->cmd), arg->path);
	return arg->fault;
}

int ubus_thread_init()
{
	int fault;

	ubus_data_init();
	fault = wait_for_dm_object();
	if (fault) {
		USP_LOG_Error("Error waiting for %s, fault %d", DM_OBJECT_NAME, fault);
	}
	return fault;
}

int ubus_thread_start(void)
{
	int fault = USP_ERR_OK;

	fault = spawn_ubus_thread();

	return fault;
}

int ubus_thread_cleanup(void)
{
	STR_VECTOR_Destroy(&g_ubus_data.updated_services);
	return USP_ERR_OK;
}
