/*
 * Copyright (C) 2022-2024 iopsys Software Solutions AB
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 *	  Author: Amin Ben Romdhane <amin.benromdhane@iopsys.eu>
 *
 */

#include "utils.h"
#include "fwbank.h"

struct sysupgrade_ev_data {
	const char *bank_id;
	bool status;
};

struct fw_download_data {
	char *url;
	char *auto_activate;
	char *username;
	char *password;
	char *file_size;
	char *checksum_algorithm;
	char *checksum;
	char *bank_id;
	char *command;
	char *obj_path;
	char *commandKey;
	char *keep_config;
	char *keep_opconf;
	char *config_scope;
	char *requestor;
};

#define CRONTABS_ROOT "/etc/crontabs/root"
#define ACTIVATE_HANDLER_FILE "/usr/share/bbfdm/scripts/bbf_activate_handler.sh"
#define MAX_TIME_WINDOW 5

/*************************************************************
* COMMON FUNCTIONS
**************************************************************/
static char *get_blobmsg_option_value(struct blob_attr *entry, const char *option_name)
{
	struct blob_attr *tb[9] = {0};
	char *option_value = NULL;

	if (!entry)
		return "";

	blobmsg_parse(sysmngr_bank_policy, 9, tb, blobmsg_data(entry), blobmsg_len(entry));

	if (DM_STRCMP(sysmngr_bank_policy[0].name, option_name) == 0 && tb[0]) // Name
		option_value = dmstrdup(blobmsg_get_string(tb[0]));
	else if (DM_STRCMP(sysmngr_bank_policy[1].name, option_name) == 0 && tb[1]) // ID
		dmasprintf(&option_value, "%d", blobmsg_get_u32(tb[1]));
	else if (DM_STRCMP(sysmngr_bank_policy[2].name, option_name) == 0 && tb[2]) // Active
		option_value = dmstrdup(blobmsg_get_bool(tb[2]) ? "true" : "false");
	else if (DM_STRCMP(sysmngr_bank_policy[3].name, option_name) == 0 && tb[3]) // Boot
		option_value = dmstrdup(blobmsg_get_bool(tb[3]) ? "true" : "false");
	else if (DM_STRCMP(sysmngr_bank_policy[4].name, option_name) == 0 && tb[4]) // Upgrade
		option_value = dmstrdup(blobmsg_get_bool(tb[4]) ? "true" : "false");
	else if (DM_STRCMP(sysmngr_bank_policy[5].name, option_name) == 0 && tb[5]) // Firmware Version
		option_value = dmstrdup(blobmsg_get_string(tb[5]));
	else if (DM_STRCMP(sysmngr_bank_policy[6].name, option_name) == 0 && tb[6]) // Software Version
		option_value = dmstrdup(blobmsg_get_string(tb[6]));
	else if (DM_STRCMP(sysmngr_bank_policy[7].name, option_name) == 0 && tb[7]) // OMCI Software Version
		option_value = dmstrdup(blobmsg_get_string(tb[7]));
	else if (DM_STRCMP(sysmngr_bank_policy[8].name, option_name) == 0 && tb[8]) // Status
		option_value = dmstrdup(blobmsg_get_string(tb[8]));

	return option_value ? option_value : "";
}

static char *get_fwbank_option_value(void *data, const char *option_name)
{
	char *option_value = NULL;

	option_value = get_blobmsg_option_value((struct blob_attr *)((struct dm_data *)data)->additional_data, option_name);

	return option_value ? option_value : "";
}

static char *get_fwbank_bank_id(const char *option_name)
{
	char *bank_id = NULL;

	struct blob_buf *dump_bb = sysmngr_fwbank_dump();
	if (!dump_bb) //dump output is empty
		return "";

	struct blob_attr *tb[1] = {0};

	blobmsg_parse(sysmngr_dump_policy, 1, tb, blobmsg_data(dump_bb->head), blobmsg_len(dump_bb->head));

	if (!tb[0]) // bank array is not found
		return "";

	struct blob_attr *entry = NULL;
	int rem = 0;

	blobmsg_for_each_attr(entry, tb[0], rem) { // parse bank array
		 char *is_true = get_blobmsg_option_value(entry, option_name);
		 if (DM_LSTRCMP(is_true, "true") == 0) {
			bank_id = get_blobmsg_option_value(entry, "id");
			break;
		 }
	}

	return bank_id ? bank_id : "";
}

