/* -*- mode: c; c-file-style: "openbsd" -*- */
/*
 * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "lldpd.h"
#include "trace.h"

#include <sys/param.h>
#include <sys/utsname.h>

static ssize_t
client_handle_none(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len,
    void **output, int *subscribed)
{
	log_info("rpc", "received noop request from client");
	*type = NONE;
	return 0;
}

/* Return the global configuration */
static ssize_t
client_handle_get_configuration(struct lldpd *cfg, enum hmsg_type *type, void *input,
    int input_len, void **output, int *subscribed)
{
	ssize_t output_len;
	log_debug("rpc", "client requested configuration");
	output_len = lldpd_config_serialize(&cfg->g_config, output);
	if (output_len <= 0) {
		output_len = 0;
		*type = NONE;
	}
	return output_len;
}

static char *
xstrdup(const char *str)
{
	if (!str) return NULL;
	return strdup(str);
}

/* Change the global configuration */
static ssize_t
client_handle_set_configuration(struct lldpd *cfg, enum hmsg_type *type, void *input,
    int input_len, void **output, int *subscribed)
{
	struct lldpd_config *config;

	log_debug("rpc", "client request a change in configuration");
	/* Get the proposed configuration. */
	if (lldpd_config_unserialize(input, input_len, &config) <= 0) {
		*type = NONE;
		return 0;
	}

#define CHANGED(w) (config->w != cfg->g_config.w)
#define CHANGED_STR(w)               \
  (!(config->w == cfg->g_config.w || \
      (config->w && cfg->g_config.w && !strcmp(config->w, cfg->g_config.w))))

	/* What needs to be done? Transmit delay? */
	if (CHANGED(c_tx_interval) && config->c_tx_interval != 0) {
		if (config->c_tx_interval < 0) {
			log_debug("rpc", "client asked for immediate retransmission");
		} else if (config->c_tx_interval <= 3600 * 1000) {
			log_debug("rpc", "client change transmit interval to %d ms",
			    config->c_tx_interval);
			cfg->g_config.c_tx_interval = config->c_tx_interval;
			cfg->g_config.c_ttl =
			    cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold;
			cfg->g_config.c_ttl =
			    MIN((cfg->g_config.c_ttl + 999) / 1000, 65535);
		}
		levent_send_now(cfg);
	}
	if (CHANGED(c_tx_hold) && config->c_tx_hold > 0 && config->c_tx_hold <= 100) {
		log_debug("rpc", "client change transmit hold to %d",
		    config->c_tx_hold);
		cfg->g_config.c_tx_hold = config->c_tx_hold;
		cfg->g_config.c_ttl =
		    cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold;
		cfg->g_config.c_ttl = MIN((cfg->g_config.c_ttl + 999) / 1000, 65535);
	}
	if (CHANGED(c_max_neighbors) && config->c_max_neighbors > 0) {
		log_debug("rpc", "client change maximum neighbors to %d",
		    config->c_max_neighbors);
		cfg->g_config.c_max_neighbors = config->c_max_neighbors;
	}
	if (CHANGED(c_lldp_portid_type) &&
	    config->c_lldp_portid_type > LLDP_PORTID_SUBTYPE_UNKNOWN &&
	    config->c_lldp_portid_type <= LLDP_PORTID_SUBTYPE_MAX) {
		log_debug("rpc", "change lldp portid tlv subtype to %d",
		    config->c_lldp_portid_type);
		cfg->g_config.c_lldp_portid_type = config->c_lldp_portid_type;
		levent_update_now(cfg);
	}
	if (CHANGED(c_lldp_agent_type) &&
	    config->c_lldp_agent_type > LLDP_AGENT_TYPE_UNKNOWN &&
	    config->c_lldp_agent_type <= LLDP_AGENT_TYPE_MAX) {
		log_debug("rpc", "change lldp agent type to %d",
		    config->c_lldp_agent_type);
		cfg->g_config.c_lldp_agent_type = config->c_lldp_agent_type;
		levent_update_now(cfg);
	}
	/* Pause/resume */
	if (CHANGED(c_paused)) {
		log_debug("rpc", "client asked to %s lldpd",
		    config->c_paused ? "pause" : "resume");
		cfg->g_config.c_paused = config->c_paused;
		levent_send_now(cfg);
	}

#ifdef ENABLE_LLDPMED
	if (CHANGED(c_enable_fast_start)) {
		cfg->g_config.c_enable_fast_start = config->c_enable_fast_start;
		log_debug("rpc", "client asked to %s fast start",
		    cfg->g_config.c_enable_fast_start ? "enable" : "disable");
	}
	if (CHANGED(c_tx_fast_interval) && config->c_tx_fast_interval > 0) {
		log_debug("rpc", "change fast interval to %d",
		    config->c_tx_fast_interval);
		cfg->g_config.c_tx_fast_interval = config->c_tx_fast_interval;
	}
#endif
	if (CHANGED_STR(c_iface_pattern)) {
		log_debug("rpc", "change interface pattern to %s",
		    config->c_iface_pattern ? config->c_iface_pattern : "(NULL)");
		free(cfg->g_config.c_iface_pattern);
		cfg->g_config.c_iface_pattern = xstrdup(config->c_iface_pattern);
		levent_update_now(cfg);
	}
	if (CHANGED_STR(c_perm_ifaces)) {
		log_debug("rpc", "change permanent interface pattern to %s",
		    config->c_perm_ifaces ? config->c_perm_ifaces : "(NULL)");
		free(cfg->g_config.c_perm_ifaces);
		cfg->g_config.c_perm_ifaces = xstrdup(config->c_perm_ifaces);
		levent_update_now(cfg);
	}
	if (CHANGED_STR(c_mgmt_pattern)) {
		log_debug("rpc", "change management pattern to %s",
		    config->c_mgmt_pattern ? config->c_mgmt_pattern : "(NULL)");
		free(cfg->g_config.c_mgmt_pattern);
		cfg->g_config.c_mgmt_pattern = xstrdup(config->c_mgmt_pattern);
		levent_update_now(cfg);
	}
	if (CHANGED_STR(c_cid_string)) {
		log_debug("rpc", "change chassis ID string to %s",
		    config->c_cid_string ? config->c_cid_string : "(NULL)");
		free(cfg->g_config.c_cid_string);
		cfg->g_config.c_cid_string = xstrdup(config->c_cid_string);
		free(LOCAL_CHASSIS(cfg)->c_id);
		LOCAL_CHASSIS(cfg)->c_id = NULL;
		lldpd_update_localchassis(cfg);
		levent_update_now(cfg);
	}
	if (CHANGED_STR(c_description)) {
		log_debug("rpc", "change chassis description to %s",
		    config->c_description ? config->c_description : "(NULL)");
		free(cfg->g_config.c_description);
		cfg->g_config.c_description = xstrdup(config->c_description);
		lldpd_update_localchassis(cfg);
		levent_update_now(cfg);
	}
	if (CHANGED_STR(c_platform)) {
		log_debug("rpc", "change platform description to %s",
		    config->c_platform ? config->c_platform : "(NULL)");
		free(cfg->g_config.c_platform);
		cfg->g_config.c_platform = xstrdup(config->c_platform);
		lldpd_update_localchassis(cfg);
		levent_update_now(cfg);
	}
	if (CHANGED_STR(c_hostname)) {
		log_debug("rpc", "change system name to %s",
		    config->c_hostname ? config->c_hostname : "(NULL)");
		free(cfg->g_config.c_hostname);
		cfg->g_config.c_hostname = xstrdup(config->c_hostname);
		lldpd_update_localchassis(cfg);
		levent_update_now(cfg);
	}
	if (CHANGED(c_set_ifdescr)) {
		log_debug("rpc",
		    "%s setting of interface description based on discovered neighbors",
		    config->c_set_ifdescr ? "enable" : "disable");
		cfg->g_config.c_set_ifdescr = config->c_set_ifdescr;
		levent_update_now(cfg);
	}
	if (CHANGED(c_promisc)) {
		log_debug("rpc", "%s promiscuous mode on managed interfaces",
		    config->c_promisc ? "enable" : "disable");
		cfg->g_config.c_promisc = config->c_promisc;
		levent_update_now(cfg);
	}
	if (CHANGED(c_cap_advertise)) {
		log_debug("rpc", "%s chassis capabilities advertisement",
		    config->c_cap_advertise ? "enable" : "disable");
		cfg->g_config.c_cap_advertise = config->c_cap_advertise;
		levent_update_now(cfg);
	}
	if (CHANGED(c_cap_override)) {
		log_debug("rpc", "%s chassis capabilities override",
		    config->c_cap_override ? "enable" : "disable");
		cfg->g_config.c_cap_override = config->c_cap_override;
		levent_update_now(cfg);
	}
	if (CHANGED(c_mgmt_advertise)) {
		log_debug("rpc", "%s management addresses advertisement",
		    config->c_mgmt_advertise ? "enable" : "disable");
		cfg->g_config.c_mgmt_advertise = config->c_mgmt_advertise;
		levent_update_now(cfg);
	}
	if (CHANGED(c_bond_slave_src_mac_type)) {
		if (config->c_bond_slave_src_mac_type >
			LLDP_BOND_SLAVE_SRC_MAC_TYPE_UNKNOWN &&
		    config->c_bond_slave_src_mac_type <=
			LLDP_BOND_SLAVE_SRC_MAC_TYPE_MAX) {
			log_debug("rpc", "change bond src mac type to %d",
			    config->c_bond_slave_src_mac_type);
			cfg->g_config.c_bond_slave_src_mac_type =
			    config->c_bond_slave_src_mac_type;
		} else {
			log_info("rpc", "Invalid bond slave src mac type: %d\n",
			    config->c_bond_slave_src_mac_type);
		}
	}

	lldpd_config_cleanup(config);
	free(config);

	return 0;
}

