/*
 * Copyright (C) 2026 iopsys Software Solutions AB
 *
 * See LICENSE file for license related information.
 *
 * Device.ConnectionMonitoring. (TR-181 2.21) bbfdm dm-service plugin.
 *
 * The data model (this file) owns configuration in the "connmon" UCI package
 * (with a dmmap v2 overlay for stable instance numbers) and reads live status
 * from the connmon engine via the /var/state/connmon varstate UCI. It contains
 * no probe/action logic —
 * that lives in the connmon engine and its per-method / per-action scripts.
 */

#include <libbbfdm-api/dmcommon.h>

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

/* Live configuration lives in the "connmon" UCI package (/etc/config/connmon).
 *
 * The dmmap v2 overlay package MUST be named after the object mounted under
 * "Device." — bbfdm derives node->current_object_file from the object name
 * ("ConnectionMonitoring"), and the AddObject machinery + generic browse both
 * key the dmmap file/type off it (/etc/bbfdm/dmmap/ConnectionMonitoring). */
#define CONNMON_UCI_PACKAGE   "connmon"
#define CONNMON_DMMAP_PACKAGE "ConnectionMonitoring"
#define CONNMON_DMMAP_FILE    "/etc/bbfdm/dmmap/ConnectionMonitoring"

/* dmmap section types (== create_dmmap_obj obj_name; also the option name a
 * nested Action row stores to point back at its parent Connection). */
#define CONNMON_OBJ_CONNECTION "Connection"
#define CONNMON_OBJ_ACTION     "Action"

/* Spec enumerations. */
static char *Method_enum[] = {"ARP", "ND", "Ping", "BFDEcho", "DNS", NULL};
static char *Action_enum[] = {"None", "RestartDHCP", "RestartInterface", "Reboot", NULL};

static DMOBJ tConnectionMonitoringObj[];
static DMLEAF tConnectionMonitoringParams[];
static DMOBJ tConnectionObj[];
static DMLEAF tConnectionParams[];
static DMLEAF tConnectionStatsParams[];
static DMLEAF tConnectionActionParams[];

/**********************************************************************************************************************************
 *                                            dmmap v2 sync + live status
 **********************************************************************************************************************************/
/*
 * The overlay mirrors the live config: one dmmap "Connection" row per
 * `config connection` section, and one nested dmmap "Action" row per
 * `config action` section, scoped to its parent connection instance.
 * Instance numbers are persisted in __instance__ so they are stable across
 * reboots and external config edits.
 */
static void sync_connection_dmmap(struct dmctx *dmctx)
{
	struct uci_section *conn = NULL, *s = NULL, *stmp = NULL;

	/* Create/refresh a Connection row (level 0) for each config section, then
	 * the nested Action rows (level 1) belonging to it. create_dmmap_obj sets
	 * dmctx->obj_buf[0]/inst_buf[0] to this connection so the level-1 call
	 * stamps the parent link on each Action row. */
	uci_foreach_sections(CONNMON_UCI_PACKAGE, "connection", conn) {
		char *conn_inst = NULL;
		struct uci_section *act = NULL;

		create_dmmap_obj(dmctx, 0, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_CONNECTION, conn, &conn_inst);

		uci_foreach_option_eq(CONNMON_UCI_PACKAGE, "action", "dm_parent", section_name(conn), act) {
			char *act_inst = NULL;

			create_dmmap_obj(dmctx, 1, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_ACTION, act, &act_inst);
		}
	}

	/* Prune Action rows whose backing config section is gone. */
	uci_path_foreach_sections_safe(bbfdm, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_ACTION, stmp, s) {
		char *sec_name = NULL;

		dmuci_get_value_by_section_string(s, "__section_name__", &sec_name);
		if (get_config_section_from_dmmap_section_name(sec_name) == NULL)
			dmuci_delete_by_section(s, NULL, NULL);
	}

	/* Prune Connection rows whose backing config section is gone. */
	uci_path_foreach_sections_safe(bbfdm, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_CONNECTION, stmp, s) {
		char *sec_name = NULL;

		dmuci_get_value_by_section_string(s, "__section_name__", &sec_name);
		if (get_config_section_from_dmmap_section_name(sec_name) == NULL)
			dmuci_delete_by_section(s, NULL, NULL);
	}
}

