/*
 * Copyright (C) 2024 iopsys Software Solutions AB
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 *	  Author Amin Ben Ramdhane <amin.benramdhane@pivasoftware.com>
 *
 */

#include <libbbfdm-ubus/bbfdm-ubus.h>
#include "dhcpmngr.h"
#include "dhcpv4.h"
#include "dhcpv6.h"

struct dhcpmngr_dhcp_option_type DHCPMNGR_DHCP_OPTIONS_ARRAY[] = {
/* config_name, tag, type, length */
{"subnet", 1, OPTION_IP, 4},                            /* DHCP_SUBNET             */
{"timezone", 2, OPTION_INT, 4},                         /* DHCP_TIME_OFFSET        */
{"router", 3, OPTION_IP|OPTION_LIST, 4},                /* DHCP_ROUTER             */
{"timesrv", 4, OPTION_IP|OPTION_LIST, 4},               /* DHCP_TIME_SERVER        */
{"namesrv", 5, OPTION_IP|OPTION_LIST, 4},               /* DHCP_NAME_SERVER        */
{"dns", 6, OPTION_IP|OPTION_LIST, 4},                   /* DHCP_DNS_SERVER         */
{"logsrv", 7, OPTION_IP|OPTION_LIST, 4},                /* DHCP_LOG_SERVER         */
{"cookiesrv", 8, OPTION_IP|OPTION_LIST, 4},             /* DHCP_COOKIE_SERVER      */
{"lprsrv", 9, OPTION_IP|OPTION_LIST, 4},                /* DHCP_LPR_SERVER         */
{"", 10, OPTION_IP|OPTION_LIST, 4},                     /* DHCP_IMPRESS_SERVER     */
{"", 11, OPTION_IP|OPTION_LIST, 4},                     /* DHCP_RLP SERVER         */
{"hostname", 12, OPTION_STRING, 0},                     /* DHCP_HOST_NAME          */
{"bootsize", 13, OPTION_INT, 2},                        /* DHCP_BOOT_SIZE          */
{"domain", 15, OPTION_STRING, 0},                       /* DHCP_DOMAIN_NAME        */
{"swapsrv", 16, OPTION_IP, 4},                          /* DHCP_SWAP_SERVER        */
{"rootpath", 17, OPTION_STRING, 0},                     /* DHCP_ROOT_PATH          */
{"", 19, OPTION_INT, 1},                                /* DHCP_FORWARD            */
{"", 20, OPTION_INT, 1},                                /* DHCP_SOURCE_ROUTING     */
{"", 21, OPTION_IP, 4},                                 /* DHCP_POLICY_FILTER      */
{"", 22, OPTION_INT, 2},                                /* DHCP_MAX_DG_ASSEMBLY    */
{"ipttl", 23, OPTION_INT, 1},                           /* DHCP_IP_TTL             */
{"", 24, OPTION_INT, 4},                                /* DHCP_MTU_TIMEOUT        */
{"", 25, OPTION_INT, 2},                                /* DHCP_MTU_PLATEAU        */
{"mtu", 26, OPTION_INT, 2},                             /* DHCP_MTU_INTERFACE      */
{"", 27, OPTION_INT, 1},                                /* DHCP_MTU_SUBNET         */
{"broadcast", 28, OPTION_IP, 4},                        /* DHCP_BROADCAST          */
{"", 29, OPTION_INT, 1},                                /* DHCP_MASK_DISCOVERY     */
{"", 30, OPTION_INT, 1},                                /* DHCP_MASK_SUPPLIER      */
{"", 31, OPTION_INT, 1},                                /* DHCP_ROUTER_DISCOVERY   */
{"", 32, OPTION_IP, 4},                                 /* DHCP_ROUTER_REQUEST     */
{"routes", 33, OPTION_IP|OPTION_LIST, 4},               /* DHCP_ROUTES             */
{"", 34, OPTION_INT, 1},                                /* DHCP_TRAILER            */
{"", 35, OPTION_INT, 4},                                /* DHCP_ARP_TIMEOUT        */
{"", 36, OPTION_INT, 1},                                /* DHCP_ETHERNET           */
{"", 37, OPTION_INT, 1},                                /* DHCP_DEFAULT_TCP_TTL    */
{"", 38, OPTION_INT, 4},                                /* DHCP_KEEPALIVE_TIME     */
{"", 39, OPTION_INT, 1},                                /* DHCP_KEEPALIVE_DATA     */
{"nisdomain", 40, OPTION_STRING, 0},                    /* DHCP_NIS_DOMAIN         */
{"nissrv", 41, OPTION_IP|OPTION_LIST, 4},               /* DHCP_NIS_SERVER         */
{"ntpsrv", 42, OPTION_IP|OPTION_LIST, 4},               /* DHCP_NTP_SERVER         */
{"", 43, OPTION_HEX, 1},                                /* DHCP_VENDOR_SPECIFIC    */
{"wins", 44, OPTION_IP|OPTION_LIST, 4},                 /* DHCP_WINS_SERVER        */
{"", 46, OPTION_INT, 1},                                /* DHCP_NETBIOS            */
{"", 50, OPTION_IP, 4},                                 /* DHCP_ADDRESS_REQUEST    */
{"lease", 51, OPTION_INT, 4},                           /* DHCP_LEASE_TIME         */
{"", 52, OPTION_INT, 1},                                /* DHCP_OVERLOAD           */
{"", 53, OPTION_INT, 1},                                /* DHCP_MESSSAGE_TYPE      */
{"serverid", 54, OPTION_IP, 4},                         /* DHCP_SERVER_ID          */
{"", 55, OPTION_INT|OPTION_LIST, 1},                	/* DHCP_PARAM_LIST         */
{"message", 56, OPTION_STRING, 0},                      /* DHCP_ERR_MESSAGE        */
{"", 57, OPTION_INT, 2},                                /* DHCP_MAX_MESSAGE_SIZE   */
{"", 58, OPTION_INT, 4},                                /* DHCP_RENEWAL_TIME       */
{"", 59, OPTION_INT, 4},                                /* DHCP_REBINDING_TIME     */
{"vendorid", 60, OPTION_STRING, 0},                       /* DHCP_VENDOR             */
{"", 61, OPTION_HEX, 1},                                /* DHCP_CLIENT_IDENTIFIER  */
{"", 65, OPTION_IP, 4},                                 /* DHCP_NIS_SERVER_ADDR    */
{"tftp", 66, OPTION_STRING, 0},                         /* DHCP_TFTP_SERVER_NAME   */
{"bootfile", 67, OPTION_STRING, 0},                     /* DHCP_BOOT_FILE          */
{"", 68, OPTION_IP, 4},                                 /* DHCP_HOME_AGENT         */
{"", 69, OPTION_IP, 4},                                 /* DHCP_SMTP_SERVER        */
{"", 70, OPTION_IP, 4},                                 /* DHCP_POP3_SERVER        */
{"", 71, OPTION_IP, 4},                                 /* DHCP_NNTP_SERVER        */
{"", 72, OPTION_IP, 4},                                 /* DHCP_WWW_SERVER         */
{"", 73, OPTION_IP, 4},                                 /* DHCP_FINGER_SERVER      */
{"", 74, OPTION_IP, 4},                                 /* DHCP_IRC_SERVER         */
{"", 75, OPTION_IP, 4},                                 /* DHCP_STREET_TALK_SERVER */
{"", 76, OPTION_IP, 4},                                 /* DHCP_STDA_SERVER        */
{"userclass", 77, OPTION_STRING, 0},                    /* DHCP_USER_CLASS         */
{"tzstr", 100, OPTION_STRING, 0},                       /* DHCP_PCODE              */
{"tzdbstr", 101, OPTION_STRING, 0},                     /* DHCP_TCODE              */
{"", 118, OPTION_IP, 4},                                /* DHCP_SUBNET_SELECTION   */
{"search", 119, OPTION_STRING|OPTION_LIST, 0},          /* DHCP_DOMAIN_SEARCH      */
{"sipsrv", 120, OPTION_STRING, 0},                      /* DHCP_SIP_SERVERS        */
{"staticroutes", 121, OPTION_STRING|OPTION_LIST, 0},    /* DHCP_STATIC_ROUTES      */
{"", 125, OPTION_HEX, 1},                               /* DHCP_VI_VENDOR_SPECIFIC */
{"vlanid", 132, OPTION_INT, 2},                         /* DHCP_VLAN_ID            */
{"vlanpriority", 133, OPTION_INT, 1},                   /* DHCP_VLAN_PRIORITY      */
{"", 145, OPTION_INT, 1},                               /* DHCP_FORCERENEW         */
{"", 152, OPTION_INT, 4},                               /* DHCP_BASE_TIME          */
{"", 153, OPTION_INT, 4},                               /* DHCP_START_TIME         */
{"", 154, OPTION_INT, 4},                               /* DHCP_QUERY_START_TIME   */
{"", 155, OPTION_INT, 4},                               /* DHCP_QUERY_END_TIME     */
{"", 156, OPTION_INT, 1},                               /* DHCP_STATE              */
{"", 157, OPTION_INT, 1},                               /* DHCP_DATA_SOURCE        */
{"", 159, OPTION_INT, 4},                               /* DHCP_PORT_PARAMS        */
{"pxeconffile", 209, OPTION_STRING, 0},                 /* DHCP_PXE_CONF_FILE      */
{"pxepathprefix", 210, OPTION_STRING, 0},               /* DHCP_PXE_PATH_PREFIX    */
{"reboottime", 211, OPTION_INT, 4},                     /* DHCP_REBOOT_TIME        */
{"ip6rd", 212, OPTION_STRING, 0},                       /* DHCP_6RD                */
{"msstaticroutes", 249, OPTION_STRING|OPTION_LIST, 0},  /* DHCP_MS_STATIC_ROUTES   */
{"wpad", 225, OPTION_STRING, 0},                        /* DHCP_WPAD               */
};