/* Return the list of interfaces.
   Input:  nothing.
   Output: list of interface names (lldpd_interface_list)
*/
static ssize_t
client_handle_get_interfaces(struct lldpd *cfg, enum hmsg_type *type, void *input,
    int input_len, void **output, int *subscribed)
{
	struct lldpd_interface *iff, *iff_next;
	struct lldpd_hardware *hardware;
	ssize_t output_len;

	/* Build the list of interfaces */
	struct lldpd_interface_list ifs;

	log_debug("rpc", "client request the list of interfaces");
	TAILQ_INIT(&ifs);
	TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
		if ((iff = (struct lldpd_interface *)malloc(
			 sizeof(struct lldpd_interface))) == NULL)
			fatal("rpc", NULL);
		iff->name = hardware->h_ifname;
		TAILQ_INSERT_TAIL(&ifs, iff, next);
	}

	output_len = lldpd_interface_list_serialize(&ifs, output);
	if (output_len <= 0) {
		output_len = 0;
		*type = NONE;
	}

	/* Free the temporary list */
	for (iff = TAILQ_FIRST(&ifs); iff != NULL; iff = iff_next) {
		iff_next = TAILQ_NEXT(iff, next);
		TAILQ_REMOVE(&ifs, iff, next);
		free(iff);
	}

	return output_len;
}

