/*
 * Copyright (C) 2020-2023, IOPSYS Software Solutions AB.
 *
 * Author: Suvendhu Hansa <suvendhu.hansa@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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <limits.h>
#include <errno.h>

#include "usp_api.h"
#include "usp_err_codes.h"
#include "os_utils.h"
#include "vendor_utils.h"
#include "msg_handler.h"
#include "vendor_datamodel_ext.h"
#include "vendor_ubus_thread.h"
#include "common_defs.h"

#define DEVICE_LOCALAGENT "Device.LocalAgent."

static char *transfer_complete_args[] = {
	"Affected", "Command", "CommandKey", "CompleteTime",
	"FaultCode", "FaultString", "Requestor", "StartTime",
	"TransferType", "TransferURL" };

// cppcheck-suppress cert-STR05-C
static char *upload_in_args[] = { "Filename" };
// cppcheck-suppress cert-STR05-C
static char *upload_out_args[] = { "Content", "Length", "Filesize", "Checksum", "Result", "Error" };
// cppcheck-suppress cert-STR05-C
static char *session_start_arg[] = { "Timeout" };


#if defined(OBUSPA_ENABLE_DOWNLOAD_EXT) || defined(OBUSPA_ENABLE_UPLOAD_EXT)
static int validate_file_path(char *filename, char **filepath)
{
	char abs_path[PATH_MAX] = {0};
	char real_path[PATH_MAX] = {0};

	if (filename == NULL || filepath == NULL) {
		USP_LOG_Error("filename or filepath is NULL");
		return -1;
	}

	int len = strlen(filename);
	if (len == 0) {
		USP_LOG_Error("Filename should not be empty");
		return -1;
	}

	/* filename should not end with '/' */
	if (filename[len - 1] == '/') {
		USP_LOG_Error("Filename should not end with /");
		return -1;
	}

	snprintf(abs_path, sizeof(abs_path), "/tmp/obuspa/%s", filename);
	char *tmp_p = strrchr(abs_path, '/');
	if (tmp_p == NULL) {
		USP_LOG_Error("String processing error");
		return -1;
	}

	*tmp_p = '\0';

	/* filename should not be '.' or '..' */
	tmp_p = tmp_p + 1;
	if (strcmp(tmp_p, "..") == 0 || strcmp(tmp_p, ".") == 0) {
		USP_LOG_Error("%s could not be used as filename", tmp_p);
		return -1;
	}

	char *res = realpath(abs_path, real_path);
	if (res == NULL) {
		char* errStr = strerror(errno);
		USP_LOG_Error("%s:%d (%s) %s", __FUNCTION__, __LINE__, abs_path, errStr);
		return -1;
	}

	if (strcmp(real_path, "/tmp/obuspa") != 0 && strncmp(real_path, "/tmp/obuspa/", 12) != 0) {
		USP_LOG_Error("%s:%d File path: (%s) can not be used", __FUNCTION__, __LINE__, real_path);
		return -1;
	}

	size_t size = strlen(real_path) + strlen(tmp_p) + 2;
	*filepath = (char*)USP_MALLOC(sizeof(char) * size);
	if (*filepath == NULL) {
		USP_LOG_Error("Memory allocation failed");
		return -1;
	}

	snprintf(*filepath, size, "%s/%s", real_path, tmp_p);

	return 0;
}
#endif

/************************************
 * Upload functionality start
 ************************************/
#ifdef OBUSPA_ENABLE_UPLOAD_EXT
typedef struct {
	int request_instance;
	char file_path[PATH_MAX];
} file_upload_input_cond_t;

