/*
 * Copyright (C) 2023-2024 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author Suvendhu Hansa <suvendhu.hansa@iopsys.eu>
 *
 * See LICENSE file for license related information.
 */


#include "libbbfdm-api/dmcommon.h"
#include <libbbfdm-ubus/bbfdm-ubus.h>
#include "easy/utils.h"

#define USERINTERFACE_ISPLOGO "/etc/userinterface/ISPLogo"
// binary file max size is limited to 3069 bytes, which gives us 4092 bytes when encoded in base64
// maximum we can return is 4095, but since base64 string length is always divisible by 4, 4092 is real maximum
#define ISPLOGO_MAX_SIZE 3069
// max encoded length is 4092, it also needs to hold null terminating character
#define ISPLOGO_MAX_SIZE_ENCODED 4093
// base64_encode() function adds newline character ('\n') after every 72 characters
// this adds overhead of 56 additional newline characters for 4092 base64 characters
// this overhead is only needed for buffer when encoding but is removed before returning base64 encoded string
#define ISPLOGO_MAX_ENCODED_OVERHEAD 56

static bool remote_access_enabled(struct uci_section *sec)
{
	char *en = NULL;

	dmuci_get_value_by_section_string(sec, "access", &en);
	return (DM_STRCMP(en, "remote") == 0) ? true : false;
}

static void get_server_port(struct uci_section *sec, char **port)
{
	if (sec == NULL || port == NULL)
		return;

	dmuci_get_value_by_section_string(sec, "port", port);
}

static void get_server_enable(struct uci_section *sec, bool *en)
{
	if (sec == NULL || en == NULL)
		return;

	char *val = NULL;
	dmuci_get_value_by_section_string(sec, "enable", &val);
	if (DM_STRLEN(val) == 0) {
		*en = 1;
	} else {
		*en = dmuci_string_to_boolean(val);
	}
}

static bool check_port_in_use(const char *port)
{
	struct uci_section *s = NULL;

	uci_foreach_sections("userinterface", "http_access", s) {
		char *tmp = NULL;

		get_server_port(s, &tmp);
		if (DM_STRCMP(tmp, port) == 0)
			return true;
	}

	return false;
}

