/*
 * hidparser.c:	HID Parser
 *
 * This file is part of the MGE UPS SYSTEMS HID Parser
 *
 * Copyright (C)
 *	1998-2003	MGE UPS SYSTEMS, Luc Descotils
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * -------------------------------------------------------------------------- */

#include <string.h>
#include <stdlib.h>

#include "config.h"
#include "hidparser.h"
#include "nut_stdint.h"  /* for int8_t, int16_t, int32_t */
#include "common.h"      /* for fatalx() */

static const uint8_t ItemSize[4] = { 0, 1, 2, 4 };

/*
 * HIDParser struct
 * -------------------------------------------------------------------------- */
typedef struct {
	const unsigned char	*ReportDesc;		/* Report Descriptor		*/
	int			ReportDescSize;		/* Size of Report Descriptor	*/

	uint16_t	Pos;				/* Store current pos in descriptor	*/
	uint8_t		Item;				/* Store current Item		*/
	long		Value;				/* Store current Value		*/

	HIDData_t	Data;				/* Store current environment	*/

	uint8_t		OffsetTab[MAX_REPORT][4];	/* Store ID, Type, offset & timestamp of report */
	uint8_t		ReportCount;			/* Store Report Count		*/
	uint8_t		Count;				/* Store local report count	*/

	uint16_t	UPage;				/* Global UPage			*/
	HIDNode_t	UsageTab[USAGE_TAB_SIZE];	/* Usage stack			*/
	uint8_t		UsageSize;			/* Design number of usage used	*/
} HIDParser_t;

/* return 1 + the position of the leftmost "1" bit of an int, or 0 if
   none. */
static inline unsigned int hibit(unsigned int x)
{
	unsigned int	res = 0;

	while (x > 0xff) {
		x >>= 8;
		res += 8;
	}

	while (x) {
		x >>= 1;
		res += 1;
	}

	return res;
}

/* Note: The USB HID specification states that Local items do not
   carry over to the next Main item (version 1.11, section
   6.2.2.8). Therefore the local state must be reset after each main
   item. In particular, any unused usages on the Usage tabs must be
   discarded and must not carry over to the next Main item. Some APC
   equipment actually sends multiple redundant "usage" commands for a
   single control, so resetting the local state is important. */
/* Also note: UsageTab[0] is used as the usage of the next control,
   even if UsageSize=0. Therefore, this must be initialized */
static void ResetLocalState(HIDParser_t* pParser)
{
	pParser->UsageSize = 0;
	memset(pParser->UsageTab, 0, sizeof(pParser->UsageTab));
}

/*
 * GetReportOffset
 *
 * Return pointer on current offset value for Report designed by 
 * ReportID/ReportType
 * -------------------------------------------------------------------------- */
static uint8_t *GetReportOffset(HIDParser_t* pParser, const uint8_t ReportID, const uint8_t ReportType)
{
	int	Pos;

	for (Pos = 0; Pos < MAX_REPORT; Pos++) {

		if (pParser->OffsetTab[Pos][0] == 0) {
			pParser->OffsetTab[Pos][0] = ReportID;
			pParser->OffsetTab[Pos][1] = ReportType;
			pParser->OffsetTab[Pos][2] = 0;
		}

		if (pParser->OffsetTab[Pos][0] != ReportID) {
			continue;
		}

		if (pParser->OffsetTab[Pos][1] != ReportType) {
			continue;
		}

		return &pParser->OffsetTab[Pos][2];
	}

	return NULL;
}

/*
 * FormatValue(long Value, uint8_t Size)
 * Format Value to fit with long format with respect of negative values
 * -------------------------------------------------------------------------- */
static long FormatValue(long Value, uint8_t Size)
{
	switch(Size)
	{
	case 1:
		return (long)(int8_t)Value;
	case 2:
		return (long)(int16_t)Value;
	case 4:
		return (long)(int32_t)Value;
	default:
		return Value;
	}
}