void *FileUploadThreadMain(void *param)
{
	file_upload_input_cond_t *cond = (file_upload_input_cond_t *)param;
	kv_vector_t *out;
	int err = USP_ERR_INTERNAL_ERROR;

	if (cond == NULL) {
		USP_LOG_Debug("%s:%d input params not found", __FUNCTION__, __LINE__);
		return NULL;
	}

	out = USP_ARG_Create();
	if (USP_ERR_OK != USP_SIGNAL_OperationStatus(cond->request_instance, "Active")) {
		USP_LOG_Debug("%s:%d File Upload failed", __FUNCTION__, __LINE__);
		USP_ARG_Add(out, "Content", "");
		USP_ARG_Add(out, "Length", "0");
		USP_ARG_Add(out, "Filesize", "0");
		USP_ARG_Add(out, "Checksum", "");
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Internal error");
		USP_SIGNAL_OperationComplete(cond->request_instance, err, "Internal Error", out);
		goto err;
	}

	if (strlen(cond->file_path) == 0) {
		USP_LOG_Debug("%s: File name not found", __FUNCTION__);
		USP_ARG_Add(out, "Content", "");
		USP_ARG_Add(out, "Length", "0");
		USP_ARG_Add(out, "Filesize", "0");
		USP_ARG_Add(out, "Checksum", "");
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Invalid Filename");
		USP_SIGNAL_OperationComplete(cond->request_instance, err, "Invalid Filename", out);
		goto err;
	}

	// cppcheck-suppress cert-MSC24-C
	FILE *fp = fopen(cond->file_path, "rb");
	if (!fp) {
		USP_LOG_Debug("%s: Failed to open the file %s", __FUNCTION__, cond->file_path);
		USP_ARG_Add(out, "Content", "");
		USP_ARG_Add(out, "Length", "0");
		USP_ARG_Add(out, "Filesize", "0");
		USP_ARG_Add(out, "Checksum", "");
		USP_ARG_Add(out, "Result", "Failed");
		char tmp[5012] = {0};
		snprintf(tmp, sizeof(tmp), "Couldn't read the file: %s", cond->file_path);
		USP_ARG_Add(out, "Error", tmp);
		USP_SIGNAL_OperationComplete(cond->request_instance, err, tmp, out);
		goto err;
	}

	//read content of the file
	fseek(fp, 0, SEEK_END);
	size_t n, length = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	char *content = calloc(length, sizeof(char));
	if (!content) {
		fclose(fp);
		USP_LOG_Debug("%s: Failed to allocate memory", __FUNCTION__);
		USP_ARG_Add(out, "Content", "");
		USP_ARG_Add(out, "Length", "0");
		USP_ARG_Add(out, "Filesize", "0");
		USP_ARG_Add(out, "Checksum", "");
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Internal Error");
		USP_SIGNAL_OperationComplete(cond->request_instance, err, "Internal Error", out);
		goto err;
	}

	n = fread(content, 1, length, fp);
	fclose(fp);

	if (n != length) {
		USP_LOG_Debug("%s: Failed to read %lu, read %lu", __FUNCTION__, (unsigned long)length, (unsigned long)n);
		USP_ARG_Add(out, "Content", "");
		USP_ARG_Add(out, "Length", "0");
		USP_ARG_Add(out, "Filesize", "0");
		USP_ARG_Add(out, "Checksum", "");
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Internal Error");
		USP_SIGNAL_OperationComplete(cond->request_instance, err, "Internal Error", out);
		goto err;
	}

	size_t datalen = 0;
	char *data = base64_encode(content, length, &datalen);
	if (data == NULL) {
		USP_FREE(content);
		USP_LOG_Debug("%s: Failed in encoding the data", __FUNCTION__);
		USP_ARG_Add(out, "Content", "");
		USP_ARG_Add(out, "Length", "0");
		USP_ARG_Add(out, "Filesize", "0");
		USP_ARG_Add(out, "Checksum", "");
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Failed in encoding the data");
		USP_SIGNAL_OperationComplete(cond->request_instance, err, "Failed to encode data", out);
		goto err;
	}

	// Calculate
	EVP_MD_CTX *mdctx;
	const EVP_MD *md;
	unsigned char md_value[EVP_MAX_MD_SIZE];
	char hash[BUFSIZ] = {0};
	uint32_t hashlen = 0;

	md = EVP_get_digestbyname("MD5");
	mdctx = EVP_MD_CTX_create();
	EVP_DigestInit_ex(mdctx, md, NULL);

	EVP_DigestUpdate(mdctx, content, length);
	EVP_DigestFinal_ex(mdctx, md_value, &hashlen);

	int i;
	for (i = 0; i < hashlen; i++)
		snprintf(&hash[i * 2], sizeof(hash) - (i * 2), "%02x", md_value[i]);

	free(content);

	char temp[20] = {0};
	USP_ARG_Add(out, "Content", data);
	snprintf(temp, sizeof(temp), "%lu", (unsigned long)datalen);
	USP_ARG_Add(out, "Length", temp);
	snprintf(temp, sizeof(temp), "%lu", (unsigned long)length);
	USP_ARG_Add(out, "Filesize", temp);
	USP_ARG_Add(out, "Checksum", hash);
	USP_ARG_Add(out, "Result", "Success");
	USP_ARG_Add(out, "Error", "");

	free(data);
	USP_SIGNAL_OperationComplete(cond->request_instance, USP_ERR_OK, NULL, out);

err:
	USP_LOG_Info("Removing file %s", cond->file_path);
	unlink(cond->file_path);
	USP_FREE(cond);

	return NULL;
}