static void fwbank_copy_config(const char *keep_settings, const char *keep_opconf, const char *config_scope)
{
	char cmd[256] = {0};
	struct blob_buf bb = {0};
	bool res = false;
	char *str = NULL;

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

	if (DM_STRLEN(keep_settings) != 0) {
		string_to_bool(keep_settings, &res);
		blobmsg_add_u8(&bb, "keep_settings", res);
	}

	if (DM_STRLEN(keep_opconf) != 0) {
		string_to_bool(keep_opconf, &res);
		blobmsg_add_u8(&bb, "keep_opconf", res);
	}

	if (DM_STRLEN(config_scope) != 0) {
		blobmsg_add_string(&bb, "config_scope", config_scope);
	}

	str = blobmsg_format_json(bb.head, true);
	if (str) {
		char output[256] = {0};
		snprintf(cmd, sizeof(cmd), "echo '%s' |/etc/sysmngr/fwbank call copy_config 2> /dev/null", str);
		run_cmd(cmd, output, sizeof(output));
		FREE(str);
	} else {
		BBFDM_ERR("Failed to get json from blob");
	}
}

static bool fwbank_set_bootbank(const char *bank_id)
{
	return sysmngr_fwbank_set_bootbank((uint32_t)DM_STRTOUL(bank_id));
}

static bool fwbank_upgrade(const char *fw_path, struct fw_download_data *dw_data)
{
	json_object *jp_in = NULL;
	json_object *json_obj = NULL;
	bool res = false;

	jp_in = json_object_new_object();
	if (jp_in == NULL) {
		BBFDM_ERR("Failed to create json object");
		return false;
	}

	json_object_object_add(jp_in, "path", json_object_new_string(fw_path));
	json_object_object_add(jp_in, "bank", json_object_new_int(strtol(dw_data->bank_id, NULL, 10)));
	if (DM_STRLEN(dw_data->auto_activate) != 0) {
		string_to_bool(dw_data->auto_activate, &res);
		json_object_object_add(jp_in, "auto_activate", json_object_new_boolean(res));
	}

	if (DM_STRLEN(dw_data->keep_config) != 0) {
		string_to_bool(dw_data->keep_config, &res);
		json_object_object_add(jp_in, "keep_settings", json_object_new_boolean(res));
	}

	if (DM_STRLEN(dw_data->keep_opconf) != 0) {
		string_to_bool(dw_data->keep_opconf, &res);
		json_object_object_add(jp_in, "keep_opconf", json_object_new_boolean(res));
	}

	if (DM_STRLEN(dw_data->config_scope) != 0) {
		json_object_object_add(jp_in, "config_scope", json_object_new_string(dw_data->config_scope));
	}

	res = true;
	dmubus_call_blob_blocking("fwbank", "upgrade", jp_in, &json_obj);
	if (json_obj) {
		char *result = dmjson_get_value(json_obj, 1, "result");
		res = (DM_LSTRCMP(result, "ok") == 0) ? true : false;
	}

	if (json_obj != NULL)
		json_object_put(json_obj);

	if (jp_in)
		json_object_put(jp_in);

	return res;
}