/**
 * Set local chassis info
 * Input: chassis object
 * Output: updated chassis object
 */
static ssize_t
client_handle_set_local_chassis(struct lldpd *cfg, enum hmsg_type *type, void *input,
    int input_len, void **output, int *subscribed)
{
	struct lldpd_chassis *chassis = NULL;
	struct lldpd_chassis *local_chassis = NULL;
#ifdef ENABLE_LLDPMED
	struct utsname un;
#endif

	log_debug("rpc", "client request a change in chassis configuration");
	if (lldpd_chassis_unserialize(input, input_len, &chassis) <= 0) {
		*type = NONE;
		return 0;
	}

	local_chassis = LOCAL_CHASSIS(cfg);

#ifdef ENABLE_LLDPMED
	free(local_chassis->c_med_hw);
	local_chassis->c_med_hw =
	    (!chassis->c_med_hw) ? dmi_hw() : strdup(chassis->c_med_hw);

	// Follows lldpd.c - only set sw if advertising is enabled
	if (cfg->g_config.c_advertise_version) {
		free(local_chassis->c_med_sw);

		if (!chassis->c_med_sw) {
			if (uname(&un) < 0) {
				log_warn("rpc",
				    "Could not get default uname. Will continue anyway.");
				local_chassis->c_med_sw = NULL;
			} else {
				local_chassis->c_med_sw = strdup(un.release);
			}
		} else {
			local_chassis->c_med_sw = strdup(chassis->c_med_sw);
		}
	}

	free(local_chassis->c_med_fw);
	local_chassis->c_med_fw =
	    (!chassis->c_med_fw) ? dmi_fw() : strdup(chassis->c_med_fw);

	free(local_chassis->c_med_sn);
	local_chassis->c_med_sn =
	    (!chassis->c_med_sn) ? dmi_sn() : strdup(chassis->c_med_sn);

	free(local_chassis->c_med_manuf);
	local_chassis->c_med_manuf =
	    (!chassis->c_med_manuf) ? dmi_manuf() : strdup(chassis->c_med_manuf);

	free(local_chassis->c_med_model);
	local_chassis->c_med_model =
	    (!chassis->c_med_model) ? dmi_model() : strdup(chassis->c_med_model);

	free(local_chassis->c_med_asset);
	local_chassis->c_med_asset =
	    (!chassis->c_med_asset) ? dmi_asset() : strdup(chassis->c_med_asset);
#endif

	if (chassis->c_cap_enabled != local_chassis->c_cap_enabled) {
		local_chassis->c_cap_enabled = chassis->c_cap_enabled;
		log_debug("rpc", "change capabilities enabled to: %d",
		    local_chassis->c_cap_enabled);
	}

#ifdef ENABLE_LLDPMED
	log_debug("rpc", "change hardware-revision to: %s", local_chassis->c_med_hw);
	log_debug("rpc", "change software-revision to: %s", local_chassis->c_med_sw);
	log_debug("rpc", "change firmware-revision to: %s", local_chassis->c_med_fw);
	log_debug("rpc", "change serial-number to: %s", local_chassis->c_med_sn);
	log_debug("rpc", "change manufacturer to: %s", local_chassis->c_med_manuf);
	log_debug("rpc", "change model to: %s", local_chassis->c_med_model);
	log_debug("rpc", "change asset to: %s", local_chassis->c_med_asset);
#endif

	lldpd_chassis_cleanup(chassis, 1);

	ssize_t output_len = lldpd_chassis_serialize(local_chassis, output);
	if (output_len <= 0) {
		*type = NONE;
		return 0;
	}

	return output_len;
}