static int upload_file_cb(dm_req_t *req, kv_vector_t *input_args,
				int instance)
{
	int i, err;
	char *filename = NULL;
	char *real_path = NULL;

	if (req == NULL)
		return USP_ERR_INTERNAL_ERROR;

	if (req->path == NULL)
		return USP_ERR_INVALID_PATH_SYNTAX;

        if (input_args == NULL)
		return USP_ERR_COMMAND_FAILURE;

	if (input_args->num_entries == 0)
		return USP_ERR_COMMAND_FAILURE;

	file_upload_input_cond_t *cond = NULL;
	cond = USP_MALLOC(sizeof(file_upload_input_cond_t));
	if (cond == NULL)
		return USP_ERR_INTERNAL_ERROR;

	memset(cond, 0, sizeof(file_upload_input_cond_t));
	cond->request_instance = instance;
	for (i = 0; i < input_args->num_entries; i++) {
		if (strcmp(input_args->vector[i].key, "Filename") == 0 && input_args->vector[i].value != NULL) {
			filename = input_args->vector[i].value;
			break;
		}
	}

	if (filename == NULL || strlen(filename) == 0) {
		USP_FREE(cond);
		return USP_ERR_COMMAND_FAILURE;
	}

	if (validate_file_path(filename, &real_path) != 0) {
		USP_FREE(cond);
		return USP_ERR_COMMAND_FAILURE;
	}

	snprintf(cond->file_path, sizeof(cond->file_path), "%s", real_path);
	USP_SAFE_FREE(real_path);
	USP_LOG_Info("Upload Filepath: %s", cond->file_path);

	err = OS_UTILS_CreateThread("UploadFile", FileUploadThreadMain, cond);
	if (err != USP_ERR_OK) {
		USP_FREE(cond);
		return USP_ERR_COMMAND_FAILURE;
	}

	return USP_ERR_OK;
}

int register_localagent_upload_dm(void)
{
	int fault = USP_ERR_OK;
	char path[MAX_DM_PATH] = {0};

	USP_SNPRINTF(path, MAX_DM_PATH, "Device.LocalAgent.%sUpload()", TO_STR(_DM_VENDOR_PREFIX));
	if (dm_node_present(path) != NULL) {
		USP_LOG_Debug("Operation (%s) already registered", path);
		return USP_ERR_OK;
	}

	fault = USP_REGISTER_AsyncOperation(path, upload_file_cb, NULL);
	fault |= USP_REGISTER_AsyncOperation_MaxConcurrency(path, 1);

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

	fault = USP_REGISTER_OperationArguments(path, upload_in_args, NUM_ELEM(upload_in_args), upload_out_args, NUM_ELEM(upload_out_args));

	return fault;
}
#endif // OBUSPA_ENABLE_UPLOAD_EXT
/************************************
 * Upload functionality end
 ************************************/

/************************************
 * Download functionality start
 ************************************/
#ifdef OBUSPA_ENABLE_DOWNLOAD_EXT
static time_t last_dw_time = 0;
static unsigned int command_key = 0;

