/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * i1905_ubus.c - implements IEEE-1905 UBUS APIs.
 *
 * Copyright (C) 2021-2024 IOPSYS Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 * Author: anjan.chanda@iopsys.eu
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <net/if.h>
#include <arpa/inet.h>

#include <json-c/json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>

#include <easy/easy.h>

#include "debug.h"
#include "util.h"
#include "timer.h"
#include "config.h"
#include "cmdu.h"
#include "cmdu_ackq.h"
#include "cmdufrag.h"
#include "1905_tlvs.h"
#include "i1905_dm.h"
#include "i1905.h"
#include "neigh.h"
#include "i1905_extension.h"

#include "i1905_wifi.h"


static const char *ubus_object_to_ifname(struct ubus_object *obj)
{
	if (strstr(obj->name, IEEE1905_OBJECT".al."))
		return obj->name + strlen(IEEE1905_OBJECT".al.");

	return NULL;
}


int i1905_ubus_info(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct blob_buf bb;
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);

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

	i1905_dump_info(p, &bb);

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

	return 0;
}

int i1905_ubus_neighbors(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct blob_buf bb;
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);

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

	i1905_dump_neighbors(p, &bb);

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

	return 0;
}

int i1905_ubus_links(struct ubus_context *ctx, struct ubus_object *obj,
		     struct ubus_request_data *req, const char *method,
		     struct blob_attr *msg)
{
	struct blob_buf bb;
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);

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

	i1905_dump_links(p, &bb);

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

	return 0;
}

int i1905_ubus_non1905neighbors(struct ubus_context *ctx, struct ubus_object *obj,
				struct ubus_request_data *req, const char *method,
				struct blob_attr *msg)
{
	struct blob_buf bb;
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);

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

	i1905_dump_non1905neighbors(p, &bb);

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

	return 0;
}

int i1905_ubus_show_arptable(struct ubus_context *ctx, struct ubus_object *obj,
			     struct ubus_request_data *req, const char *method,
			     struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	struct neigh_queue *q = &p->neigh_q;
	struct blob_buf bb;
	void *a, *aa;
	int i;

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

	a = blobmsg_open_array(&bb, "arptable");
	for (i = 0; i < NEIGH_ENTRIES_MAX; i++) {
		struct neigh_entry *e = NULL;
		char macstr[18] = {0};

		if (hlist_empty(&q->table[i]))
			continue;

		hlist_for_each_entry(e, &q->table[i], hlist) {
			aa = blobmsg_open_table(&bb, "");
			uint16_t brport;
			char *ifname = NULL;

			hwaddr_ntoa(e->macaddr, macstr);
			blobmsg_add_string(&bb, "macaddr", macstr);
			blobmsg_add_u8(&bb, "reachable", e->unreachable? false : true);
			blobmsg_add_u8(&bb, "is1905", e->is1905 ? true : false);
			blobmsg_add_u8(&bb, "is1905_slave", e->is1905_slave ? true : false);
			blobmsg_add_u32(&bb, "state", e->state);

			brport = neigh_get_brport(q, e->macaddr);
			if (brport != 0xffff) {
				ifname = i1905_brport_to_ifname(p, brport);
				if (ifname)
					blobmsg_add_string(&bb, "ifname", ifname);
			}

			blobmsg_close_table(&bb, aa);
		}
	}
	blobmsg_close_array(&bb, a);

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

	return 0;
}

enum {
	I1905_APCONFIG_IFNAME,
	I1905_APCONFIG_BAND,
	I1905_APCONFIG_ACTION,
	NUM_I1905_APCONFIG_POLICY,
};

