#include "dynbh.h"

#include <1905_tlvs.h>
#include <cmdu.h>
#include <easy/if_utils.h>
#include <easy/utils.h>
#include <easymesh.h>
#include <libubox/blob.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <map_module.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <timer.h>
#include <ubusmsg.h>
#include <linux/if.h>

#include "dbh_config.h"
#include "dbh_nl.h"
#include "../utils/utils.h"

#define DBH_MAX_ETHPORTS 16

#ifndef BIT
#define BIT(n)	(1U << (n))
#endif

const char *PROG_NAME = "dynbhd";
bool syslogging;
bool usefifo;
const char *outfile_path;
const char *objname;

#define map_plugin			IEEE1905_OBJECT_MULTIAP
#define HEARTBEAT_PROBE_TIMEOUT 	30
#define APCONF_MAX_RETRIES 		5
#define APCONF_INTERVAL 		2


/**
 * disconnect & disable bstas
 * create /var/run/multiap/map.agent.bsta_global_disable
 * write active iface to /var/run/multiap/ file
 */
static void link_up(struct dynbh_ctx *priv, struct ethport *port)
{
	fprintf(stderr, "|%s:%d|\n", __func__, __LINE__);
	runCmd("/lib/wifi/dynbhd/api up %s", port->ifname);
	runCmd("/lib/wifi/multiap set_uplink eth %s", port->ifname);
	runCmd("/lib/wifi/dynbhd/api handle_uplink_switch %s %s", port->ifname, (priv->prev_wan ? priv->prev_wan->ifname : "" ));

}

/**
 * remove /var/run/multiap/map.agent.bsta_global_disable
 * enable all bstas
 */
static void link_down(void)
{
	fprintf(stderr, "|%s:%d|\n", __func__, __LINE__);
	runCmd("/lib/wifi/dynbhd/api down");
}

/* check for eth uplink existence */
static bool is_backhaul_type(const char *bhtype)
{
	struct blob_buf bk = { 0 };
	char *type;
	struct blob_attr *tb[4];
	static const struct blobmsg_policy bk_attr[4] = {
		[0] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "backhaul_device_id", .type = BLOBMSG_TYPE_TABLE },
		[3] = { .name = "backhaul_device_macaddr", .type = BLOBMSG_TYPE_TABLE },
	};

	blob_buf_init(&bk, 0);

	if (!blobmsg_add_json_from_file(&bk, MAP_UPLINK_PATH)) {
		fprintf(stderr, "Failed to parse %s\n", MAP_UPLINK_PATH);
		goto out;
	}

	blobmsg_parse(bk_attr, 4, tb, blob_data(bk.head), blob_len(bk.head));

	if (!tb[0])
		goto out;

	type = blobmsg_data(tb[0]);

	fprintf(stderr, "---- type = %s ------\n", type);

	blob_buf_free(&bk);

	return !strncmp(type, bhtype, strlen(bhtype));
out:
	blob_buf_free(&bk);
	return false;
}


/* check for eth uplink existence */
static bool is_backhaul_type_eth(void)
{
	return is_backhaul_type("eth");
}

#ifdef PERSIST_CONTROLLER
static void controller_discovery_persist(void)
{
	fprintf(stderr, "|%s:%d|\n", __func__, __LINE__);
	runCmd("/lib/wifi/dynbhd/api persist_cntlr");
}

static void controller_discovery_disable(void)
{
	fprintf(stderr, "|%s:%d|\n", __func__, __LINE__);
	runCmd("/lib/wifi/dynbhd/api disable_cntlr");
}

static int controller_discovery_dhcp(char *ifname)
{
	char rc[2] = {0};

	fprintf(stderr, "|%s:%d|\n", __func__, __LINE__);
	chrCmd(rc, 2, "/lib/wifi/dynbhd/api dhcp_discovery %s", ifname);

	return atoi(rc);
}

static bool is_dhcp_server_available(struct dynbh_ctx *priv)
{
	struct ethport *port;

	list_for_each_entry(port, &priv->ethportlist, list) {
		if (port->has_dhcp_server)
			return true;
	}

	return false;
}
#endif

void delif(struct ethport *port)
{
	runCmd("/lib/wifi/dynbhd/api bridge_delif %s", port->ifname);
	runCmd("ubus call ieee1905 add_interface '{\"ifname\":\"%s\"}'", port->ifname);
	fprintf(stderr, "brctl delif %s %s\n", port->ctx->al_bridge, port->ifname);
}

void addif(struct ethport *port)
{
	if (if_isbridge_interface(port->ifname))
		return;

	runCmd("ubus call ieee1905 del_interface '{\"ifname\":\"%s\"}'", port->ifname);
	runCmd("/lib/wifi/dynbhd/api bridge_addif %s", port->ifname);  /* add back to bridge */
	fprintf(stderr, "cmd: brctl addif %s %s\n", port->ctx->al_bridge, port->ifname); /* add back to bridge */
}