/*************************************************************
* DMMAP SYNC (v2): generate the "UserInterface" dmmap with standard object
* names once at load (init) and on external UCI change (sync), instead of
* diffing config vs dmmap on every browse.
**************************************************************/
static void dmmap_synchronize_HTTPAccess(struct dmctx *dmctx)
{
	struct uci_section *s = NULL, *stmp = NULL;
	char *instance = NULL;

	// add: one dmmap "HTTPAccess" row per real http_access config section,
	// excluding redirect sections and soft-deleted (removed_*) sections.
	uci_foreach_sections("userinterface", "http_access", s) {
		char *redirect = NULL;

		dmuci_get_value_by_section_string(s, "redirect", &redirect);
		if (DM_STRLEN(redirect) != 0)
			continue;

		if (DM_STRNCMP(section_name(s), "removed_", 8) == 0)
			continue;

		// create_dmmap_obj de-dups by __section_name__, so this is idempotent
		create_dmmap_obj(dmctx, 0, "UserInterface", "HTTPAccess", s, &instance);
	}

	// prune: drop dmmap rows whose config section is gone, soft-deleted or now redirect
	uci_path_foreach_sections_safe(bbfdm, "UserInterface", "HTTPAccess", stmp, s) {
		char *sec_name = NULL, *redirect = NULL;
		struct uci_section *cfg = NULL;

		dmuci_get_value_by_section_string(s, "__section_name__", &sec_name);
		cfg = get_config_section_from_dmmap_section_name(sec_name);
		if (cfg == NULL) { // gone, or soft-deleted (renamed removed_* -> lookup fails)
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		dmuci_get_value_by_section_string(cfg, "redirect", &redirect);
		if (DM_STRLEN(redirect) != 0)
			dmuci_delete_by_section(s, NULL, NULL);
	}
}

static void userinterface_dmmap_rebuild(void)
{
	struct dmctx bbf_ctx = {0};

	bbf_ctx_init(&bbf_ctx, NULL);
	dmmap_synchronize_HTTPAccess(&bbf_ctx);
	dmuci_commit_package_bbfdm("UserInterface");
	bbf_ctx_clean(&bbf_ctx);
}

int userinterface_init(void *data)
{
	(void)data;
	// AddObject auto-links the dmmap row only when the dmmap file already
	// exists (see dm_entry_add_object), so ensure it from first load.
	bbfdm_create_empty_file("/etc/bbfdm/dmmap/UserInterface");
	userinterface_dmmap_rebuild();
	return 0;
}

int userinterface_sync(void *data)
{
	struct bbfdm_context *bbfdm_ctx = (struct bbfdm_context *)data;

	if (bbfdm_ctx == NULL)
		return 0;

	// Only resync when an external agent (not the datamodel itself) changed
	// the userinterface UCI; ignore datamodel-initiated commits and changes
	// to other config files.
	if (DM_STRCMP(bbfdm_ctx->uci_change_proto, "external") == 0 && !list_empty(&bbfdm_ctx->changed_uci)) {
		struct apply_handler_node *node = NULL;

		list_for_each_entry(node, &bbfdm_ctx->changed_uci, list) {
			if (DM_STRCMP(node->file_path, "/etc/config/userinterface") == 0) {
				userinterface_dmmap_rebuild();
				break;
			}
		}
	}

	return 0;
}

/*************************************************************
* ENTRY METHODS
**************************************************************/
static int addHTTPAccess(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct dm_data *curr_data = (struct dm_data *)ctx->addobj_instance;
	char s_name[16];

	snprintf(s_name, sizeof(s_name), "server_%s", *instance);

	// set config_section on addobj_instance so the core links __section_name__
	dmuci_add_section("userinterface", "http_access", &curr_data->config_section);
	dmuci_rename_section_by_section(curr_data->config_section, s_name);
	dmuci_set_value_by_section(curr_data->config_section, "enable", "0");
	dmuci_set_value_by_section(curr_data->config_section, "path_prefix", "/www");
	dmuci_set_value_by_section(curr_data->config_section, "protocol", "HTTP");

	return 0;
}

static int delHTTPAccess(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	struct uci_section *s = ((struct dm_data *)data)->config_section;
	char s_name[16];

	dmuci_set_value_by_section(s, "section_name", section_name(s));

	snprintf(s_name, sizeof(s_name), "removed_%s", instance);
	dmuci_rename_section_by_section(s, s_name);

	// drop the dmmap row too; the removed_* skip in sync stops it resurfacing
	dmuci_delete_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);
	return 0;
}

static int browseHTTPAccess(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct dm_data curr_data = {0};
	char *inst = NULL;
	json_object *res = NULL;

	dmubus_call("userinterface", "http_session", UBUS_ARGS{0}, 0, &res);

	// iterate the pre-built dmmap; redirect sections never get a row (see sync)
	uci_path_foreach_sections(bbfdm, "UserInterface", "HTTPAccess", curr_data.dmmap_section) {
		char *sec_name = NULL;

		dmuci_get_value_by_section_string(curr_data.dmmap_section, "__section_name__", &sec_name);
		curr_data.config_section = get_config_section_from_dmmap_section_name(sec_name);
		if (curr_data.config_section == NULL)
			continue; // config vanished before next sync prunes the row

		curr_data.json_object = res;
		inst = uci_handle_instance(dmctx, parent_node, &curr_data);

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

static int browseHTTPSession(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	char *tmp = NULL, *inst = NULL;
	bool enable = false;
	json_object *res = NULL, *arr_http_session = NULL, *http_session_obj = NULL;
	int idx = 0;
	struct dm_data data;

	res = ((struct dm_data *)prev_data)->json_object;
	if (res == NULL)
		return 0;

	get_server_enable(((struct dm_data *)prev_data)->config_section, &enable);
	get_server_port(((struct dm_data *)prev_data)->config_section, &tmp);
	if (!enable || DM_STRLEN(tmp) == 0)
		return 0;

	memset(&data, 0, sizeof(data));
	dmjson_foreach_obj_in_array(res, arr_http_session, http_session_obj, idx, 1, "http_sessions") {
		char *srv_port = dmjson_get_value(http_session_obj, 1, "server_port");

		if (DM_STRCMP(tmp, srv_port) != 0)
			continue;

		data.json_object = http_session_obj;
		inst = handle_instance_without_section(dmctx, parent_node, ++idx);

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

	return 0;
}

/*************************************************************
* GET & SET PARAM
**************************************************************/
static int get_ui_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *enable = NULL;

	dmuci_get_option_value_string("userinterface", "global", "enable", &enable);
	if (DM_STRLEN(enable)) {
		*value = dmuci_string_to_boolean(enable) ? dmstrdup("1") : dmstrdup("0");
	} else {
		*value = dmstrdup("1");
	}
	return 0;
}

static int set_ui_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value("userinterface", "global", "enable", value);
			break;
	}
	return 0;
}