/*
 * netifd registers a per-interface ubus object "network.interface.<name>" only for
 * interfaces it currently knows about. Probing that object before invoking the
 * "status" method lets us skip the call for unknown interfaces, which would otherwise
 * return "Not found" and emit a spurious error log from bbfdm's dm_ubus_call_sync().
 * Costs one extra ubus lookup on the common (interface present) path.
 */
int dhcpmngr_get_interface_status(const char *interface, json_object **res)
{
	char obj_path[64] = {0};

	if (res)
		*res = NULL;

	if (DM_STRLEN(interface) == 0)
		return -1;

	snprintf(obj_path, sizeof(obj_path), "network.interface.%s", interface);
	if (!dmubus_object_method_exists(obj_path))
		return -1;

	return dmubus_call("network.interface", "status", UBUS_ARGS{{"interface", interface, String}}, 1, res);
}

/* Returns dnsmasq section name belongs to LAN network */
char *get_dnsmasq_section_name(void)
{
	struct uci_section *s = NULL;

	uci_foreach_sections("dhcp", "dnsmasq", s) {
		char *sec = section_name(s);
		if (DM_STRCMP(sec, "dns_client") != 0)
			return sec;
	}

	return "";
}

void set_dhcp_pool_option_value(void *data, char *value)
{
	struct option_args *option = (struct option_args *)((struct dm_data *)data)->additional_data;
	struct uci_list *option_list = NULL;
	char res[512] = {0};
	bool b = false;

	dhcpmngr_convert_hex_option_to_string(DM_STRTOL(option->tag), value, res, sizeof(res));

	if (((struct dm_data *)data)->config_section) {
		char *enable = NULL;

		dmuci_get_value_by_section_string(((struct dm_data *)data)->dmmap_section, "enable", &enable);
		string_to_bool(enable, &b);

		if (b == true) {
			dmuci_get_value_by_section_list(((struct dm_data *)data)->config_section, "dhcp_option", &option_list);

			if (option_list != NULL) {
				struct uci_element *e = NULL;
				size_t length;

				uci_foreach_element(option_list, e) {
					char **buf = strsplit(e->name, ",", &length);
					if (buf && *buf && DM_STRCMP(buf[0], option->tag) == 0) {
						dmuci_del_list_value_by_section(((struct dm_data *)data)->config_section, "dhcp_option", e->name);
						break;
					}
				}
			}

			char new_tag_value[1024] = {0};

			snprintf(new_tag_value, sizeof(new_tag_value), "%s,%s", option->tag, res);
			dmuci_add_list_value_by_section(((struct dm_data *)data)->config_section, "dhcp_option", new_tag_value);
		}
	}
}