/* Return the local chassis.
   Input:  nothing.
   Output: local chassis (lldpd_chassis)
*/
static ssize_t
client_handle_get_local_chassis(struct lldpd *cfg, enum hmsg_type *type, void *input,
    int input_len, void **output, int *subscribed)
{
	struct lldpd_chassis *chassis = LOCAL_CHASSIS(cfg);
	ssize_t output_len;

	log_debug("rpc", "client request the local chassis");
	output_len = lldpd_chassis_serialize(chassis, output);
	if (output_len <= 0) {
		output_len = 0;
		*type = NONE;
	}

	return output_len;
}

/* Return all available information related to an interface
   Input:  name of the interface (serialized)
   Output: Information about the interface (lldpd_hardware)
*/
static ssize_t
client_handle_get_interface(struct lldpd *cfg, enum hmsg_type *type, void *input,
    int input_len, void **output, int *subscribed)
{
	char *name;
	struct lldpd_hardware *hardware;
	void *p;

	/* Get name of the interface */
	if (marshal_unserialize(string, input, input_len, &p) <= 0) {
		*type = NONE;
		return 0;
	}
	name = p;

	/* Search appropriate hardware */
	log_debug("rpc", "client request interface %s", name);
	TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries)
		if (!strcmp(hardware->h_ifname, name)) {
			ssize_t output_len = lldpd_hardware_serialize(hardware, output);
			free(name);
			if (output_len <= 0) {
				*type = NONE;
				return 0;
			}
			return output_len;
		}

	log_warnx("rpc", "no interface %s found", name);
	free(name);
	*type = NONE;
	return 0;
}

/* Return all available information related to an interface
   Input:  name of the interface (serialized)
   Output: Information about the interface (lldpd_hardware)
*/
static ssize_t
client_handle_get_default_port(struct lldpd *cfg, enum hmsg_type *type, void *input,
    int input_len, void **output, int *subscribed)
{
	log_debug("rpc", "client request the default local port");
	ssize_t output_len = lldpd_port_serialize(cfg->g_default_local_port, output);
	if (output_len <= 0) {
		*type = NONE;
		return 0;
	}
	return output_len;
}

