/*
 * ubus_utils.c - ubus methods and utility functions
 *
 * Copyright (C) 2022, IOPSYS Software Solutions AB.
 *
 * Author: suvendhu.hansa@iopsys.eu
 *
 * See LICENSE file for license related information
 *
 */

#include "ubus_utils.h"
#include "log.h"
#include "sched_inform.h"
#include "event.h"
#include "session.h"
#include "cwmp_event.h"
#include "autonomous_complpolicy.h"
#include "heartbeat.h"

typedef int (*callback)(struct blob_buf *b);

static bool g_bbf_object_available = false;
static struct ubus_context *ubus_ctx = NULL;
struct uloop_timeout u_timeout;
static int ubus_timeout = DEFAULT_UBUS_TIMEOUT;

static int icwmp_register_object(struct ubus_context *ctx);

struct command_cb {
	char *str;
	callback cb;
	char *help;
};

struct get_result {
	struct blob_buf bb;
	bool service_added;
};

static const char *arr_session_status[] = {
	[SESSION_WAITING] = "waiting",
	[SESSION_RUNNING] = "running",
	[SESSION_FAILURE] = "failure",
	[SESSION_SUCCESS] = "success",
};


static void interface_update_handler(struct ubus_context *ctx __attribute__((unused)),
			      struct ubus_event_handler *ev __attribute__((unused)),
			      const char *type __attribute__((unused)), struct blob_attr *msg)
{
	if (!msg)
		return;

	const struct blobmsg_policy p[2] = {
		{ "interface", BLOBMSG_TYPE_STRING },
		{ "action", BLOBMSG_TYPE_STRING },
	};

	struct blob_attr *tb[2] = {NULL};
	blobmsg_parse(p, 2, tb, blob_data(msg), blob_len(msg));

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

	const char *intf_name = blobmsg_get_string(tb[0]);
	const char *intf_up = blobmsg_get_string(tb[1]);

	if (CWMP_STRCMP(intf_up, "ifup") != 0 || CWMP_STRCMP(cwmp_ctx.conf.default_wan_iface, intf_name) != 0)
		return;

	/* If the last session was failure then schedule a session */
	if (cwmp_ctx.session->session_status.last_status == SESSION_FAILURE) {
		CWMP_LOG(INFO, "Schedule session for interface_update on %s, since last session was failure", intf_name);
		trigger_cwmp_session_timer();
	}
}

static int reload_cmd(struct blob_buf *b)
{
	CWMP_LOG(INFO, "triggered ubus reload");
	if (cwmp_ctx.session->session_status.last_status == SESSION_RUNNING) {
		blobmsg_add_u32(b, "status", 0);
		blobmsg_add_string(b, "info", "Session running, reload at the end of the session");
	} else {
		int error = cwmp_apply_acs_changes();
		if (error != CWMP_OK) {
			// Failed to load cwmp config
			CWMP_LOG(ERROR, "cwmp failed to reload the configuration");
			blobmsg_add_u32(b, "status", -1);
			blobmsg_add_string(b, "info", "icwmpd config reload failed");
		} else {
			blobmsg_add_u32(b, "status", 0);
			blobmsg_add_string(b, "info", "icwmpd config reloaded");

			if (cwmp_ctx.cr_ip_port_change == true) {
				CWMP_LOG(INFO, "Allowed CR IPs are changed");
				apply_allowed_cr_ip_port();
				cwmp_ctx.cr_ip_port_change = false;
			}

			if (cwmp_ctx.acs_changed) {
				CWMP_LOG(INFO, "%s: Schedule session with new ACS since URL changed", __func__);
				uloop_timeout_cancel(&session_timer);
				cwmp_ctx.retry_count_session = 0;
				uloop_timeout_cancel(&heartbeat_session_timer);
				cwmp_ctx.session->session_status.next_heartbeat = true;
				cwmp_ctx.session->session_status.is_heartbeat = false;
				trigger_cwmp_session_timer();
				cwmp_ctx.acs_changed = false;
			}
		}
	}

	return 0;
}

static struct command_cb cmd_cb[] ={
	{ "reload", reload_cmd, "Reload icwmpd with new configuration" }
};