/*
 * HIDParse(HIDParser_t* pParser, HIDData_t *pData)
 *
 * Analyse Report descriptor stored in HIDParser struct and store local and
 * global context. 
 * Return in pData the last object found.
 * Return -1 when there is no other Item to parse, 1 if a new object was found
 * or 0 if a continuation of a previous object was found.
 * -------------------------------------------------------------------------- */
static int HIDParse(HIDParser_t *pParser, HIDData_t *pData)
{
	int	Found = -1;

	while ((Found < 0) && (pParser->Pos < pParser->ReportDescSize)) {
		/* Get new pParser->Item if current pParser->Count is empty */
		if (pParser->Count == 0) {
			pParser->Item = pParser->ReportDesc[pParser->Pos++];
			pParser->Value = 0;
#if WORDS_BIGENDIAN
			{
				int	i;
				unsigned valTmp = 0;

				for (i = 0; i < ItemSize[pParser->Item & SIZE_MASK]; i++) {
					memcpy(&valTmp, &pParser->ReportDesc[(pParser->Pos)+i], 1);
					pParser->Value += valTmp >> ((3-i)*8);
					valTmp = 0;
				}
			}
#else
			memcpy(&pParser->Value, &pParser->ReportDesc[pParser->Pos], ItemSize[pParser->Item & SIZE_MASK]);
#endif
			/* Pos on next item */
			pParser->Pos += ItemSize[pParser->Item & SIZE_MASK];
		}

		switch (pParser->Item & ITEM_MASK)
		{
		case ITEM_UPAGE:
			/* Copy UPage in Usage stack */
			pParser->UPage=(uint16_t)pParser->Value;
			break;

		case ITEM_USAGE:
			/* Copy global or local UPage if any, in Usage stack */
			if ((pParser->Item & SIZE_MASK) > 2) {
				pParser->UsageTab[pParser->UsageSize] = pParser->Value;
			} else {
				pParser->UsageTab[pParser->UsageSize] = (pParser->UPage << 16) | (pParser->Value & 0xFFFF);
			}

			/* Increment Usage stack size */
			pParser->UsageSize++;
			break;

		case ITEM_COLLECTION:
			/* Get UPage/Usage from UsageTab and store them in pParser->Data.Path */
			pParser->Data.Path.Node[pParser->Data.Path.Size] = pParser->UsageTab[0];
			pParser->Data.Path.Size++;

			/* Unstack UPage/Usage from UsageTab (never remove the last) */
			if (pParser->UsageSize > 0) {
				int	i;
			
				for (i = 0; i < pParser->UsageSize; i++) {
					pParser->UsageTab[i] = pParser->UsageTab[i+1];
				}

				/* Remove Usage */
				pParser->UsageSize--;
		        }

			/* Get Index if any */
			if (pParser->Value >= 0x80) {
				pParser->Data.Path.Node[pParser->Data.Path.Size] = 0x00ff0000 | (pParser->Value & 0x7F);
				pParser->Data.Path.Size++;
			}

			ResetLocalState(pParser);
			break;

		case ITEM_END_COLLECTION :
			pParser->Data.Path.Size--;
			
			/* Remove Index if any */
			if((pParser->Data.Path.Node[pParser->Data.Path.Size] & 0xffff0000) == 0x00ff0000) {
				pParser->Data.Path.Size--;
			}
			
			ResetLocalState(pParser);
			break;
			
		case ITEM_FEATURE:
		case ITEM_INPUT:
		case ITEM_OUTPUT:
			if (pParser->UsageTab[0] != 0x00000000) {
				/* An object was found if the path does not end with 0x00000000 */
				Found = 1;
			} else {
				/* It is a continuation of a previous object */
				Found = 0;
			}
			
			/* Get new pParser->Count from global value */
			if(pParser->Count == 0) {
				pParser->Count = pParser->ReportCount;
			}

			/* Get UPage/Usage from UsageTab and store them in pParser->Data.Path */
			pParser->Data.Path.Node[pParser->Data.Path.Size] = pParser->UsageTab[0];
			pParser->Data.Path.Size++;
			
			/* Unstack UPage/Usage from UsageTab (never remove the last) */
			if(pParser->UsageSize > 0) {
				int i;
				
				for (i = 0; i < pParser->UsageSize; i++) {
					pParser->UsageTab[i] = pParser->UsageTab[i+1];
				}
				/* Remove Usage */
				pParser->UsageSize--;
			}
			
			/* Copy data type */
			pParser->Data.Type = (uint8_t)(pParser->Item & ITEM_MASK);
			
			/* Copy data attribute */
			pParser->Data.Attribute = (uint8_t)pParser->Value;
			
			/* Store offset */
			pParser->Data.Offset = *GetReportOffset(pParser, pParser->Data.ReportID, (uint8_t)(pParser->Item & ITEM_MASK));
			
			/* Get Object in pData */
			/* -------------------------------------------------------------------------- */
			memcpy(pData, &pParser->Data, sizeof(HIDData_t));
			/* -------------------------------------------------------------------------- */
			
			/* Increment Report Offset */
			*GetReportOffset(pParser, pParser->Data.ReportID, (uint8_t)(pParser->Item & ITEM_MASK)) += pParser->Data.Size;
			
			/* Remove path last node */
			pParser->Data.Path.Size--;
			
			/* Decrement count */
			pParser->Count--;
			
			if (pParser->Count == 0) {
				ResetLocalState(pParser);
			}
			break;
			
		case ITEM_REP_ID :
			pParser->Data.ReportID = (uint8_t)pParser->Value;
			break;
			
		case ITEM_REP_SIZE :
			pParser->Data.Size = (uint8_t)pParser->Value;
			break;
			
		case ITEM_REP_COUNT :
			pParser->ReportCount = (uint8_t)pParser->Value;
			break;
			
		case ITEM_UNIT_EXP :
			pParser->Data.UnitExp = (int8_t)pParser->Value;
			if (pParser->Data.UnitExp > 7) {
				pParser->Data.UnitExp |= 0xF0;
			}
			break;
			
		case ITEM_UNIT :
			pParser->Data.Unit = pParser->Value;
			break;
			
		case ITEM_LOG_MIN :
			pParser->Data.LogMin = FormatValue(pParser->Value, ItemSize[pParser->Item & SIZE_MASK]);
			break;
			
		case ITEM_LOG_MAX :
			pParser->Data.LogMax = FormatValue(pParser->Value, ItemSize[pParser->Item & SIZE_MASK]);
			break;
			
		case ITEM_PHY_MIN :
			pParser->Data.PhyMin=FormatValue(pParser->Value, ItemSize[pParser->Item & SIZE_MASK]);
			pParser->Data.have_PhyMin = 1;
			break;
			
		case ITEM_PHY_MAX :
			pParser->Data.PhyMax=FormatValue(pParser->Value, ItemSize[pParser->Item & SIZE_MASK]);
			pParser->Data.have_PhyMax = 1;
			break;
			
		case ITEM_LONG :
			/* can't handle long items, but should at least skip them */
			pParser->Pos += (uint8_t)(pParser->Value & 0xff);
			break;
		}
	} /* while ((Found < 0) && (pParser->Pos < pParser->ReportDescSize)) */

	if(pParser->Data.Path.Size >= PATH_SIZE)
		upslogx(LOG_ERR, "%s: HID path too long", __func__);
	if(pParser->ReportDescSize >= REPORT_DSC_SIZE)
		upslogx(LOG_ERR, "%s: Report descriptor too big", __func__);
	if(pParser->UsageSize >= USAGE_TAB_SIZE)
		upslogx(LOG_ERR, "%s: HID Usage too high", __func__);
	if(pParser->Data.ReportID >= MAX_REPORT)
		upslogx(LOG_ERR, "%s: Too many HID reports", __func__);

	return Found;
}