struct ethport *ethport_by_ifname(struct dynbh_ctx *p,
		const char *ifname)
{
	struct ethport *port = NULL;

	list_for_each_entry(port, &p->ethportlist, list) {
		if (!strncmp(port->ifname, ifname, 16))
			return port;
	}

	return NULL;
}

struct ethport *ethport_by_mid(struct dynbh_ctx *p, uint16_t mid)
{
	struct ethport *port = NULL;

	fprintf(stderr, "%s %d mid %d\n", __func__, __LINE__, mid);

	list_for_each_entry(port, &p->ethportlist, list) {
		int i;

		for (i = 0; i < port->num_mid; i++) {
			fprintf(stderr, "%s %d mid[%d] %d\n", __func__, __LINE__, i, port->mid[i]);

			if (port->mid[i] == mid)
				return port;
		}
	}

	return NULL;
}

/* if link times out, no loop was found */
static void bridge_readd(atimer_t *t)
{
	struct ethport *port = container_of(t, struct ethport,
			bridge_add);
	int timeout = 5 * port->retries;

	if (port->active_uplink)
		link_down();

	fprintf(stderr, "|%s:%d| link timed out for iface:%s mid:%d, %d, add back to bridge\n", __func__, __LINE__, port->ifname, port->mid[0], port->mid[1]);
	port->loop = false;
	port->active_uplink = false;
	addif(port);

	port->num_mid = 0;

	if (timeout > HEARTBEAT_PROBE_TIMEOUT)
		timeout = HEARTBEAT_PROBE_TIMEOUT;

	fprintf(stderr, "|%s:%d| timeout = %d\n", __func__, __LINE__, timeout);

	port->retries++;
	timer_set(&port->send_apconf, timeout * 1000);
	timer_set(&port->bridge_add,
		  ((APCONF_MAX_RETRIES * APCONF_INTERVAL) + timeout) * 1000);
#ifdef PERSIST_CONTROLLER
	if (port->has_dhcp_server) {
		struct dynbh_ctx *priv = port->ctx;

		if (priv->discovery_mode == DYNAMIC_CNTLR_MODE_AUTO) {
			controller_discovery_persist();
			priv->discovery_mode = DYNAMIC_CNTLR_MODE_CONTROLLER;
			fprintf(stderr, "%s: set mode 'controller', port:%s\n",
				__func__, port->ifname);
		}

		if (priv->discovery_mode != DYNAMIC_CNTLR_MODE_DISABLED && !priv->wan) {
			priv->wan = port;
			runCmd("/lib/wifi/dynbhd/api handle_uplink_switch %s %s",
				port->ifname,
				(priv->prev_wan ? priv->prev_wan->ifname : "" ));
			fprintf(stderr, "%s: set uplink switch for port:%s prev_wan=%s\n",
				__func__, port->ifname,
				(priv->prev_wan ? priv->prev_wan->ifname : "" ));
		}
	}
#endif
}

/* callback to send ap autoconfig search */
static void send_apconf_cb(atimer_t *t)
{
	struct ethport *port = container_of(t, struct ethport, send_apconf);
	char mid[16] = {0};

	fprintf(stderr, "|%s:%d| sending query num %d for ifname:%s alid:%s \n", __func__, __LINE__, (port->num_mid+1), port->ifname, port->ctx->alidstr);
	runCmd("[ -n \"$(ubus list ieee1905.al.%s)\" ] || ubus call ieee1905 add_interface '{\"ifname\":\"%s\"}'", port->ifname, port->ifname);

/*
ubus call ieee1905 cmdu '{"ifname":"eth0", "dst":"01:80:C2:00:00:13", "type":7, "mid":0, "data":"010006A6CEDA6DAF8412s0d0001000e00010080000201018100020100b3000102000000"}'
*/
	chrCmd(mid, 16, "ubus call ieee1905.al.%s cmdu '{\"dst\":\"01:80:C2:00:00:13\", \"type\":7, \"mid\":0, \"data\":\"010006%12s0d0001000e00010080000201018100020100b3000102000000\"}' | grep mid | cut -d' ' -f2", port->ifname, port->ctx->alidstr);
	runCmd("ubus list ieee1905.al.%s", port->ifname);
	fprintf(stderr, "mid = %s\n", mid);
	errno = 0;
	port->mid[port->num_mid] = (uint16_t) strtoul(mid, NULL, 10);
	if (errno) {
		fprintf(stderr, "Invalid mid value: %s\n", mid);
		port->mid[port->num_mid] = 0;
	}
	fprintf(stderr, "mid[%d] = %u\n", port->num_mid, port->mid[port->num_mid]);
	if (port->num_mid < 31)
		port->num_mid++;

#ifdef PERSIST_CONTROLLER
	fprintf(stderr, "%s: sending dhcp discovery num:%d\n", __func__, port->dhcp_retries);

	if (!port->has_dhcp_server && !is_dhcp_server_available(port->ctx) &&
	     controller_discovery_dhcp(port->ifname) == 0) {
		fprintf(stderr, "%s: %s has dhcp server\n", __func__, port->ifname);
		port->has_dhcp_server = true;
	}
#endif

	if (port->num_mid < APCONF_MAX_RETRIES)
		timer_set(&port->send_apconf, 2000);
}