static void _exec_reboot(const void *arg1, void *arg2)
{
	char config_name[16] = {0};

	snprintf(config_name, sizeof(config_name), "%s", "sysmngr");

	// Set last_reboot_cause to 'RemoteReboot' because the upcoming reboot will be initiated by USP Operate
	dmuci_set_value(config_name, "reboots", "last_reboot_cause", "RemoteReboot");
	dmuci_commit_package(config_name);

	sleep(3);
	dmubus_call_blocking("system", "reboot", UBUS_ARGS{0}, 0, NULL);
	sleep(15); // Wait for reboot
	BBFDM_INFO("Reboot call failed with system, trying again with reboot rpc-sys...");
	dmubus_call_blocking("rpc-sys", "reboot", UBUS_ARGS{0}, 0, NULL);
	sleep(15); // Wait for reboot to happen
	BBFDM_WARNING("Reboot call failed with rpc-sys, trying again with reboot cmd ...");
	run_cmd("reboot", NULL, 0);
	sleep(15); // Wait for reboot
	BBFDM_ERR("Reboot calls failed, trying hard reboot!!!");
	run_cmd("reboot -f", NULL, 0);
	sleep(15); // Wait for reboot
	BBFDM_ERR("### Tried all reboot methods, nothing worked .....");

	// Set last_reboot_cause to empty because there is a problem in the system reboot
	dmuci_set_value(config_name, "reboots", "last_reboot_cause", "");
	dmuci_commit_package(config_name);
}

static void dmubus_receive_sysupgrade(struct ubus_context *ctx, struct ubus_event_handler *ev,
				const char *type, struct blob_attr *msg)
{
	struct dmubus_event_data *data;
	struct blob_attr *msg_attr;

	if (!msg || !ev)
		return;

	data = container_of(ev, struct dmubus_event_data, ev);
	if (data == NULL)
		return;

	struct sysupgrade_ev_data *ev_data = (struct sysupgrade_ev_data *)data->ev_data;
	if (ev_data == NULL)
		return;

	size_t msg_len = (size_t)blobmsg_data_len(msg);
	__blob_for_each_attr(msg_attr, blobmsg_data(msg), msg_len) {

		if (DM_STRCMP("bank_id", blobmsg_name(msg_attr)) == 0) {
			char *attr_val = (char *)blobmsg_data(msg_attr);
			if (DM_STRCMP(attr_val, ev_data->bank_id) != 0)
				return;
		}

		if (DM_STRCMP("status", blobmsg_name(msg_attr)) == 0) {
			char *attr_val = (char *)blobmsg_data(msg_attr);
			if (DM_STRCMP(attr_val, "Downloading") == 0)
				return;
			else if (DM_STRCMP(attr_val, "Available") == 0)
				ev_data->status = true;
			else
				ev_data->status = false;
		}
	}

	uloop_end();
	return;
}