int dhcpmngr_get_dhcp_option_number_by_name(const char *tag_name)
{
	if (!DM_STRLEN(tag_name))
		return -1;

	for (int i = 0; i < ARRAY_SIZE(DHCPMNGR_DHCP_OPTIONS_ARRAY); i++) {

		if (DM_STRLEN(DHCPMNGR_DHCP_OPTIONS_ARRAY[i].config_name) == 0)
			continue;

		if (strcmp(DHCPMNGR_DHCP_OPTIONS_ARRAY[i].config_name, tag_name) == 0)
			return DHCPMNGR_DHCP_OPTIONS_ARRAY[i].tag;
	}

	return -1;
}

void dhcpmngr_convert_str_option_to_hex(unsigned int tag, const char *str, char *hex, size_t size)
{
	int idx = -1;

	if (str == NULL || hex == NULL || size == 0)
		return;

	for (int i = 0; i < ARRAY_SIZE(DHCPMNGR_DHCP_OPTIONS_ARRAY); i++) {
		if (DHCPMNGR_DHCP_OPTIONS_ARRAY[i].tag == tag) {
			idx = i;
			break;
		}
	}

	if (idx == -1) {
		convert_string_to_hex(str, hex, size);
		return;
	}

	char *pch = NULL, *spch = NULL;
	unsigned pos = 0;
	char buf[512] = {0};

	DM_STRNCPY(buf, str, sizeof(buf));

	char *separator = (DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].type & OPTION_LIST) ? "," : "\0";

	for (pch = strtok_r(buf, separator, &spch); pch != NULL; pch = strtok_r(NULL, separator, &spch)) {
		if (DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].type & OPTION_IP) {
			struct in_addr ip_bin;

			if (!inet_aton(pch, &ip_bin))
				continue;

			unsigned int ip = ntohl(ip_bin.s_addr);

			if (size - pos < DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].len * 2)
				return;

			pos += snprintf(&hex[pos], size - pos, "%08X", ip);
		} else if (DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].type & OPTION_HEX) {
			for (int j = 0; j < DM_STRLEN(pch) && pos < size - 1; j++) {
				if (pch[j] == ':')
					continue;

				pos += snprintf(&hex[pos], size - pos, "%c", pch[j]);
			}
		} else if (DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].type & OPTION_STRING) {
			convert_string_to_hex(pch, hex, size);
		} else {
			long int val = DM_STRTOL(pch);

			if (size - pos < DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].len * 2)
				return;

			if (DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].len == 4)
				pos += snprintf(&hex[pos], size - pos, "%08lX", val);
			else if (DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].len == 2)
				pos += snprintf(&hex[pos], size - pos, "%04lX", val);
			else
				pos += snprintf(&hex[pos], size - pos, "%02lX", val);
		}
	}
}

void dhcpmngr_convert_hex_option_to_string(unsigned int tag, const char *hex, char *str, size_t size)
{
	int idx = -1;

	if (hex == NULL || str == NULL || size == 0)
		return;

	for (int i = 0; i < ARRAY_SIZE(DHCPMNGR_DHCP_OPTIONS_ARRAY); i++) {
		if (DHCPMNGR_DHCP_OPTIONS_ARRAY[i].tag == tag) {
			idx = i;
			break;
		}
	}

	if (idx == -1) {
		convert_hex_to_string(hex, str, size);
		return;
	}

	unsigned pos = 0;
	unsigned int str_len = DM_STRLEN(hex);
	unsigned int len = DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].len * 2;
	char buffer[32] = {0};
	char buf[16] = {0};

	for (int i = 0; i + len <= str_len; i = i + len) {
		DM_STRNCPY(buf, &hex[i], len + 1);

		if (DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].type & OPTION_IP) {
			struct in_addr addr;
			unsigned int ip;

			sscanf(buf, "%X", &ip);
			addr.s_addr = htonl(ip);
			char *ipaddr = inet_ntoa(addr);
			snprintf(buffer, sizeof(buffer), "%s,", ipaddr);
		} else if (DHCPMNGR_DHCP_OPTIONS_ARRAY[idx].type & OPTION_HEX) {
			snprintf(buffer, sizeof(buffer), "%s:", buf);
		} else {
			snprintf(buffer, sizeof(buffer), "%d,", (int)strtol(buf, NULL, 16));
		}

		if (size - pos < DM_STRLEN(buffer) + 1)
			break;

		pos += snprintf(&str[pos], size - pos, "%s", buffer);
	}

	if (pos)
		str[pos - 1] = 0;
}