static int get_http_access_protocols(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmstrdup("HTTP,HTTPS");
	return 0;
}

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

static int get_http_access_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *enable = NULL;

	enable = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enable", "1");
	*value = dmuci_string_to_boolean(enable) ? "1" : "0";
	return 0;
}

static int set_http_access_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;

			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "enable", value);
			break;
	}
	return 0;
}

static int get_http_access_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

static int set_http_access_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

static int get_http_access_status(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *tmp = NULL;
	char *enable = NULL;
	bool serv_enable = false, ui_enable = false;

	dmuci_get_option_value_string("userinterface", "global", "enable", &enable);
	if (DM_STRLEN(enable) == 0) {
		ui_enable = 1;
	} else {
		ui_enable = dmuci_string_to_boolean(enable);
	}

	get_server_enable(((struct dm_data *)data)->config_section, &serv_enable);

	if (ui_enable == false || serv_enable == false) {
		*value = dmstrdup("Down");
		return 0;
	}

	get_server_port(((struct dm_data *)data)->config_section, &tmp);
	if (DM_STRLEN(tmp) == 0) {
		*value = dmstrdup("Error");
		return 0;
	}

	json_object *res = NULL;
	dmubus_call("userinterface", "http_status", UBUS_ARGS{{"port", tmp, String}}, 1, &res);
	DM_ASSERT(res, *value = dmstrdup("Error"));

	*value = dmstrdup(dmjson_get_value(res, 1, "server_status"));

	return 0;
}

static int get_http_access_type(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	if (remote_access_enabled(((struct dm_data *)data)->config_section) == true)
		*value = dmstrdup("RemoteAccess");
	else
		*value = dmstrdup("LocalAccess");

	return 0;
}

static int set_http_access_type(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *allowed_values[] = {"RemoteAccess", "LocalAccess", NULL};
	struct uci_section *stmp = NULL;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, -1, allowed_values, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			stmp = ((struct dm_data *)data)->config_section;
			if (DM_STRCMP(value, "RemoteAccess") == 0 && !remote_access_enabled(stmp)) {
				dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "access", "remote");
			}

			if (DM_STRCMP(value, "LocalAccess") == 0 && remote_access_enabled(stmp)) {
				dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "access", "");
			}

			break;
	}
	return 0;
}

static int get_http_access_roles(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct uci_list *roles = NULL;

	dmuci_get_value_by_section_list(((struct dm_data *)data)->config_section, "role", &roles);

	if (roles != NULL) {
		char buf[2056] = {0};
		struct uci_element *e = NULL;

		uci_foreach_element(roles, e) {
			if (DM_STRLEN(e->name) == 0)
				continue;

			bbfdm_get_references(ctx, MATCH_ALL, "Device.Users.Role.", "RoleName", e->name, buf, sizeof(buf));
		}

		*value = dmstrdup(buf);
	}

	return 0;
}