static int
_client_handle_set_port(struct lldpd *cfg, struct lldpd_port *port,
    struct lldpd_port_set *set)
{
#ifdef ENABLE_LLDPMED
	struct lldpd_med_loc *loc = NULL;
#endif
	if (set->local_id) {
		log_debug("rpc", "requested change to Port ID");
		free(port->p_id);
		port->p_id = strdup(set->local_id);
		port->p_id_len = strlen(set->local_id);
		port->p_id_subtype = LLDP_PORTID_SUBTYPE_LOCAL;
		port->p_descr_force = 0;
	}
	if (set->local_descr) {
		log_debug("rpc", "requested change to Port Description");
		free(port->p_descr);
		port->p_descr = strdup(set->local_descr);
		port->p_descr_force = 1;
	}
	switch (set->rxtx) {
	case LLDPD_RXTX_TXONLY:
		log_debug("rpc", "requested TX only mode");
		port->p_disable_rx = 1;
		port->p_disable_tx = 0;
		break;
	case LLDPD_RXTX_RXONLY:
		log_debug("rpc", "requested RX only mode");
		port->p_disable_rx = 0;
		port->p_disable_tx = 1;
		break;
	case LLDPD_RXTX_BOTH:
		log_debug("rpc", "requested RX/TX mode");
		port->p_disable_rx = port->p_disable_tx = 0;
		break;
	case LLDPD_RXTX_DISABLED:
		log_debug("rpc", "requested disabled mode");
		port->p_disable_rx = port->p_disable_tx = 1;
		break;
	}
	if (set->vlan_tx_enabled > -1) {
		port->p_vlan_tx_enabled = set->vlan_tx_enabled;
		port->p_vlan_tx_tag = set->vlan_tx_tag;
	}
#ifdef ENABLE_LLDPMED
	if (set->med_policy && set->med_policy->type > 0) {
		log_debug("rpc", "requested change to MED policy");
		if (set->med_policy->type > LLDP_MED_APPTYPE_LAST) {
			log_warnx("rpc", "invalid policy provided: %d",
			    set->med_policy->type);
			return -1;
		}
		memcpy(&port->p_med_policy[set->med_policy->type - 1], set->med_policy,
		    sizeof(struct lldpd_med_policy));
		port->p_med_cap_enabled |= LLDP_MED_CAP_POLICY;
	}
	if (set->med_location && set->med_location->format > 0) {
		char *newdata = NULL;
		log_debug("rpc", "requested change to MED location");
		if (set->med_location->format > LLDP_MED_LOCFORMAT_LAST) {
			log_warnx("rpc", "invalid location format provided: %d",
			    set->med_location->format);
			return -1;
		}
		loc = &port->p_med_location[set->med_location->format - 1];
		free(loc->data);
		memcpy(loc, set->med_location, sizeof(struct lldpd_med_loc));
		if (!loc->data || !(newdata = malloc(loc->data_len))) loc->data_len = 0;
		if (newdata) memcpy(newdata, loc->data, loc->data_len);
		loc->data = newdata;
		port->p_med_cap_enabled |= LLDP_MED_CAP_LOCATION;
	}
	if (set->med_power) {
		log_debug("rpc", "requested change to MED power");
		memcpy(&port->p_med_power, set->med_power,
		    sizeof(struct lldpd_med_power));
		switch (set->med_power->devicetype) {
		case LLDP_MED_POW_TYPE_PD:
			port->p_med_cap_enabled |= LLDP_MED_CAP_MDI_PD;
			port->p_med_cap_enabled &= ~LLDP_MED_CAP_MDI_PSE;
			break;
		case LLDP_MED_POW_TYPE_PSE:
			port->p_med_cap_enabled |= LLDP_MED_CAP_MDI_PSE;
			port->p_med_cap_enabled &= ~LLDP_MED_CAP_MDI_PD;
			break;
		}
	}
#endif
#ifdef ENABLE_DOT3
	if (set->dot3_power) {
		log_debug("rpc", "requested change to Dot3 power");
		memcpy(&port->p_power, set->dot3_power,
		    sizeof(struct lldpd_dot3_power));
	}
#endif
#ifdef ENABLE_CUSTOM
	if (set->custom_list_clear) {
		log_debug("rpc", "requested custom TLVs clear");
		lldpd_custom_list_cleanup(port);
	} else {
		if (set->custom) {
			log_info("rpc",
			    "custom TLV op %s oui %02x:%02x:%02x subtype %x",
			    (set->custom_tlv_op == CUSTOM_TLV_REMOVE)  ? "remove" :
				(set->custom_tlv_op == CUSTOM_TLV_ADD) ? "add" :
									 "replace",
			    set->custom->oui[0], set->custom->oui[1],
			    set->custom->oui[2], set->custom->subtype);
			switch (set->custom_tlv_op) {
			case CUSTOM_TLV_REMOVE:
				lldpd_custom_tlv_cleanup(port, set->custom);
				break;
			case CUSTOM_TLV_ADD:
				lldpd_custom_tlv_add(port, set->custom);
				break;
			case CUSTOM_TLV_REPLACE:
			default:
				lldpd_custom_tlv_cleanup(port, set->custom);
				lldpd_custom_tlv_add(port, set->custom);
				break;
			}
		}
	}
#endif
	return 0;
}