static void sync_dhcpv4_client_sent_option(struct dmctx *dmctx, struct uci_section *config_s, const char *instance)
{
	char *vendorid = NULL, *clientid = NULL, *hostname = NULL, *sendopts = NULL;
	char value[256] = {0};

	// Device.DHCPv4.Client.{i}.SentOption.{i}.
	dmuci_get_value_by_section_string(config_s, "vendorid", &vendorid);
	if (DM_STRLEN(vendorid)) {
		// vendorid (option 60)
		char *inst = NULL;
		struct uci_section *new_s = NULL;
		bool sec_exist = false;

		memset(value, 0, sizeof(value));
		dhcpmngr_convert_str_option_to_hex(60, vendorid, value, sizeof(value));

		uci_path_foreach_sections(bbfdm, "DHCPv4", "SentOption", new_s) {
			char *client = NULL, *tag = NULL;

			dmuci_get_value_by_section_string(new_s, "Client", &client);
			dmuci_get_value_by_section_string(new_s, "option_tag", &tag);
			dmuci_get_value_by_section_string(new_s, "__instance__", &inst);

			if (DM_STRCMP(client, instance) != 0 || DM_STRCMP(tag, "60") != 0)
				continue;

			sec_exist = true;
			dmctx->obj_buf[1] = "SentOption";
			dmctx->inst_buf[1] = inst;
			break;
		}

		if (sec_exist == false) {
			new_s = create_dmmap_obj(dmctx, 1, "DHCPv4", "SentOption", NULL, &inst);
		}

		if (new_s && DM_STRLEN(inst)) {
			dmuci_set_value_by_section(new_s, "enable", "1");
			dmuci_set_value_by_section(new_s, "option_tag", "60");
			dmuci_set_value_by_section(new_s, "option_value", value);
		}
	}

	dmuci_get_value_by_section_string(config_s, "clientid", &clientid);
	if (DM_STRLEN(clientid)) {
		// clientid (option 61)
		char *inst = NULL;
		struct uci_section *new_s = NULL;
		bool sec_exist = false;

		memset(value, 0, sizeof(value));
		dhcpmngr_convert_str_option_to_hex(61, clientid, value, sizeof(value));

		uci_path_foreach_sections(bbfdm, "DHCPv4", "SentOption", new_s) {
			char *client = NULL, *tag = NULL;

			dmuci_get_value_by_section_string(new_s, "Client", &client);
			dmuci_get_value_by_section_string(new_s, "option_tag", &tag);
			dmuci_get_value_by_section_string(new_s, "__instance__", &inst);

			if (DM_STRCMP(client, instance) != 0 || DM_STRCMP(tag, "61") != 0)
				continue;

			sec_exist = true;
			dmctx->obj_buf[1] = "SentOption";
			dmctx->inst_buf[1] = inst;
			break;
		}

		if (sec_exist == false) {
			new_s = create_dmmap_obj(dmctx, 1, "DHCPv4", "SentOption", NULL, &inst);
		}

		if (new_s && DM_STRLEN(inst)) {
			dmuci_set_value_by_section(new_s, "enable", "1");
			dmuci_set_value_by_section(new_s, "option_tag", "61");
			dmuci_set_value_by_section(new_s, "option_value", value);
		}
	}

	dmuci_get_value_by_section_string(config_s, "hostname", &hostname);
	if (DM_STRLEN(hostname)) {
		// hostname (option 12)
		char *inst = NULL;
		struct uci_section *new_s = NULL;
		bool sec_exist = false;

		memset(value, 0, sizeof(value));
		dhcpmngr_convert_str_option_to_hex(12, hostname, value, sizeof(value));

		uci_path_foreach_sections(bbfdm, "DHCPv4", "SentOption", new_s) {
			char *client = NULL, *tag = NULL;

			dmuci_get_value_by_section_string(new_s, "Client", &client);
			dmuci_get_value_by_section_string(new_s, "option_tag", &tag);
			dmuci_get_value_by_section_string(new_s, "__instance__", &inst);

			if (DM_STRCMP(client, instance) != 0 || DM_STRCMP(tag, "12") != 0)
				continue;

			sec_exist = true;
			dmctx->obj_buf[1] = "SentOption";
			dmctx->inst_buf[1] = inst;
			break;
		}

		if (sec_exist == false) {
			new_s = create_dmmap_obj(dmctx, 1, "DHCPv4", "SentOption", NULL, &inst);
		}

		if (new_s && DM_STRLEN(inst)) {
			dmuci_set_value_by_section(new_s, "enable", "1");
			dmuci_set_value_by_section(new_s, "option_tag", "12");
			dmuci_set_value_by_section(new_s, "option_value", value);
		}
	}

	// sendopts option
	dmuci_get_value_by_section_string(config_s, "sendopts", &sendopts);
	if (DM_STRLEN(sendopts)) {
		// sendopts (other options)
		char *pch = NULL, *spch = NULL;

		for (pch = strtok_r(sendopts, " ", &spch); pch != NULL; pch = strtok_r(NULL, " ", &spch)) {
			char tag[16] = {0};
			char *inst = NULL;
			struct uci_section *new_s = NULL;
			bool sec_exist = false;

			memset(value, 0, sizeof(value));

			fill_dhcp_option_value(pch, tag, sizeof(tag), value, sizeof(value));

			if (!DM_STRLEN(tag) || !DM_STRLEN(tag))
				continue;

			uci_path_foreach_sections(bbfdm, "DHCPv4", "SentOption", new_s) {
				char *client = NULL, *optag = NULL;

				dmuci_get_value_by_section_string(new_s, "Client", &client);
				dmuci_get_value_by_section_string(new_s, "option_tag", &optag);
				dmuci_get_value_by_section_string(new_s, "__instance__", &inst);

				if (DM_STRCMP(client, instance) != 0 || DM_STRCMP(tag, optag) != 0)
					continue;

				sec_exist = true;
				dmctx->obj_buf[1] = "SentOption";
				dmctx->inst_buf[1] = inst;
				break;
			}

			if (sec_exist == false) {
				new_s = create_dmmap_obj(dmctx, 1, "DHCPv4", "SentOption", NULL, &inst);
			}

			if (new_s && DM_STRLEN(inst)) {
				dmuci_set_value_by_section(new_s, "enable", "1");
				dmuci_set_value_by_section(new_s, "option_tag", tag);
				dmuci_set_value_by_section(new_s, "option_value", value);
			}
		}
	}

	return;
}

static void sync_dhcpv4_client_reqt_option(struct dmctx *dmctx, struct uci_section *config_s, const char *instance)
{
	char *reqopts = NULL;

	// Device.DHCPv4.Client.{i}.ReqOption.{i}.
	dmuci_get_value_by_section_string(config_s, "reqopts", &reqopts);
	if (DM_STRLEN(reqopts)) {
		// reqopts option
		char *pch = NULL, *spch = NULL;

		for (pch = strtok_r(reqopts, " ", &spch); pch != NULL; pch = strtok_r(NULL, " ", &spch)) {
			char *inst = NULL;
			struct uci_section *new_s = NULL;
			bool sec_exist = false;

			uci_path_foreach_sections(bbfdm, "DHCPv4", "ReqOption", new_s) {
				char *client = NULL, *tag = NULL, *op_instance = NULL;

				dmuci_get_value_by_section_string(new_s, "Client", &client);
				dmuci_get_value_by_section_string(new_s, "option_tag", &tag);
				dmuci_get_value_by_section_string(new_s, "__instance__", &op_instance);

				if (DM_STRCMP(client, instance) != 0 || DM_STRCMP(tag, pch) != 0)
					continue;

				sec_exist = true;
				dmctx->obj_buf[1] = "ReqOption";
				dmctx->inst_buf[1] = op_instance;
				break;
			}

			if (sec_exist == false) {
				new_s = create_dmmap_obj(dmctx, 1, "DHCPv4", "ReqOption", NULL, &inst);
				if (new_s && DM_STRLEN(inst)) {
					dmuci_set_value_by_section(new_s, "enable", "1");
					dmuci_set_value_by_section(new_s, "option_tag", pch);
				}
			}
		}
	}

	return;
}