void dynbh_handle_port_down(struct dynbh_ctx *priv, struct ethport *port)
{
	timer_del(&port->send_apconf);
	timer_del(&port->bridge_add);
	port->connected = false;
	port->loop = false;

	if (!if_isbridge_interface(port->ifname))
		addif(port);
#ifdef PERSIST_CONTROLLER
	if (priv->discovery_mode == DYNAMIC_CNTLR_MODE_CONTROLLER && port->has_dhcp_server) {
		port->has_dhcp_server = false;
		priv->prev_wan = port;
		priv->wan = NULL;
	}
#endif
	if (port->active_uplink) {
		struct ethport *p = NULL;
		bool found = false;

		port->active_uplink = false;

		list_for_each_entry(p, &priv->ethportlist, list) {
			if (!p->loop)
				continue;

			addif(p);
			runCmd("/lib/wifi/multiap set_uplink eth %s", p->ifname);
			runCmd("/lib/wifi/dynbhd/api handle_uplink_switch %s %s", p->ifname, port->ifname);
			p->loop = false;
			p->active_uplink = true;
			found = true;
			priv->wan = p;
		}

		if (!found) {
			link_down();
			runCmd("/lib/wifi/multiap unset_uplink eth");
		}
		priv->prev_wan = port;
	}
}

void dynbh_handle_port_up(struct ethport *port)
{
	port->connected = true;

	/* immediately send apconf search */
	port->retries = 1;
	port->num_mid = 0;
#ifdef PERSIST_CONTROLLER
	port->dhcp_retries = 0;
#endif
	timer_set(&port->send_apconf, 0);
	/* re-add iface to bridge in 10s if no answer */
	timer_set(&port->bridge_add, (APCONF_MAX_RETRIES * APCONF_INTERVAL) * 1000);

	/* remove iface from brdige */
	delif(port);
}

struct ethport *alloc_ethport_search(struct dynbh_ctx *priv,
		char *ifname)
{
	struct ethport *port;
	uint8_t operstate;

	port = calloc(1, sizeof(struct ethport));
	if (!port)
		return NULL;

	port->ctx = priv;
	strncpy(port->ifname, ifname, sizeof(port->ifname) - 1);
	timer_init(&port->send_apconf, send_apconf_cb);
	timer_init(&port->bridge_add, bridge_readd);
	list_add_tail(&port->list, &priv->ethportlist);

	if_getoperstate(ifname, &operstate);
	if (operstate == IF_OPER_UP)
		dynbh_handle_port_up(port);

	return port;
}

void free_ethport_search(struct ethport *port)
{
	timer_del(&port->send_apconf);
	timer_del(&port->bridge_add);
	list_del(&port->list);
	free(port);
}

#ifdef PERSIST_CONTROLLER
/* { "map.agent": {"event":"configuration","data":{"action":"reload","status":"success"}} }
 */
/* mapagent event handler */
static void mapagent_event_handler(void *agent, struct blob_attr *msg)
{
	char event[32] = {0};
	struct dynbh_ctx *a = (struct dynbh_ctx *) agent;
	struct blob_attr *tb[2];
	static const struct blobmsg_policy ev_attr[2] = {
		[0] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};

	blobmsg_parse(ev_attr, 2, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1])
		return;

	strncpy(event, blobmsg_data(tb[0]), sizeof(event) - 1);
	if (!strncmp(event, "configuration", sizeof(event))) {
		struct blob_attr *tb2[2];
		char action[16] = {0};
		static const struct blobmsg_policy conf_attrs[2] = {
			[0] = { .name = "action", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
		};

		blobmsg_parse(conf_attrs, 2, tb2,
			      blobmsg_data(tb[1]), blobmsg_data_len(tb[1]));

		if (!tb2[0] || !tb2[1]) {
			fprintf(stderr, "|%s:%d| event parse error.\n", __func__, __LINE__);
			return;
		}

		strncpy(action, blobmsg_data(tb2[0]), sizeof(action) - 1);
		if (!strncmp(action, "reload", sizeof(action)) &&
		    a->discovery_mode == DYNAMIC_CNTLR_MODE_CONTROLLER) {
			if (is_local_cntlr_available()
			    && !is_local_cntlr_running()) {
				set_value_by_string("mapcontroller",
						    "controller", "enabled",
						    "1", UCI_TYPE_STRING);
				runCmd("/etc/init.d/mapcontroller reload");
				/* reloading wifidmd to sync with mapcontroller config */
				runCmd("/etc/init.d/wifidmd reload");
			}

			if (!is_process_running("decollector") &&
			    is_package_available("decollector"))
				runCmd("/etc/init.d/decollector reload");
		}
	}
}
#endif