/* Rebuild the dmmap overlay (Connection + nested Action instances) from the
 * live connmon config, pruning rows whose backing config section disappeared.
 * Runs in its own bbf context and commits. Called from connmon_init() at load,
 * connmon_sync() on external UCI change, and (implicitly) so a new instance is
 * immediately resolvable. */
static void connmon_dmmap_rebuild(void)
{
	struct dmctx bbf_ctx = {0};

	bbf_ctx_init(&bbf_ctx, NULL);
	sync_connection_dmmap(&bbf_ctx);
	dmuci_commit_package_bbfdm(CONNMON_DMMAP_PACKAGE);
	bbf_ctx_clean(&bbf_ctx);
}

/* Fetch a per-connection live field from the published state in the "connmon"
 * varstate package (/var/state/connmon), written by the connmon engine. The
 * varstate section name is the connection's config section name; `option` is
 * one of status/monitoring_status/attempts/total_attempts/total_actions.
 * Returns a dmstrdup'd value, or `fallback` when the daemon has not (yet)
 * written state. Never returns NULL. */
static char *connmon_get_conn_field(const char *sec_name, const char *option, const char *fallback)
{
	char *v = NULL;

	if (DM_STRLEN(sec_name))
		dmuci_get_option_value_string_varstate(CONNMON_UCI_PACKAGE, sec_name, option, &v);

	return dmstrdup(DM_STRLEN(v) ? v : (fallback ? fallback : ""));
}

/* Fetch the global monitor status ("Disabled"|"Enabled"|"Error") from the
 * varstate "globals" section, falling back to `fallback`. */
static char *connmon_get_global_status(const char *fallback)
{
	char *v = NULL;

	dmuci_get_option_value_string_varstate(CONNMON_UCI_PACKAGE, "globals", "status", &v);
	return dmstrdup(DM_STRLEN(v) ? v : (fallback ? fallback : ""));
}

/**********************************************************************************************************************************
 *                                            BROWSE / ADD / DELETE — Action
 **********************************************************************************************************************************/
/*
 * Nested table: the dmmap "Action" rows carry a "Connection" option holding
 * their parent Connection instance (stamped by create_dmmap_obj at level 1);
 * browse selects the rows matching the parent instance passed via prev_data.
 */
static int browseActionInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct dm_data curr_data = {0};
	char *conn_inst = NULL, *inst = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)prev_data)->dmmap_section, "__instance__", &conn_inst);

	uci_path_foreach_option_eq(bbfdm, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_ACTION, CONNMON_OBJ_CONNECTION, conn_inst, 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;

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

static int get_Connection_ActionNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, browseActionInst);

	dmasprintf(value, "%d", cnt);
	return 0;
}

static int add_Action(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct dm_data *curr_data = (struct dm_data *)ctx->addobj_instance;
	struct uci_section *conn = ((struct dm_data *)data)->config_section;
	const char *cname = conn ? section_name(conn) : "";
	struct uci_section *s = NULL;
	char sec_name[64] = {0};

	snprintf(sec_name, sizeof(sec_name), "act_%s_%s", cname, *instance);

	dmuci_add_section(CONNMON_UCI_PACKAGE, "action", &s);
	dmuci_rename_section_by_section(s, sec_name);
	curr_data->config_section = s;

	/* Parent link (datamodel parent reference) + TR-181 defaults. */
	dmuci_set_value_by_section(s, "dm_parent", cname);
	dmuci_set_value_by_section(s, "enable", "0");
	dmuci_set_value_by_section(s, "attempt_count", "5");
	dmuci_set_value_by_section(s, "action", "None");
	return 0;
}

static int delete_Action(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	struct uci_section *s = NULL, *tmp = NULL;

	switch (del_action) {
	case DEL_INST:
		if (((struct dm_data *)data)->config_section)
			dmuci_delete_by_section(((struct dm_data *)data)->config_section, NULL, NULL);
		if (((struct dm_data *)data)->dmmap_section)
			dmuci_delete_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);
		break;
	case DEL_ALL:
		uci_foreach_sections_safe(CONNMON_UCI_PACKAGE, "action", tmp, s) {
			dmuci_delete_by_section(s, NULL, NULL);
		}
		uci_path_foreach_sections_safe(bbfdm, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_ACTION, tmp, s) {
			dmuci_delete_by_section(s, NULL, NULL);
		}
		break;
	}
	return 0;
}