static int bbf_fw_image_download(struct ubus_context *ctx, struct fw_download_data *dw_data)
{
	char fw_image_path[256] = {0};
	json_object *json_obj = NULL;
	bool activate = false, valid = false;
	int res = 0;
	char fault_msg[128] = {0};
	time_t complete_time = 0;
	time_t start_time = time(NULL);
	char *active_bank_id = NULL;

	// Check if trying to download in active bank
	active_bank_id = get_fwbank_bank_id("active");
	if (DM_STRCMP(dw_data->bank_id, active_bank_id) == 0 ) {
		res = -1;
		snprintf(fault_msg, sizeof(fault_msg), "Trying to download in active bank %s", active_bank_id);
		goto end;
	}

	DM_STRNCPY(fw_image_path, "/tmp/firmware-XXXXXX", sizeof(fw_image_path));

	// Check the file system size if there is sufficient space for downloading the firmware image
	if (!validate_file_system_size(dw_data->file_size)) {
		res = -1;
		snprintf(fault_msg, sizeof(fault_msg), "Available memory space is lower than required for downloading");
		goto end;
	}

	res = mkstemp(fw_image_path);
	if (res == -1) {
		snprintf(fault_msg, sizeof(fault_msg), "Operation failed due to some internal failure");
		goto end;
	} else {
		close(res); // close the fd, as only filename required
		res = 0;
	}

	// Download the firmware image
	long res_code = download_file(fw_image_path, dw_data->url, dw_data->username, dw_data->password);
	complete_time = time(NULL);

	// Check if the download operation was successful
	if (!validate_server_response_code(dw_data->url, res_code)) {
		snprintf(fault_msg, sizeof(fault_msg), "Download operation is failed, fault code (%ld)", res_code);
		res = -1;
		goto end;
	}

	// Validate the CheckSum value according to its algorithm
	if (!validate_checksum_value(fw_image_path, dw_data->checksum_algorithm, dw_data->checksum)) {
		res = -1;
		snprintf(fault_msg, sizeof(fault_msg), "Checksum of the file is not matched with the specified value");
		goto end;
	}

	dmubus_call_blocking("system", "validate_firmware_image", UBUS_ARGS{{"path", fw_image_path, String}}, 1, &json_obj);
	if (json_obj == NULL) {
		res = -1;
		snprintf(fault_msg, sizeof(fault_msg), "Failed in validation of the file");
		goto end;
	}

	char *val = dmjson_get_value(json_obj, 1, "valid");
	string_to_bool(val, &valid);

	// Free json_obj
	json_object_put(json_obj);
	json_obj = NULL;

	if (valid == false) {
		snprintf(fault_msg, sizeof(fault_msg), "File is not a valid firmware image");
		res = -1;
		goto end;
	}

	// Apply Firmware Image
	if (!fwbank_upgrade(fw_image_path, dw_data)) {
		res = 1;
		snprintf(fault_msg, sizeof(fault_msg), "Internal error occurred when applying the firmware");
		goto end;
	}

	struct sysupgrade_ev_data ev_data = {
		.bank_id = dw_data->bank_id,
		.status = false,
	};

	dmubus_wait_for_event("sysupgrade", 120, &ev_data, dmubus_receive_sysupgrade, NULL);
	if (ev_data.status == false) {
		res = 1;
		snprintf(fault_msg, sizeof(fault_msg), "Failed to apply the downloaded image file");
		goto end;
	}

	// Schedule a device Reboot, if auto activation is true
	string_to_bool(dw_data->auto_activate, &activate);
	if (activate) {
		bbfdm_task_fork(_exec_reboot, NULL, NULL, NULL);
	}

end:
	// Send the transfer complete event
	send_transfer_complete_event(ctx, dw_data->command, dw_data->obj_path, dw_data->url, fault_msg, start_time, complete_time,
	                             dw_data->commandKey, "Download", dw_data->requestor);

	// Remove temporary file if ubus upgrade failed and file exists
	if (file_exists(fw_image_path) && strncmp(dw_data->url, FILE_URI, strlen(FILE_URI)))
		remove(fw_image_path);

	return res;
}

/*************************************************************
* ENTRY METHOD
**************************************************************/
int browseDeviceInfoFirmwareImageInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct dm_data curr_data = {0};
	struct blob_attr *tb[1] = {0};
	char *inst = NULL;
	int id = 0;

	struct blob_buf *dump_bb = sysmngr_fwbank_dump();
	if (!dump_bb) //dump output is empty
		return 0;

	blobmsg_parse(sysmngr_dump_policy, 1, tb, blobmsg_data(dump_bb->head), blobmsg_len(dump_bb->head));

	if (tb[0]) { // bank array defined
		struct blob_attr *entry = NULL;
		int rem = 0;

		blobmsg_for_each_attr(entry, tb[0], rem) { // parse bank array

			curr_data.additional_data = (void *)entry;

			inst = handle_instance_without_section(dmctx, parent_node, ++id);

			if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)&curr_data, inst) == DM_STOP)
				break;
		}
	}

	return 0;
}

/*************************************************************
* GET & SET PARAM
**************************************************************/
int get_device_active_fwimage(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *bank_id = get_fwbank_bank_id("active");
	if (DM_STRLEN(bank_id) == 0) {
		*value = dmstrdup("");
		return 0;
	}

	char linker[16] = {0};

	snprintf(linker, sizeof(linker), "cpe-%s", bank_id);
	_bbfdm_get_references(ctx, "Device.DeviceInfo.FirmwareImage.", "Alias", linker, value);
	return 0;
}