static void remove_all_fragments(void)
{
	DIR *d = NULL;
	struct dirent *dir;
	char file_path[256] = {0};

	d = opendir("/tmp/obuspa/");
	if (d) {
		while ((dir = readdir(d)) != NULL) {
			if (strstr(dir->d_name, "dw.fragment.") != NULL) {
				snprintf(file_path, sizeof(file_path), "/tmp/obuspa/%.243s", dir->d_name);
				remove(file_path);
			}
		}

		closedir(d);
	}
}

static void reset_download_task(void)
{
	command_key = 0;
	last_dw_time = 0;
}

static int download_file(kv_vector_t *in, kv_vector_t *out)
{
	int i;
	char *content = NULL, *filename = NULL, *checksum = NULL;
	size_t filesize = 0, length = 0, total_frag = 1, frag_num = 1, req_id = 0;
	char file_path[PATH_MAX] = {0};
	char *real_path = NULL;
	FILE *fp = NULL;
	size_t file_count = 0;

	if (in == NULL || out == NULL) {
		USP_LOG_Debug("%s:%d USP_ERR_INVALID_COMMAND_ARGS", __FUNCTION__, __LINE__);
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Invalid arguments");
		return USP_ERR_OK;
	}

	if (in->num_entries == 0) {
		USP_LOG_Debug("%s:%d USP_ERR_INVALID_COMMAND_ARGS", __FUNCTION__, __LINE__);
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Invalid arguments");
		return USP_ERR_OK;
	}

	for (i = 0; i < in->num_entries; i++) {
		if (strcmp(in->vector[i].key, "Content") == 0 && in->vector[i].value != NULL) {
			content = in->vector[i].value;
			continue;
		}

		if (strcmp(in->vector[i].key, "Checksum") == 0 && in->vector[i].value != NULL) {
			checksum = in->vector[i].value;
			continue;
		}

		if (strcmp(in->vector[i].key, "Filesize") == 0 && in->vector[i].value != NULL) {
			filesize = strtoul(in->vector[i].value, NULL, 10);
			continue;
		}

		if (strcmp(in->vector[i].key, "Filename") == 0 && in->vector[i].value != NULL) {
			filename = in->vector[i].value;
			continue;
		}

		if (strcmp(in->vector[i].key, "TotalFragment") == 0 && in->vector[i].value != NULL) {
			total_frag = strtoul(in->vector[i].value, NULL, 10);
			continue;
		}

		if (strcmp(in->vector[i].key, "SequenceNumber") == 0 && in->vector[i].value != NULL) {
			frag_num = strtoul(in->vector[i].value, NULL, 10);
			continue;
		}

		if (strcmp(in->vector[i].key, "CommandKey") == 0 && in->vector[i].value != NULL) {
			req_id = strtoul(in->vector[i].value, NULL, 10);
			continue;
		}
	}

	if (content == NULL || filename == NULL) {
		USP_LOG_Error("%s: Empty content or filename", __FUNCTION__);
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Invalid argument value");
		return USP_ERR_OK;
	}

	if (strlen(filename) == 0 || filesize == 0 || total_frag == 0 ||
	    frag_num == 0 || frag_num > total_frag || req_id == 0) {
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Invalid argument value");
		USP_LOG_Debug("%s:%d USP_ERR_INVALID_COMMAND_ARGS", __FUNCTION__, __LINE__);
		return USP_ERR_OK;
	}

	if (validate_file_path(filename, &real_path) != 0) {
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "File name can not be used");
		return USP_ERR_OK;
	}

	/* check if 15 seconds expired from the last fragment download time, then
	 * cancel last download task and start a new one. */
	time_t now = time(NULL);
	double diff_time = difftime(now, last_dw_time);
	if (diff_time > 15) {
		remove_all_fragments();
		reset_download_task();
	}

	if (command_key == 0) {
		USP_LOG_Debug("Start a new download request");
		command_key = req_id;
	} else if (command_key != req_id) {
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "One download task is ongoing, please wait to finish");
		USP_LOG_Debug("%s:%d New download request rejected since one is going on", __FUNCTION__, __LINE__);
		USP_SAFE_FREE(real_path);
		return USP_ERR_OK;
	}

	snprintf(file_path, sizeof(file_path), "/tmp/obuspa/dw.fragment.%u", (unsigned int)frag_num);

	// Check if extra header in base64 data
	if (strncmp(content, "data:", 5) == 0) {
		char *tmp = strstr(content, "base64,");
		if (tmp != NULL) {
			content = tmp + strlen("base64,");
		}
	}

	length = strlen(content);
	if (length == 0) {
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Empty content");
		USP_LOG_Info("%s:%d Empty file content", __FUNCTION__, __LINE__);
		remove_all_fragments();
		reset_download_task();
		USP_SAFE_FREE(real_path);
		return USP_ERR_OK;
	}

	// cppcheck-suppress cert-MSC24-C
	fp = fopen(file_path, "wb");
	if (fp == NULL) {
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Failed to write data");
		remove_all_fragments();
		reset_download_task();
		USP_SAFE_FREE(real_path);
		return USP_ERR_OK;
	}

	fwrite(content, 1, strlen(content), fp);
	fclose(fp);

	if (frag_num != total_frag) {
		last_dw_time = time(NULL);
		USP_ARG_Add(out, "Result", "Success");
		USP_ARG_Add(out, "Error", "");
		USP_SAFE_FREE(real_path);
		return USP_ERR_OK;
	}

	const char *frag_merge_path = "/tmp/obuspa/dw.merged";
	// cppcheck-suppress cert-MSC24-C
	fp = fopen(frag_merge_path, "wb");
	if (!fp) {
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Failed to start reassembling the package");
		remove_all_fragments();
		reset_download_task();
		USP_SAFE_FREE(real_path);
		return USP_ERR_OK;
	}

	bool result = true;
	for (file_count = 1; file_count <= total_frag; file_count++) {
		char frag_path[256] = {0};
		FILE *frag = NULL;
		char frag_data[1024] = {0};
		int frag_bytes = 0;

		snprintf(frag_path, sizeof(frag_path), "/tmp/obuspa/dw.fragment.%u", (unsigned int)file_count);
		// cppcheck-suppress cert-MSC24-C
		frag = fopen(frag_path, "rb");
		if (!frag) {
			result = false;
			break;
		}

		while ((frag_bytes = fread(frag_data, 1, 1024, frag)) != 0) {
			fwrite(frag_data, 1, frag_bytes, fp);
		}

		fclose(frag);
		remove(frag_path);
	}

	fclose(fp);

	if (result == false) {
		remove_all_fragments();
		remove(frag_merge_path);
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Failed to reassemble the package");
		reset_download_task();
		USP_SAFE_FREE(real_path);
		return USP_ERR_OK;
	}

	USP_LOG_Info("Download Filepath: %s", real_path);

	size_t datalen = 0;
	if (0 != base64_decode(frag_merge_path, &datalen, real_path)) {
		USP_LOG_Debug("%s: Failed to decode the content", __FUNCTION__);
		remove(frag_merge_path);
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", "Failed to decode the data");
		reset_download_task();
		USP_SAFE_FREE(real_path);
		return USP_ERR_OK;
	}

	remove(frag_merge_path);
	if (filesize != datalen) {
		remove(real_path);
		char tmp[512] = {0};
		snprintf(tmp, sizeof(tmp), "Received filesize: %lu mismatched with decoded filesize:%lu", (unsigned long)filesize, (unsigned long)datalen);
		USP_LOG_Info("%s", tmp);
		USP_ARG_Add(out, "Result", "Failed");
		USP_ARG_Add(out, "Error", tmp);
		reset_download_task();
		USP_SAFE_FREE(real_path);
		return USP_ERR_OK;
	}

	if (checksum && strlen(checksum)) {
		// Calculate md5sum
		// cppcheck-suppress cert-MSC24-C
		fp = fopen(real_path, "rb");
		if (fp == NULL) {
			USP_ARG_Add(out, "Result", "Failed");
			USP_ARG_Add(out, "Error", "Failed to open file");
			remove(real_path);
			reset_download_task();
			USP_SAFE_FREE(real_path);
			return USP_ERR_OK;
		}

		int bytes;
		EVP_MD_CTX *mdctx;
		const EVP_MD *md;
		unsigned char md_value[EVP_MAX_MD_SIZE];
		char file_data[1024];
		char hash[BUFSIZ] = {0};
		uint32_t hashlen = 0;

		md = EVP_get_digestbyname("MD5");
		mdctx = EVP_MD_CTX_create();
		EVP_DigestInit_ex(mdctx, md, NULL);

		while ((bytes = fread(file_data, 1, 1024, fp)) != 0) {
			EVP_DigestUpdate(mdctx, file_data, bytes);
		}
		EVP_DigestFinal_ex(mdctx, md_value, &hashlen);
		fclose(fp);

		for (i = 0; i < hashlen; i++)
			snprintf(&hash[i * 2], sizeof(hash) - (i * 2), "%02x", md_value[i]);

		if (strcmp(checksum, hash) != 0) {
			USP_ARG_Add(out, "Result", "Failed");
			USP_ARG_Add(out, "Error", "Checksum mismatched");
			USP_LOG_Debug("%s: checksum %s not matched %s", __FUNCTION__, checksum, hash);
			remove(real_path);
			reset_download_task();
			USP_SAFE_FREE(real_path);
			return USP_ERR_OK;
		}
	}


	USP_ARG_Add(out, "Result", "Success");
	USP_ARG_Add(out, "Error", "");
	reset_download_task();
	USP_SAFE_FREE(real_path);

	return USP_ERR_OK;
}