/*************************************************************
 * PARAM HANDLERS — Connection.{i}.Action.{i}.
 *************************************************************/
static struct uci_section *act_cfg(void *data)
{
	return ((struct dm_data *)data)->config_section;
}

static int get_Action_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_Action_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:
		return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
	}
	return 0;
}

static int get_Action_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *v = dmuci_get_value_by_section_fallback_def(act_cfg(data), "enable", "0");

	*value = dmstrdup(DM_LSTRCMP(v, "0") == 0 ? "false" : "true");
	return 0;
}

static int set_Action_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_boolean(ctx, value))
			return FAULT_9007;
		break;
	case VALUESET:
		string_to_bool(value, &b);
		dmuci_set_value_by_section(act_cfg(data), "enable", b ? "1" : "0");
		break;
	}
	return 0;
}

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

	dmuci_get_value_by_section_string(act_cfg(data), "attempt_count", &v);
	*value = dmstrdup(DM_STRLEN(v) ? v : "5");
	return 0;
}

static int set_Action_AttemptCount(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1", "100"}}, 1))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(act_cfg(data), "attempt_count", value);
		break;
	}
	return 0;
}

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

	dmuci_get_value_by_section_string(act_cfg(data), "action", &v);
	*value = dmstrdup(DM_STRLEN(v) ? v : "None");
	return 0;
}

static int set_Action_Action(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string(ctx, value, -1, -1, Action_enum, NULL))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(act_cfg(data), "action", value);
		break;
	}
	return 0;
}

static int get_Action_Reference(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *linker = NULL;
	char buf[512] = {0};

	dmuci_get_value_by_section_string(act_cfg(data), "reference", &linker);
	if (DM_STRLEN(linker) == 0) {
		*value = dmstrdup("");
		return 0;
	}

	bbfdm_get_references(ctx, MATCH_FIRST, "Device.IP.Interface.", "Name", linker, buf, sizeof(buf));
	bbfdm_get_references(ctx, MATCH_FIRST, "Device.DHCPv4.Client.", "Alias", linker, buf, sizeof(buf));
	*value = dmstrdup(buf);
	return 0;
}

static int set_Action_Reference(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *allowed_objects[] = {"Device.IP.Interface.", "Device.DHCPv4.Client.", 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_STRLEN(value) && dm_validate_allowed_objects(ctx, &reference, allowed_objects))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(act_cfg(data), "reference", reference.value ? reference.value : "");
		break;
	}
	return 0;
}

/**********************************************************************************************************************************
 *                                            BROWSE / ADD / DELETE — Connection
 **********************************************************************************************************************************/
static int browseConnectionInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct dm_data curr_data = {0};
	char *inst = NULL;

	uci_path_foreach_sections(bbfdm, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_CONNECTION, 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);

		/* Skip a row whose backing config section was removed out-of-band
		 * (before the next sync prunes it). */
		if (curr_data.config_section == NULL)
			continue;

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

static int get_ConnectionMonitoring_ConnectionNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, browseConnectionInst);

	dmasprintf(value, "%d", cnt);
	return 0;
}

static int add_Connection(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct dm_data *curr_data = (struct dm_data *)ctx->addobj_instance;
	struct uci_section *s = NULL;
	char sec_name[32] = {0};

	snprintf(sec_name, sizeof(sec_name), "conn_%s", *instance);

	dmuci_add_section(CONNMON_UCI_PACKAGE, "connection", &s);
	dmuci_rename_section_by_section(s, sec_name);
	curr_data->config_section = s;

	/* TR-181 defaults. */
	dmuci_set_value_by_section(s, "enable", "0");
	dmuci_set_value_by_section(s, "method", "Ping");
	dmuci_set_value_by_section(s, "ip_version", "-1");
	dmuci_set_value_by_section(s, "monitor_interval", "60");
	dmuci_set_value_by_section(s, "fail_interval", "15");
	dmuci_set_value_by_section(s, "max_response_timeout", "1");
	dmuci_set_value_by_section(s, "dscp_mark", "48");
	dmuci_set_value_by_section(s, "ethernet_priority", "-1");
	return 0;
}