int get_device_boot_fwimage(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *bank_id = get_fwbank_bank_id("boot");
	if (DM_STRLEN(bank_id) == 0) {
		*value = dmstrdup("");
		return 0;
	}

	char linker[16] = {0};

	snprintf(linker, sizeof(linker), "cpe-%s", bank_id);
	_bbfdm_get_references(ctx, "Device.DeviceInfo.FirmwareImage.", "Alias", linker, value);
	return 0;
}

int set_device_boot_fwimage(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *allowed_objects[] = {"Device.DeviceInfo.FirmwareImage.", NULL};
	struct dm_reference reference = {0};

	bbfdm_get_reference_linker(ctx, value, &reference);

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, reference.path, -1, -1, NULL, NULL))
				return FAULT_9007;

			if (dm_validate_allowed_objects(ctx, &reference, allowed_objects))
				return FAULT_9007;

			break;
		case VALUESET:
			if (DM_STRLEN(reference.value)) {
				struct uci_section *dmmap_s = NULL;
				char *available = NULL;

				char *bank_id = DM_STRCHR(reference.value, '-'); // Get bank id 'X' which is linker from Alias prefix 'cpe-X'
				if (!bank_id)
					return FAULT_9001;

				get_dmmap_section_of_config_section_cont("dmmap_fw_image", "fw_image", "id", bank_id + 1, &dmmap_s);
				dmuci_get_value_by_section_string(dmmap_s, "available", &available);
				if (DM_LSTRCMP(available, "false") == 0)
					return FAULT_9001;

				if (!fwbank_set_bootbank(bank_id + 1))
					return FAULT_9001;
			}
			break;
	}
	return 0;
}

int get_DeviceInfo_MaxNumberOfActivateTimeWindows(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmasprintf(value, "%d", MAX_TIME_WINDOW);
	return 0;
}

int get_DeviceInfo_FirmwareImageNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, browseDeviceInfoFirmwareImageInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int get_DeviceInfoFirmwareImage_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *id = get_fwbank_option_value(data, "id");
	dmasprintf(value, "cpe-%s", id ? id : instance);
	return 0;
}

static int set_DeviceInfoFirmwareImage_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, 64, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			bbfdm_set_fault_message(ctx, "Internal designated unique identifier, not allowed to update");
			return FAULT_9007;
	}
	return 0;
}

static int get_DeviceInfoFirmwareImage_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *name = get_fwbank_option_value(data, "fwver");
	if (DM_STRLEN(name) > 64 ) {
		name[64] = '\0';
	}

	*value = name;
	return 0;
}

static int get_DeviceInfoFirmwareImage_Version(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = get_fwbank_option_value(data, "swver");
	return 0;
}

static int get_DeviceInfoFirmwareImage_Available(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct uci_section *s = NULL;

	char *id = get_fwbank_option_value(data, "id");

	uci_path_foreach_option_eq(bbfdm, "dmmap_fw_image", "fw_image", "id", id, s) {
		dmuci_get_value_by_section_string(s, "available", value);
		break;
	}

	if ((*value)[0] == '\0')
		*value = dmstrdup("true");
	return 0;
}

static int set_DeviceInfoFirmwareImage_Available(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct uci_section *s = NULL, *dmmap = NULL;
	char *id = NULL;
	bool b;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			string_to_bool(value, &b);

			if (!b) {
				char *boot = get_fwbank_option_value(data, "boot");
				char *active = get_fwbank_option_value(data, "active");
				if (DM_LSTRCMP(boot, "true") == 0 || DM_LSTRCMP(active, "true") == 0)
					return FAULT_9001;
			}

			id = get_fwbank_option_value(data, "id");

			uci_path_foreach_option_eq(bbfdm, "dmmap_fw_image", "fw_image", "id", id, s) {
				dmuci_set_value_by_section_bbfdm(s, "available", b ? "true" : "false");
				return 0;
			}

			dmuci_add_section_bbfdm("dmmap_fw_image", "fw_image", &dmmap);
			dmuci_set_value_by_section(dmmap, "id", id);
			dmuci_set_value_by_section(dmmap, "available", b ? "true" : "false");
			break;
	}
	return 0;
}