static int call_command_cb(char *cmd, struct blob_buf *b)
{
	int cmd_num, i;
	callback cb = NULL;

	if (cmd == NULL || b == NULL)
		return -1;

	cmd_num = sizeof(cmd_cb)/sizeof(struct command_cb);
	for (i = 0; i < cmd_num; i++) {
		if (CWMP_STRCMP(cmd, cmd_cb[i].str) == 0) {
			cb = cmd_cb[i].cb;
			break;
		}
	}

	if (cb == NULL) {
		char info[128] = {0};
		if (snprintf(info, sizeof(info), "\'%s\' is not supported. Check the supported commands.", cmd) == -1)
			return -1;

		blobmsg_add_u32(b, "status", -1);
		blobmsg_add_string(b, "info", info);
		return 0;
	}

	return cb(b);
}

enum command
{
	COMMAND_NAME,
	__COMMAND_MAX
};

static const struct blobmsg_policy icwmp_cmd_policy[] = {
	[COMMAND_NAME] = {.name = "command", .type = BLOBMSG_TYPE_STRING },
};

static int icwmp_command_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), struct ubus_request_data *req, const char *method __attribute__((unused)), struct blob_attr *msg)
{
	if (ctx == NULL)
		return -1;
	struct blob_attr *tb[__COMMAND_MAX] = {0};
	struct blob_buf blob_command;

	CWMP_MEMSET(&blob_command, 0, sizeof(struct blob_buf));
	blob_buf_init(&blob_command, 0);

	int ret = blobmsg_parse(icwmp_cmd_policy, ARRAY_SIZE(icwmp_cmd_policy), tb, blob_data(msg), blob_len(msg));
	if (ret != 0) {
		int i;
		int cmd_num = sizeof(cmd_cb)/sizeof(struct command_cb);
		void *arr = blobmsg_open_array(&blob_command, "SupportedCommands");
		for (i = 0; i < cmd_num; i++) {
			void *tbl_in = blobmsg_open_table(&blob_command, "");
			bb_add_string(&blob_command, "command", cmd_cb[i].str);
			bb_add_string(&blob_command, "description", cmd_cb[i].help);
			blobmsg_close_table(&blob_command, tbl_in);
		}
		blobmsg_close_array(&blob_command, arr);
	} else {
		char *cmd = blobmsg_get_string(tb[COMMAND_NAME]);
		call_command_cb(cmd, &blob_command);
	}

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

	return 0;
}

static time_t get_nonzero_min_time(time_t time1, time_t time2, time_t time3)
{
	time_t arr[] = { time1, time2, time3 };
	time_t min = 0;
	int i;
	int size = sizeof(arr)/sizeof(time_t);

	for (i = 0; i < size && arr[i] == 0; i++) {} // find the first non zero element

	if (i == size) {
		return min; // array has no non-zero values
	}

	min = arr[i];
	for (; i < size; i++) {
		if (arr[i] != 0 && arr[i] < min)
			min = arr[i];
	}

	return min;
}

static time_t get_next_session_time()
{
	time_t sched_time = 0;
	if (list_schedule_inform.next != &(list_schedule_inform)) {
		struct schedule_inform *schedule_inform;
		schedule_inform = list_entry(list_schedule_inform.next, struct schedule_inform, list);
		sched_time = schedule_inform->scheduled_time;
	}

	time_t next_time = get_nonzero_min_time(sched_time, cwmp_ctx.session->session_status.next_retry, cwmp_ctx.session->session_status.next_periodic);

	return next_time;
}

static void bb_add_icwmp_status(struct blob_buf *bb)
{
	if (bb == NULL) {
		CWMP_LOG(ERROR, "icwmp status blob is null");
		return;
	}

	char start_time[26] = {0};
	get_time(cwmp_ctx.start_time, start_time, sizeof(start_time));

	void *tbl = blobmsg_open_table(bb, "cwmp");
	bb_add_string(bb, "status", "up");
	bb_add_string(bb, "start_time", start_time);
	bb_add_string(bb, "acs_url", cwmp_ctx.conf.acs_url);
	blobmsg_close_table(bb, tbl);
}