static char *agent_get_backhaul_ifname(char *ifname)
{
	struct blob_buf bk = { 0 };
	struct blob_attr *tb[1];
	static const struct blobmsg_policy bk_attr[1] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
	};

	blob_buf_init(&bk, 0);

        if (!blobmsg_add_json_from_file(&bk, MAP_UPLINK_PATH)) {
		fprintf(stderr, "Failed to parse %s\n", MAP_UPLINK_PATH);
		goto out;
        }

	blobmsg_parse(bk_attr, 1, tb, blob_data(bk.head), blob_len(bk.head));

	if (!tb[0])
		goto out;

	strncpy(ifname, blobmsg_data(tb[0]), 15);
	ifname[15] = '\0';

	blob_buf_free(&bk);
	return ifname;
out:
	blob_buf_free(&bk);
	return NULL;
}

static int handle_autoconfig_response(const char *ifname, uint8_t *src,
				      uint8_t *from, uint16_t mid,
				      struct cmdu_buff *rxdata, size_t rxlen,
				      void *priv, void *cookie)
{
	struct dynbh_ctx *p = (struct dynbh_ctx *) priv;
	char almac_str[18] = {0};
	struct ethport *port;
	struct tlv_policy a_policy[] = {
		[0] = {
			.type = TLV_TYPE_SUPPORTED_ROLE,
			.present = TLV_PRESENT_ONE
		},
		[1] = {
			.type = MAP_TLV_SUPPORTED_SERVICE,
			.present = TLV_PRESENT_OPTIONAL_ONE
		},
		[2] = {
			.type = MAP_TLV_MULTIAP_PROFILE,
			.present = TLV_PRESENT_ONE
		},
		[3] = {
			.type = TLV_TYPE_SUPPORTED_FREQ_BAND,
			.present = TLV_PRESENT_ONE
		}
	};
	struct tlv *tv[4][TLV_MAXNUM] = {0};
	bool has_cntlr = false;
	int timeout = HEARTBEAT_PROBE_TIMEOUT;
	int ret;

	fprintf(stderr, "mapclient: %s ===>\n", __func__);

	ret = cmdu_parse_tlvs(rxdata, tv, a_policy, 4);
	if (ret) {
		fprintf(stderr, "%s: parse_tlv failed\n", __func__);
		return -1;
	}

	if (!tv[0][0] || !tv[1][0] || !tv[2][0] || !tv[3][0]) {
		fprintf(stderr, "malformed data %d %d %d %d\n", !!tv[0][0], !!tv[1][0], !!tv[2][0], !!tv[3][0]);
		return -1;
	}

	if (tv[1][0]->data[0] > 0) {
		int i;

		for (i = 0; i < tv[1][0]->data[0]; i++) {
			if (tv[1][0]->data[(i+1)] ==
					SUPPORTED_SERVICE_MULTIAP_CONTROLLER) {
				has_cntlr = true;
				break;
			}
		}
	}
	if (!has_cntlr) {
		fprintf(stderr, "Response did not support controller!\n");
		return -1;
	}


	port = ethport_by_mid(p, mid);
	if (!port) {
		fprintf(stderr, "No matching mid found\n");

		port = ethport_by_ifname(p, ifname);
		if (!port) {
			fprintf(stderr, "No interface matching %s found - no action\n", ifname);
			return -1;
		} else if (port->active_uplink || port->loop) {
			fprintf(stderr, "Interface %s already known to be connected to a controller - no action\n", ifname);
			goto out;
		} else {
			fprintf(stderr, "Interface %s is not known to have a controller\n", ifname);
		}
	}

	memcpy(port->backhaul_mac, src, 6);
	memcpy(port->backhaul_device_id, from, 6);

	if (!is_backhaul_type_eth()) {
		/* if there is no active eth backhaul use this link as backhaul */
		port->active_uplink = true;
		port->loop = false;
		link_up(p, port);
		fprintf(stderr, "|%s:%d| Interface %s is active uplink\n", __func__, __LINE__, port->ifname);
		addif(port);
#ifdef PERSIST_CONTROLLER
		if (port->ctx->discovery_mode == DYNAMIC_CNTLR_MODE_AUTO) {
			controller_discovery_disable();
			port->ctx->discovery_mode = DYNAMIC_CNTLR_MODE_DISABLED;
		}
#endif
		goto out;
	} else if (port->active_uplink) {
		char ul_ifname[16] = {0};

		if (agent_get_backhaul_ifname(ul_ifname)) {
			if (strncmp(ul_ifname, port->ifname, 16))
				link_up(p, port);
		} else {
			link_up(p, port);
		}

		fprintf(stderr, "|%s:%d| Interface %s is already known as active uplink\n", __func__, __LINE__, port->ifname);
		goto out;
	}

	fprintf(stderr, "|%s:%d| active controller (%s) found for iface:%s mid:%d, keep out of bridge\n",
			__func__, __LINE__, almac_str, port->ifname, mid);

	if (if_isbridge_interface(port->ifname))
		runCmd("/lib/wifi/dynbhd/api bridge_delif %s", port->ifname);  /* add back to bridge */

	runCmd("ubus call ieee1905 del_interface '{\"ifname\":\"%s\"}'", port->ifname);

	port->loop = true;
out:
	port->retries = port->num_mid = 0;
	port->retries++;
	timer_set(&port->send_apconf, timeout * 1000);
	timer_set(&port->bridge_add,
			((APCONF_INTERVAL * APCONF_MAX_RETRIES) + timeout) * 1000);
	return 0;
}