/*
 * FindObject
 * Get pData characteristics from pData->Path
 * Return TRUE if object was found
 * -------------------------------------------------------------------------- */
int FindObject(HIDDesc_t *pDesc, HIDData_t *pData)
{
	HIDData_t	*pFoundData = FindObject_with_Path(pDesc, &pData->Path, pData->Type);
	
	if (!pFoundData) {
		return 0;
	}
	
	memcpy(pData, pFoundData, sizeof(*pData));
	return 1;
}

/*
 * FindObject_with_Path
 * Get pData item with given Path and Type. Return NULL if not found.
 * -------------------------------------------------------------------------- */
HIDData_t *FindObject_with_Path(HIDDesc_t *pDesc, HIDPath_t *Path, uint8_t Type)
{
	int	i;

	for (i = 0; i < pDesc->nitems; i++) {
		HIDData_t *pData = &pDesc->item[i];
		
		if (pData->Type != Type) {
			continue;
		}
		
		if (memcmp(pData->Path.Node, Path->Node, (Path->Size) * sizeof(HIDNode_t))) {
			continue;
		}
		
		return pData;
	}
	
	return NULL;
}

/*
 * FindObject_with_ID
 * Get pData item with given ReportID, Offset, and Type. Return NULL
 * if not found.
 * -------------------------------------------------------------------------- */