static void dmmap_sync_dhcpv4_client(struct dmctx *dmctx)
{
	struct uci_section *s = NULL;

	uci_foreach_sections("network", "interface", s) {
		char *proto = NULL, *instance = NULL;

		char sec_name[128] = {0};
		snprintf(sec_name, sizeof(sec_name), "%s.%s", section_config(s), section_name(s));

		struct uci_section *dmmap_sec = get_dup_section_in_dmmap_opt("DHCPv4", "Client", "__section_name__", sec_name);

		dmuci_get_value_by_section_string(s, "proto", &proto);
		if (DM_LSTRCMP(proto, "dhcp") != 0) {
			continue;
		}

		// Device.DHCPv4.Client.{i}.
		if (dmmap_sec == NULL) {
			create_dmmap_obj(dmctx, 0, "DHCPv4", "Client", s, &instance);
		} else {
			dmuci_get_value_by_section_string(dmmap_sec, "__instance__", &instance);
			dmctx->obj_buf[0] = "Client";
			dmctx->inst_buf[0] = instance;
		}

		sync_dhcpv4_client_sent_option(dmctx, s, instance);
		sync_dhcpv4_client_reqt_option(dmctx, s, instance);
	}

	struct uci_section *new_s = NULL, *tmp = NULL;
	uci_path_foreach_sections_safe(bbfdm, "DHCPv4", "SentOption", tmp, new_s) {
		char *client = NULL, *tag = NULL, *orig_sec_name = NULL, *enab = NULL, *opt_val = NULL;
		bool enable = false;

		dmuci_get_value_by_section_string(new_s, "Client", &client);
		dmuci_get_value_by_section_string(new_s, "option_tag", &tag);
		dmuci_get_value_by_section_string(new_s, "enable", &enab);

		if (DM_STRLEN(tag) == 0) {
			dmuci_delete_by_section(new_s, NULL, NULL);
			continue;
		}

		struct uci_section *client_s = get_dup_section_in_dmmap_opt("DHCPv4", "Client", "__instance__", client);
		if (client_s == NULL) {
			dmuci_delete_by_section(new_s, NULL, NULL);
			continue;
		}

		dmuci_get_value_by_section_string(client_s, "__section_name__", &orig_sec_name);
		if (DM_STRLEN(orig_sec_name) == 0) {
			continue;
		}

		struct uci_section *orig_sec = get_config_section_from_dmmap_section_name(orig_sec_name);
		if (orig_sec == NULL) {
			continue;
		}

		string_to_bool(enab, &enable);
		if (enable == false) {
			continue;
		}

		dmuci_get_value_by_section_string(client_s, "option_value", &opt_val);
		if (DM_STRLEN(opt_val) == 0) {
			continue;
		}

		char *option_name = get_dhcp_option_name(DM_STRTOL(tag));
		if (DM_LSTRCMP(option_name, "sendopts") != 0) {
			char *val = NULL;

			dmuci_get_value_by_section_string(orig_sec, option_name, &val);
			if (DM_STRLEN(val) == 0) {
				dmuci_delete_by_section(new_s, NULL, NULL);
			}

			continue;
		}

		char *sendopts = NULL;
		char *pch = NULL, *spch = NULL;

		dmuci_get_value_by_section_string(orig_sec, "sendopts", &sendopts);
		if (DM_STRLEN(sendopts) == 0) {
			dmuci_delete_by_section(new_s, NULL, NULL);
			continue;
		}

		char *list = dmstrdup(sendopts);
		bool exist = false;

		for (pch = strtok_r(list, " ", &spch); pch != NULL; pch = strtok_r(NULL, " ", &spch)) {
			char *p = strchr(pch, ':');
			if (!p)
				continue;

			*p = 0;
			long tag_num = (isdigit_str(pch) || ishex_str(pch)) ? strtol(pch, NULL, 0) : dhcpmngr_get_dhcp_option_number_by_name(pch);
			if (DM_STRTOL(tag) == tag_num) {
				exist = true;
				break;
			}
		}

		if (exist == false) {
			dmuci_delete_by_section(new_s, NULL, NULL);
		}

		dmfree(list);
	}

	new_s = NULL;
	tmp = NULL;
	uci_path_foreach_sections_safe(bbfdm, "DHCPv4", "ReqOption", tmp, new_s) {
		char *client = NULL, *tag = NULL, *orig_sec_name = NULL, *enab = NULL;
		bool enable = false;

		dmuci_get_value_by_section_string(new_s, "Client", &client);
		dmuci_get_value_by_section_string(new_s, "option_tag", &tag);
		dmuci_get_value_by_section_string(new_s, "enable", &enab);

		if (DM_STRLEN(tag) == 0) {
			dmuci_delete_by_section(new_s, NULL, NULL);
			continue;
		}

		struct uci_section *client_s = get_dup_section_in_dmmap_opt("DHCPv4", "Client", "__instance__", client);
		if (client_s == NULL) {
			dmuci_delete_by_section(new_s, NULL, NULL);
			continue;
		}

		dmuci_get_value_by_section_string(client_s, "__section_name__", &orig_sec_name);
		if (DM_STRLEN(orig_sec_name) == 0) {
			continue;
		}

		struct uci_section *orig_sec = get_config_section_from_dmmap_section_name(orig_sec_name);
		if (orig_sec == NULL) {
			continue;
		}

		string_to_bool(enab, &enable);
		if (enable == false) {
			continue;
		}

		char *reqopts = NULL;
		dmuci_get_value_by_section_string(orig_sec, "reqopts", &reqopts);
		if (!value_exits_in_str_list(reqopts, " ", tag)) {
			dmuci_delete_by_section(new_s, NULL, NULL);
		}
	}
}

static void sync_dhcpv4_pool_static_address(struct dmctx *dmctx, struct uci_section *s)
{
	struct uci_section *host_s = NULL;

	// Device.DHCPv4.Server.Pool.{i}.StaticAddress.{i}.
	uci_foreach_sections("dhcp", "host", host_s) {
		char *parent_s = NULL;
		char *instance = NULL;
		char *host_name = NULL;

		dmuci_get_value_by_section_string(host_s, "name", &host_name);
		if (DM_LSTRCMP(host_name, "reserved") == 0)
			continue;

		dmuci_get_value_by_section_string(host_s, "dhcp_pool", &parent_s);
		if (DM_STRCMP(parent_s, section_name(s)) != 0)
			continue;

		char sec_name[128] = {0};
		snprintf(sec_name, sizeof(sec_name), "%s.%s", section_config(host_s), section_name(host_s));

		struct uci_section *dmmap_s = get_dup_section_in_dmmap_opt("DHCPv4", "StaticAddress", "__section_name__", sec_name);
		if (dmmap_s == NULL) {
			create_dmmap_obj(dmctx, 1, "DHCPv4", "StaticAddress", host_s, &instance);
		} else {
			dmuci_get_value_by_section_string(dmmap_s, "__instance__", &instance);
			dmctx->obj_buf[1] = "StaticAddress";
			dmctx->inst_buf[1] = instance;
		}
	}
}