/* Set some port related settings (policy, location, power)
   Input: name of the interface, policy/location/power setting to be modified
   Output: nothing
*/
static ssize_t
client_handle_set_port(struct lldpd *cfg, enum hmsg_type *type, void *input,
    int input_len, void **output, int *subscribed)
{
	int ret = 0;
	struct lldpd_port_set *set = NULL;
	struct lldpd_hardware *hardware = NULL;

	if (lldpd_port_set_unserialize(input, input_len, &set) <= 0) {
		*type = NONE;
		return 0;
	}
	if (!set->ifname) {
		log_warnx("rpc", "no interface provided");
		goto set_port_finished;
	}

	/* Search the appropriate hardware */
	if (strlen(set->ifname) == 0) {
		log_debug("rpc", "client request change to default port");
		if (_client_handle_set_port(cfg, cfg->g_default_local_port, set) == -1)
			goto set_port_finished;
		ret = 1;
	} else {
		log_debug("rpc", "client request change to port %s", set->ifname);
		TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
			if (!strcmp(hardware->h_ifname, set->ifname)) {
				struct lldpd_port *port = &hardware->h_lport;
				if (_client_handle_set_port(cfg, port, set) == -1)
					goto set_port_finished;
				ret = 1;
				break;
			}
		}
	}

	if (ret == 0)
		log_warn("rpc", "no interface %s found", set->ifname);
	else
		levent_update_now(cfg);

set_port_finished:
	if (!ret) *type = NONE;
	free(set->ifname);
	free(set->local_id);
	free(set->local_descr);
#ifdef ENABLE_LLDPMED
	free(set->med_policy);
	if (set->med_location) free(set->med_location->data);
	free(set->med_location);
	free(set->med_power);
#endif
#ifdef ENABLE_DOT3
	free(set->dot3_power);
#endif
#ifdef ENABLE_CUSTOM
	if (set->custom) {
		free(set->custom->oui_info);
		free(set->custom);
	}
#endif
	free(set);
	return 0;
}

/* Register subscribtion to neighbor changes */
static ssize_t
client_handle_subscribe(struct lldpd *cfg, enum hmsg_type *type, void *input,
    int input_len, void **output, int *subscribed)
{
	log_debug("rpc", "client subscribe to changes");
	*subscribed = 1;
	return 0;
}

struct client_handle {
	enum hmsg_type type;
	const char *name;
	ssize_t (
	    *handle)(struct lldpd *, enum hmsg_type *, void *, int, void **, int *);
};

static struct client_handle client_handles[] = { { NONE, "None", client_handle_none },
	{ GET_CONFIG, "Get configuration", client_handle_get_configuration },
	{ SET_CONFIG, "Set configuration", client_handle_set_configuration },
	{ GET_INTERFACES, "Get interfaces", client_handle_get_interfaces },
	{ GET_INTERFACE, "Get interface", client_handle_get_interface },
	{ GET_DEFAULT_PORT, "Get default port", client_handle_get_default_port },
	{ SET_CHASSIS, "Set local chassis", client_handle_set_local_chassis },
	{ GET_CHASSIS, "Get local chassis", client_handle_get_local_chassis },
	{ SET_PORT, "Set port", client_handle_set_port },
	{ SUBSCRIBE, "Subscribe", client_handle_subscribe }, { 0, NULL } };

int
client_handle_client(struct lldpd *cfg, ssize_t (*send)(void *, int, void *, size_t),
    void *out, enum hmsg_type type, void *buffer, size_t n, int *subscribed)
{
	struct client_handle *ch;
	void *answer;
	ssize_t len, sent;

	log_debug("rpc", "handle client request");
	for (ch = client_handles; ch->handle != NULL; ch++) {
		if (ch->type == type) {
			TRACE(LLDPD_CLIENT_REQUEST(ch->name));
			answer = NULL;
			len = ch->handle(cfg, &type, buffer, n, &answer, subscribed);
			sent = send(out, type, answer, len);
			free(answer);
			return sent;
		}
	}

	log_warnx("rpc", "unknown message request (%d) received", type);
	return -1;
}