HIDData_t *FindObject_with_ID(HIDDesc_t *pDesc, uint8_t ReportID, uint8_t Offset, uint8_t Type)
{
	int	i;

	for (i = 0; i < pDesc->nitems; i++) {
		HIDData_t *pData = &pDesc->item[i];
		
		if (pData->ReportID != ReportID) {
			continue;
		}
		
		if (pData->Type != Type) {
			continue;
		}
 
		if (pData->Offset != Offset) {
			continue;
		}
		
		return pData;
	}
	
	return NULL;
}

/*
 * GetValue
 * Extract data from a report stored in Buf.
 * Use Value, Offset, Size and LogMax of pData.
 * Return response in Value.
 * -------------------------------------------------------------------------- */
void GetValue(const unsigned char *Buf, HIDData_t *pData, long *pValue)
{
	int	Weight, Bit;
	long	value = 0, rawvalue;
	long	range, mask, signbit, b, m;

	Bit = pData->Offset + 8;	/* First byte of report is report ID */
	
	for (Weight = 0; Weight < pData->Size; Weight++, Bit++) {
		int	State = Buf[Bit >> 3] & (1 << (Bit & 7));
		
		if(State) {
			value += (1 << Weight);
		}
	}

	/* translate Value into a signed/unsigned value in the range
	LogMin..LogMax, as appropriate. See HID spec, p.38: "If both the
	Logical Minimum and Logical Maximum extents are defined as
	positive values (0 or greater), then the report field can be
	assumed to be an unsigned value. Otherwise, all integer values
	are signed values represented in 2's complement format."

	Also note that the variable can take values from LogMin
	(inclusive) to LogMax (inclusive), so there are LogMax - LogMin +
	1 possible values.

	Special cases arise if the value that has been read lies outside
	the interval LogMin..LogMax. Some devices, notably the APC
	Back-UPS BF500, do this. In one case I observed, LogMin=0,
	LogMax=0xffff, Size=32, and the supplied value is
	0xffffffff80080a00. Presumably they expect us to throw away the
	higher-order bits, and use 0x0a00, rather than choosing the
	closest value in the interval, which would be 0xffff.  However,
	if LogMax - LogMin + 1 isn't a power of 2, it is not clear what
	"throwing away higher-order bits" exacly means, so we try to do
	something sensible. -PS */

	rawvalue = value; /* remember this for later */

	/* figure out how many bits are significant */
	range = pData->LogMax - pData->LogMin + 1;
	if (range <= 0) {
		/* makes no sense, give up */
		*pValue = value;
		return;
	}
	b = hibit(range-1);

	/* throw away insignificant bits; the result is >= 0 */
	mask = (1 << b) - 1;
	signbit = 1 << (b - 1);
	value = value & mask;

	/* sign-extend it, if appropriate */
	if (pData->LogMin < 0 && (value & signbit) != 0) {
		value |= ~mask;
	}

	/* if the resulting value is in the desired range, stop */
	if (value >= pData->LogMin && value <= pData->LogMax) {
		*pValue = value;
		return;
	}

	/* else, try to reach interval by adjusting high-order bits */
	m = (value - pData->LogMin) & mask;
	value = pData->LogMin + m;
	if (value <= pData->LogMax) {
		*pValue = value;
		return;
	}

	/* if everything else failed, sign-extend the original raw value,
	and simply round it to the closest point in the interval. */
	value = rawvalue;
	mask = (1 << pData->Size) - 1;
	signbit = 1 << (pData->Size - 1);
	if (pData->LogMin < 0 && (value & signbit) != 0) {
		value |= ~mask;
	}
	if (value < pData->LogMin) {
		value = pData->LogMin;
	} else if (value > pData->LogMax) {
		value = pData->LogMax;
	}

	*pValue = value;
	return;
}

