/**
 * @file cntlr_commands.c - mapcontroller commands.
 *
 * Copyright (C) 2024 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 */

#include "cntlr_commands.h"
#include "cntlr_commands_impl.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libubox/blob.h>
#include <libubox/utils.h>
#include <libubox/blobmsg.h>

#include "utils/debug.h"


int COMMAND(null_command)(void *controller, void *args, void *out)
{
	return 0;
}

#define DEFINE_ATTR(name)	static struct controller_command_attr cntlr_cmd_attr_ ## name[]


#define CNTLR_CMD(c, h)		{ .command = #c, .help = h, \
				  .handler = COMMAND(c), \
				  .num_attrs = ARRAY_SIZE(cntlr_cmd_attr_ ## c), \
				  .attrs = cntlr_cmd_attr_ ## c }

DEFINE_ATTR(null_command) = {
};

DEFINE_ATTR(help) = {
	[HELP_ATTR_COMMAND_NAME] = {
		.optional = 1,
		.pol = {
			.name = "command",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Show usage of command.",
	},
};

DEFINE_ATTR(log) = {
	[LOG_ATTR_FEATURE] = {
		.optional = 1,
		.pol = {
			.name = "feature",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Feature to log; +<feature> to add, -<feature> to remove feature from logging",
	},
	[LOG_ATTR_LEVEL] = {
		.optional = 1,
		.pol = {
			.name = "level",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Log level",
	},
};

DEFINE_ATTR(query_ap_caps) = {
	[AP_CAPS_ATTR_AGENT] = {
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which AP-Capability Query is to be sent",
	},
};

DEFINE_ATTR(query_sta_caps) = {
	[STA_CAPS_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which STA-Capability Query is to be sent",
	},
	[STA_CAPS_ATTR_STA] = {
		.optional = 0,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "STA's macaddress.", /* TODO: if not passed assume all STAs */
	},
	[STA_CAPS_ATTR_BSSID] = {
		.optional = 1,
		.pol = {
			.name = "bssid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "BSSID in Agent to which the STA is associated.",
	},
};

DEFINE_ATTR(query_bsta_caps) = {
	[BSTA_CAPS_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which BSTA Capability Query is to be sent",
	},
};

DEFINE_ATTR(query_ap_metrics) = {
	[AP_METRICS_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which AP Metrics Query is to be sent",
	},
	[AP_METRICS_ATTR_BSSIDS] = {
		.optional = 1,
		.pol = {
			.name = "bsslist",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "array of BSSIDs in Agent",
	},
	[AP_METRICS_ATTR_RADIOS] = {
		.optional = 1,
		.pol = {
			.name = "radiolist",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "array of Radio-IDs (or Radio macaddress) in Agent",
	},
};

DEFINE_ATTR(query_sta_metrics) = {
	[STA_METRICS_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which STA Metrics Query is to be sent",
	},
	[STA_METRICS_ATTR_STA] = {
		.optional = 0,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "macaddress of STA",
	},
};

DEFINE_ATTR(query_unassoc_sta_metrics) = {
	[UNASSOC_STA_METRICS_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Unassociated STA Metrics Query is to be sent",
	},
	[UNASSOC_STA_METRICS_ATTR_OPCLASS] = {
		.optional = 0,
		.pol = {
			.name = "opclass",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Opclass where unassociated sta metrics is to be measured",
	},
	[UNASSOC_STA_METRICS_ATTR_CHANNEL] = {
		.optional = 1,
		.pol = {
			.name = "channel",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Channel in opclass where unassociated sta metrics is to be measured",
	},
	[UNASSOC_STA_METRICS_ATTR_STALIST] = {
		.optional = 1,
		.pol = {
			.name = "stalist",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "Array of unassociated STA macaddresses to be measured",
	},
};

DEFINE_ATTR(query_channel_pref) = {
	[CHANNEL_PREF_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Channel Preference Query is to be sent",
	},
};

DEFINE_ATTR(send_channel_sel) = {
	[CHANNEL_SEL_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Channel Selection Request is to be sent",
	},
	[CHANNEL_SEL_ATTR_RADIO] = {
		.optional = 0,
		.pol = {
			.name = "radioid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Radio-ID or radio macaddress in Agent for which the CAC request is made",
	},
	[CHANNEL_SEL_ATTR_CHANNEL] = {
		.optional = 0,
		.pol = {
			.name = "channel",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Control channel Agent should switch",
	},
	[CHANNEL_SEL_ATTR_BANDWIDTH] = {
		.optional = 1,
		.pol = {
			.name = "bandwidth",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Bandwidth Agent should switch",
	},
};

DEFINE_ATTR(send_channel_pref) = {
	[CHANNEL_PREF_REQ_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Channel Preference Request is to be sent",
	},
	[CHANNEL_PREF_REQ_ATTR_RADIO] = {
		.optional = 1,
		.pol = {
			.name = "radioid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Radio-ID or radio MAC address in Agent",
	},
	[CHANNEL_PREF_REQ_ATTR_BAND] = {
		.optional = 1,
		.pol = {
			.name = "band",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "radio frequency band used to identify the target radio. Valid values are 2, 5 or 6 for 2.4, 5 or 6 GHz respectively",
	},
	[CHANNEL_PREF_REQ_ATTR_OPCLASS] = {
		.optional = 1,
		.pol = {
			.name = "opclass",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "Array of operating class IDs",
	},
	[CHANNEL_PREF_REQ_ATTR_CHANNEL] = {
		.optional = 1,
		.pol = {
			.name = "channel",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "Array of arrays, each containing channel numbers for a given opclass",
	},
	[CHANNEL_PREF_REQ_ATTR_PREF] = {
		.optional = 0,
		.pol = {
			.name = "pref",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "Array of preference values, one per opclass",
	},
};

DEFINE_ATTR(trigger_channel_clearing) = {
	[CHANNEL_CLEARING_ATTR_AGENT] = {
		.optional = 1,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which DFS Channel clearing CAC request is to be sent",
	},
};

DEFINE_ATTR(trigger_acs) = {
	[CHANNEL_ACS_ATTR_AGENT] = {
		.optional = 1,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent for channel recalc request",
	},
	[CHANNEL_ACS_ATTR_BAND] = {
		.optional = 1,
		.pol = {
			.name = "band",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Band for channel recalc request",
	},
	[CHANNEL_ACS_ATTR_SKIP_DFS] = {
		.optional = 1,
		.pol = {
			.name = "skip_dfs",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "skip DFS channels when recalc",
	},
	[CHANNEL_ACS_ATTR_OPCLASS] = {
		.optional = 1,
		.pol = {
			.name = "opclass",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "opclass for channel recalc request",
	},
	[CHANNEL_ACS_ATTR_BANDWIDTH] = {
		.optional = 1,
		.pol = {
			.name = "bandwidth",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "bandwidth for channel recalc request",
	},
	[CHANNEL_ACS_ATTR_PREVENT_CAC] = {
		.optional = 1,
		.pol = {
			.name = "prevent_cac",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "prevent DFS CAC when recalc",
	},
};

DEFINE_ATTR(query_beacon_metrics) = {
	[BCN_METRICS_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Beacon Metrics Query is to be sent",
	},
	[BCN_METRICS_ATTR_STA] = {
		.optional = 0,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "macaddress of STA",
	},
	[BCN_METRICS_ATTR_OPCLASS] = {
		.optional = 1,
		.pol = {
			.name = "opclass",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Opclass for Beacon metrics measurement",
	},
	[BCN_METRICS_ATTR_CHANNEL] = {
		.optional = 1,
		.pol = {
			.name = "channel",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Channel for Beacon metrics measurement",
	},
	[BCN_METRICS_ATTR_BSSID] = {
		.optional = 1,
		.pol = {
			.name = "bssid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "BSSID",
	},
	[BCN_METRICS_ATTR_REPORTING_DETAIL] = {
		.optional = 1,
		.pol = {
			.name = "reporting_detail",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Reporting detail",
	},
	[BCN_METRICS_ATTR_SSID] = {
		.optional = 1,
		.pol = {
			.name = "ssid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "SSID",
	},
	[BCN_METRICS_ATTR_CHAN_REPORT] = {
		.optional = 1,
		.pol = {
			.name = "channel_report",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "Channel report",
	},
	[BCN_METRICS_ATTR_ELEMENT_IDS] = {
		.optional = 1,
		.pol = {
			.name = "request_element",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "Requested element-ids",
	},
};

DEFINE_ATTR(steer) = {
	[STEER_ATTR_AGENT] = {
		.optional = 1,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which the STA is connected to (optional; auto-determined from 'sta' if not specified)",
	},
	[STEER_ATTR_STA] = {
		.optional = 0,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "macaddress of STA to be steered",
	},
	[STEER_ATTR_TARGET_AGENT] = {
		.optional = 1,
		.pol = {
			.name = "target_agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "target Agent's AL-address (required if 'target_bssid' is not specified; used with 'band' to auto-determine target_bssid)",
	},
	[STEER_ATTR_TARGET_BAND] = {
		.optional = 1,
		.pol = {
			.name = "target_band",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "target band (2, 5, or 6); used with 'target_agent' to auto-determine target_bssid when 'target_bssid' is not specified",
	},
	[STEER_ATTR_TARGET_BSSID] = {
		.optional = 1,
		.pol = {
			.name = "target_bssid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "target BSSID (required if 'target_agent'+'band' are not specified)",
	},
	[STEER_ATTR_FROM_BSSID] = {
		.optional = 1,
		.pol = {
			.name = "src_bssid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "source BSSID (optional; auto-determined from 'sta' if not specified)",
	},
	[STEER_ATTR_BTM_ABRIDGED] = {
		.optional = 1,
		.pol = {
			.name = "abridged",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "abridged bit in Bssinfo of BTM Request",
	},
	[STEER_ATTR_DISASSOC_TIMEOUT] = {
		.optional = 1,
		.pol = {
			.name = "disassoc_tmo",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "maps to Diassoc Immenent field and Disassoc Timer in TUs. When not set, Controller does not set the Disassoc Immenent field. Otherwise, it holds the Disassoc timer value in TUs",
	},
	[STEER_ATTR_MBO_REASON] = {
		.optional = 1,
		.pol = {
			.name = "mbo_reason",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "MBO Transition Reason Code to be sent when the STA is Agile Multiband capable",
	},
};

DEFINE_ATTR(steer_op) = {
	[STEER_OP_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Steer Request is to be sent",
	},
	[STEER_OP_ATTR_STA] = {
		.optional = 1,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "macaddress of STA to be steered; if not provided, assume all STAs in Agent",
	},
	[STEER_OP_ATTR_FROM_BSSID] = {
		.optional = 1,
		.pol = {
			.name = "src_bssid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "source BSSID",
	},
	[STEER_OP_ATTR_BTM_ABRIDGED] = {
		.optional = 1,
		.pol = {
			.name = "abridged",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "abridged bit in Bssinfo field of BTM Request",
	},
	[STEER_OP_ATTR_WINDOW] = {
		.optional = 0,
		.pol = {
			.name = "window",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "steer opportunity time window in seconds",
	},

	[STEER_OP_ATTR_DISASSOC_TIMEOUT] = {
		.optional = 1,
		.pol = {
			.name = "disassoc_tmo",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "maps to Diassoc Immenent field and Disassoc Timer in TUs. When not set, Controller does not set the Disassoc Immenent field. Otherwise, it holds the Disassoc timer value in TUs",
	},
	[STEER_OP_ATTR_MBO_REASON] = {
		.optional = 1,
		.pol = {
			.name = "mbo_reason",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "MBO Transition Reason Code to be sent if STA is Agile Multiband capable",
	},
};

DEFINE_ATTR(steer_backhaul) = {
	[BSTA_STEER_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which BSTA Steer Request is to be sent",
	},
	[BSTA_STEER_ATTR_TARGET_AGENT] = {
		.optional = 1,
		.pol = {
			.name = "target_agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "target Agent node",
	},
	[BSTA_STEER_ATTR_TARGET_BAND] = {
		.optional = 1,
		.pol = {
			.name = "target_band",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "radio band in the target Agent. Valid values: 2, 5, 6",
	},
	[BSTA_STEER_ATTR_TARGET_BSSID] = {
		.optional = 1,
		.pol = {
			.name = "target_bssid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "target BSSID",
	},
	[BSTA_STEER_ATTR_BSTA] = {
		.optional = 1,
		.pol = {
			.name = "bsta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "bSTA macaddress to be steered",
	},
	[BSTA_STEER_ATTR_TIMEOUT] = {
		.optional = 1,
		.pol = {
			.name = "timeout",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "timeout in milliseconds for the backhaul steering to complete",
	},
};

DEFINE_ATTR(dump_assoc_control) = {
};

DEFINE_ATTR(assoc_control) = {
	[ASSOC_CONTROL_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Association Control Request is to be sent",
	},
	[ASSOC_CONTROL_ATTR_BSSID] = {
		.optional = 1,
		.pol = {
			.name = "bssid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "BSSID for which this assoc-control request applies",
	},
	[ASSOC_CONTROL_ATTR_MODE] = {
		.optional = 1,
		.pol = {
			.name = "mode",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Association Control mode; 0 = block, 1 = unblock, 2 = timed, 3 = indefinite",
	},
	[ASSOC_CONTROL_ATTR_VALIDITY_TIME] = {
		.optional = 1,
		.pol = {
			.name = "validity_int",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Association control validity period in seconds",
	},
	[ASSOC_CONTROL_ATTR_STA] = {
		.optional = 1,
		.pol = {
			.name = "stalist",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "Array of STA macaddress for which this request applies",
	},
};

DEFINE_ATTR(scan) = {
	[SCAN_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Scan Request is to be sent",
	},
	[SCAN_ATTR_BAND] = {
		.optional = 1,
		.pol = {
			.name = "band",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "radio frequency band to trigger scanning on. Valid values are 2, 5 or 6 for 2.4, 5 or 6 GHz respectively",
	},
	[SCAN_ATTR_RADIO] = {
		.optional = 1,
		.pol = {
			.name = "radio",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Radio-ID or radio macaddress in Agent",
	},
	[SCAN_ATTR_OPCLASS] = {
		.optional = 1,
		.pol = {
			.name = "opclass",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "array of opclasses to scan",
	},
	[SCAN_ATTR_CHANNEL] = {
		.optional = 1,
		.pol = {
			.name = "channel",
			.type = BLOBMSG_TYPE_ARRAY,
		},
		.help = "array of channels to scan",
	},
	[SCAN_ATTR_FRESH_SCAN] = {
		.optional = 1,
		.pol = {
			.name = "fresh_scan",
			.type = BLOBMSG_TYPE_BOOL,
		},
		.help = "whether to issue fresh scanning or not; default is 'true'",
	},
};

DEFINE_ATTR(scanresults) = {
	[SCANRESULTS_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent from which Scan Results is to be dumped",
	},
	[SCANRESULTS_ATTR_BAND] = {
		.optional = 1,
		.pol = {
			.name = "band",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "radio frequency band to dump scanresults for. Valid values are 2, 5 or 6 for 2.4, 5 or 6 GHz respectively",
	},
	[SCANRESULTS_ATTR_RADIO] = {
		.optional = 1,
		.pol = {
			.name = "radio",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Radio-ID or radio macaddress in Agent",
	},
};

DEFINE_ATTR(cac_start) = {
	[CAC_START_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which CAC Request is to be sent",
	},
	[CAC_START_ATTR_RADIO] = {
		.optional = 1,
		.pol = {
			.name = "radio",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Radio-ID or radio macaddress in Agent for which the CAC request is made",
	},
	[CAC_START_ATTR_OPCLASS] = {
		.optional = 1,
		.pol = {
			.name = "opclass",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Opclass number from Table-E4 in IEEE802.11 Std.",
	},
	[CAC_START_ATTR_CHANNEL] = {
		.optional = 1,
		.pol = {
			.name = "channel",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "channel",
	},
	[CAC_START_ATTR_METHOD] = {
		.optional = 1,
		.pol = {
			.name = "method",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "CAC method. One of { 0 = continuous-CAC, 1 = continuous-CAC with dedicated radio, 2 = MIMO dimension reduced, 3 = Time sliced }",
	},
	[CAC_START_ATTR_ACTION] = {
		.optional = 0,
		.pol = {
			.name = "action",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "CAC completion action. One of { 0 = remain on CAC'd channel and do In-Service Monitoring, 1 = return to previous channel }",
	},
};

DEFINE_ATTR(cac_stop) = {
	[CAC_TERMINATE_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which CAC Terminate is to be sent",
	},
	[CAC_TERMINATE_ATTR_RADIO] = {
		.optional = 1,
		.pol = {
			.name = "radio",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Radio-ID or radio macaddress in Agent for which the CAC terminate is applicable",
	},
	[CAC_TERMINATE_ATTR_OPCLASS] = {
		.optional = 1,
		.pol = {
			.name = "opclass",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Opclass number from Table-E4 in IEEE802.11 Std.",
	},
	[CAC_TERMINATE_ATTR_CHANNEL] = {
		.optional = 1,
		.pol = {
			.name = "channel",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "channel",
	},
};

DEFINE_ATTR(send_hld) = {
	[HLD_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Higher Layer Data is to be sent",
	},
	[HLD_ATTR_PROTOCOL] = {
		.optional = 0,
		.pol = {
			.name = "protocol",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Higher Layer Data protocol",
	},
	[HLD_ATTR_DATA] = {
		.optional = 1,
		.pol = {
			.name = "data",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Higher Layer Data payload in hex-string",
	},
};

DEFINE_ATTR(send_combined_metrics) = {
	[COMBINED_METRICS_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Combined Infrastructure Metrics is to be sent",
	},
	[COMBINED_METRICS_ATTR_BSSID] = {
		.optional = 0,	/* TODO: make it optional */
		.pol = {
			.name = "bssid",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "BSSID",
	},
};

DEFINE_ATTR(send_topology_query) = {
	[TOPOLOGY_QUERY_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which Topology Query is to be sent",
	},
};

DEFINE_ATTR(dump_steer_summary) = {
	[STEER_SUMMARY_ATTR_STA] = {
		.optional = 1,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "dump STA steering summary",
	},
};

DEFINE_ATTR(dump_steer_history) = {
	[STEER_HISTORY_ATTR_STA] = {
		.optional = 1,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "dump STA steering history",
	},
};

DEFINE_ATTR(dump_unassoc_sta_metrics) = {
	[UNASSOC_STA_METRICS_DUMP_ATTR_STA] = {
		.optional = 1,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "dump unassociated STA link metrics",
	},
};

DEFINE_ATTR(dump_beacon_metrics) = {
	[BCN_METRICS_DUMP_ATTR_STA] = {
		.optional = 1,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "dump Beacon Metrics responses for STA",
	},
};

DEFINE_ATTR(dump_policy) = {
	[DUMP_POLICY_AGENT] = {
		.optional = 1,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "dump policy for the given agent",
	},
};

#if (EASYMESH_VERSION >= 6)
DEFINE_ATTR(dump_mlo_caps) = {
	[MLO_CAPABILITIES_ATTR_AGENT] = {
		.optional = 1,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent of which MLO capabilities are to be displayed",
	},
};
#endif

DEFINE_ATTR(status) = {
};

DEFINE_ATTR(status_full) = {
};

DEFINE_ATTR(metrics) = {
};

DEFINE_ATTR(timers) = {
};

DEFINE_ATTR(reconf) = {
};

#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
DEFINE_ATTR(dump_dpp_uri) = {
};

DEFINE_ATTR(dpp_add_uri) = {
	[DPP_ADD_URI_ATTR_URI] = {
		.optional = 1,
		.pol = {
			.name = "uri",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "DPP enrollee bootstrapping URI",
	},
	[DPP_ADD_URI_ATTR_ALIAS] = {
		.optional = 1,
		.pol = {
			.name = "alias",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Label used for identification. If a label is not provided, one will be automatically generated as 'cpe-N'",
	}
};

DEFINE_ATTR(dpp_del_uri) = {
	[DPP_DEL_URI_ATTR_URI] = {
		.optional = 1,
		.pol = {
			.name = "uri",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "DPP enrollee bootstrapping URI",
	},
	[DPP_DEL_URI_ATTR_ALIAS] = {
		.optional = 1,
		.pol = {
			.name = "alias",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Label used for identification.",
	}
};

DEFINE_ATTR(dpp_advertise_cce) = {
	[DPP_ADVERTISE_CCE_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which DPP CCE Indication is to be sent",
	},
	[DPP_ADVERTISE_CCE_ATTR_ENABLE] = {
		.optional = 1,
		.pol = {
			.name = "enable",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Enable (1), disable (0), if not provided defaults to enable (1)",
	}
};
#ifdef ZEROTOUCH_DPP
DEFINE_ATTR(zerotouch_add_key) = {
	[ZEROTOUCH_ADD_KEY_ATTR_KEY] = {
		.optional = 0,
		.pol = {
			.name = "key",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Extender Wi-Fi key",
	},
	[ZEROTOUCH_ADD_KEY_ATTR_ALIAS] = {
		.optional = 1,
		.pol = {
			.name = "alias",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Label used for identification. If a label is not provided, one will be automatically generated as 'cpe-N'",
	}
};

DEFINE_ATTR(zerotouch_del_key) = {
	[ZEROTOUCH_DEL_KEY_ATTR_KEY] = {
		.optional = 1,
		.pol = {
			.name = "key",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Extender Wi-Fi key",
	},
	[ZEROTOUCH_DEL_KEY_ATTR_ALIAS] = {
		.optional = 1,
		.pol = {
			.name = "alias",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "Label used for identification. If a label is not provided, one will be automatically generated as 'cpe-N'",
	}
};
#endif
#endif
#endif

#ifdef EASYMESH_VENDOR_EXT
DEFINE_ATTR(disassociate_sta) = {
	[DISASSOCIATE_STA_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which the disassociate STA CMDU is to be sent",
	},
	[DISASSOCIATE_STA_ATTR_STA] = {
		.optional = 0,
		.pol = {
			.name = "sta",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "STA's macaddress.",
	},
	[DISASSOCIATE_STA_ATTR_REASON] = {
		.optional = 1,
		.pol = {
			.name = "reason",
			.type = BLOBMSG_TYPE_INT32,
		},
		.help = "Reason code for the disassociation.",
	}
};

DEFINE_ATTR(reset_agent) = {
	[RESET_AGENT_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which the reset_agent CMDU is to be sent",
	}
};

DEFINE_ATTR(reload_wifi) = {
	[RELOAD_WIFI_ATTR_AGENT] = {
		.optional = 0,
		.pol = {
			.name = "agent",
			.type = BLOBMSG_TYPE_STRING,
		},
		.help = "AL-address of Agent to which the Wi-Fi reload CMDU is to be sent",
	}
};
#endif

DEFINE_ATTR(dump_topology) = {
};


/**
 * @ingroup cmdlist
 * @brief List of Controller APIs
 *
 * @help: Show usage.
 * @query_ap_caps: Send AP Capability Query.
 * @query_sta_caps: Send STA Capability Query.
 * @query_bsta_caps: Send Backhaul STA Capability Query.
 * @query_ap_metrics: Send AP Metrics Query.
 * @query_sta_metrics: Send STA/bSTA Link Metrics Query.
 * @query_unassoc_sta_metrics: Send Unassociated STA Link Metrics Query.
 * @query_channel_pref: Send Channel Preference Query.
 * @query_beacon_metrics: Send Beacon Metrics Query to STA.
 * @send_channel_sel: Send Channel Selection Request.
 * @trigger_channel_clearing: Perform DFS channel(s) clearing.
 * @steer: Send STA Steering Request for immediate steering of STA.
 * @steer_opt: Send STA Steering Request for opportunity steering.
 * @assoc_control: Send STA/bSTA Association Control Request.
 * @steer_backhaul: Send bSTA Steering Request.
 * @set_policy: Set Controller Policy.
 * @scan: Send Scan Request.
 * @scanresults: Show collected scan results.
 * @cac_start: Send CAC Start Request.
 * @cac_stop: Send CAC Termination Request.
 * @send_hld: Send Higher Layer Data.
 * @send_combined_metrics: Send Combined Infrastructure Metrics.
 * @send_topology_query: Send Topology Query.
 * @dump_steer_summary: Show STA steering summary.
 * @dump_steer_history: Show STA steering history.
 * @dump_unassoc_sta_metrics: Show collected Unassociated STA link metrics Responses.
 * @dump_beacon_metrics: Show collected Beacon metrics from STAs.
 * @dump_mlo_caps: Show verbose controller status information.
 * @status: Show controller status information.
 * @status_full: Show verbose controller status information.
 * @timers: Show status of timers in controller (for debug).
 * @reconf: Reload configuration and apply changes.
 * @dpp_add_uri: Add a DPP URI to be used for onboarding",
 * @dpp_del_uri: Remove DPP URI,
 * @dpp_advertise_cce: Inform an agent to start or stop advertising CCE,
 * @zerotouch_add_key: Add Wi-Fi key of extender for DPP bootstrapping,
 * @zerotouch_del_key: Del Wi-Fi key of extender for DPP bootstrapping,
 * @dump_topology: Show topology tree of the Multi-AP network.
 */
static struct controller_command cntlr_commandlist[] = {
	CNTLR_CMD(help, "Show usage"),
	CNTLR_CMD(log, "Log settings"),
	CNTLR_CMD(query_ap_caps, "Send AP-Capability Query to Agent(s)"),
	CNTLR_CMD(query_sta_caps, "Send STA Capability Query to Agent(s)"),
	CNTLR_CMD(query_bsta_caps, "Send BSTA Capability Query to Agent(s)"),
	CNTLR_CMD(query_ap_metrics, "Send AP Metrics Query to Agent(s)"),
	CNTLR_CMD(query_sta_metrics, "Send STA/bSTA Link Metrics Query to Agent"),
	CNTLR_CMD(query_unassoc_sta_metrics, "Send Unassociated STA Link Metrics Query to Agent(s)"),
	CNTLR_CMD(query_channel_pref, "Send Channel Preference Query to Agent(s)"),
	CNTLR_CMD(query_beacon_metrics, "Send Beacon Metrics Query to Agent"),
	CNTLR_CMD(send_channel_sel, "Send Channel Selection Request to Agent"),
	CNTLR_CMD(send_channel_pref, "Send Channel Preference Request to Agent"),
	CNTLR_CMD(trigger_channel_clearing, "Trigger CAC Request in Agent for clearing DFS channels"),
	CNTLR_CMD(trigger_acs, "Trigger ACS"),
	CNTLR_CMD(steer, "Send STA steering request to Agent"),
	CNTLR_CMD(steer_op, "Send STA steering opportunity request to Agent"),
	CNTLR_CMD(dump_assoc_control, "Dump configured STA/bSTA association control entries"),
	CNTLR_CMD(assoc_control, "Send STA/bSTA association control request to Agent"),
	CNTLR_CMD(steer_backhaul, "Send bSTA Steering Request to Agent"),
	//CNTLR_CMD(set_policy, "Set Controller Policy in Agent(s)"),
	CNTLR_CMD(scan, "Send Scan Request to Agent(s)"),
	CNTLR_CMD(scanresults, "Dump collected scanresults from Agent(s)"),
	CNTLR_CMD(cac_start, "Send CAC Request to Agent"),
	CNTLR_CMD(cac_stop, "Send CAC Terminate to Agent"),
	CNTLR_CMD(send_hld, "Send Higher Layer Data to Agent"),
	CNTLR_CMD(send_combined_metrics, "Send Combined Infrastructure Metrics to Agent"),
	CNTLR_CMD(send_topology_query, "Send Topology Query to Agent"),
	CNTLR_CMD(dump_steer_summary, "Dump STA steering summary"),
	CNTLR_CMD(dump_steer_history, "Dump STA steering history"),
	CNTLR_CMD(dump_unassoc_sta_metrics, "Dump Unassociated STA link metrics"),
	CNTLR_CMD(dump_beacon_metrics, "Dump Beacon metrics from STA"),
	CNTLR_CMD(dump_policy, "Dump policies"),
#if (EASYMESH_VERSION >= 6)
	CNTLR_CMD(dump_mlo_caps, "Dump MLO caps from Agents in the network"),
#endif
	CNTLR_CMD(status, "Show status"),
	CNTLR_CMD(status_full, "Show verbose status"),
	CNTLR_CMD(metrics, "Show collected link metrics"),
	CNTLR_CMD(timers, "Show timers (for debugging)"),
	CNTLR_CMD(reconf, "Reload configuration and apply changes"),
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	CNTLR_CMD(dump_dpp_uri, "Dump configured DPP enrollee URIs"),
	CNTLR_CMD(dpp_add_uri, "Add a DPP URI to be used for onboarding"),
	CNTLR_CMD(dpp_del_uri, "Remove DPP URI"),
	CNTLR_CMD(dpp_advertise_cce, "Inform an agent to start or stop advertising CCE"),
#ifdef ZEROTOUCH_DPP
	CNTLR_CMD(zerotouch_add_key, "Add Wi-Fi key of extender for DPP bootstrapping"),
	CNTLR_CMD(zerotouch_del_key, "Delete Wi-Fi key of extender for DPP bootstrapping"),
#endif
#endif
#endif
#ifdef EASYMESH_VENDOR_EXT
	CNTLR_CMD(disassociate_sta, "Tell an agent to disassociate a STA"),
	CNTLR_CMD(reset_agent, "Reset the multiap stack of an agent"),
	CNTLR_CMD(reload_wifi, "Reload the Wi-Fi subsystem on an agent"),
#endif
	CNTLR_CMD(dump_topology, "Show tree topology of the Multi-AP network"),
	CNTLR_CMD(null_command, ""),
};

#define NUM_CONTROLLER_CMDS	ARRAY_SIZE(cntlr_commandlist)
int num_controller_cmds = NUM_CONTROLLER_CMDS;


size_t num_controller_commands(void *cntlr)
{
	return NUM_CONTROLLER_CMDS - 1;
}

struct controller_command *controller_command_last(void)
{
	return &cntlr_commandlist[NUM_CONTROLLER_CMDS - 1];
}

struct controller_command *controller_command_first(void)
{
	return &cntlr_commandlist[0];
}

#define controller_for_each_command(p)	\
	for (p = controller_command_first(); p != controller_command_last(); p++)


int iterate_commands()
{
	struct controller_command *cmd = NULL;

	controller_for_each_command(cmd) {
		printf("cmdname = %s\n", cmd->command);
	}

	return 0;
}

struct controller_command *controller_command_lookup(const char *command)
{
	int num_cmds = ARRAY_SIZE(cntlr_commandlist);
	struct controller_command *cmd = NULL;

	for (int i = 0; i < num_cmds; i++) {
		int len;

		cmd = &cntlr_commandlist[i];
		len = (strlen(cmd->command) > strlen(command) ?
			strlen(cmd->command) : strlen(command));
		if (!strncmp(cmd->command, command, len))
			return cmd;
	}

	return NULL;
}

int controller_command_get_policy(const char *command,
				  void **policy,
				  size_t *num_policy)
{
	int num_cmds = ARRAY_SIZE(cntlr_commandlist);
	struct controller_command *cmd = NULL;

	*num_policy = 0;
	*policy = NULL;

	for (int i = 0; i < num_cmds; i++) {
		int len;

		cmd = &cntlr_commandlist[i];
		len = (strlen(cmd->command) > strlen(command) ?
			strlen(cmd->command) : strlen(command));
		if (!strncmp(cmd->command, command, len)) {
			struct blobmsg_policy *pol, *p;

			if (!cmd->num_attrs)
				return 0;

			pol = calloc(cmd->num_attrs, sizeof(struct blobmsg_policy));
			if (!pol)
				return -1;

			for (i = 0; i < cmd->num_attrs; i++) {
				p = pol + i;
				//memcpy(&pol[i], &cmd->attrs[i].pol, sizeof(struct blobmsg_policy));
				p->name = strdup(cmd->attrs[i].pol.name);
				p->type = cmd->attrs[i].pol.type;
			}

			*num_policy = cmd->num_attrs;
			*policy = pol;
			dbg("%s: num-policy = %zu\n", __func__, *num_policy);
			return 0;
		}
	}

	return -1;
}

int controller_command_parse(const char *command, void *args, struct blob_attr **tb)
{
	struct controller_command *cmd = NULL;
	//struct blob_buf bi = {0};
	struct blob_attr *msg;
	int ret;

	cmd = controller_command_lookup(command);
	if (!cmd) {
		warn("%s: cmd '%s' not found\n", __func__, command);
		return -1;
	}

	for (int i = 0; i < cmd->num_attrs; i++)  {
		dbg("%s: attr[%d] = %s, type = %d, optional = %d\n",
			__func__, i, cmd->attrs[i].pol.name,
			cmd->attrs[i].pol.type, cmd->attrs[i].optional);
	}

	if (cmd->num_attrs == 0)
		return 0;

	memset(tb, 0, cmd->num_attrs * sizeof(*tb));
	msg = (struct blob_attr *)args;
	if (!msg || !blob_len(msg)) {
		for (int i = 0; i < cmd->num_attrs; i++)  {
			if (cmd->attrs[i].optional == 0) {
				warn("%s: mandatory arg '%s' not provided\n",
					 __func__, cmd->attrs[i].pol.name);
				//blob_buf_free(&bi);
				return -1;
			}
		}

		return 0;
	}

#if 0	//TODO: for json string as input instead of blob_attr
	printf("cmd: name = %s, num-pol = %zd args = %s\n", cmd->command, cmd->num_attrs, args);
	blob_buf_init(&bi, 0);
	if (!blobmsg_add_json_from_string(&bi, args)) {
		printf("%s: blobmsg from json-string error\n", __func__);
		blob_buf_free(&bi);
		return -1;
	}

	msg = (struct blob_attr *)bi.head;
#endif
	struct blobmsg_policy pol[cmd->num_attrs];

	memset(pol, 0, cmd->num_attrs * sizeof(struct blobmsg_policy));
	for (int i = 0; i < cmd->num_attrs; i++) {
		pol[i].name = cmd->attrs[i].pol.name;
		pol[i].type = cmd->attrs[i].pol.type;
	}

	ret = blobmsg_parse(pol, cmd->num_attrs, tb, blob_data(msg), blob_len(msg));
	if (ret) {
		err("%s: blobmsg_parse() error\n", __func__);
		//blob_buf_free(&bi);
		return -1;
	}

	for (int i = 0; i < cmd->num_attrs; i++)  {
		if (cmd->attrs[i].optional == 0 && !tb[i]) {
			warn("%s: mandatory argument '%s' not provided\n",
				 __func__, cmd->attrs[i].pol.name);
			//blob_buf_free(&bi);
			return -1;
		}
	}

	//blob_buf_free(&bi);
	return 0;
}