/* Remove the config `action` sections (and their dmmap rows) that belong to
 * the connection named `conn_name`. */
static void delete_connection_actions(const char *conn_name)
{
	struct uci_section *s = NULL, *tmp = NULL;

	if (DM_STRLEN(conn_name) == 0)
		return;

	uci_path_foreach_sections_safe(bbfdm, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_ACTION, tmp, s) {
		char *sec_name = NULL;
		struct uci_section *cfg;

		dmuci_get_value_by_section_string(s, "__section_name__", &sec_name);
		cfg = get_config_section_from_dmmap_section_name(sec_name);
		if (cfg == NULL)
			continue;

		char *parent = NULL;
		dmuci_get_value_by_section_string(cfg, "dm_parent", &parent);
		if (DM_STRCMP(parent, conn_name) == 0)
			dmuci_delete_by_section(s, NULL, NULL);
	}

	uci_foreach_option_eq_safe(CONNMON_UCI_PACKAGE, "action", "dm_parent", conn_name, tmp, s) {
		dmuci_delete_by_section(s, NULL, NULL);
	}
}

static int delete_Connection(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	struct uci_section *s = NULL, *tmp = NULL;

	switch (del_action) {
	case DEL_INST:
		s = ((struct dm_data *)data)->config_section;
		if (s)
			delete_connection_actions(section_name(s));
		if (s)
			dmuci_delete_by_section(s, NULL, NULL);
		if (((struct dm_data *)data)->dmmap_section)
			dmuci_delete_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);
		break;
	case DEL_ALL:
		uci_foreach_sections_safe(CONNMON_UCI_PACKAGE, "action", tmp, s) {
			dmuci_delete_by_section(s, NULL, NULL);
		}
		uci_foreach_sections_safe(CONNMON_UCI_PACKAGE, "connection", tmp, s) {
			dmuci_delete_by_section(s, NULL, NULL);
		}
		uci_path_foreach_sections_safe(bbfdm, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_ACTION, tmp, s) {
			dmuci_delete_by_section(s, NULL, NULL);
		}
		uci_path_foreach_sections_safe(bbfdm, CONNMON_DMMAP_PACKAGE, CONNMON_OBJ_CONNECTION, tmp, s) {
			dmuci_delete_by_section(s, NULL, NULL);
		}
		break;
	}
	return 0;
}

/*************************************************************
 * Small helpers
 *************************************************************/
static struct uci_section *conn_cfg(void *data)
{
	return ((struct dm_data *)data)->config_section;
}

static const char *conn_name(void *data)
{
	struct uci_section *s = conn_cfg(data);

	return s ? section_name(s) : "";
}

/* Get a config option with a spec default when absent/empty. */
static int get_opt_def(void *data, const char *opt, const char *def, char **value)
{
	char *v = NULL;

	dmuci_get_value_by_section_string(conn_cfg(data), opt, &v);
	*value = dmstrdup(DM_STRLEN(v) ? v : def);
	return 0;
}

/*************************************************************
 * PARAM HANDLERS — Connection.{i}.
 *************************************************************/
static int get_Connection_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *v = dmuci_get_value_by_section_fallback_def(conn_cfg(data), "enable", "0");

	*value = dmstrdup(DM_LSTRCMP(v, "0") == 0 ? "false" : "true");
	return 0;
}

static int set_Connection_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_boolean(ctx, value))
			return FAULT_9007;
		break;
	case VALUESET:
		string_to_bool(value, &b);
		dmuci_set_value_by_section(conn_cfg(data), "enable", b ? "1" : "0");
		break;
	}
	return 0;
}

static int get_Connection_Status(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *en = NULL;
	const char *fallback;

	dmuci_get_value_by_section_string(conn_cfg(data), "enable", &en);
	fallback = (DM_STRLEN(en) && DM_LSTRCMP(en, "0") == 0) ? "Disabled" : "Enabled";
	*value = connmon_get_conn_field(conn_name(data), "status", fallback);
	return 0;
}