/*
 * SetValue
 * Set a data in a report stored in Buf. Use Value, Offset and Size of pData.
 * Return response in Buf.
 * -------------------------------------------------------------------------- */
void SetValue(const HIDData_t *pData, unsigned char *Buf, long Value)
{
	int	Weight, Bit;
	
	Bit = pData->Offset + 8;	/* First byte of report is report ID */

	for (Weight = 0; Weight < pData->Size; Weight++, Bit++) {
		int	State = Value & (1 << Weight);

		if (State) {
			Buf[Bit >> 3] |= (1 << (Bit & 7));
		} else {
			Buf[Bit >> 3] &= ~(1 << (Bit & 7));
		}
	}
}

/* ---------------------------------------------------------------------- */

/* parse HID Report Descriptor. Input: byte array ReportDesc[n].
   Output: parsed data structure. Returns allocated HIDDesc structure
   on success, NULL on failure with errno set. Note: the value
   returned by this function must be freed with Free_ReportDesc(). */
HIDDesc_t *Parse_ReportDesc(const unsigned char *ReportDesc, const int n)
{
	int		ret;
	HIDDesc_t	*pDesc;
	HIDParser_t	*parser;

	pDesc = calloc(1, sizeof(*pDesc));
	if (!pDesc) {
		return NULL;
	}

	pDesc->item = calloc(MAX_REPORT, sizeof(*pDesc->item));
	if (!pDesc->item) {
		Free_ReportDesc(pDesc);
		return NULL;
	}

	parser = calloc(1, sizeof(*parser));
	if (!parser) {
		Free_ReportDesc(pDesc);
		return NULL;
	}

	parser->ReportDesc = ReportDesc;
	parser->ReportDescSize = n;

	for (pDesc->nitems = 0; pDesc->nitems < MAX_REPORT; pDesc->nitems += ret) {
		int	id, max;

		ret = HIDParse(parser, &pDesc->item[pDesc->nitems]);
		if (ret < 0) {
			break;
		}

		id = pDesc->item[pDesc->nitems].ReportID;

		/* calculate bit range of this item within report */
		max = pDesc->item[pDesc->nitems].Offset + pDesc->item[pDesc->nitems].Size;

		/* convert to bytes */
		max = (max + 7) >> 3;

		/* update report length */
		if (max > pDesc->replen[id]) {
			pDesc->replen[id] = max;
		}
	}

	/* Sanity check: are there remaining HID objects that can't
	 * be processed? */
	if ((pDesc->nitems == MAX_REPORT) && (parser->Pos < parser->ReportDescSize))
		upslogx(LOG_ERR, "ERROR in %s: Too many HID objects", __func__);

	free(parser);

	if (pDesc->nitems == 0) {
		Free_ReportDesc(pDesc);
		return NULL;
	}

	pDesc->item = realloc(pDesc->item, pDesc->nitems * sizeof(*pDesc->item));

	return pDesc;
}

/* free a parsed report descriptor, as allocated by Parse_ReportDesc() */
void Free_ReportDesc(HIDDesc_t *pDesc)
{
	if (!pDesc) {
		return;
	}

	free(pDesc->item);
	free(pDesc);
}