static void bb_add_icwmp_last_session(struct blob_buf *bb)
{

	char start_time[26] = {0};
	char end_time[26] = {0};

	void *tbl = blobmsg_open_table(bb, "last_session");
	const char *status = cwmp_ctx.session->session_status.last_start_time ? arr_session_status[cwmp_ctx.session->session_status.last_status] : "N/A";

	if (cwmp_ctx.session->session_status.last_start_time) {
		get_time(cwmp_ctx.session->session_status.last_start_time, start_time, sizeof(start_time));
	} else {
		snprintf(start_time, sizeof(start_time), "N/A");
	}

	if (cwmp_ctx.session->session_status.last_end_time) {
		get_time(cwmp_ctx.session->session_status.last_end_time, end_time, sizeof(end_time));
	} else {
		snprintf(end_time, sizeof(end_time), "N/A");
	}

	bb_add_string(bb, "status", status);
	bb_add_string(bb, "start_time", start_time);
	bb_add_string(bb, "end_time", end_time);
	blobmsg_close_table(bb, tbl);
}

static void bb_add_icwmp_next_session(struct blob_buf *bb)
{
	char next_time[26] = {0};
	time_t ntime = get_next_session_time();

	if (ntime) {
		get_time(ntime, next_time, sizeof(next_time));
	} else {
		snprintf(next_time, sizeof(next_time), "N/A");
	}
	
	void *tbl = blobmsg_open_table(bb, "next_session");
	bb_add_string(bb, "status", arr_session_status[SESSION_WAITING]);
	bb_add_string(bb, "start_time", next_time);
	bb_add_string(bb, "end_time", "N/A");
	blobmsg_close_table(bb, tbl);
}

static void bb_add_icwmp_statistics(struct blob_buf *bb)
{
	void *tbl = blobmsg_open_table(bb, "statistics");
	blobmsg_add_u32(bb, "success_sessions", cwmp_ctx.session->session_status.success_session);
	blobmsg_add_u32(bb, "failure_sessions", cwmp_ctx.session->session_status.failure_session);
	blobmsg_add_u32(bb, "total_sessions", cwmp_ctx.session->session_status.success_session + cwmp_ctx.session->session_status.failure_session);
	blobmsg_close_table(bb, tbl);

}

static int icwmp_status_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), struct ubus_request_data *req, const char *method __attribute__((unused)), struct blob_attr *msg __attribute__((unused)))
{
	struct blob_buf bb;

	CWMP_MEMSET(&bb, 0, sizeof(struct blob_buf));
	blob_buf_init(&bb, 0);

	bb_add_icwmp_status(&bb);
	bb_add_icwmp_last_session(&bb);
	bb_add_icwmp_next_session(&bb);
	bb_add_icwmp_statistics(&bb);

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

	return 0;
}

enum enum_inform
{
	INFORM_GET_RPC_METHODS,
	INFORM_EVENT,
	__INFORM_MAX
};

static const struct blobmsg_policy icwmp_inform_policy[] = {
	[INFORM_GET_RPC_METHODS] = {.name = "GetRPCMethods", .type = BLOBMSG_TYPE_BOOL },
	[INFORM_EVENT] = {.name = "event", .type = BLOBMSG_TYPE_STRING },
};

static int icwmp_inform_get_rpc_method(struct blob_buf *bb)
{
	if (cwmp_ctx.conf.acs_getrpc && cwmp_add_session_rpc_acs(RPC_ACS_GET_RPC_METHODS) == NULL)
		return -1;

	blobmsg_add_u32(bb, "status", 1);
	blobmsg_add_string(bb, "info", "Session with GetRPCMethods will start");

	return EVENT_IDX_2PERIODIC;
}

static int icwmp_inform_event(struct blob_buf *bb, const char *event)
{
	int event_code = cwmp_get_int_event_code(event);
	if (event_code != -1) {
		if (cwmp_ctx.session->session_status.last_status == SESSION_RUNNING) {
			blobmsg_add_u32(bb, "status", 1);
			blobmsg_add_string(bb, "info", "Session already running, event will be sent at the end of the session");
		} else {
			blobmsg_add_u32(bb, "status", 1);
			blobmsg_add_string(bb, "info", "Session started");
		}
	}
	return event_code;
}