static int get_Connection_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_Connection_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:
		return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
	}
	return 0;
}

static int get_Connection_Interface(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *linker = NULL;
	char buf[512] = {0};

	dmuci_get_value_by_section_string(conn_cfg(data), "interface", &linker);
	if (DM_STRLEN(linker) == 0) {
		*value = dmstrdup("");
		return 0;
	}

	bbfdm_get_references(ctx, MATCH_FIRST, "Device.IP.Interface.", "Name", linker, buf, sizeof(buf));
	*value = dmstrdup(buf);
	return 0;
}

static int set_Connection_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;
		/* Empty clears the reference (spec: set to "" when target deleted). */
		if (DM_STRLEN(value) && dm_validate_allowed_objects(ctx, &reference, allowed_objects))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(conn_cfg(data), "interface", reference.value ? reference.value : "");
		break;
	}
	return 0;
}

static int get_Connection_MonitorInterval(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_opt_def(data, "monitor_interval", "60", value);
}

static int set_Connection_MonitorInterval(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1", "60"}}, 1))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(conn_cfg(data), "monitor_interval", value);
		break;
	}
	return 0;
}

static int get_Connection_FailInterval(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_opt_def(data, "fail_interval", "15", value);
}

static int set_Connection_FailInterval(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1", "60"}}, 1))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(conn_cfg(data), "fail_interval", value);
		break;
	}
	return 0;
}

static int get_Connection_MaxResponseTimeout(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_opt_def(data, "max_response_timeout", "1", value);
}

static int set_Connection_MaxResponseTimeout(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1", "60"}}, 1))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(conn_cfg(data), "max_response_timeout", value);
		break;
	}
	return 0;
}

static int get_Connection_NumberOfAttempts(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = connmon_get_conn_field(conn_name(data), "attempts", "0");
	return 0;
}

static int get_Connection_Method(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_opt_def(data, "method", "Ping", value);
}

static int set_Connection_Method(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string(ctx, value, -1, -1, Method_enum, NULL))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(conn_cfg(data), "method", value);
		break;
	}
	return 0;
}

static int get_Connection_IPVersion(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_opt_def(data, "ip_version", "-1", value);
}

static int set_Connection_IPVersion(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		/* Discrete set {4, 6, -1}. */
		if (DM_STRCMP(value, "4") != 0 && DM_STRCMP(value, "6") != 0 && DM_STRCMP(value, "-1") != 0)
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(conn_cfg(data), "ip_version", value);
		break;
	}
	return 0;
}

static int get_Connection_DestAddress(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_opt_def(data, "dest_address", "", value);
}

static int set_Connection_DestAddress(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string(ctx, value, -1, 256, NULL, NULL))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(conn_cfg(data), "dest_address", value);
		break;
	}
	return 0;
}

static int get_Connection_DSCPMark(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_opt_def(data, "dscp_mark", "48", value);
}

static int set_Connection_DSCPMark(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"0", "63"}}, 1))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(conn_cfg(data), "dscp_mark", value);
		break;
	}
	return 0;
}

static int get_Connection_EthernetPriority(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_opt_def(data, "ethernet_priority", "-1", value);
}

static int set_Connection_EthernetPriority(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_int(ctx, value, RANGE_ARGS{{"-1", "7"}}, 1))
			return FAULT_9007;
		break;
	case VALUESET:
		dmuci_set_value_by_section(conn_cfg(data), "ethernet_priority", value);
		break;
	}
	return 0;
}

static int get_Connection_MonitoringStatus(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = connmon_get_conn_field(conn_name(data), "monitoring_status", "Up");
	return 0;
}

/*************************************************************
 * PARAM HANDLERS — Connection.{i}.Stats.
 *************************************************************/
static int get_ConnectionStats_TotalNumberOfAttempts(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = connmon_get_conn_field(conn_name(data), "total_attempts", "0");
	return 0;
}

static int get_ConnectionStats_TotalTriggeredActions(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = connmon_get_conn_field(conn_name(data), "total_actions", "0");
	return 0;
}