static int set_http_access_roles(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *allowed_values[] = {"Device.Users.Role.", NULL};
	struct dm_reference reference = {0};
	char **arr;
	size_t length;
	int i;

	switch (action)	{
		case VALUECHECK:
			if (DM_STRLEN(value) == 0)
				break;

			arr = strsplit(value, ",", &length);
			for (i = 0; i < length; i++) {
				memset(&reference, 0, sizeof(struct dm_reference));
				bbfdm_get_reference_linker(ctx, arr[i], &reference);

				if (DM_STRLEN(reference.path) == 0)
					continue;

				if (dm_validate_allowed_objects(ctx, &reference, allowed_values))
					return FAULT_9007;
			}
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "role", "");
			if (DM_STRLEN(value) == 0)
				break;

			arr = strsplit(value, ",", &length);
			for (i = 0; i < length; i++) {
				memset(&reference, 0, sizeof(struct dm_reference));
				bbfdm_get_reference_linker(ctx, arr[i], &reference);

				if (DM_STRLEN(reference.path) == 0 || DM_STRLEN(reference.value) == 0) {
					continue;
				}

				dmuci_add_list_value_by_section(((struct dm_data *)data)->config_section, "role", reference.value);
			}

			break;
	}
	return 0;
}

static int get_http_access_interface(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *linker = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "interface", &linker);
	_bbfdm_get_references(ctx, "Device.IP.Interface.", "Name", linker, value);
	return 0;
}

static int set_http_access_interface(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *allowed_objects[] = {"Device.IP.Interface.", 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, 256, NULL, NULL))
				return FAULT_9007;

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

			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "interface", reference.value);
			break;
	}
	return 0;
}

static int get_http_access_port(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *tmp = NULL;

	get_server_port(((struct dm_data *)data)->config_section, &tmp);
	if (DM_STRLEN(tmp) != 0)
		*value = dmstrdup(tmp);

	return 0;
}

static int set_http_access_port(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *cur_val = NULL;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1","65535"}},1))
				return FAULT_9007;

			// check if same as current value
			get_server_port(((struct dm_data *)data)->config_section, &cur_val);
			if (DM_STRCMP(value, cur_val) == 0)
				break;

			// check if any server is configured with same port
			if (check_port_in_use(value)) {
				bbfdm_set_fault_message(ctx, "Port is already configured with other instance.");
				return FAULT_9001;
			}
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "port", value);
			break;
	}
	return 0;
}

static int get_http_access_protocol(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "protocol", value);
	return 0;
}

static int get_http_access_hosts(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct uci_list *uci_opt_list = NULL;

	dmuci_get_value_by_section_list(((struct dm_data *)data)->config_section, "allow_host", &uci_opt_list);
	*value = dmuci_list_to_string(uci_opt_list, ",");
	return 0;
}

static int set_http_access_hosts(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	size_t length = 0;
	char **arr = NULL;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, -1, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			arr = strsplit(value, ",", &length);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "allow_host", "");

			for (int i = 0; i < length; i++)
				dmuci_add_list_value_by_section(((struct dm_data *)data)->config_section, "allow_host", arr[i]);

			break;
	}
	return 0;
}

static int get_http_access_path(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *res = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "path_prefix", "/");
	if (DM_STRNCMP(res, "/www", 4) == 0) {
		*value = dmstrdup(res+4);
	} else {
		*value = dmstrdup(res);
	}

	return 0;
}

static int set_http_access_path(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char path[512] = {0};

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, -1, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			snprintf(path, sizeof(path), "/www%s%s", (value[0] != '/') ? "/" : "", value);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "path_prefix", path);
			break;
	}
	return 0;
}

static int get_http_access_activationdate(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "activationdate", value);
	return 0;
}

static int get_http_access_session_num(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	unsigned int cnt = get_number_of_entries(ctx, data, instance, browseHTTPSession);
	dmasprintf(value, "%u", cnt);
	return 0;
}

static int get_http_session_ip(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *obj = ((struct dm_data *)data)->json_object;
	*value = obj ? dmstrdup(dmjson_get_value(obj, 1, "client_ip")) : "";
	return 0;
}

static int get_http_session_port(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *obj = ((struct dm_data *)data)->json_object;
	*value = obj ? dmstrdup(dmjson_get_value(obj, 1, "client_port")) : "";
	return 0;
}