static int get_DeviceInfoFirmwareImage_Status(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = get_fwbank_option_value(data, "status");
	return 0;
}

/*************************************************************
 * OPERATE COMMANDS
 *************************************************************/
static operation_args firmware_image_download_args = {
	.in = (const char *[]) {
		"URL",
		"AutoActivate",
		"Username",
		"Password",
		"FileSize",
		"CheckSumAlgorithm",
		"CheckSum",
#ifdef SYSMNGR_VENDOR_EXTENSIONS
		CUSTOM_PREFIX"KeepConfig",
		CUSTOM_PREFIX"KeepOpConf",
		CUSTOM_PREFIX"ConfigScope",
#endif
		NULL
	}
};

static int get_operate_args_DeviceInfoFirmwareImage_Download(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = (char *)&firmware_image_download_args;
	return 0;
}

static int operate_DeviceInfoFirmwareImage_Download(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char obj_path[256] = {0};
	struct fw_download_data dw_data;
	bool activate = false;

	memset(&dw_data, 0, sizeof(struct fw_download_data));

	char *ret = DM_STRRCHR(refparam, '.');
	if (!ret)
		return USP_FAULT_INVALID_ARGUMENT;

	if ((ret - refparam + 2) < sizeof(obj_path)) {
		snprintf(obj_path, ret - refparam + 2, "%s", refparam);
	}

	if (ctx->dm_type == BBFDM_USP) {
		dw_data.command = dmstrdup(refparam);
		dw_data.commandKey = dmjson_get_value((json_object *)value, 1, "__BBF_CommandKey");
		dw_data.requestor = dmjson_get_value((json_object *)value, 1, "__BBF_Requestor");
	}

	dw_data.url = dmjson_get_value((json_object *)value, 1, "URL");
	if (DM_STRLEN(dw_data.url) == 0)
		return USP_FAULT_INVALID_ARGUMENT;

	// Assuming auto activate as false, if not provided by controller, in case of strict validation,
	// this should result into a fault
	dw_data.auto_activate = dmjson_get_value((json_object *)value, 1, "AutoActivate");
	if (DM_STRLEN(dw_data.auto_activate) == 0)
		dw_data.auto_activate = dmstrdup("0");

	string_to_bool(dw_data.auto_activate, &activate);

	dw_data.username = dmjson_get_value((json_object *)value, 1, "Username");
	dw_data.password = dmjson_get_value((json_object *)value, 1, "Password");
	dw_data.file_size = dmjson_get_value((json_object *)value, 1, "FileSize");
	dw_data.checksum_algorithm = dmjson_get_value((json_object *)value, 1, "CheckSumAlgorithm");
	dw_data.checksum = dmjson_get_value((json_object *)value, 1, "CheckSum");

#ifdef SYSMNGR_VENDOR_EXTENSIONS
	dw_data.keep_config = dmjson_get_value((json_object *)value, 1, CUSTOM_PREFIX"KeepConfig");
	dw_data.keep_opconf = dmjson_get_value((json_object *)value, 1, CUSTOM_PREFIX"KeepOpConf");
	dw_data.config_scope = dmjson_get_value((json_object *)value, 1, CUSTOM_PREFIX"ConfigScope");
#endif
	dw_data.bank_id = get_fwbank_option_value(data, "id");
	dw_data.obj_path = dmstrdup(obj_path);

	int res = bbf_fw_image_download(ctx->ubus_ctx, &dw_data);
	if (res != 0) {
		bbfdm_set_fault_message(ctx, "Firmware validation failed");
	}

	if (activate == false && res == 0) {
		sleep(5); // Allow new firmware's FS work
	}

	return res ? USP_FAULT_COMMAND_FAILURE : 0;
}

