text: /*
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   */
  
  #include "usbg/usbg.h"
  #include "usbg/usbg_internal.h"
  #include "usbg/function/hid.h"
  
  #include <malloc.h>
  #include <assert.h>
  #ifdef HAS_GADGET_SCHEMES
  #include <libconfig.h>
  #endif
  
  struct usbg_f_hid {
  	struct usbg_function func;
  };
  
  #define HID_DEC_ATTR(_name)						\
  	{								\
  		.name = #_name,						\
  		.ro = false,						\
  		.offset = offsetof(struct usbg_f_hid_attrs, _name),    \
  		.get = usbg_get_dec,				        \
  		.set = usbg_set_dec,				        \
  		.import = usbg_get_config_node_int,	                \
  		.export = usbg_set_config_node_int,		        \
  	}
  
  #define HID_DEV_ATTR_RO(_name)						\
  	{								\
  		.name = #_name,						\
  		.ro = true,						\
  		.offset = offsetof(struct usbg_f_hid_attrs, _name),    \
  		.get = usbg_get_dev,				        \
  		.export = usbg_set_config_node_dev,		        \
  	}
  
  #ifdef HAS_GADGET_SCHEMES
  static int hid_get_report(const char *path, const char *name, const char *attr,
  			  void *val)
  {
  	struct usbg_f_hid_report_desc *report_desc = val;
  	char buf[USBG_MAX_FILE_SIZE];
  	int ret;
  
  	ret = usbg_read_buf(path, name, attr, buf);
  	if (ret < 0)
  		return ret;
  
  	report_desc->len = ret;
  	if (ret == 0) {
  		report_desc->desc = NULL;
  	} else {
  		report_desc->desc = malloc(report_desc->len);
  		if (!report_desc->desc)
  			return USBG_ERROR_NO_MEM;
  		memcpy(report_desc->desc, buf, report_desc->len);
  	}
  
  	return 0;
  }
  
  static int hid_set_report(const char *path, const char *name, const char *attr,
  			  void *val)
  {
  	struct usbg_f_hid_report_desc *report_desc = val;
  	char *buf = report_desc->desc;
  	int len = report_desc->len;
  	int ret;
  
  	if (len == 0) {
  		buf = "";
  		len = 1;
  	}
  
  	ret = usbg_write_buf(path, name, attr, buf, len);
  	if (ret > 0)
  		ret = USBG_SUCCESS;
  
  	return ret;
  }
  
  static int hid_get_config_node_report(config_setting_t *root,
  				      const char *node_name, void *val)
  {
  	struct usbg_f_hid_report_desc *report_desc = val;
  	char *buf;
  	int tmp, i;
  	config_setting_t *list_node, *node;
  	int len;
  	int ret;
  
  	list_node = config_setting_get_member(root, node_name);
  	if (!list_node)
  		return 0;
  
  	if (!config_setting_is_list(list_node))
  		return USBG_ERROR_INVALID_TYPE;
  
  	len = config_setting_length(list_node);
  
  	buf = malloc(len * sizeof(char));
  	if (!buf)
  		return USBG_ERROR_NO_MEM;
  
  	for (i = 0; i < len; ++i) {
  		node = config_setting_get_elem(list_node, i);
  		assert(node);
  
  		if (!usbg_config_is_int(node)) {
  			ret = USBG_ERROR_INVALID_TYPE;
  			goto free_buf;
  		}
  
  		tmp = config_setting_get_int(node);
  		if (tmp < 0 || tmp > 255) {
  			ret = USBG_ERROR_INVALID_VALUE;
  			goto free_buf;
  		}
  		buf[i] = (char)tmp;
  	}
  
  	report_desc->desc = buf;
  	report_desc->len = len;
  
  	return 1;
  
  free_buf:
  	free(buf);
  	return ret;
  }
  
  static int hid_set_config_node_report(config_setting_t *root,
  				      const char *node_name, void *val)
  {
  	struct usbg_f_hid_report_desc *report_desc = val;
  	config_setting_t *node;
  	int i;
  	int ret = 0;
  
  	node = config_setting_add(root, node_name, CONFIG_TYPE_LIST);
  	if (!node)
  		return USBG_ERROR_NO_MEM;
  
  	for (i = 0; i < report_desc->len; ++i) {
  		int tmp = report_desc->desc[i];
  		ret = usbg_set_config_node_int_hex(node, NULL, &tmp);
  		if (ret)
  			return ret;
  	}
  
  	return 0;
  }
  #else
  
  #define hid_get_report			NULL
  #define hid_set_report			NULL
  #define hid_get_config_node_report	NULL
  #define hid_set_config_node_report	NULL
  
  #endif
  
  #define HID_RD_ATTR(_name)						\
  	{								\
  		.name = #_name,						\
  		.ro = false,						\
  		.offset = offsetof(struct usbg_f_hid_attrs, _name),    \
  		.get = hid_get_report,				        \
  		.set = hid_set_report,				        \
  		.import = hid_get_config_node_report,		        \
  		.export = hid_set_config_node_report,		        \
  	}
  
  struct {
  	const char *name;
  	bool ro;
  	size_t offset;
  	usbg_attr_get_func get;
  	usbg_attr_set_func set;
  	usbg_import_node_func import;
  	usbg_export_node_func export;
  } hid_attr[USBG_F_HID_ATTR_MAX] = {
  	[USBG_F_HID_DEV] = HID_DEV_ATTR_RO(dev),
  	[USBG_F_HID_PROTOCOL] = HID_DEC_ATTR(protocol),
  	[USBG_F_HID_REPORT_DESC] = HID_RD_ATTR(report_desc),
  	[USBG_F_HID_REPORT_LENGTH] = HID_DEC_ATTR(report_length),
  	[USBG_F_HID_SUBCLASS] = HID_DEC_ATTR(subclass),
  };
  
  #undef HID_DEC_ATTR
  #undef HID_DEV_ATTR_RO
  #undef HID_RD_ATTR
  
  GENERIC_ALLOC_INST(hid, struct usbg_f_hid, func);
  
  GENERIC_FREE_INST(hid, struct usbg_f_hid, func);
  
  static int hid_set_attrs(struct usbg_function *f, void *f_attrs)
  {
  	return usbg_f_hid_set_attrs(usbg_to_hid_function(f), f_attrs);
  }
  
  static int hid_get_attrs(struct usbg_function *f, void *f_attrs)
  {
  	return usbg_f_hid_get_attrs(usbg_to_hid_function(f), f_attrs);
  }
  
  static void hid_cleanup_attrs(struct usbg_function *f, void *f_attrs)
  {
  	usbg_f_hid_cleanup_attrs(f_attrs);
  }
  
  #ifdef HAS_GADGET_SCHEMES
  
  static int hid_libconfig_import(struct usbg_function *f,
  				  config_setting_t *root)
  {
  	struct usbg_f_hid *hf = usbg_to_hid_function(f);
  	union usbg_f_hid_attr_val val;
  	int i;
  	int ret = 0;
  
  	for (i = USBG_F_HID_ATTR_MIN; i < USBG_F_HID_ATTR_MAX; ++i) {
  		if (hid_attr[i].ro)
  			continue;
  
  		ret = hid_attr[i].import(root, hid_attr[i].name, &val);
  		/* node not  found */
  		if (ret == 0)
  			continue;
  		/* error */
  		if (ret < 0)
  			break;
  
  		ret = usbg_f_hid_set_attr_val(hf, i, val);
  		if (ret)
  			break;
  	}
  
  	return ret;
  }
  
  static int hid_libconfig_export(struct usbg_function *f,
  				  config_setting_t *root)
  {
  	struct usbg_f_hid *hf = usbg_to_hid_function(f);
  	union usbg_f_hid_attr_val val;
  	int i;
  	int ret = 0;
  
  	for (i = USBG_F_HID_ATTR_MIN; i < USBG_F_HID_ATTR_MAX; ++i) {
  		ret = usbg_f_hid_get_attr_val(hf, i, &val);
  		if (ret)
  			break;
  
  		ret = hid_attr[i].export(root, hid_attr[i].name, &val);
  		if (ret)
  			break;
  	}
  
  	return ret;
  }
  
  #endif /* HAS_GADGET_SCHEMES */
  
  struct usbg_function_type usbg_f_type_hid = {
  	.name = "hid",
  	.alloc_inst = hid_alloc_inst,
  	.free_inst = hid_free_inst,
  	.set_attrs = hid_set_attrs,
  	.get_attrs = hid_get_attrs,
  	.cleanup_attrs = hid_cleanup_attrs,
  
  #ifdef HAS_GADGET_SCHEMES
  	.import = hid_libconfig_import,
  	.export = hid_libconfig_export,
  #endif
  };
  
  /* API implementation */
  
  usbg_f_hid *usbg_to_hid_function(usbg_function *f)
  {
  	return f->ops == &usbg_f_type_hid ?
  		container_of(f, struct usbg_f_hid, func) : NULL;
  }
  
  usbg_function *usbg_from_hid_function(usbg_f_hid *hf)
  {
  	return &hf->func;
  }
  
  int usbg_f_hid_get_attrs(usbg_f_hid *hf,
  			  struct usbg_f_hid_attrs *attrs)
  {
  	int i;
  	int ret = 0;
  
  	for (i = USBG_F_HID_ATTR_MIN; i < USBG_F_HID_ATTR_MAX; ++i) {
  		ret = usbg_f_hid_get_attr_val(hf, i,
  					       (union usbg_f_hid_attr_val *)
  					       ((char *)attrs
  						+ hid_attr[i].offset));
  		if (ret)
  			break;
  	}
  
  	return ret;
  
  }
  
  int usbg_f_hid_set_attrs(usbg_f_hid *hf,
  			 const struct usbg_f_hid_attrs *attrs)
  {
  	int i;
  	int ret = 0;
  
  	for (i = USBG_F_HID_ATTR_MIN; i < USBG_F_HID_ATTR_MAX; ++i) {
  		if (hid_attr[i].ro)
  			continue;
  
  		ret = usbg_f_hid_set_attr_val(hf, i,
  					       *(union usbg_f_hid_attr_val *)
  					       ((char *)attrs
  						+ hid_attr[i].offset));
  		if (ret)
  			break;
  	}
  
  	return ret;
  
  }
  
  int usbg_f_hid_get_attr_val(usbg_f_hid *hf, enum usbg_f_hid_attr attr,
  			    union usbg_f_hid_attr_val *val)
  {
  	return hid_attr[attr].get(hf->func.path, hf->func.name,
  				  hid_attr[attr].name, val);
  }
  
  int usbg_f_hid_set_attr_val(usbg_f_hid *hf, enum usbg_f_hid_attr attr,
  			    union usbg_f_hid_attr_val val)
  {
  	return hid_attr[attr].ro ?
  		USBG_ERROR_INVALID_PARAM :
  		hid_attr[attr].set(hf->func.path, hf->func.name,
  				  hid_attr[attr].name, &val);
  }
  
  