static int get_isp_logo(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	// cppcheck-suppress cert-MSC24-C
	FILE* fp = fopen(USERINTERFACE_ISPLOGO, "rb");
	if (!fp) { // file not present
		return 0;
	}

	fseek(fp, 0, SEEK_END);
	size_t filesize = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	if (filesize > ISPLOGO_MAX_SIZE) { // file is bigger than allowed
		BBFDM_ERR("Logo size if bigger %d, allowed %d", filesize, ISPLOGO_MAX_SIZE);
		fclose(fp);
		return FAULT_9002;
	}

	unsigned char buffer[filesize];
	fread(buffer, 1, filesize, fp);
	fclose(fp);

	size_t encoded_size = ISPLOGO_MAX_SIZE_ENCODED + ISPLOGO_MAX_ENCODED_OVERHEAD;
	unsigned char encoded_buff[encoded_size];
	memset(&encoded_buff, 0, encoded_size);

	int ret = base64_encode(buffer, filesize, encoded_buff, &encoded_size);
	if (ret != 0) {
		BBFDM_WARNING("base64 encoding failed, buffer size %d, ret %d", filesize, ret);
		return FAULT_9007;
	}
	// encoded_buff has newline characters '\n', which needs to be removed
	*value = dmcalloc(1, ISPLOGO_MAX_SIZE_ENCODED);
	if (*value == NULL) {
		BBFDM_ERR("error allocating memmory for value");
		return FAULT_9007;
	}
	replace_str((char *)encoded_buff, "\n", "", *value, ISPLOGO_MAX_SIZE_ENCODED);
	return 0;
}

static int set_isp_logo(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, 0, ISPLOGO_MAX_SIZE_ENCODED, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			size_t encoded_size = strlen(value);
			if (encoded_size == 0) {
				remove(USERINTERFACE_ISPLOGO);
				return 0;
			}

			size_t decoded_size = ISPLOGO_MAX_SIZE;
			unsigned char decoded_buff[ISPLOGO_MAX_SIZE] = {0};
			memset(&decoded_buff, 0, ISPLOGO_MAX_SIZE);
			int ret = base64_decode((unsigned char *)value, encoded_size, decoded_buff, &decoded_size);
			if (ret != 0) {
				BBFDM_WARNING("base64 decoding failed, ret %d", ret);
				return FAULT_9007;
			}

			// cppcheck-suppress cert-MSC24-C
			FILE *fp = fopen(USERINTERFACE_ISPLOGO, "wb");
			if (fp == NULL) {
				bbfdm_set_fault_message(ctx, "Failed to open ISPLogo[%s]", USERINTERFACE_ISPLOGO);
				return FAULT_9002;
			}
			fwrite(decoded_buff, 1, decoded_size, fp);
			fclose(fp);
			break;
	}
	return 0;
}

static int get_isp_param_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *param = NULL;

	param = DM_STRRCHR(refparam, '.');
	if (param)
		param = param + 1;

	dmuci_get_option_value_string("userinterface", "global", param, value);
	return 0;
}

static int set_isp_param_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	int max_length = 0;
	char *param = NULL;

	param = DM_STRRCHR(refparam, '.');
	if (param)
		param = param + 1;

	if (DM_STRCMP(param, "ISPName") == 0)
		max_length = 64;
	else if(DM_STRCMP(param, "ISPHelpDesk") == 0)
		max_length = 32;
	else if(DM_STRCMP(param, "ISPHomePage") == 0 || DM_STRCMP(param, "ISPHelpPage") == 0)
		max_length = 2048;
	else
		return FAULT_9007;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, 0, max_length, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value("userinterface", "global", param, value);
			break;
	}
	return 0;
}

#ifdef USERINTERFACE_VENDOR_EXTENSIONS
static int get_first_login(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *first_login = NULL;

	dmuci_get_option_value_string("userinterface", "global", "FirstLogin", &first_login);
	if (DM_STRLEN(first_login)) {
		*value = dmuci_string_to_boolean(first_login) ? dmstrdup("1") : dmstrdup("0");
	} else {
		*value = dmstrdup("0");
	}
	return 0;
}

static int set_first_login(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value("userinterface", "global", "FirstLogin", value);
			break;
	}
	return 0;
}
#endif

/**********************************************************************************************************************************
*                                            OBJ & LEAF DEFINITION
***********************************************************************************************************************************/
DMLEAF tHTTPSessionParams[] = {
{"IPAddress", &DMREAD, DMT_STRING, get_http_session_ip, NULL, BBFDM_BOTH},
{"Port", &DMREAD, DMT_UNINT, get_http_session_port, NULL, BBFDM_BOTH},
{0}
};