static operation_args firmware_image_activate_args = {
	.in = (const char *[]) {
		"TimeWindow.{i}.Start",
		"TimeWindow.{i}.End",
		"TimeWindow.{i}.Mode",
		"TimeWindow.{i}.UserMessage",
		"TimeWindow.{i}.MaxRetries",
#ifdef SYSMNGR_VENDOR_EXTENSIONS
		CUSTOM_PREFIX"KeepConfig",
		CUSTOM_PREFIX"KeepOpConf",
		CUSTOM_PREFIX"ConfigScope",
#endif
		NULL
	}
};

static int get_operate_args_DeviceInfoFirmwareImage_Activate(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = (char *)&firmware_image_activate_args;
	return 0;
}

static int operate_DeviceInfoFirmwareImage_Activate(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *FW_Mode[] = {"AnyTime", "Immediately", "WhenIdle", "ConfirmationNeeded", NULL};
	char *start_time[MAX_TIME_WINDOW] = {0};
	char *end_time[MAX_TIME_WINDOW] = {0};
	char *mode[MAX_TIME_WINDOW] = {0};
	char *user_message[MAX_TIME_WINDOW] = {0};
	char *max_retries[MAX_TIME_WINDOW] = {0};
	char *keep_config = NULL;
	char *keep_opconf = NULL;
	char *config_scope = NULL;
	int res = 0, last_idx = -1;

#ifdef SYSMNGR_VENDOR_EXTENSIONS
	keep_config = dmjson_get_value((json_object *)value, 1, CUSTOM_PREFIX"KeepConfig");
	keep_opconf = dmjson_get_value((json_object *)value, 1, CUSTOM_PREFIX"KeepOpConf");
	config_scope = dmjson_get_value((json_object *)value, 1, CUSTOM_PREFIX"ConfigScope");
#endif

	for (int i = 0; i < MAX_TIME_WINDOW; i++) {
		char buf[32] = {0};

		snprintf(buf, sizeof(buf), "TimeWindow.%d.Start", i + 1);
		start_time[i] = dmjson_get_value((json_object *)value, 1, buf);

		snprintf(buf, sizeof(buf), "TimeWindow.%d.End", i + 1);
		end_time[i] = dmjson_get_value((json_object *)value, 1, buf);

		snprintf(buf, sizeof(buf), "TimeWindow.%d.Mode", i + 1);
		mode[i] = dmjson_get_value((json_object *)value, 1, buf);

		snprintf(buf, sizeof(buf), "TimeWindow.%d.UserMessage", i + 1);
		user_message[i] = dmjson_get_value((json_object *)value, 1, buf);

		snprintf(buf, sizeof(buf), "TimeWindow.%d.MaxRetries", i + 1);
		max_retries[i] = dmjson_get_value((json_object *)value, 1, buf);

		if (!DM_STRLEN(start_time[i]))
			break;

		if (!DM_STRLEN(end_time[i]) || !DM_STRLEN(mode[i]))
			return USP_FAULT_INVALID_ARGUMENT;

		if (bbfdm_validate_unsignedInt(ctx, start_time[i], RANGE_ARGS{{NULL,NULL}}, 1))
			return USP_FAULT_INVALID_ARGUMENT;

		if (bbfdm_validate_unsignedInt(ctx, end_time[i], RANGE_ARGS{{NULL,NULL}}, 1))
			return USP_FAULT_INVALID_ARGUMENT;

		if (DM_STRLEN(max_retries[i]) && bbfdm_validate_int(ctx, max_retries[i], RANGE_ARGS{{"-1","10"}}, 1))
			return USP_FAULT_INVALID_ARGUMENT;

		if (bbfdm_validate_string(ctx, mode[i], -1, -1, FW_Mode, NULL))
			return USP_FAULT_INVALID_ARGUMENT;

		if (DM_STRTOL(start_time[i]) > DM_STRTOL(end_time[i]))
			return USP_FAULT_INVALID_ARGUMENT;

		if (i != 0 && DM_STRTOL(end_time[i - 1]) > DM_STRTOL(start_time[i]))
			return USP_FAULT_INVALID_ARGUMENT;

		last_idx++;
	}

	char *bank_id = get_fwbank_option_value(data, "id");

	if (!DM_STRLEN(bank_id))
		return USP_FAULT_COMMAND_FAILURE;

	if (DM_STRLEN(start_time[0])) {
		// cppcheck-suppress cert-MSC24-C
		FILE *file = fopen(CRONTABS_ROOT, "a");
		if (!file)
			return USP_FAULT_COMMAND_FAILURE;

		for (int i = 0; i < MAX_TIME_WINDOW && DM_STRLEN(start_time[i]); i++) {
			char buffer[512] = {0};
			time_t t_time = time(NULL);
			long int start_t = (DM_STRTOL(start_time[i]) > 60) ? DM_STRTOL(start_time[i]) : 60;
			t_time += start_t;
			struct tm *tm_local = localtime(&t_time);
			size_t len;

			snprintf(buffer, sizeof(buffer), "%d %d %d %d * sh %s",
					tm_local->tm_min, tm_local->tm_hour,
					tm_local->tm_mday, tm_local->tm_mon + 1,
					ACTIVATE_HANDLER_FILE);

			len = strlen(buffer);
			snprintf(buffer+len, sizeof(buffer)-len, " '%s' '%s' '%ld' '%d' '%s' '%s' '%s' '%s' '%s'\n",
					mode[i], bank_id, (DM_STRTOL(end_time[i]) - DM_STRTOL(start_time[i])),
					(i == last_idx), max_retries[i], (keep_config)?keep_config:"1",
					(keep_opconf)?keep_opconf:"1", (config_scope)?config_scope:"", user_message[i]);

			fprintf(file, "%s", buffer);
		}

		fclose(file);

		res = dmcmd_no_wait("/etc/init.d/cron", 1, "restart");
	} else {
		if (!fwbank_set_bootbank(bank_id))
			return USP_FAULT_COMMAND_FAILURE;

		fwbank_copy_config(keep_config, keep_opconf, config_scope);
		bbfdm_task_fork(_exec_reboot, NULL, NULL, NULL);
	}

	return res ? USP_FAULT_COMMAND_FAILURE : 0;
}