static int download_file_cb(dm_req_t *req, char *command_key, kv_vector_t *input_args,
				kv_vector_t *output_args)
{
	int fault = USP_ERR_OK;

	if (req == NULL)
		return USP_ERR_INTERNAL_ERROR;

	if (req->path == NULL)
		return USP_ERR_INVALID_PATH_SYNTAX;

	fault = download_file(input_args, output_args);

	return fault;
}

static int register_localagent_download_dm(void)
{
	char path[MAX_DM_PATH] = {0};
	int fault = USP_ERR_OK;
	char *in[] = { "Content", "Filesize", "Checksum", "Filename", "TotalFragment", "SequenceNumber", "CommandKey" }, *out[] = { "Result", "Error" };
	int in_nr = 7, out_nr = 2;

	USP_SNPRINTF(path, MAX_DM_PATH, "Device.LocalAgent.%sDownload()", TO_STR(_DM_VENDOR_PREFIX));
	if (dm_node_present(path) != NULL) {
		USP_LOG_Debug("Operation (%s) already registered", path);
		return USP_ERR_OK;
	}

	fault = USP_REGISTER_SyncOperation(path, download_file_cb);
	if (fault != USP_ERR_OK) {
		return fault;
	}

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

	return fault;
}
#endif // OBUSPA_ENABLE_DOWNLOAD_EXT
/************************************
 * Download functionality end
 ************************************/