DMOBJ tHTTPSessionObj[] = {
{"Session", &DMREAD, NULL, NULL, NULL, browseHTTPSession, NULL, NULL, NULL, tHTTPSessionParams, NULL, BBFDM_BOTH, NULL},
{0}
};

/* *** Device.UserInterface.HTTPAccess. *** */
DMLEAF tHTTPAccessParams[] = {
{"Enable", &DMWRITE, DMT_BOOL, get_http_access_enable, set_http_access_enable, BBFDM_BOTH},
{"Alias", &DMWRITE, DMT_STRING, get_http_access_alias, set_http_access_alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Status", &DMREAD, DMT_STRING, get_http_access_status, NULL, BBFDM_BOTH},
{"AccessType", &DMWRITE, DMT_STRING, get_http_access_type, set_http_access_type, BBFDM_BOTH},
{"AllowedRoles", &DMWRITE, DMT_STRING, get_http_access_roles, set_http_access_roles, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"Interface", &DMWRITE, DMT_STRING, get_http_access_interface, set_http_access_interface, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"Port", &DMWRITE, DMT_UNINT, get_http_access_port, set_http_access_port, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Protocol", &DMREAD, DMT_STRING, get_http_access_protocol, NULL, BBFDM_BOTH},
{"AllowedHosts", &DMWRITE, DMT_STRING, get_http_access_hosts, set_http_access_hosts, BBFDM_BOTH},
{"AllowedPathPrefixes", &DMWRITE, DMT_STRING, get_http_access_path, set_http_access_path, BBFDM_BOTH},
{"ActivationDate", &DMREAD, DMT_TIME, get_http_access_activationdate, NULL, BBFDM_BOTH},
{"SessionNumberOfEntries", &DMREAD, DMT_UNINT, get_http_access_session_num, NULL, BBFDM_BOTH},
{0}
};

DMLEAF tUIParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Enable", &DMWRITE, DMT_BOOL, get_ui_enable, set_ui_enable, BBFDM_BOTH},
{"HTTPAccessSupportedProtocols", &DMREAD, DMT_STRING, get_http_access_protocols, NULL, BBFDM_BOTH},
{"HTTPAccessNumberOfEntries", &DMREAD, DMT_UNINT, get_http_entries_count, NULL, BBFDM_BOTH},
{"ISPLogo", &DMWRITE, DMT_BASE64, get_isp_logo, set_isp_logo, BBFDM_BOTH},
{"ISPName", &DMWRITE, DMT_STRING, get_isp_param_value, set_isp_param_value, BBFDM_BOTH},
{"ISPHomePage", &DMWRITE, DMT_STRING, get_isp_param_value, set_isp_param_value, BBFDM_BOTH},
{"ISPHelpPage", &DMWRITE, DMT_STRING, get_isp_param_value, set_isp_param_value, BBFDM_BOTH},
{"ISPHelpDesk", &DMWRITE, DMT_STRING, get_isp_param_value, set_isp_param_value, BBFDM_BOTH},
#ifdef USERINTERFACE_VENDOR_EXTENSIONS
{BBF_VENDOR_PREFIX"FirstLogin", &DMWRITE, DMT_BOOL, get_first_login, set_first_login, BBFDM_USP},
#endif
{0}
};

DMOBJ tUIObjs[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"HTTPAccess", &DMWRITE, addHTTPAccess, delHTTPAccess, NULL, browseHTTPAccess, NULL, NULL, tHTTPSessionObj, tHTTPAccessParams, NULL, BBFDM_BOTH, NULL},
{0}
};

/*** Device. ***/
DMOBJ tDeviceObjs[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys*/
{"UserInterface", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, tUIObjs, tUIParams, NULL, BBFDM_BOTH, NULL},
{0}
};

/* ********** DynamicObj ********** */
DM_MAP_OBJ tDynamicObj[] = {
/* parentobj, nextobject, parameter, init_module, clean_module, uci_sync_handler */
{"Device.", tDeviceObjs, NULL, userinterface_init, NULL, userinterface_sync},
{0}
};