/**********************************************************************************************************************************
*                                            OBJ & LEAF DEFINITION
***********************************************************************************************************************************/
/* *** Device.DeviceInfo.FirmwareImage.{i}. *** */
DMLEAF tDeviceInfoFirmwareImageParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Alias", &DMWRITE, DMT_STRING, get_DeviceInfoFirmwareImage_Alias, set_DeviceInfoFirmwareImage_Alias, BBFDM_BOTH, DM_FLAG_UNIQUE|DM_FLAG_LINKER},
{"Name", &DMREAD, DMT_STRING, get_DeviceInfoFirmwareImage_Name, NULL, BBFDM_BOTH},
{"Version", &DMREAD, DMT_STRING, get_DeviceInfoFirmwareImage_Version, NULL, BBFDM_BOTH},
{"Available", &DMWRITE, DMT_BOOL, get_DeviceInfoFirmwareImage_Available, set_DeviceInfoFirmwareImage_Available, BBFDM_BOTH},
{"Status", &DMREAD, DMT_STRING, get_DeviceInfoFirmwareImage_Status, NULL, BBFDM_BOTH},
{"BootFailureLog", &DMREAD, DMT_STRING, get_empty, NULL, BBFDM_BOTH},
{"Download()", &DMASYNC, DMT_COMMAND, get_operate_args_DeviceInfoFirmwareImage_Download, operate_DeviceInfoFirmwareImage_Download, BBFDM_USP},
{"Activate()", &DMASYNC, DMT_COMMAND, get_operate_args_DeviceInfoFirmwareImage_Activate, operate_DeviceInfoFirmwareImage_Activate, BBFDM_USP},
{0}
};