static int register_localagent_transfer_complete(void)
{
	int fault;
	char dm_path[MAX_DM_PATH] = {0};

	USP_SNPRINTF(dm_path, MAX_DM_PATH, "%s.%s", DEVICE_LOCALAGENT, "TransferComplete!");
	fault = USP_REGISTER_Event(dm_path);
	if (fault == USP_ERR_OK) {
		fault = USP_REGISTER_EventArguments(dm_path, transfer_complete_args, NUM_ELEM(transfer_complete_args));
	}

	return fault;
}

static int register_localagent_wake_up(void)
{
	char dm_path[MAX_DM_PATH] = {0};

	USP_SNPRINTF(dm_path, MAX_DM_PATH, "%s.%s", DEVICE_LOCALAGENT, "WakeUp!");
	return USP_REGISTER_Event(dm_path);
}

/************************************
 * Session functionality start
 ************************************/
#ifdef OBUSPA_ENABLE_SESSION_EXT
static int get_session_status(vendor_data_t *arg)
{
	arg->cmd = CMD_SESSION_MGMT;
	arg->path = "status";

	return ubus_enqueue_cmd(arg);
}

int session_state_get(dm_req_t *req, char *buf, int len)
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));
	get_session_status(&arg);
	if (arg.len > 0) {
		USP_STRNCPY(buf, "1", len)
	} else {
		USP_STRNCPY(buf, "0", len)
	}

	return USP_ERR_OK;
}