static void sync_dhcpv4_pool_option(struct dmctx *dmctx, struct uci_section *s, const char *instance)
{
	struct uci_list *options_list = NULL;

	// Device.DHCPv4.Server.Pool.{i}.Option.{i}.
	dmuci_get_value_by_section_list(s, "dhcp_option", &options_list);

	if (options_list != NULL) {
		struct uci_element *e = NULL;

		uci_foreach_element(options_list, e) {
			char buf[512] = {0};
			struct uci_section *dmmap_s = NULL;
			char *inst = NULL;
			bool sec_exist = false;


			snprintf(buf, sizeof(buf), "%s", e->name);
			char *p = strchr(buf, ',');
			if (p)
				*p = 0;

			uci_path_foreach_sections(bbfdm, "DHCPv4", "Option", dmmap_s) {
				char *pool = NULL, *optag = NULL, *op_instance = NULL;

				dmuci_get_value_by_section_string(dmmap_s, "Pool", &pool);
				dmuci_get_value_by_section_string(dmmap_s, "option_tag", &optag);
				dmuci_get_value_by_section_string(dmmap_s, "__instance__", &op_instance);

				if (DM_STRCMP(pool, instance) != 0 || DM_STRCMP(buf, optag) != 0)
					continue;

				sec_exist = true;
				dmctx->obj_buf[1] = "Option";
				dmctx->inst_buf[1] = op_instance;
				break;
			}

			if (sec_exist == false) {
				dmmap_s = create_dmmap_obj(dmctx, 1, "DHCPv4", "Option", NULL, &inst);
			}

			if (dmmap_s && DM_STRLEN(inst)) {
				dmuci_set_value_by_section_bbfdm(dmmap_s, "enable", "1");
				dmuci_set_value_by_section_bbfdm(dmmap_s, "option_tag", buf);

				char hex[256] = {0};
				dhcpmngr_convert_str_option_to_hex(DM_STRTOL(buf), p ? p + 1 : "", hex, sizeof(hex));
				dmuci_set_value_by_section_bbfdm(dmmap_s, "option_value", p ? hex : "");
			}
		}
	}
}

static void sync_dhcpv4_pool_config(struct uci_section *conf_s, struct uci_section *dmmap_s, const char *instance)
{
	char *intf = NULL, *start = NULL, *limit = NULL;
	unsigned iface_addr, iface_cidr, bits, net_start, net_end;
	char addr_min[INET_ADDRSTRLEN] = {0};
	char addr_max[INET_ADDRSTRLEN] = {0};

	dmuci_get_value_by_section_string(conf_s, "interface", &intf);
	start = dmuci_get_value_by_section_fallback_def(conf_s, "start", "0");
	limit = dmuci_get_value_by_section_fallback_def(conf_s, "limit", "0");

	if (interface_get_ipv4(intf, &iface_addr, &iface_cidr)) {
		dmuci_set_value_by_section(dmmap_s, "MinAddress", "0.0.0.0");
		dmuci_set_value_by_section(dmmap_s, "MaxAddress", "255.255.255.0");
		dmuci_set_value_by_section(dmmap_s, "SubnetMask", "255.255.255.255");
		set_section_order("DHCPv4", dmmap_s, conf_s, 0, instance);
	} else {
		bits = ~((1 << (32 - iface_cidr)) - 1);
		net_start = (ntohl(iface_addr) & bits) + DM_STRTOL(start);
		net_end = (ntohl(iface_addr) & bits) + DM_STRTOL(start) + DM_STRTOL(limit) - 1;

		unsigned iface_addr_min = htonl(net_start);
		inet_ntop(AF_INET, &iface_addr_min, addr_min, INET_ADDRSTRLEN);

		unsigned iface_addr_max = htonl(net_end);
		inet_ntop(AF_INET, &iface_addr_max, addr_max, INET_ADDRSTRLEN);

		dmuci_set_value_by_section(dmmap_s, "MinAddress", addr_min);
		dmuci_set_value_by_section(dmmap_s, "MaxAddress", addr_max);
		dmuci_set_value_by_section(dmmap_s, "SubnetMask", cidr2netmask(iface_cidr));
		set_section_order("DHCPv4", dmmap_s, conf_s, 0, instance);
	}
}

static void remove_unsynced_dhcp_options(const char *dmmap)
{
	struct uci_section *s = NULL, *tmp = NULL;

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

	uci_path_foreach_sections_safe(bbfdm, dmmap, "Option", tmp, s) {
		char *pool = NULL, *orig_sec_name = NULL, *opt_tag = NULL, *enable = NULL, *opt_val = NULL;
		bool enab = false;

		dmuci_get_value_by_section_string(s, "Pool", &pool);
		struct uci_section *pool_s = get_dup_section_in_dmmap_opt(dmmap, "Pool", "__instance__", pool);
		if (pool_s == NULL) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		dmuci_get_value_by_section_string(pool_s, "__section_name__", &orig_sec_name);
		struct uci_section *pool_orig = get_config_section_from_dmmap_section_name(orig_sec_name);
		if (pool_orig == NULL) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		dmuci_get_value_by_section_string(s, "option_tag", &opt_tag);
		if (DM_STRLEN(opt_tag) == 0) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		dmuci_get_value_by_section_string(s, "enable", &enable);
		string_to_bool(enable, &enab);
		if (enab == false) {
			continue;
		}

		dmuci_get_value_by_section_string(s, "option_value", &opt_val);
		if (DM_STRLEN(opt_val) == 0) {
			continue;
		}

		struct uci_list *option_list = NULL;
		dmuci_get_value_by_section_list(pool_orig, "dhcp_option", &option_list);
		if (option_list == NULL) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		struct uci_element *e = NULL;
		size_t length;
		bool option_found = false;

		uci_foreach_element(option_list, e) {
			char **buf = strsplit(e->name, ",", &length);
			if (buf && *buf && DM_STRCMP(buf[0], opt_tag) == 0) {
				option_found = true;
				break;
			}
		}

		if (option_found == false) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}
	}
}