static int handle_autoconfig_search(const char *ifname, uint8_t *src,
				      uint8_t *from, uint16_t mid,
				      struct cmdu_buff *rxdata, size_t rxlen,
				      void *priv, void *cookie)
{
	struct dynbh_ctx *p = (struct dynbh_ctx *) priv;
	char almac_str[18] = {0};
	struct ethport *port;
	struct tlv_policy a_policy[] = {
		[0] = { .type = TLV_TYPE_AL_MAC_ADDRESS_TYPE,
			.present = TLV_PRESENT_ONE,
			.len = 6, /* macaddr */
		},
		[1] = { .type = TLV_TYPE_SEARCHED_ROLE,
			.present = TLV_PRESENT_ONE,
			.len = 1, /* tlv_searched_role */
		},
		[2] = { .type = TLV_TYPE_AUTOCONFIG_FREQ_BAND,
			.present = TLV_PRESENT_ONE,
			.len = 1, /* tlv_autoconfig_band */
		},
		[3] = { .type = MAP_TLV_SUPPORTED_SERVICE,
			.present = TLV_PRESENT_OPTIONAL_ONE,
			.minlen = 1, /* num of services */
		},
		[4] = { .type = MAP_TLV_SEARCHED_SERVICE,
			.present = TLV_PRESENT_OPTIONAL_ONE,
			.minlen = 1, /* num of services */
		},
		[5] = { .type = MAP_TLV_MULTIAP_PROFILE,
			.present = TLV_PRESENT_ONE,
			.len = 1, /* tlv_map_profile */
		},
	};
	struct tlv *tv[6][TLV_MAXNUM] = {0};
	bool has_cntlr = false;
	int timeout = HEARTBEAT_PROBE_TIMEOUT;
	int ret;

	fprintf(stderr, "mapclient: %s ===>\n", __func__);

	ret = cmdu_parse_tlvs(rxdata, tv, a_policy, 6);
	if (ret) {
		fprintf(stderr, "%s: parse_tlv failed\n", __func__);
		return -1;
	}

	if (!tv[0][0] || !tv[1][0] || !tv[2][0] || !tv[5][0]) {
		fprintf(stderr, "malformed data %d %d %d %d\n", !!tv[0][0], !!tv[1][0], !!tv[2][0], !!tv[3][0]);
		return -1;
	}

	if (tv[3][0]->data[0] > 0) {
		int i;

		for (i = 0; i < tv[3][0]->data[0]; i++) {
			if (tv[3][0]->data[(i+1)] ==
					SUPPORTED_SERVICE_MULTIAP_CONTROLLER) {
				has_cntlr = true;
				break;
			}
		}
	}
	if (!has_cntlr) {
		fprintf(stderr, "Search did not support controller!\n");
		return -1;
	}


	port = ethport_by_ifname(p, ifname);
	if (!port) {
		fprintf(stderr, "No interface matching %s found - no action\n", ifname);
		return -1;
	} else if (port->active_uplink || port->loop) {
		fprintf(stderr, "Interface %s already known to be connected to a controller - no action\n", ifname);
		return -1;
	} else {
		fprintf(stderr, "Interface %s is not known to have a controller\n", ifname);
	}

	memcpy(port->backhaul_mac, src, 6);
	memcpy(port->backhaul_device_id, from, 6);

	if (!is_backhaul_type_eth()) {
		/* if there is no active eth backhaul use this link as backhaul */
		port->active_uplink = true;
		port->loop = false;
		link_up(p, port);
		fprintf(stderr, "|%s:%d| Interface %s is active uplink\n", __func__, __LINE__, port->ifname);
		addif(port);
#ifdef PERSIST_CONTROLLER
		if (port->ctx->discovery_mode == DYNAMIC_CNTLR_MODE_AUTO) {
			controller_discovery_disable();
			port->ctx->discovery_mode = DYNAMIC_CNTLR_MODE_DISABLED;
		}
#endif
		goto out;
	} else if (port->active_uplink) {
		char ul_ifname[16] = {0};

		if (agent_get_backhaul_ifname(ul_ifname)) {
			if (strncmp(ul_ifname, port->ifname, 16))
				link_up(p, port);
		} else {
			link_up(p, port);
		}

		fprintf(stderr, "|%s:%d| Interface %s is already known as active uplink\n", __func__, __LINE__, port->ifname);
		goto out;
	}

	fprintf(stderr, "|%s:%d| active controller (%s) found for iface:%s mid:%d, keep out of bridge\n",
			__func__, __LINE__, almac_str, port->ifname, mid);

	if (if_isbridge_interface(port->ifname))
		runCmd("/lib/wifi/dynbhd/api bridge_delif %s", port->ifname);  /* add back to bridge */

	runCmd("ubus call ieee1905 del_interface '{\"ifname\":\"%s\"}'", port->ifname);

	port->loop = true;
out:
	port->retries = port->num_mid = 0;
	port->retries++;
	timer_set(&port->send_apconf, timeout * 1000);
	timer_set(&port->bridge_add,
			((APCONF_INTERVAL * APCONF_MAX_RETRIES) + timeout) * 1000);
	return 0;
}