int session_time_get(dm_req_t *req, char *buf, int len)
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));
	get_session_status(&arg);
	USP_SNPRINTF(buf, len, "%d", arg.len);
	return USP_ERR_OK;
}

int session_controller_get(dm_req_t *req, char *buf, int len)
{
	vendor_data_t arg;

	memset(&arg, 0, sizeof(vendor_data_t));
	get_session_status(&arg);
	USP_STRNCPY(buf, arg.ceid, len);

	return USP_ERR_OK;
}

int session_start(dm_req_t *req, char *command_key, kv_vector_t *input_args, kv_vector_t *output_args)
{
	vendor_data_t arg;
	unsigned timeout = 300;

	memset(&arg, 0, sizeof(vendor_data_t));
	arg.cmd = CMD_SESSION_MGMT;
	arg.path = "start";

	USP_ARG_GetUnsigned(input_args, "Timeout", 300, &timeout);
	arg.len = (int) timeout;

	_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);

	return arg.fault;
}

int session_commit(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_SESSION_MGMT;
	arg.path = "commit";
	_get_controller_info(&arg);

	ubus_enqueue_cmd(&arg);

	return arg.fault;
}

int session_abort(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_SESSION_MGMT;
	arg.path = "abort";
	_get_controller_info(&arg);

	// check and reset session
	ubus_enqueue_cmd(&arg);

	return arg.fault;
}

int register_localagent_sessions(void)
{
	int err = USP_ERR_OK;

	err |= USP_REGISTER_VendorParam_ReadOnly(DEVICE_LOCALAGENT TO_STR(_DM_VENDOR_PREFIX) "Session.State", session_state_get, DM_BOOL);
	err |= USP_REGISTER_VendorParam_ReadOnly(DEVICE_LOCALAGENT TO_STR(_DM_VENDOR_PREFIX) "Session.RemainingTime", session_time_get, DM_UINT);
	err |= USP_REGISTER_VendorParam_ReadOnly(DEVICE_LOCALAGENT TO_STR(_DM_VENDOR_PREFIX) "Session.Controller", session_controller_get, DM_STRING);
	err |= USP_REGISTER_SyncOperation(DEVICE_LOCALAGENT TO_STR(_DM_VENDOR_PREFIX) "Session.Start()", session_start);
	err |= USP_REGISTER_OperationArguments(DEVICE_LOCALAGENT TO_STR(_DM_VENDOR_PREFIX) "Session.Start()", session_start_arg, 1, NULL, 0);

	err |= USP_REGISTER_SyncOperation(DEVICE_LOCALAGENT TO_STR(_DM_VENDOR_PREFIX) "Session.Commit()", session_commit);
	err |= USP_REGISTER_SyncOperation(DEVICE_LOCALAGENT TO_STR(_DM_VENDOR_PREFIX) "Session.Abort()", session_abort);

	return err;
}
#endif // OBUSPA_ENABLE_SESSION_EXT
/************************************
 * Session functionality end
 ************************************/

int register_localagent_extn(void)
{

	int fault;

	fault = register_localagent_transfer_complete();

#ifdef OBUSPA_ENABLE_DOWNLOAD_EXT
	if (fault == USP_ERR_OK)
		fault = register_localagent_download_dm();
#endif

#ifdef OBUSPA_ENABLE_UPLOAD_EXT
	if (fault == USP_ERR_OK)
		fault = register_localagent_upload_dm();
#endif

#ifdef OBUSPA_ENABLE_SESSION_EXT
	if (fault == USP_ERR_OK)
		fault = register_localagent_sessions();
#endif

	if (fault == USP_ERR_OK)
		fault = register_localagent_wake_up();

	return fault;
}