static void dmmap_sync_dhcpv4_server_pool(struct dmctx *dmctx)
{
	struct uci_section *s = NULL, *tmp = NULL;

	uci_foreach_sections("dhcp", "dhcp", s) {
		char *ignore = NULL, *instance = NULL;

		dmuci_get_value_by_section_string(s, "ignore", &ignore);
		if (DM_LSTRCMP(ignore, "1") == 0) {
			continue;
		}

		// Device.DHCPv4.Server.Pool.{i}.
		char sec_name[128] = {0};
		snprintf(sec_name, sizeof(sec_name), "%s.%s", section_config(s), section_name(s));

		struct uci_section *dmmap_s = get_dup_section_in_dmmap_opt("DHCPv4", "Pool", "__section_name__", sec_name);

		// Device.DHCPv4.Server.Pool.{i}.
		if (dmmap_s == NULL) {
			dmmap_s = create_dmmap_obj(dmctx, 0, "DHCPv4", "Pool", s, &instance);
			if (dmmap_s && DM_STRLEN(instance)) {
				sync_dhcpv4_pool_config(s, dmmap_s, instance);
			}
		} else {
			dmuci_get_value_by_section_string(dmmap_s, "__instance__", &instance);
			dmctx->obj_buf[0] = "Pool";
			dmctx->inst_buf[0] = instance;
		}

		sync_dhcpv4_pool_static_address(dmctx, s);
		sync_dhcpv4_pool_option(dmctx, s, instance);
	}

	s = NULL;
	uci_path_foreach_sections_safe(bbfdm, "DHCPv4", "Pool", tmp, s) {
		char *orig_sec_name = NULL, *ignore = NULL;

		dmuci_get_value_by_section_string(s, "__section_name__", &orig_sec_name);
		if (DM_STRLEN(orig_sec_name) == 0) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		struct uci_section *orig_sec = get_config_section_from_dmmap_section_name(orig_sec_name);
		if (orig_sec == NULL) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		dmuci_get_value_by_section_string(orig_sec, "ignore", &ignore);
		if (DM_LSTRCMP(ignore, "1") == 0) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}
	}

	s = NULL;
	tmp = NULL;
	uci_path_foreach_sections_safe(bbfdm, "DHCPv4", "StaticAddress", tmp, s) {
		char *pool = NULL, *orig_sec_name = NULL;

		dmuci_get_value_by_section_string(s, "Pool", &pool);
		struct uci_section *pool_s = get_dup_section_in_dmmap_opt("DHCPv4", "Pool", "__instance__", pool);
		if (pool_s == NULL) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		dmuci_get_value_by_section_string(s, "__section_name__", &orig_sec_name);
		if (DM_STRLEN(orig_sec_name) == 0) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		struct uci_section *orig_sec = get_config_section_from_dmmap_section_name(orig_sec_name);
		if (orig_sec == NULL) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}
	}

	remove_unsynced_dhcp_options("DHCPv4");
}

static void dmmap_sync_dhcpv4_relay_forward(struct dmctx *dmctx)
{
	struct uci_section *s = NULL;

	uci_foreach_sections("network", "interface", s) {
		struct uci_section *dmmap_s = NULL;
		char *proto = NULL, *instance = NULL;

		char sec_name[128] = {0};
		snprintf(sec_name, sizeof(sec_name), "%s.%s", section_config(s), section_name(s));

		dmmap_s = get_dup_section_in_dmmap_opt("DHCPv4", "Forwarding", "__section_name__", sec_name);

		dmuci_get_value_by_section_string(s, "proto", &proto);
		if (DM_LSTRCMP(proto, "relay") != 0) {
			if (dmmap_s != NULL) {
				dmuci_set_value_by_section_bbfdm(dmmap_s, "__section_name__", "");
			}
			continue;
		}

		// Device.DHCPv4.Relay.Forwarding.{i}.
		if (dmmap_s == NULL) {
			create_dmmap_obj(dmctx, 0, "DHCPv4", "Forwarding", s, &instance);
		} else {
			dmuci_get_value_by_section_string(dmmap_s, "__instance__", &instance);
			dmctx->obj_buf[0] = "Forwarding";
			dmctx->inst_buf[0] = instance;
		}
	}
}

static void dmmap_sync_dhcpv6_client(struct dmctx *dmctx)
{
	struct uci_section *s = NULL;

	uci_foreach_sections("network", "interface", s) {
		char *proto = NULL, *instance = NULL;

		char sec_name[128] = {0};
		snprintf(sec_name, sizeof(sec_name), "%s.%s", section_config(s), section_name(s));

		struct uci_section *dmmap_sec = get_dup_section_in_dmmap_opt("DHCPv6", "Client", "__section_name__", sec_name);

		dmuci_get_value_by_section_string(s, "proto", &proto);
		if (DM_LSTRCMP(proto, "dhcpv6") != 0) {
			continue;
		}

		// Device.DHCPv6.Client.{i}.
		if (dmmap_sec == NULL) {
			create_dmmap_obj(dmctx, 0, "DHCPv6", "Client", s, &instance);
		} else {
			dmuci_get_value_by_section_string(dmmap_sec, "__instance__", &instance);
			dmctx->obj_buf[0] = "Client";
			dmctx->inst_buf[0] = instance;
		}
	}
}