/**********************************************************************************************************************************
 *                                            ConnectionMonitoring-level params + plugin manifest
 **********************************************************************************************************************************/
static int get_ConnectionMonitoring_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *v = NULL;

	/* Absent or "0" -> disabled. */
	dmuci_get_option_value_string(CONNMON_UCI_PACKAGE, "globals", "enable", &v);
	*value = (DM_STRLEN(v) && DM_LSTRCMP(v, "0") != 0) ? dmstrdup("true") : dmstrdup("false");
	return 0;
}

static int set_ConnectionMonitoring_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_boolean(ctx, value))
			return FAULT_9007;
		break;
	case VALUESET:
		string_to_bool(value, &b);
		dmuci_set_value(CONNMON_UCI_PACKAGE, "globals", "enable", b ? "1" : "0");
		break;
	}
	return 0;
}

static int get_ConnectionMonitoring_Status(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *en = NULL;
	const char *fallback;

	dmuci_get_option_value_string(CONNMON_UCI_PACKAGE, "globals", "enable", &en);
	fallback = (DM_STRLEN(en) && DM_LSTRCMP(en, "0") != 0) ? "Enabled" : "Disabled";
	*value = connmon_get_global_status(fallback);
	return 0;
}

/* module hooks (DM_MAP_OBJ init/clean/uci_sync) */
static int connmon_init(void *data)
{
	(void)data;

	/* The v2 AddObject path only creates a dmmap row when the dmmap file
	 * already exists (see mobj_add_object -> file_exists). With no connections
	 * configured the rebuild below commits nothing, so ensure the (possibly
	 * empty) package file exists first — a 0-byte file is a valid empty UCI
	 * package that create_dmmap_obj can then populate. */
	mkdir("/etc/bbfdm", 0755);
	mkdir("/etc/bbfdm/dmmap", 0755);
	if (!file_exists(CONNMON_DMMAP_FILE)) {
		int fd = open(CONNMON_DMMAP_FILE, O_WRONLY | O_CREAT, 0644);
		if (fd >= 0)
			close(fd);
	}

	/* Generate the dmmap overlay so browse/GET and cross-service reference
	 * resolution work from first load. */
	connmon_dmmap_rebuild();
	return 0;
}

static int connmon_clean(void *data)
{
	(void)data;
	return 0;
}

static int connmon_sync(void *data)
{
	(void)data;
	/* uci_sync_handler: the connmon config changed underneath us (external
	 * `uci set` or the daemon); rebuild the derived dmmap index to match. */
	connmon_dmmap_rebuild();
	return 0;
}

/**********************************************************************************************************************************
 *                                            OBJ & PARAM DEFINITION
 **********************************************************************************************************************************/
/* Device. -> ConnectionMonitoring. — mounted under the existing "Device."
 * node: this table's entry IS the ConnectionMonitoring object (its
 * children/params hang off it). */
static DMOBJ tDeviceConnectionMonitoringObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type */
{"ConnectionMonitoring", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, tConnectionMonitoringObj, tConnectionMonitoringParams, NULL, BBFDM_BOTH},
{0}
};

/* Device.ConnectionMonitoring. */
static DMOBJ tConnectionMonitoringObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type */
{"Connection", &DMWRITE, add_Connection, delete_Connection, NULL, browseConnectionInst, NULL, NULL, tConnectionObj, tConnectionParams, NULL, BBFDM_BOTH},
{0}
};

static DMLEAF tConnectionMonitoringParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type */
{"Enable", &DMWRITE, DMT_BOOL, get_ConnectionMonitoring_Enable, set_ConnectionMonitoring_Enable, BBFDM_BOTH},
{"Status", &DMREAD, DMT_STRING, get_ConnectionMonitoring_Status, NULL, BBFDM_BOTH},
{"ConnectionNumberOfEntries", &DMREAD, DMT_UNINT, get_ConnectionMonitoring_ConnectionNumberOfEntries, NULL, BBFDM_BOTH},
{0}
};