static int handle_1905_ack(const char *ifname, uint8_t *src,
			   uint8_t *from, uint16_t mid,
			   struct cmdu_buff *rxdata, size_t rxlen, void *priv,
			   void *cookie)
{
	fprintf(stderr, "mapclient: %s ===>\n", __func__);
	return 0;
}

typedef int (*cmdu_handler_t)(const char *ifname, uint8_t *src, uint8_t *from,
			      uint16_t mid, struct cmdu_buff *rxdata,
			      size_t rxlen, void *priv, void *cookie);


static const cmdu_handler_t i1905ftable[] = {
	[0x00] = NULL,
	[0x01] = NULL,
	[0x02] = NULL,
	[0x03] = NULL,
	[0x04] = NULL,
	[0x05] = NULL,
	[0x06] = NULL,
	[0x07] = handle_autoconfig_search,
	[0x08] = handle_autoconfig_response,
	[0x09] = NULL,
	[0x0a] = NULL,
};

#define CMDU_TYPE_MAP_START	0x8000
#define CMDU_TYPE_MAP_END	0x8001

static const cmdu_handler_t mapclient_ftable[] = {
	[0x00] = handle_1905_ack,
};


static int mapclient_handle_cmdu_notification(struct blob_attr *msg, struct dynbh_ctx *priv)
{
	static const struct blobmsg_policy cmdu_attrs[6] = {
		[0] = { .name = "type", .type = BLOBMSG_TYPE_INT16 },
		[1] = { .name = "mid", .type = BLOBMSG_TYPE_INT16 },
		[2] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "source", .type = BLOBMSG_TYPE_STRING },
		[4] = { .name = "origin", .type = BLOBMSG_TYPE_STRING },
		[5] = { .name = "cmdu", .type = BLOBMSG_TYPE_STRING },
	};
	char in_ifname[16] = {0};
	struct blob_attr *tb[6];
	char src[18] = { 0 }, src_origin[18] = { 0 };
	uint8_t *tlv = NULL;
	char *tlvstr = NULL;
	uint16_t type;
	uint8_t srcmac[6], origin[6];
	uint16_t mid = 0;
	int len = 0;
	struct cmdu_buff *cmdu;
	int ret = 0;
	int idx;
	const cmdu_handler_t *f;

	blobmsg_parse(cmdu_attrs, 6, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1])
		return -1;

	if (tb[0]) {
		int t;

		t = blobmsg_get_u16(tb[0]);
		if (t < 0)
			return -1;

		type = (uint16_t)t;
	}

	if (tb[1])
		mid = (uint16_t)blobmsg_get_u16(tb[1]);


	if (tb[2])
		strncpy(in_ifname, blobmsg_data(tb[2]), 15);

	if (tb[3]) {
		strncpy(src, blobmsg_data(tb[3]), 17);
		hwaddr_aton(src, srcmac);
	}

	if (tb[4]) {
		strncpy(src_origin, blobmsg_data(tb[4]), 17);
		hwaddr_aton(src_origin, origin);
	}


	if (tb[5]) {
		len = blobmsg_data_len(tb[5]) - 16;

		tlvstr = calloc(1, len + 1);

		if (!tlvstr)
			return -1;

		strncpy(tlvstr, (blobmsg_data(tb[5]) + 16), len);
		len = (len - 1) / 2;
		tlv = calloc(1, len);
		if (!tlv) {
			free(tlvstr);
			return -1;
		}

		strtob(tlvstr, len, tlv);
		free(tlvstr);
	}

	cmdu = cmdu_alloc_custom(type, &mid, in_ifname, srcmac, tlv, len);
	if (!cmdu) {
		fprintf(stderr, "%s: cmdu_alloc_custom() failed!\n", __func__);
		if (tlv)
			free(tlv);
		return -1;
	}
	memcpy(cmdu->origin, origin, 6);
	fprintf(stderr, "%s: cmdu_alloc_custom() succeeded! cmdu->cdata->hdr.mid %u\n", __func__, cmdu_get_mid(cmdu));


	if (type >= CMDU_TYPE_MAP_START) {
		idx = type - CMDU_TYPE_MAP_START;
		f = mapclient_ftable;
		if (ARRAY_SIZE(mapclient_ftable) < idx)
			goto error;
	} else {
		idx = type;
		f = i1905ftable;
		if (ARRAY_SIZE(i1905ftable) < idx)
			goto error;
	}

	if (f[idx]) {
		ret = f[idx](in_ifname, srcmac, origin, mid, cmdu, len, priv, NULL);
	}