static void sync_dhcpv6_pool_option(struct dmctx *dmctx, struct uci_section *s, const char *instance)
{
	struct uci_list *options_list = NULL;

	// Device.DHCPv6.Server.Pool.{i}.Option.{i}.
	dmuci_get_value_by_section_list(s, "dhcp_option", &options_list);

	if (options_list != NULL) {
		struct uci_element *e = NULL;

		uci_foreach_element(options_list, e) {
			char buf[512] = {0};
			struct uci_section *dmmap_s = NULL;
			char *inst = NULL;
			bool sec_exist = false;


			snprintf(buf, sizeof(buf), "%s", e->name);
			char *p = strchr(buf, ',');
			if (p)
				*p = 0;

			uci_path_foreach_sections(bbfdm, "DHCPv6", "Option", dmmap_s) {
				char *pool = NULL, *optag = NULL, *op_instance = NULL;

				dmuci_get_value_by_section_string(dmmap_s, "Pool", &pool);
				dmuci_get_value_by_section_string(dmmap_s, "option_tag", &optag);
				dmuci_get_value_by_section_string(dmmap_s, "__instance__", &op_instance);

				if (DM_STRCMP(pool, instance) != 0 || DM_STRCMP(buf, optag) != 0)
					continue;

				sec_exist = true;
				dmctx->obj_buf[1] = "Option";
				dmctx->inst_buf[1] = op_instance;
				break;
			}

			if (sec_exist == false) {
				dmmap_s = create_dmmap_obj(dmctx, 1, "DHCPv6", "Option", NULL, &inst);
			}

			if (dmmap_s && DM_STRLEN(inst)) {
				dmuci_set_value_by_section_bbfdm(dmmap_s, "enable", "1");
				dmuci_set_value_by_section_bbfdm(dmmap_s, "option_tag", buf);

				char hex[256] = {0};
				dhcpmngr_convert_str_option_to_hex(DM_STRTOL(buf), p ? p + 1 : "", hex, sizeof(hex));
				dmuci_set_value_by_section_bbfdm(dmmap_s, "option_value", p ? hex : "");
			}
		}
	}
}

static void dmmap_sync_dhcpv6_server_pool(struct dmctx *dmctx)
{
	struct uci_section *s = NULL, *tmp = NULL;

	uci_foreach_sections("dhcp", "dhcp", s) {
		char *ignore = NULL, *instance = NULL;

		dmuci_get_value_by_section_string(s, "ignore", &ignore);
		if (DM_LSTRCMP(ignore, "1") == 0)
			continue;

		// Device.DHCPv6.Server.Pool.{i}.
		char sec_name[128] = {0};
		snprintf(sec_name, sizeof(sec_name), "%s.%s", section_config(s), section_name(s));

		struct uci_section *dmmap_s = get_dup_section_in_dmmap_opt("DHCPv6", "Pool", "__section_name__", sec_name);
		if (dmmap_s == NULL) {
			dmmap_s = create_dmmap_obj(dmctx, 0, "DHCPv6", "Pool", s, &instance);
			if (dmmap_s && DM_STRLEN(instance)) {
				set_section_order("DHCPv6", dmmap_s, s, 0, instance);
			}
		} else {
			dmuci_get_value_by_section_string(dmmap_s, "__instance__", &instance);
			dmctx->obj_buf[0] = "Pool";
			dmctx->inst_buf[0] = instance;
		}

		sync_dhcpv6_pool_option(dmctx, s, instance);
	}

	s = NULL;
	uci_path_foreach_sections_safe(bbfdm, "DHCPv6", "Pool", tmp, s) {
		char *orig_sec_name = NULL, *ignore = NULL;

		dmuci_get_value_by_section_string(s, "__section_name__", &orig_sec_name);
		if (DM_STRLEN(orig_sec_name) == 0) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		struct uci_section *orig_sec = get_config_section_from_dmmap_section_name(orig_sec_name);
		if (orig_sec == NULL) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}

		dmuci_get_value_by_section_string(orig_sec, "ignore", &ignore);
		if (DM_LSTRCMP(ignore, "1") == 0) {
			dmuci_delete_by_section(s, NULL, NULL);
			continue;
		}
	}

	remove_unsynced_dhcp_options("DHCPv6");
}

static void dmmap_synchronize_dhcpv4(struct dmctx *dmctx)
{
	bbfdm_create_empty_file("/etc/bbfdm/dmmap/DHCPv4");

	dmmap_sync_dhcpv4_client(dmctx);
	dmmap_sync_dhcpv4_server_pool(dmctx);
	dmmap_sync_dhcpv4_relay_forward(dmctx);

	dmuci_commit_package_bbfdm("DHCPv4");
}

static void dmmap_synchronize_dhcpv6(struct dmctx *dmctx)
{
	bbfdm_create_empty_file("/etc/bbfdm/dmmap/DHCPv6");

	dmmap_sync_dhcpv6_client(dmctx);
	dmmap_sync_dhcpv6_server_pool(dmctx);

	dmuci_commit_package_bbfdm("DHCPv6");
}

int init_dhcp_module(void *data)
{
	struct dmctx bbf_ctx = {0};

	bbf_ctx_init(&bbf_ctx, NULL);
	if (file_exists("/lib/netifd/proto/dhcp.sh") && file_exists("/etc/config/dhcp")) {
		dmmap_synchronize_dhcpv4(&bbf_ctx);
	}

	if (file_exists("/lib/netifd/proto/dhcpv6.sh") && file_exists("/etc/config/dhcp")) {
		dmmap_synchronize_dhcpv6(&bbf_ctx);
	}

	bbf_ctx_clean(&bbf_ctx);
	return 0;
}

int sync_dhcp_module(void *data)
{
	struct bbfdm_context *bbfdm_ctx = (struct bbfdm_context *)data;
	if (bbfdm_ctx == NULL)
		return 0;

	if (DM_STRCMP(bbfdm_ctx->uci_change_proto, "external") == 0 && !list_empty(&bbfdm_ctx->changed_uci)) {
		struct apply_handler_node *node = NULL;
		list_for_each_entry(node, &bbfdm_ctx->changed_uci, list) {
			if (DM_STRCMP(node->file_path, "/etc/config/network") == 0 ||
			    DM_STRCMP(node->file_path, "/etc/config/dhcp") == 0) {
				BBF_DEBUG("Syncing dhcp module config");
				init_dhcp_module(data);
				break;
			}
		}
	}

	return 0;
}

DMOBJ tDHCPObjs[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys*/
{"DHCPv4", &DMREAD, NULL, NULL, "file:/lib/netifd/proto/dhcp.sh,/etc/config/dhcp", NULL, NULL, NULL, tDHCPv4Obj, tDHCPv4Params, NULL, BBFDM_BOTH, NULL},
{"DHCPv6", &DMREAD, NULL, NULL, "file:/lib/netifd/proto/dhcpv6.sh,/etc/config/dhcp", NULL, NULL, NULL, tDHCPv6Obj, tDHCPv6Params, NULL, BBFDM_BOTH, NULL},
{0}
};

/* ********** DynamicObj ********** */
DM_MAP_OBJ tDynamicObj[] = {
/* parentobj, nextobject, parameter */
{"Device.", tDHCPObjs, NULL, init_dhcp_module, NULL, sync_dhcp_module},
{0}
};