static int icwmp_inform_handler(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)), struct ubus_request_data *req, const char *method __attribute__((unused)), struct blob_attr *msg)
{
	struct blob_buf bb;
	CWMP_MEMSET(&bb, 0, sizeof(struct blob_buf));
	blob_buf_init(&bb, 0);

	struct blob_attr *tb[__INFORM_MAX] = {0};
	bool is_get_rpc = false;
	const char *event = "";
	int event_code;

	int ret = blobmsg_parse(icwmp_inform_policy, ARRAY_SIZE(icwmp_inform_policy), tb, blob_data(msg), blob_len(msg));

	if (ret == 0 && tb[INFORM_GET_RPC_METHODS] != NULL) {
		is_get_rpc = blobmsg_get_u8(tb[INFORM_GET_RPC_METHODS]);
	}

	if (ret == 0 && tb[INFORM_EVENT] != NULL) {
		event = blobmsg_get_string(tb[INFORM_EVENT]);
	}

	if (is_get_rpc) {
		event_code = icwmp_inform_get_rpc_method(&bb);
	} else {
		event_code = icwmp_inform_event(&bb, event);
	}
	if (event_code == -1) {
		CWMP_LOG(WARNING, "tr069 ubus: ubus inform method not able to get the event code");
		blobmsg_add_u32(&bb, "status", -1);
		blobmsg_add_string(&bb, "info", "not able to get the event code");
		goto end;
	}

	struct session_timer_event *ubus_inform_event = calloc(1, sizeof(struct session_timer_event));

	ubus_inform_event->session_timer_evt.cb = cwmp_schedule_session_with_event;
	ubus_inform_event->event = event_code;
	trigger_cwmp_session_timer_with_event(&ubus_inform_event->session_timer_evt);

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

static const struct ubus_method icwmp_methods[] = {
	UBUS_METHOD("command", icwmp_command_handler, icwmp_cmd_policy),
	UBUS_METHOD_NOARG("status", icwmp_status_handler),
	UBUS_METHOD("inform", icwmp_inform_handler, icwmp_inform_policy),
};

static struct ubus_object_type tr069_object_type = UBUS_OBJECT_TYPE("icwmpd", icwmp_methods);

static struct ubus_object tr069_object = {
	.name = "tr069",
	.type = &tr069_object_type,
	.methods = icwmp_methods,
	.n_methods = ARRAY_SIZE(icwmp_methods),
};

int icwmp_register_object(struct ubus_context *ctx)
{
	return ubus_add_object(ctx, &tr069_object);
}

void bb_add_string(struct blob_buf *bb, const char *name, const char *value)
{
	if (bb == NULL)
		return;

	if (value)
		blobmsg_add_string(bb, name, value);
	else
		blobmsg_add_string(bb, name, "");
}

int icwmp_connect_ubus()
{
	ubus_ctx = ubus_connect(NULL);
	if (!ubus_ctx)
		return -1;

	return 0;
}

int icwmp_uloop_ubus_register()
{
	if (!ubus_ctx)
		return -1;

	ubus_add_uloop(ubus_ctx);

	if (icwmp_register_object(ubus_ctx))
		return -1;

	return 0;
}

void icwmp_uloop_ubus_exit()
{
	if (ubus_ctx) {
		ubus_remove_object(ubus_ctx, &tr069_object);
		ubus_free(ubus_ctx);
		ubus_ctx = NULL;
	}
}

void icwmp_ubus_send_event(const char *event, const char *data)
{
	if (CWMP_STRLEN(event) == 0) {
		CWMP_LOG(ERROR, "No event name found");
		return;
	}

	if (ubus_ctx == NULL) {
		CWMP_LOG(ERROR, "Failed to connect with ubus err: %d", errno);
		return;
	}

	struct blob_buf b = {0};
	blob_buf_init(&b, 0);

	if (CWMP_STRLEN(data)) {
		if (blobmsg_add_json_from_string(&b, data) != true) {
			CWMP_LOG(ERROR, "Failed to parse event data: %s", data);
			blob_buf_free(&b);
			return;
		}
	}

	if (ubus_send_event(ubus_ctx, event, b.head) != UBUS_STATUS_OK) {
		CWMP_LOG(ERROR, "Failed to send event: %s", event);
	}

	blob_buf_free(&b);
	return;
}

int icwmp_ubus_invoke(const char *obj, const char *method, struct blob_attr *msg, icwmp_ubus_cb icwmp_callback, void *callback_arg)
{
	uint32_t id;
	int rc = 0;

	if (ubus_ctx == NULL) {
		CWMP_LOG(ERROR, "Failed to connect with ubus err: %d", errno);
		return -1;
	}

	if (!ubus_lookup_id(ubus_ctx, obj, &id))
		rc = ubus_invoke(ubus_ctx, id, method, msg, icwmp_callback, callback_arg, ubus_timeout);
	else
		rc = -1;

	return rc;
}

int icwmp_ubus_invoke_timeout(const char *obj, const char *method, struct blob_attr *msg,
			icwmp_ubus_cb icwmp_callback, void *callback_arg, int timeout)
{
	int rc = 0;

	if (timeout > DEFAULT_UBUS_TIMEOUT)
		ubus_timeout = timeout;

	rc = icwmp_ubus_invoke(obj, method, msg, icwmp_callback, callback_arg);

	ubus_timeout = DEFAULT_UBUS_TIMEOUT;

	return rc;
}

int icwmp_ubus_invoke_async(const char *obj, const char *method, struct blob_attr *msg,
			    icwmp_ubus_cb data_callback, icwmp_ubus_async_cb complete_callback)
{
	uint32_t id;
	int fault = UBUS_STATUS_OK;
	struct ubus_request *req;

	if (ubus_ctx == NULL) {
		CWMP_LOG(ERROR, "Failed to connect with ubus err: %d", errno);
		return -1;
	}

	fault = ubus_lookup_id(ubus_ctx, obj, &id);
	if (fault) {
		CWMP_LOG(ERROR, "failed to lookup object: %s", obj);
		return -1;
	}

	req = (struct ubus_request *)malloc(sizeof(struct ubus_request));
	if (req == NULL) {
		CWMP_LOG(ERROR, "failed to allocate memory for ubus request");
		return -1;
	}

	memset(req, 0, sizeof(struct ubus_request));

	fault = ubus_invoke_async(ubus_ctx, id, method, msg, req);
	if (fault) {
		CWMP_LOG(ERROR, "ubus async call failed");
		FREE(req);
		return -1;
	}

	if (data_callback) {
		req->data_cb = data_callback;
	}

	if (complete_callback) {
		req->complete_cb = complete_callback;
	}

	ubus_complete_request_async(ubus_ctx, req);
	return 0;
}

int initiate_autonomous_complpolicy(void)
{
	cwmp_ctx.ev = (struct ubus_event_handler *)malloc(sizeof(struct ubus_event_handler));
	if (cwmp_ctx.ev == NULL)
		return -1;

	CWMP_MEMSET(cwmp_ctx.ev, 0, sizeof(struct ubus_event_handler));
	cwmp_ctx.ev->cb = autonomous_notification_handler;

	int ret = ubus_register_event_handler(ubus_ctx, cwmp_ctx.ev, "bbfdm.event");
	if (ret) {
		return -1;
	}

	return 0;
}

void clean_autonomous_complpolicy(void)
{
	if (cwmp_ctx.ev == NULL)
		return;

	ubus_unregister_event_handler(ubus_ctx, cwmp_ctx.ev);
}

int initiate_interface_update(void)
{
	cwmp_ctx.intf_ev = (struct ubus_event_handler *)malloc(sizeof(struct ubus_event_handler));
	if (cwmp_ctx.intf_ev == NULL)
		return -1;

	CWMP_MEMSET(cwmp_ctx.intf_ev, 0, sizeof(struct ubus_event_handler));
	cwmp_ctx.intf_ev->cb = interface_update_handler;

	int ret = ubus_register_event_handler(ubus_ctx, cwmp_ctx.intf_ev, "network.interface");
	if (ret) {
		return -1;
	}

	return 0;
}

void clean_interface_update(void)
{
	if (cwmp_ctx.intf_ev == NULL)
		return;

	ubus_unregister_event_handler(ubus_ctx, cwmp_ctx.intf_ev);
}

static void lookup_event_cb(struct ubus_context *ctx __attribute__((unused)),
		struct ubus_event_handler *ev __attribute__((unused)),
		const char *type, struct blob_attr *msg)
{
	const struct blobmsg_policy policy = {
		"path", BLOBMSG_TYPE_STRING
	};
	struct blob_attr *attr;
	const char *path;

	if (CWMP_STRCMP(type, "ubus.object.add") != 0)
		return;

	blobmsg_parse(&policy, 1, &attr, blob_data(msg), blob_len(msg));
	if (!attr)
		return;

	path = blobmsg_data(attr);
	if (CWMP_STRCMP(path, BBFDM_OBJECT_NAME) == 0) {
		g_bbf_object_available = true;
		uloop_timeout_cancel(&u_timeout);
		uloop_end();
	}
}

static void lookup_timeout_cb(struct uloop_timeout *timeout __attribute__((unused)))
{
	uloop_end();
}

int wait_for_bbf_object()
{
#define BBF_WAIT_TIMEOUT 60

	int ret;
	uint32_t ubus_id;
	struct ubus_event_handler add_event;

	g_bbf_object_available = false;
	if (ubus_ctx == NULL) {
		CWMP_LOG(ERROR, "Can't create ubus context");
		return FAULT_CPE_INTERNAL_ERROR;
	}

	uloop_init();
	ubus_add_uloop(ubus_ctx);

	// register for add event
	CWMP_MEMSET(&add_event, 0, sizeof(struct ubus_event_handler));
	add_event.cb = lookup_event_cb;
	ubus_register_event_handler(ubus_ctx, &add_event, "ubus.object.add");

	// check if object already present
	ret = ubus_lookup_id(ubus_ctx, BBFDM_OBJECT_NAME, &ubus_id);
	if (ret == 0) {
		g_bbf_object_available = true;
		goto end;
	}

	// Set timeout to expire lookup
	CWMP_MEMSET(&u_timeout, 0, sizeof(struct uloop_timeout));
	u_timeout.cb = lookup_timeout_cb;
	uloop_timeout_set(&u_timeout, BBF_WAIT_TIMEOUT * 1000);

	uloop_run();
	uloop_done();

end:
	ubus_remove_object(ubus_ctx, &add_event.obj);

	if (g_bbf_object_available == false) {
		CWMP_LOG(ERROR, "%s object not found", BBFDM_OBJECT_NAME);
		return FAULT_CPE_INTERNAL_ERROR;
	}

	return 0;
}

void icwmp_free_ubus()
{
	if (ubus_ctx) {
		ubus_free(ubus_ctx);
		ubus_ctx = NULL;
	}
}

static void ubus_changed_uci_cb(struct ubus_request *req, int type __attribute__((unused)), struct blob_attr *msg)
{
	int rem = 0;
	struct blob_attr *cur = NULL;

	if (msg == NULL || req == NULL)
		return;

	struct blob_attr *tb[1] = {0};
	const struct blobmsg_policy p[1] = {
		{ "modified_uci", BLOBMSG_TYPE_ARRAY }
	};

	blobmsg_parse(p, 1, tb, blobmsg_data(msg), blobmsg_len(msg));
	if (!tb[0]) {
		return;
	}

	struct get_result *res = (struct get_result *)req->priv;
	if (res == NULL) {
		return;
	}

	res->service_added = false;

	void *array = blobmsg_open_array(&res->bb, "services");
	blobmsg_for_each_attr(cur, tb[0], rem) {
		char *config_name = blobmsg_get_string(cur);
		blobmsg_add_string(&res->bb, NULL, config_name);
		res->service_added = true;
	}
	blobmsg_close_array(&res->bb, array);
	blobmsg_add_string(&res->bb, "proto", "cwmp");
}

void cwmp_initiate_get_DM(void)
{
	struct blob_buf b = {0};
	struct get_result res = {0};

	CWMP_MEMSET(&res, 0, sizeof(struct get_result));
	blob_buf_init(&res.bb, 0);

	CWMP_MEMSET(&b, 0, sizeof(struct blob_buf));
	blob_buf_init(&b, 0);
	bb_add_string(&b, "path", "Device.");

	void *table = blobmsg_open_table(&b, "optional");
	bb_add_string(&b, "proto", "cwmp");
	bb_add_string(&b, "format", "raw");
	blobmsg_close_table(&b, table);

	icwmp_ubus_invoke(BBFDM_OBJECT_NAME, "get", b.head, ubus_changed_uci_cb, &res);
	blob_buf_free(&b);

	if (res.service_added) {
		icwmp_ubus_invoke("bbf.config", "commit", res.bb.head, NULL, NULL);
	}

	blob_buf_free(&res.bb);
}