/* Device.ConnectionMonitoring.Connection.{i}. */
static DMOBJ tConnectionObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type */
{"Action", &DMWRITE, add_Action, delete_Action, NULL, browseActionInst, NULL, NULL, NULL, tConnectionActionParams, NULL, BBFDM_BOTH},
{"Stats", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, NULL, tConnectionStatsParams, NULL, BBFDM_BOTH},
{0}
};

static DMLEAF tConnectionParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, flags */
{"Enable", &DMWRITE, DMT_BOOL, get_Connection_Enable, set_Connection_Enable, BBFDM_BOTH},
{"Status", &DMREAD, DMT_STRING, get_Connection_Status, NULL, BBFDM_BOTH},
{"Alias", &DMWRITE, DMT_STRING, get_Connection_Alias, set_Connection_Alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Interface", &DMWRITE, DMT_STRING, get_Connection_Interface, set_Connection_Interface, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"MonitorInterval", &DMWRITE, DMT_UNINT, get_Connection_MonitorInterval, set_Connection_MonitorInterval, BBFDM_BOTH},
{"FailInterval", &DMWRITE, DMT_UNINT, get_Connection_FailInterval, set_Connection_FailInterval, BBFDM_BOTH},
{"MaxResponseTimeout", &DMWRITE, DMT_UNINT, get_Connection_MaxResponseTimeout, set_Connection_MaxResponseTimeout, BBFDM_BOTH},
{"NumberOfAttempts", &DMREAD, DMT_UNINT, get_Connection_NumberOfAttempts, NULL, BBFDM_BOTH},
{"Method", &DMWRITE, DMT_STRING, get_Connection_Method, set_Connection_Method, BBFDM_BOTH},
{"IPVersion", &DMWRITE, DMT_INT, get_Connection_IPVersion, set_Connection_IPVersion, BBFDM_BOTH},
{"DestAddress", &DMWRITE, DMT_STRING, get_Connection_DestAddress, set_Connection_DestAddress, BBFDM_BOTH},
{"DSCPMark", &DMWRITE, DMT_UNINT, get_Connection_DSCPMark, set_Connection_DSCPMark, BBFDM_BOTH},
{"EthernetPriority", &DMWRITE, DMT_INT, get_Connection_EthernetPriority, set_Connection_EthernetPriority, BBFDM_BOTH},
{"MonitoringStatus", &DMREAD, DMT_STRING, get_Connection_MonitoringStatus, NULL, BBFDM_BOTH},
{"ActionNumberOfEntries", &DMREAD, DMT_UNINT, get_Connection_ActionNumberOfEntries, NULL, BBFDM_BOTH},
{0}
};

/* Device.ConnectionMonitoring.Connection.{i}.Stats. */
static DMLEAF tConnectionStatsParams[] = {
{"TotalNumberOfAttempts", &DMREAD, DMT_UNLONG, get_ConnectionStats_TotalNumberOfAttempts, NULL, BBFDM_BOTH},
{"TotalTriggeredActions", &DMREAD, DMT_UNLONG, get_ConnectionStats_TotalTriggeredActions, NULL, BBFDM_BOTH},
{0}
};

/* Device.ConnectionMonitoring.Connection.{i}.Action.{i}. */
static DMLEAF tConnectionActionParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, flags */
{"Alias", &DMWRITE, DMT_STRING, get_Action_Alias, set_Action_Alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Enable", &DMWRITE, DMT_BOOL, get_Action_Enable, set_Action_Enable, BBFDM_BOTH},
{"AttemptCount", &DMWRITE, DMT_UNINT, get_Action_AttemptCount, set_Action_AttemptCount, BBFDM_BOTH},
{"Action", &DMWRITE, DMT_STRING, get_Action_Action, set_Action_Action, BBFDM_BOTH},
{"Reference", &DMWRITE, DMT_STRING, get_Action_Reference, set_Action_Reference, BBFDM_BOTH, DM_FLAG_REFERENCE},
{0}
};

/* Plugin manifest read by libbbfdm-api/legacy/plugin/dotso_plugin.c via dlsym. */
DM_MAP_OBJ tDynamicObj[] = {
/* path, root_obj, root_leaf, init_module, clean_module, uci_sync_handler */
{"Device.", tDeviceConnectionMonitoringObj, NULL, connmon_init, connmon_clean, connmon_sync},
{0}
};