error:
	if (tlv)
		free(tlv);
	cmdu_free(cmdu);
	return ret;
}

int dynbh_map_sub_cb(void *bus, void *priv, void *data)
{
	struct blob_attr *msg = (struct blob_attr *)data;
	char *str;


	str = blobmsg_format_json(msg, true);
	fprintf(stderr, "Received notification '%s'\n", str);
	free(str);

	mapclient_handle_cmdu_notification(msg, priv);

	return 0;
}

int dynbh_map_del_cb(void *bus, void *priv, void *data)
{
	struct dynbh_ctx *p = (struct dynbh_ctx *)priv;
	uint32_t *obj = (uint32_t *)data;

	p->subscribed = false;
	map_unsubscribe(bus, p->subscriber);
	p->subscriber = NULL;
	fprintf(stderr, "Object 0x%x no longer present\n", *obj);
	return 0;
}

static int dynbh_subscribe_for_cmdus(struct dynbh_ctx *priv)
{
	mapmodule_cmdu_mask_t cmdu_mask = {0};
	int ret;
	uint32_t map_id;


	map_prepare_cmdu_mask(cmdu_mask,
			      CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH,
			      CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE,
			      -1);

	ret = ubus_lookup_id(priv->ctx, map_plugin, &map_id);
	if (ret) {
		fprintf(stderr, "%s: %s\n", map_plugin, ubus_strerror(ret));
		return -1;
	}

	priv->map_oid = map_id;
	ret = map_subscribe(priv->ctx,
			    &priv->map_oid,
			    "dynbhd", &cmdu_mask, priv,
			    dynbh_map_sub_cb,
			    dynbh_map_del_cb,
			    &priv->subscriber);
	if (!ret) {
		priv->subscribed = true;
	} else {
		fprintf(stderr, "dynbh: Failed to 'register' with %s (err = %s)\n",
			map_plugin, ubus_strerror(ret));
	}

	return ret;
}

static void dynbh_periodic_run(atimer_t *t)
{
	struct dynbh_ctx *p = container_of(t, struct dynbh_ctx, dynbh_periodic_run);

	if (!p->subscribed) {
		fprintf(stderr, "%s: not subscribed for 1905 CMDUs, attempting to resubscribe\n", __func__);
		dynbh_subscribe_for_cmdus(p);
	}

	timer_set(&p->dynbh_periodic_run, 1 * 1000);
}

void remove_newline(char *buf)
{
        int len;

        len = strlen(buf) - 1;
        if (buf[len] == '\n')
                buf[len] = 0;
}

static void uobj_add_event_handler(void *priv, struct blob_attr *msg)
{
	char path[32] = {0};
	uint32_t id = 0;
	struct dynbh_ctx *p = (struct dynbh_ctx *) priv;
	struct blob_attr *tb[2];
	static const struct blobmsg_policy ev_attr[2] = {
		[0] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
		[1] = { .name = "path", .type = BLOBMSG_TYPE_STRING }
	};

	blobmsg_parse(ev_attr, 2, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1])
		return;

	strncpy(path, blobmsg_data(tb[1]), sizeof(path) - 1);
	id = (uint32_t) blobmsg_get_u32(tb[0]);

	fprintf(stderr, "%s: path = [%s] id = [%u]\n", __func__, path, id);
	if (!strncmp(path, map_plugin, strlen(map_plugin)) && p->subscribed == false)
		dynbh_subscribe_for_cmdus(p);
}

static void dynbh_event_handler(struct ubus_context *ctx,
		struct ubus_event_handler *ev,
		const char *type, struct blob_attr *msg)
{
	int i;
	char *str;
	struct dynbh_ctx *a = container_of(ev, struct dynbh_ctx, evh);
	struct wifi_ev_handler {
		const char *ev_type;
		void (*handler)(void *ctx, struct blob_attr *ev_data);
	} evs[] = {
#ifdef PERSIST_CONTROLLER
		{ "map.agent", mapagent_event_handler },
#endif
		{ "ubus.object.add", uobj_add_event_handler },
	};


	str = blobmsg_format_json(msg, true);
	if (!str)
		return;

	fprintf(stderr, "[ &dynbhd = %p ] Received [event = %s]  [val = %s]\n",
			a, type, str);
	for (i = 0; i < ARRAY_SIZE(evs); i++) {
		if (!strcmp(type, evs[i].ev_type)) {
			evs[i].handler(a, msg);
			break;
		}
	}

	free(str);
}