static const struct blobmsg_policy apconfig_policy[NUM_I1905_APCONFIG_POLICY] = {
	[I1905_APCONFIG_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
	[I1905_APCONFIG_BAND] = { .name = "band", .type = BLOBMSG_TYPE_INT32 },
	[I1905_APCONFIG_ACTION] = { .name = "action", .type = BLOBMSG_TYPE_STRING },
};

int i1905_ubus_apconfig(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	struct blob_buf bb = {0};
	int ret;

	blob_buf_init(&bb, 0);
	ret = i1905_do_apconfig(p, msg, &bb);
	if (ret) {
		if (ret == -EINVAL)
			return UBUS_STATUS_INVALID_ARGUMENT;

		goto out;
	}

	ubus_send_reply(ctx, req, bb.head);
out:
	blob_buf_free(&bb);
	return ret;
}

int i1905_ubus_refresh(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	//struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	int ret = 0;

	//ret = i1905_refresh(p);		//TODO

	return ret;
}

int i1905_ubus_start(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	int ret = 0;

	ret = i1905_start(p);

	return ret;
}

int i1905_ubus_stop(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	int ret = 0;

	ret = i1905_stop(p);

	return ret;
}

int i1905_ubus_status(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	struct blob_buf bb = {0};

	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "status", p->paused ? "stopped" : "running");
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int i1905_ubus_aladdr(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct i1905_private *priv = container_of(obj, struct i1905_private, obj);
	struct i1905_selfdevice *self = &priv->dm.self;
	struct blob_buf bb = {0};
	char alstr[18] = {0};


	blob_buf_init(&bb, 0);
	hwaddr_ntoa(self->aladdr, alstr);
	blobmsg_add_string(&bb, "ieee1905id", alstr);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

/* log policy */
enum {
	LOG_ATTR_FEATURE,
	LOG_ATTR_LEVEL,
	NUM_ATTRS_LOG,
};

static const struct blobmsg_policy log_policy[NUM_ATTRS_LOG] = {
	[LOG_ATTR_FEATURE] = { .name = "feature", .type = BLOBMSG_TYPE_STRING },
	[LOG_ATTR_LEVEL] = { .name = "level", .type = BLOBMSG_TYPE_INT32 },
};

int i1905_ubus_log(struct ubus_context *ctx, struct ubus_object *obj,
		   struct ubus_request_data *req, const char *method,
		   struct blob_attr *msg)
{
	struct i1905_private *priv = container_of(obj, struct i1905_private, obj);
	struct blob_attr *tb[NUM_ATTRS_LOG];
	struct blob_buf bb = {0};
	int ret = 0;

	blobmsg_parse(log_policy, NUM_ATTRS_LOG, tb, blob_data(msg), blob_len(msg));
	blob_buf_init(&bb, 0);

	/* return current log settings */
	if (!tb[LOG_ATTR_FEATURE] && !tb[LOG_ATTR_LEVEL]) {
		void *tt, *a;

		tt = blobmsg_open_table(&bb, "");
		blobmsg_add_u32(&bb, "loglevel", priv->uopts.loglevel);
		a = blobmsg_open_array(&bb, "feature");
		for (int i = 0; i <= LOG_DEFAULT; i++)  {
			if (!(priv->uopts.features & BIT(i)))
				continue;

			blobmsg_add_string(&bb, "", logfeature_to_string(i));
		}
		blobmsg_close_array(&bb, a);
		blobmsg_close_table(&bb, tt);

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

	if (tb[LOG_ATTR_LEVEL]) {
		uint32_t loglevel = blobmsg_get_u32(tb[LOG_ATTR_LEVEL]);

		if (loglevel <= LOGLEVEL_TRACE)
			priv->uopts.loglevel = loglevel;
	}

	if (tb[LOG_ATTR_FEATURE]) {
		const char *fstr = blobmsg_data(tb[LOG_ATTR_FEATURE]);
		uint32_t feature_orig = priv->uopts.features;
		uint32_t feature = feature_orig;
		char *str, *s, *tmp;

		str = strdup(fstr);
		foreach_token_r(s, str, tmp, ", ") {
			if (s[0] == '+') {
				feature |= logfeature_to_enum(s+1);
			} else if (s[0] == '-') {
				feature &= ~logfeature_to_enum(s+1);
			} else if (s[0] == '*' || !strncmp(s, "all", 3)) {
				feature |= LOG_FEATURE_ALL;
			} else {
				ret = -1;
				break;
			}
		}

		free(str);
		priv->uopts.features = ret ? feature_orig : feature;
	}

	if (tb[LOG_ATTR_LEVEL] || tb[LOG_ATTR_FEATURE])
		restart_logging(&priv->uopts);

	blobmsg_add_string(&bb, "status", !ret ? "ok" : "fail");
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return ret == 0 ? UBUS_STATUS_OK : UBUS_STATUS_UNKNOWN_ERROR;
}

int i1905_ubus_iface_status(struct ubus_context *ctx, struct ubus_object *obj,
			    struct ubus_request_data *req, const char *method,
			    struct blob_attr *msg)
{
	//struct i1905_interface_private *ifp =
	//		container_of(obj, struct i1905_interface_private, obj);
	int ret = 0;

	//ret = i1905_get_interface_status(ifp);	// TODO

	return ret;
}

int i1905_ubus_iface_neighbors(struct ubus_context *ctx, struct ubus_object *obj,
			       struct ubus_request_data *req, const char *method,
			       struct blob_attr *msg)
{
	struct i1905_interface_private *ifp =
			container_of(obj, struct i1905_interface_private, obj);
	struct i1905_interface *iface = i1905_interface_priv(ifp);
	struct i1905_neighbor_interface *nif = NULL;
	struct blob_buf bb;
	void *a, *b;


	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	a = blobmsg_open_array(&bb, "neighbors");

	list_for_each_entry(nif, &iface->nbriflist, list) {
		b = blobmsg_open_table(&bb, "");
		blobmsg_add_macaddr(&bb, "aladdr", nif->aladdr);
		blobmsg_add_macaddr(&bb, "macaddress", nif->r_macaddr);
		blobmsg_add_string(&bb, "media", IS_MEDIA_WIFI(nif->media) ?
				   "wifi" : "ethernet");
		blobmsg_add_u8(&bb, "direct", nif->direct ? true : false);
		blobmsg_close_table(&bb, b);
	}

	blobmsg_close_array(&bb, a);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

/* cmdu tx policy */
enum {
	CMDU_TX_DST,		/* dest macaddress */
	CMDU_TX_SRC,		/* optional; interface's macaddress */
	CMDU_TX_TYPE,		/* can be in hex '0xaaaa' or int */
	CMDU_TX_MID,		/* optional; otherwise autogenerated */
	CMDU_TX_VID,		/* optional; vlanid for tagging frames */
	CMDU_TX_DATA,		/* tlv data in hexstring format */
	CMDU_TX_IFNAME,		/* use as outgoing interface if provided */
	NUM_CMDU_TX_POLICY,
};

static const struct blobmsg_policy cmdu_tx_policy[NUM_CMDU_TX_POLICY] = {
	[CMDU_TX_DST] = { .name = "dst", .type = BLOBMSG_TYPE_STRING },
	[CMDU_TX_SRC] = { .name = "src", .type = BLOBMSG_TYPE_STRING },
	[CMDU_TX_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_TX_MID] = { .name = "mid", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_TX_VID] = { .name = "vid", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_TX_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING },
	[CMDU_TX_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
};

int i1905_ubus_iface_cmdu_tx(struct ubus_context *ctx, struct ubus_object *obj,
			     struct ubus_request_data *req, const char *method,
			     struct blob_attr *msg)
{
	struct i1905_interface_private *ifp =
			container_of(obj, struct i1905_interface_private, obj);
	struct blob_attr *tb[NUM_CMDU_TX_POLICY];
	struct blob_buf bb = {};
	char dst_macstr[18] = {0};
	char src_macstr[18] = {0};
	uint8_t dst[6] = {0};
	uint8_t src[6] = {0};
	char *data_str = NULL;
	uint8_t *data = NULL;
	const char *ifname;
	uint16_t type = 0;
	uint16_t mid = 0;
	uint16_t vid = 0;
	int data_len = 0;
	int ret;


	ifname = ubus_object_to_ifname(obj);
	UNUSED(ifname);

	blobmsg_parse(cmdu_tx_policy, NUM_CMDU_TX_POLICY, tb,
					blob_data(msg), blob_len(msg));

	/* cmdu type and destination macaddress are mandatory */
	if (!tb[CMDU_TX_DST] || !tb[CMDU_TX_TYPE])
		return UBUS_STATUS_INVALID_ARGUMENT;


	if (tb[CMDU_TX_DST]) {
		strncpy(dst_macstr, blobmsg_data(tb[CMDU_TX_DST]),
			sizeof(dst_macstr)-1);

		if (hwaddr_aton(dst_macstr, dst) == NULL)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[CMDU_TX_SRC]) {
		strncpy(src_macstr, blobmsg_data(tb[CMDU_TX_SRC]),
			sizeof(src_macstr)-1);

		if (hwaddr_aton(src_macstr, src) == NULL)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[CMDU_TX_TYPE])
		type = blobmsg_get_u32(tb[CMDU_TX_TYPE]);

	if (tb[CMDU_TX_MID])
		mid = blobmsg_get_u32(tb[CMDU_TX_MID]);

	if (tb[CMDU_TX_VID]) {
		vid = (uint16_t)blobmsg_get_u32(tb[CMDU_TX_VID]);
		if (vid > 4094)
			vid = 0;
	}

	if (tb[CMDU_TX_DATA]) {
		int data_strlen;

		data_strlen = blobmsg_data_len(tb[CMDU_TX_DATA]);
		data_len = (data_strlen - 1) / 2;
		data_str = calloc(1, data_strlen * sizeof(char));
		data = calloc(1, data_len * sizeof(uint8_t));

		if (data_str && data) {
			strncpy(data_str, blobmsg_data(tb[CMDU_TX_DATA]), data_strlen);
			strtob(data_str, data_len, data);
		}
	}

	ret = i1905_cmdu_tx(ifp, vid, dst, src, type, &mid, data, data_len, false);

	if (data_str)
		free(data_str);

	if (data)
		free(data);


	/* reply with mid and status of cmdu tx */
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "status", ret == 0 ? "ok" : "fail");
	if (!ret)
		blobmsg_add_u32(&bb, "mid", mid);

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

	return 0;
}

int i1905_ubus_cmdu_tx(struct ubus_context *ctx, struct ubus_object *obj,
		       struct ubus_request_data *req, const char *method,
		       struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	struct blob_buf bb;
	int ret;

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

	ret = i1905_do_cmdu_tx(p, msg, &bb);
	if (ret) {
		if (ret == -EINVAL)
			ret = UBUS_STATUS_INVALID_ARGUMENT;

		goto out;
	}

	ubus_send_reply(ctx, req, bb.head);
out:
	blob_buf_free(&bb);
	return ret;
}

/* cmdu rx policy */
enum {
	CMDU_RX_SRC,		/* sender's macaddress */
	CMDU_RX_IFNAME,		/* receiving interface's name */
	CMDU_RX_TYPE,		/* in hex "0xaaaa" or int */
	CMDU_RX_MID,		/* optional; default is 0 */
	CMDU_RX_DATA,		/* data in hexstring representing tlvs */
	NUM_CMDU_RX_POLICY,
};

static const struct blobmsg_policy cmdu_rx_policy[NUM_CMDU_RX_POLICY] = {
	[CMDU_RX_SRC] = { .name = "src", .type = BLOBMSG_TYPE_STRING },
	[CMDU_RX_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
	[CMDU_RX_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
	[CMDU_RX_MID] = { .name = "mid", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_RX_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING },
};

int i1905_ubus_cmdu_rx(struct ubus_context *ctx, struct ubus_object *obj,
		       struct ubus_request_data *req, const char *method,
		       struct blob_attr *msg)
{
	struct i1905_private *priv = container_of(obj, struct i1905_private, obj);
	struct blob_buf bb;
	int ret;

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

	ret = i1905_do_cmdu_rx(priv, msg, &bb);
	if (ret) {
		if (ret == -EINVAL)
			ret = UBUS_STATUS_INVALID_ARGUMENT;

		goto out;
	}

	ubus_send_reply(ctx, req, bb.head);
out:
	blob_buf_free(&bb);
	return ret;
}

/* cmdu prepare policy */
enum {
	CMDU_PREP_TYPE,		/* cmdu type */
	CMDU_PREP_IFNAME,	/* interface name (optional) */
	CMDU_PREP_ARGS,		/* cmdu specific argument list */
	NUM_CMDU_PREP_POLICY,
};

static const struct blobmsg_policy cmdu_prep_policy[NUM_CMDU_PREP_POLICY] = {
	[CMDU_PREP_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_PREP_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
	[CMDU_PREP_ARGS] = { .name = "args", .type = BLOBMSG_TYPE_ARRAY },
};

int i1905_ubus_cmdu_prepare(struct ubus_context *ctx, struct ubus_object *obj,
			    struct ubus_request_data *req, const char *method,
			    struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	struct blob_buf bb;
	int ret;

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

	ret = i1905_do_cmdu_prepare(p, msg, &bb);
	if (ret) {
		if (ret == -EINVAL)
			ret = UBUS_STATUS_INVALID_ARGUMENT;
		else if (ret == -ENOTSUP)
			ret = UBUS_STATUS_NOT_SUPPORTED;

		goto out;
	}

	ubus_send_reply(ctx, req, bb.head);
out:
	blob_buf_free(&bb);

	return ret;
}

/* add/del interface policy */
enum {
	I1905_INTERFACE_IFNAME,		/* interface name */
	NUM_I1905_INTERFACE_POLICY,
};

static const struct blobmsg_policy interface_policy[NUM_I1905_INTERFACE_POLICY] = {
	[I1905_INTERFACE_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
};

int i1905_ubus_interface_add(struct ubus_context *ctx, struct ubus_object *obj,
			     struct ubus_request_data *req, const char *method,
			     struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	struct blob_attr *tb[NUM_I1905_INTERFACE_POLICY];
	struct blob_buf bb = {0};
	char ifname[16] = {0};
	int ret;

	blobmsg_parse(interface_policy, NUM_I1905_INTERFACE_POLICY, tb,
		      blob_data(msg), blob_len(msg));

	if (!tb[I1905_INTERFACE_IFNAME])
		return UBUS_STATUS_INVALID_ARGUMENT;

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


	blob_buf_init(&bb, 0);
	ret = i1905_interface_add(p, msg, &bb);
	if (ret) {
		if (ret == -EINVAL)
			return UBUS_STATUS_INVALID_ARGUMENT;

		goto out;
	}

	ret = i1905_publish_interface_object(p, ifname);

out:
	blob_buf_free(&bb);
	return ret;
}

int i1905_ubus_interface_del(struct ubus_context *ctx, struct ubus_object *obj,
			     struct ubus_request_data *req, const char *method,
			     struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	struct blob_attr *tb[NUM_I1905_INTERFACE_POLICY];
	struct blob_buf bb = {0};
	char ifname[16] = {0};
	int ret;

	blobmsg_parse(interface_policy, NUM_I1905_INTERFACE_POLICY, tb,
		      blob_data(msg), blob_len(msg));

	if (!tb[I1905_INTERFACE_IFNAME])
		return UBUS_STATUS_INVALID_ARGUMENT;

	strncpy(ifname, blobmsg_data(tb[I1905_INTERFACE_IFNAME]), 16);
	ifname[15] = '\0';
	ret = i1905_remove_interface_object(p, ifname);

	blob_buf_init(&bb, 0);
	i1905_interface_del(p, msg, &bb);
	blob_buf_free(&bb);

	return ret;
}

#define MAX_IFACE_METHODS	8
static int add_iface_methods(struct ubus_object *iface_obj)
{
	struct ubus_method *iface_methods;
	int n_methods = 0;

	iface_methods = calloc(MAX_IFACE_METHODS, sizeof(struct ubus_method));
	if (!iface_methods)
		return -ENOMEM;

#define UBUS_METHOD_ADD(_tab, iter, __m)				\
do {									\
	struct ubus_method ___m = __m;					\
	memcpy(&_tab[iter++], &___m, sizeof(struct ubus_method));	\
} while(0)


	UBUS_METHOD_ADD(iface_methods, n_methods,
			UBUS_METHOD_NOARG("status", i1905_ubus_iface_status));

	UBUS_METHOD_ADD(iface_methods, n_methods,
			UBUS_METHOD_NOARG("neighbors", i1905_ubus_iface_neighbors));

	UBUS_METHOD_ADD(iface_methods, n_methods,
			UBUS_METHOD("cmdu", i1905_ubus_iface_cmdu_tx, cmdu_tx_policy));

#undef UBUS_METHOD_ADD

	iface_obj->methods = iface_methods;
	iface_obj->n_methods = n_methods;

	return 0;
}

static void free_iface_methods(struct ubus_object *iface_obj)
{
	if (iface_obj && iface_obj->methods)
		free((void *)iface_obj->methods);
}

int i1905_remove_interface_objects(struct i1905_private *priv)
{
	struct i1905_interface *iface = NULL;
	struct i1905_interface_private *ifpriv;
	int ret = 0;

	if (!priv)
		return -1;

	list_for_each_entry(iface, &priv->dm.self.iflist, list) {
		ifpriv = (struct i1905_interface_private *)iface->priv;

		if (ifpriv->obj.id != OBJECT_INVALID) {
			free_iface_methods(&ifpriv->obj);
			ret = ubus_remove_object(priv->ctx, &ifpriv->obj);
			if (ret)
				err("Failed to delete; err = %s\n", ubus_strerror(ret));

			ifpriv->obj.id = OBJECT_INVALID;
			free((void *)ifpriv->obj.name);
		}
	}

	return ret;
}

int i1905_publish_interface_objects(struct i1905_private *p)
{
	struct i1905_interface *iface = NULL;
	struct i1905_interface_private *priv;
	char objname[64] = {0};
	int ret = 0;


	list_for_each_entry(iface, &p->dm.self.iflist, list) {
		if (iface->exclude)
			continue;

		priv = (struct i1905_interface_private *)iface->priv;
		snprintf(objname, 63, "%s.al.%s", IEEE1905_OBJECT, iface->ifname);
		add_iface_methods(&priv->obj);

		priv->obj.name = strdup(objname);
		priv->obj_type.name = priv->obj.name;
		priv->obj_type.n_methods = priv->obj.n_methods;
		priv->obj_type.methods = priv->obj.methods;
		priv->obj.type = &priv->obj_type;

		ret = ubus_add_object(p->ctx, &priv->obj);
		if (ret) {
			err("Failed to add '%s' err = %s\n", objname,
			    ubus_strerror(ret));
			free((char *)priv->obj.name);
			priv->obj.name = NULL;
			goto out_rollback;
		}

		dbg("Added object '%s'\n", objname);
	}

	return ret;

out_rollback:
	//TODO: i1905_remove_interface_objects(p);
	return ret;
}

/* vlan policy */
enum {
	I1905_VLAN_ID,		/* vlan id for tagging */
	NUM_I1905_VLAN_POLICY,
};

static const struct blobmsg_policy vlan_policy[NUM_I1905_VLAN_POLICY] = {
	[I1905_VLAN_ID] = { .name = "vid", .type = BLOBMSG_TYPE_INT32 },
};

int i1905_ubus_vlan(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	struct blob_attr *tb[NUM_I1905_VLAN_POLICY];
	struct i1905_config *cfg = &p->cfg;
	int ret = 0;


	blobmsg_parse(vlan_policy, NUM_I1905_VLAN_POLICY, tb,
		      blob_data(msg), blob_len(msg));

	if (!tb[I1905_VLAN_ID]) {
		struct blob_buf bb;
		void *a;

		memset(&bb, 0, sizeof(bb));
		blob_buf_init(&bb, 0);
		a = blobmsg_open_table(&bb, "vlan");
		blobmsg_add_u32(&bb, "vid", cfg->primary_vid);
		blobmsg_close_table(&bb, a);

		ubus_send_reply(ctx, req, bb.head);
		blob_buf_free(&bb);
	} else {
		uint16_t vid;

		vid = (uint16_t)blobmsg_get_u32(tb[I1905_VLAN_ID]);
		if (vid > 4094) {
			ret = -1;
		} else {
			struct i1905_selfdevice *self = &p->dm.self;
			struct i1905_interface *iface;

			cfg->primary_vid = vid;
			list_for_each_entry(iface, &self->iflist, list) {
				iface->vid = vid;
			}
		}
	}

	return ret;
}

/* cmdu fragmentation policy */
enum {
	CMDU_FRAG_DST,		/* macaddress of the receiving node */
	CMDU_FRAG_SCHEME,	/* scheme = 1 to fragment at octet-boundary */
	NUM_CMDU_FRAG_POLICY,
};

static const struct blobmsg_policy cmdu_frag_policy[NUM_CMDU_FRAG_POLICY] = {
	[CMDU_FRAG_DST] = { .name = "dst", .type = BLOBMSG_TYPE_STRING },
	[CMDU_FRAG_SCHEME] = { .name = "mode", .type = BLOBMSG_TYPE_INT32 },
};

int i1905_ubus_cmdu_fragment_scheme(struct ubus_context *ctx,
				    struct ubus_object *obj,
				    struct ubus_request_data *req,
				    const char *method,
				    struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, obj);
	struct blob_attr *tb[NUM_CMDU_FRAG_POLICY];
	struct blob_buf bb;
	char dst_macstr[18] = {0};
	uint8_t dst[6] = {0};
	uint8_t scheme;
	int ret;

	blobmsg_parse(cmdu_frag_policy, NUM_CMDU_FRAG_POLICY, tb, blob_data(msg),
		      blob_len(msg));

	/* destination macaddress is mandatory */
	if (!tb[CMDU_FRAG_DST])
		return -EINVAL;

	if (tb[CMDU_FRAG_DST]) {
		strncpy(dst_macstr, blobmsg_data(tb[CMDU_FRAG_DST]),
			sizeof(dst_macstr)-1);

		if (hwaddr_aton(dst_macstr, dst) == NULL)
			return -EINVAL;

		if (hwaddr_is_zero(dst) || hwaddr_is_mcast(dst) || hwaddr_is_bcast(dst))
			return -EINVAL;
	}

	if (tb[CMDU_FRAG_SCHEME]) {
		scheme = (uint8_t)blobmsg_get_u32(tb[CMDU_FRAG_SCHEME]);
	} else {
#if (EASYMESH_VERSION >= 3)
		scheme = CMDU_FRAG_SCHEME_BOUNDARY_OCTET;
#else
		scheme = CMDU_FRAG_SCHEME_BOUNDARY_TLV;
#endif
	}
	ret = i1905_set_fragment_scheme(p, dst, scheme);

	memset(&bb, 0, sizeof(bb));
	/* reply with status */
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "status", ret == 0 ? "ok" : "fail");

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


enum {
	I1905_EXT_NAME,
	NUM_I1905_EXT_POLICY,
};

static const struct blobmsg_policy extension_policy[NUM_I1905_EXT_POLICY] = {
	[I1905_EXT_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
};

int i1905_ubus_extension_cmd(struct ubus_context *ctx, struct ubus_object *obj,
			     struct ubus_request_data *req, const char *method,
			     struct blob_attr *msg)
{
	struct i1905_private *p = container_of(obj, struct i1905_private, objext);
	struct blob_attr *tb[NUM_I1905_EXT_POLICY];
	int ret = UBUS_STATUS_INVALID_ARGUMENT;
	char extname[64] = { 0 };


	blobmsg_parse(extension_policy, NUM_I1905_EXT_POLICY, tb,
			blob_data(msg), blob_len(msg));


	if (!tb[I1905_EXT_NAME]) {
		if (!strcmp(method, "list")) {
			struct i1905_extmodule *m = NULL;
			struct blob_buf bb;
			void *a, *b;

			memset(&bb, 0, sizeof(bb));
			blob_buf_init(&bb, 0);
			a = blobmsg_open_array(&bb, "extensions");

			list_for_each_entry(m, &p->extlist, list) {
				char tmpstr[8] = {0};

				b = blobmsg_open_table(&bb, "");
				blobmsg_add_string(&bb, "name", m->name);
				blobmsg_add_string(&bb, "status", m->paused ?
						   "paused" : "active");
				blobmsg_add_u8(&bb, "extends",
					       m->num_ext > 0 ? true : false);
				sprintf(tmpstr, "0x%04x", m->from_newtype);
				blobmsg_add_string(&bb, "newcmdu_from", tmpstr);
				sprintf(tmpstr, "0x%04x", m->to_newtype);
				blobmsg_add_string(&bb, "newcmdu_upto", tmpstr);
				blobmsg_close_table(&bb, b);
			}

			blobmsg_close_array(&bb, a);
			ubus_send_reply(ctx, req, bb.head);
			blob_buf_free(&bb);
			ret = 0;
		}

		return ret;
	}

	strncpy(extname, blobmsg_data(tb[I1905_EXT_NAME]), 63);

	if (!strcmp(method, "load"))
		ret = i1905_extension_register(p, extname);
	else if (!strcmp(method, "unload"))
		ret = i1905_extension_unregister(p, extname);
	else if (!strcmp(method, "start"))
		ret = i1905_extension_start(p, extname);
	else if (!strcmp(method, "stop"))
		ret = i1905_extension_stop(p, extname);

	return ret;
}

int i1905_publish_extension_object(struct i1905_private *p, const char *objname)
{
	struct ubus_object *obj;
	struct ubus_object_type *obj_type;
	struct ubus_method *obj_methods;
	struct ubus_method m[5] = {
		UBUS_METHOD("load", i1905_ubus_extension_cmd, extension_policy),
		UBUS_METHOD("unload", i1905_ubus_extension_cmd, extension_policy),
		UBUS_METHOD("start", i1905_ubus_extension_cmd, extension_policy),
		UBUS_METHOD("stop", i1905_ubus_extension_cmd, extension_policy),
		UBUS_METHOD_NOARG("list", i1905_ubus_extension_cmd),
	};
	int num_methods = ARRAY_SIZE(m);
	int ret;


	obj = &p->objext;
	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 = strdup(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(p->ctx, obj);
	if (ret) {
		i1905_dbg(LOG_DM, "Failed to add '%s' err = %s\n",
			  ubus_strerror(ret), objname);
		free((char *)obj->name);
		obj->name = NULL;
		free(obj_methods);
		free(obj_type);

		return ret;
	}

	i1905_dbg(LOG_DM, "Added object '%s'\n", objname);

	return 0;
}

int i1905_publish_object(struct i1905_private *p, const char *objname)
{
	struct ubus_object *obj;
	struct ubus_object_type *obj_type;
	struct ubus_method *obj_methods;
	int ret;
	struct ubus_method m[19] = {
		UBUS_METHOD_NOARG("id", i1905_ubus_aladdr),
		UBUS_METHOD("log", i1905_ubus_log, log_policy),
		UBUS_METHOD_NOARG("start", i1905_ubus_start),
		UBUS_METHOD_NOARG("stop", i1905_ubus_stop),
		UBUS_METHOD_NOARG("status", i1905_ubus_status),
		UBUS_METHOD_NOARG("info", i1905_ubus_info),
		UBUS_METHOD_NOARG("neighbors", i1905_ubus_neighbors),
		UBUS_METHOD_NOARG("links", i1905_ubus_links),
		UBUS_METHOD_NOARG("others", i1905_ubus_non1905neighbors),
		UBUS_METHOD_NOARG("arptable", i1905_ubus_show_arptable),
		UBUS_METHOD("apconfig", i1905_ubus_apconfig, apconfig_policy),
		UBUS_METHOD_NOARG("refresh", i1905_ubus_refresh),
		UBUS_METHOD("cmdu", i1905_ubus_cmdu_tx, cmdu_tx_policy),
		UBUS_METHOD("buildcmdu", i1905_ubus_cmdu_prepare, cmdu_prep_policy),
		UBUS_METHOD("rxcmdu", i1905_ubus_cmdu_rx, cmdu_rx_policy),
		UBUS_METHOD("add_interface", i1905_ubus_interface_add, interface_policy),
		UBUS_METHOD("del_interface", i1905_ubus_interface_del, interface_policy),
		UBUS_METHOD("vlan", i1905_ubus_vlan, vlan_policy),
		UBUS_METHOD("frag_scheme", i1905_ubus_cmdu_fragment_scheme, cmdu_frag_policy),
	};
	int num_methods = ARRAY_SIZE(m);

	if (!p->ctx) {
		dbg("%s: connect to ubus!\n", __func__);
		p->ctx = ubus_connect(NULL);
		if (!p->ctx) {
			dbg("Failed to connect to ubus\n");
			return -1;
		}
		ubus_add_uloop(p->ctx);
	}

	obj = &p->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 = strdup(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(p->ctx, obj);
	if (ret) {
		err("Failed to add '%s' err = %s\n", objname, ubus_strerror(ret));
		free(obj_methods);
		free(obj_type);

		return ret;
	}

	dbg("Added object '%s'\n", objname);
	i1905_publish_interface_objects(p);
	i1905_publish_extension_object(p, IEEE1905_OBJECT_EXT);
	return 0;
}

static int i1905_add_ubus_interface_object(struct i1905_private *priv, const char *ifname)
{
	struct i1905_interface *iface = NULL;
	char objname[64] = {0};
	int ret = 0;

	list_for_each_entry(iface, &priv->dm.self.iflist, list) {
		struct i1905_interface_private *ifpriv;

		if (memcmp(iface->ifname, ifname, 16))
			continue;

		if (iface->exclude)
			continue;

		iface->invalid = false;
		ifpriv = (struct i1905_interface_private *)iface->priv;
		snprintf(objname, 63, "%s.al.%s", IEEE1905_OBJECT, iface->ifname);
		ret = add_iface_methods(&ifpriv->obj);
		if (ret)
			return -1;

		ifpriv->obj.name = strdup(objname);
		ifpriv->obj_type.name = ifpriv->obj.name;
		ifpriv->obj_type.n_methods = ifpriv->obj.n_methods;
		ifpriv->obj_type.methods = ifpriv->obj.methods;
		ifpriv->obj.type = &ifpriv->obj_type;

		ret = ubus_add_object(priv->ctx, &ifpriv->obj);
		if (ret) {
			dbg("Failed to publish '%s', err = %s\n", objname,
			    ubus_strerror(ret));
		} else {
			dbg("Added object '%s'\n", objname);
		}
		break;
	}

	return ret;
}

static int i1905_del_ubus_interface_object(struct i1905_private *priv, const char *ifname)
{
	struct i1905_interface *iface = NULL;
	int ret = -1;

	list_for_each_entry(iface, &priv->dm.self.iflist, list) {
		struct i1905_interface_private *ifpriv;

		if (memcmp(iface->ifname, ifname, 16))
			continue;

		iface->invalid = true;
		ifpriv = (struct i1905_interface_private *)iface->priv;
		if (ifpriv->obj.id != OBJECT_INVALID) {
			free_iface_methods(&ifpriv->obj);
			ret = ubus_remove_object(priv->ctx, &ifpriv->obj);
			if (ret) {
				warn("Failed to delete '%s', err = %s\n",
				     ifpriv->obj.name, ubus_strerror(ret));
			} else {
				dbg("Removed object '%s'\n", ifpriv->obj.name);
			}
			ifpriv->obj.id = OBJECT_INVALID;
			free((void *)ifpriv->obj.name);
			break;
		}
	}

	return ret;
}

int i1905_publish_interface_object(struct i1905_private *priv, const char *ifname)
{
	int ret = 0;

	if (!priv || !ifname)
		return -1;

	if (!if_isbridge(ifname))
		return i1905_add_ubus_interface_object(priv, ifname);

	return ret;
}

int i1905_remove_interface_object(struct i1905_private *priv, const char *ifname)
{
	int ret = -1;

	if (!priv || !ifname)
		return -1;

	if (!if_isbridge(ifname))
		return i1905_del_ubus_interface_object(priv, ifname);

	return ret;
}

int i1905_remove_object(struct i1905_private *p)
{
	i1905_remove_interface_objects(p);

	if (p->ctx) {
		if (p->objext.id != OBJECT_INVALID) {
			ubus_remove_object(p->ctx, &p->objext);
			free(p->objext.type);
			free((void *)p->objext.methods);
			free((void *)p->objext.name);
		}

		if (p->obj.id != OBJECT_INVALID) {
			ubus_remove_object(p->ctx, &p->obj);
			free(p->obj.type);
			free((void *)p->obj.methods);
			free((void *)p->obj.name);
		}
	}

	return 0;
}

static void i1905_wifi_sta_event_handler(struct i1905_private *p,
					 struct blob_attr *msg)
{
	char ifname[16] = {0}, event[16] = {0};
	struct blob_attr *tb[3];
	static const struct blobmsg_policy ev_attr[3] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};
	bool add = false, del = false;


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

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

	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname) - 1);
	strncpy(event, blobmsg_data(tb[1]), sizeof(event) - 1);

	add = !strcmp(event, "connected");
	del = !strcmp(event, "disconnected");

	if (add || del) {
		struct blob_attr *data[1];
		static const struct blobmsg_policy data_attr[1] = {
			[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		};
		char mac_str[18] = {0};
		uint8_t mac[6] = {0};

		blobmsg_parse(data_attr, 1, data, blobmsg_data(tb[2]),
			      blobmsg_data_len(tb[2]));

		if (!data[0])
			return;

		strncpy(mac_str, blobmsg_data(data[0]), sizeof(mac_str) - 1);
		if (!hwaddr_aton(mac_str, mac))
			return;

		if (add) {
			dbg("%s: STA Connected\n", __func__);
			neigh_enqueue(&p->neigh_q, mac,
				      NEIGH_STATE_REACHABLE,
				      ifname,
				      NEIGH_TYPE_WIFI,
				      NULL,
				      NEIGH_AGEOUT_DEFAULT,
				      NULL);

			i1905_extmodules_notify(p, IEEE1905_TOPOLOGY_CHANGED,
						ifname, 1, 0, mac);
		} else if (del) {
			dbg("%s: STA Disconnected\n", __func__);
			neigh_dequeue(&p->neigh_q, mac, NULL);

			i1905_extmodules_notify(p, IEEE1905_TOPOLOGY_CHANGED,
						ifname, 0, 0, mac);
		}

		i1905_send_topology_notification(p, ifname);
	}
}

static void i1905_ethport_event_handler(struct i1905_private *p,
					struct blob_attr *msg)
{
	static const struct blobmsg_policy ev_attr[4] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "link", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "speed", .type = BLOBMSG_TYPE_TABLE },
		[3] = { .name = "duplex", .type = BLOBMSG_TYPE_TABLE },
	};
	char ifname[16] = {0}, link[8] = {0};
	struct blob_attr *tb[4];
	bool up, down;


	blobmsg_parse(ev_attr, 4, tb, blob_data(msg), blob_len(msg));
	if (!tb[0] || !tb[1])
		return;

	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname) - 1);
	strncpy(link, blobmsg_data(tb[1]), sizeof(link) - 1);

	up = !strcmp(link, "up");
	down = !strcmp(link, "down");

	//TODO
	UNUSED(up);
	UNUSED(down);

	return;
}

static void i1905_wifi_event_handler(struct ubus_context *ctx,
				     struct ubus_event_handler *e,
				     const char *type, struct blob_attr *msg)
{
	struct i1905_private *p = container_of(e, struct i1905_private, evh);
	struct wifi_ev_handler {
		const char *type;
		void (*handler)(struct i1905_private *, struct blob_attr *);
	} evs[] = {
		{ "wifi.sta", i1905_wifi_sta_event_handler },
		{ "ethport", i1905_ethport_event_handler },
	};
	char *str;
	int i;


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

	//info("Received event: [%s] event = '%s'\n", type, str);
	free(str);

	for (i = 0; i < ARRAY_SIZE(evs); i++) {
		if (!strcmp(type, evs[i].type)) {
			evs[i].handler(p, msg);
			break;
		}
	}
}

int i1905_register_misc_events(struct i1905_private *p)
{
	if (!p || !p->ctx)
		return -1;

	p->evh.cb = i1905_wifi_event_handler;
	return ubus_register_event_handler(p->ctx, &p->evh, "wifi.*");
}

int i1905_unregister_misc_events(struct i1905_private *p)
{
	if (p)
		ubus_unregister_event_handler(p->ctx, &p->evh);

	return 0;
}