static int dynbhd_status(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct dynbh_ctx *c = container_of(obj, struct dynbh_ctx, obj);
	struct blob_buf bb;
	struct ethport *port;
	void *a;

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

	a = blobmsg_open_array(&bb, "ports");
	list_for_each_entry(port, &c->ethportlist, list) {
		void *t;

		t = blobmsg_open_table(&bb, "");
		blobmsg_add_string(&bb, "ifname", port->ifname);
		blobmsg_add_u8(&bb, "connected", port->connected);
		blobmsg_add_u8(&bb, "active_uplink", port->active_uplink);
#ifdef PERSIST_CONTROLLER
		blobmsg_add_u8(&bb, "has_dhcp_server", port->has_dhcp_server);
#endif
		blobmsg_add_u8(&bb, "loop", port->loop);

		blobmsg_close_table(&bb, t);
	}

	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);
	return UBUS_STATUS_OK;
}



int cntlr_publish_object(struct dynbh_ctx *c, const char *objname)
{
	struct ubus_object *obj;
	struct ubus_object_type *obj_type;
	struct ubus_method *obj_methods;
	struct ubus_method m[] = {
		UBUS_METHOD_NOARG("status", dynbhd_status),
	};
	int num_methods = ARRAY_SIZE(m);
	int ret;

	obj = &c->obj;
	memset(obj, 0, sizeof(*obj));

	obj_type = calloc(1, sizeof(struct ubus_object_type));
	if (!obj_type)
		return -1;

	obj_methods = calloc(num_methods, sizeof(struct ubus_method));
	if (!obj_methods) {
		free(obj_type);
		return -1;
	}

	obj->name = objname;
	memcpy(obj_methods, m, num_methods * sizeof(struct ubus_method));
	obj->methods = obj_methods;
	obj->n_methods = num_methods;

	obj_type->name = obj->name;
	obj_type->n_methods = obj->n_methods;
	obj_type->methods = obj->methods;
	obj->type = obj_type;

	ret = ubus_add_object(c->ctx, obj);
	if (ret) {
		fprintf(stderr, "Failed to add '%s' err = %s\n",
				objname, ubus_strerror(ret));
		free(obj_methods);
		free(obj_type);
		return ret;
	}

	fprintf(stderr, "Published '%s' object\n", objname);

	return 0;
}

int main(int argc, char **argv)
{
	const char *ubus_socket = NULL;
	struct dynbh_ctx *priv;
	char ifs[DBH_MAX_ETHPORTS][16] = {0};
	int num = 0;
	int ret;
	int ch;
	int i;

	while ((ch = getopt(argc, argv, "s:o:")) != -1) {
		switch (ch) {
		case 's':
			ubus_socket = optarg;
			break;
		case 'o':
			objname = optarg;
			break;
		default:
			break;
		}
	}

	priv = calloc(1, sizeof(*priv));
	if (!priv)
		return -1;

	uloop_init();
	priv->ctx = ubus_connect(ubus_socket);
	if (!priv->ctx) {
		fprintf(stderr, "Failed to connect to ubus\n");
		free(priv);
		return -1;
	}

	INIT_LIST_HEAD(&priv->ethportlist);

	ubus_add_uloop(priv->ctx);

	priv->evh.cb = dynbh_event_handler;
	ubus_register_event_handler(priv->ctx, &priv->evh, "ubus.object.add");
#if PERSIST_CONTROLLER
	ubus_register_event_handler(priv->ctx, &priv->evh, "map.agent");

	dbh_config_load(priv);
#endif
	priv->oid = 0xdeadbeaf;

	chrCmd(priv->alidstr, 16, "uci get ieee1905.ieee1905.macaddress | tr -d :");
	chrCmd(priv->al_bridge, 16, "uci get mapagent.agent.al_bridge");

	get_ethportslist(&num, ifs);
	for (i = 0; i < num; i++)
		alloc_ethport_search(priv, ifs[i]);

	timer_init(&priv->dynbh_periodic_run, dynbh_periodic_run);

	ret = dynbh_subscribe_for_cmdus(priv);
	if (!ret) {
		timer_set(&priv->dynbh_periodic_run, 1 * 1000);
		i1905_register_nlevents(priv);
		cntlr_publish_object(priv, "dynbh");
		uloop_run();
	}

	map_unsubscribe(priv->ctx, priv->subscriber);
	ubus_free(priv->ctx);
	uloop_done();
	free(priv);

	return 0;
}
