123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- using System;
- using System.Collections.Generic;
-
- ////TODO: array support
- ////TODO: delimiter support
- ////TODO: designator support
-
- #pragma warning disable CS0649
- namespace UnityEngine.InputSystem.HID
- {
- /// <summary>
- /// Turns binary HID descriptors into <see cref="HID.HIDDeviceDescriptor"/> instances.
- /// </summary>
- /// <remarks>
- /// For information about the format, see the <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf">
- /// Device Class Definition for Human Interface Devices</a> section 6.2.2.
- /// </remarks>
- internal static class HIDParser
- {
- /// <summary>
- /// Parse a HID report descriptor as defined by section 6.2.2 of the
- /// <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf">HID
- /// specification</a> and add the elements and collections from the
- /// descriptor to the given <paramref name="deviceDescriptor"/>.
- /// </summary>
- /// <param name="buffer">Buffer containing raw HID report descriptor.</param>
- /// <param name="deviceDescriptor">HID device descriptor to complete with the information
- /// from the report descriptor. Elements and collections will get added to this descriptor.</param>
- /// <returns>True if the report descriptor was successfully parsed.</returns>
- /// <remarks>
- /// Will also set <see cref="HID.HIDDeviceDescriptor.inputReportSize"/>,
- /// <see cref="HID.HIDDeviceDescriptor.outputReportSize"/>, and
- /// <see cref="HID.HIDDeviceDescriptor.featureReportSize"/>.
- /// </remarks>
- public static unsafe bool ParseReportDescriptor(byte[] buffer, ref HID.HIDDeviceDescriptor deviceDescriptor)
- {
- if (buffer == null)
- throw new ArgumentNullException(nameof(buffer));
-
- fixed(byte* bufferPtr = buffer)
- {
- return ParseReportDescriptor(bufferPtr, buffer.Length, ref deviceDescriptor);
- }
- }
-
- public unsafe static bool ParseReportDescriptor(byte* bufferPtr, int bufferLength, ref HID.HIDDeviceDescriptor deviceDescriptor)
- {
- // Item state.
- var localItemState = new HIDItemStateLocal();
- var globalItemState = new HIDItemStateGlobal();
-
- // Lists where we accumulate the data from the HID items.
- var reports = new List<HIDReportData>();
- var elements = new List<HID.HIDElementDescriptor>();
- var collections = new List<HID.HIDCollectionDescriptor>();
- var currentCollection = -1;
-
- // Parse the linear list of items.
- var endPtr = bufferPtr + bufferLength;
- var currentPtr = bufferPtr;
- while (currentPtr < endPtr)
- {
- var firstByte = *currentPtr;
-
- ////TODO
- if (firstByte == 0xFE)
- throw new NotImplementedException("long item support");
-
- // Read item header.
- var itemSize = (byte)(firstByte & 0x3);
- var itemTypeAndTag = (byte)(firstByte & 0xFC);
- ++currentPtr;
-
- // Process item.
- switch (itemTypeAndTag)
- {
- // ------------ Global Items --------------
- // These set item state permanently until it is reset.
-
- // Usage Page
- case (int)HIDItemTypeAndTag.UsagePage:
- globalItemState.usagePage = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Report Count
- case (int)HIDItemTypeAndTag.ReportCount:
- globalItemState.reportCount = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Report Size
- case (int)HIDItemTypeAndTag.ReportSize:
- globalItemState.reportSize = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Report ID
- case (int)HIDItemTypeAndTag.ReportID:
- globalItemState.reportId = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Logical Minimum
- case (int)HIDItemTypeAndTag.LogicalMinimum:
- globalItemState.logicalMinimum = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Logical Maximum
- case (int)HIDItemTypeAndTag.LogicalMaximum:
- globalItemState.logicalMaximum = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Physical Minimum
- case (int)HIDItemTypeAndTag.PhysicalMinimum:
- globalItemState.physicalMinimum = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Physical Maximum
- case (int)HIDItemTypeAndTag.PhysicalMaximum:
- globalItemState.physicalMaximum = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Unit Exponent
- case (int)HIDItemTypeAndTag.UnitExponent:
- globalItemState.unitExponent = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Unit
- case (int)HIDItemTypeAndTag.Unit:
- globalItemState.unit = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // ------------ Local Items --------------
- // These set the state for the very next elements to be generated.
-
- // Usage
- case (int)HIDItemTypeAndTag.Usage:
- localItemState.SetUsage(ReadData(itemSize, currentPtr, endPtr));
- break;
-
- // Usage Minimum
- case (int)HIDItemTypeAndTag.UsageMinimum:
- localItemState.usageMinimum = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // Usage Maximum
- case (int)HIDItemTypeAndTag.UsageMaximum:
- localItemState.usageMaximum = ReadData(itemSize, currentPtr, endPtr);
- break;
-
- // ------------ Main Items --------------
- // These emit things into the descriptor based on the local and global item state.
-
- // Collection
- case (int)HIDItemTypeAndTag.Collection:
-
- // Start new collection.
- var parentCollection = currentCollection;
- currentCollection = collections.Count;
- collections.Add(new HID.HIDCollectionDescriptor
- {
- type = (HID.HIDCollectionType)ReadData(itemSize, currentPtr, endPtr),
- parent = parentCollection,
- usagePage = globalItemState.GetUsagePage(0, ref localItemState),
- usage = localItemState.GetUsage(0),
- firstChild = elements.Count
- });
-
- HIDItemStateLocal.Reset(ref localItemState);
- break;
-
- // EndCollection
- case (int)HIDItemTypeAndTag.EndCollection:
- if (currentCollection == -1)
- return false;
-
- // Close collection.
- var collection = collections[currentCollection];
- collection.childCount = elements.Count - collection.firstChild;
- collections[currentCollection] = collection;
-
- // Switch back to parent collection (if any).
- currentCollection = collection.parent;
-
- HIDItemStateLocal.Reset(ref localItemState);
- break;
-
- // Input/Output/Feature
- case (int)HIDItemTypeAndTag.Input:
- case (int)HIDItemTypeAndTag.Output:
- case (int)HIDItemTypeAndTag.Feature:
-
- // Determine report type.
- var reportType = itemTypeAndTag == (int)HIDItemTypeAndTag.Input
- ? HID.HIDReportType.Input
- : itemTypeAndTag == (int)HIDItemTypeAndTag.Output
- ? HID.HIDReportType.Output
- : HID.HIDReportType.Feature;
-
- // Find report.
- var reportIndex = HIDReportData.FindOrAddReport(globalItemState.reportId, reportType, reports);
- var report = reports[reportIndex];
-
- // If we have a report ID, then reports start with an 8 byte report ID.
- // Shift our offsets accordingly.
- if (report.currentBitOffset == 0 && globalItemState.reportId.HasValue)
- report.currentBitOffset = 8;
-
- // Add elements to report.
- var reportCount = globalItemState.reportCount.GetValueOrDefault(1);
- var flags = ReadData(itemSize, currentPtr, endPtr);
- for (var i = 0; i < reportCount; ++i)
- {
- var element = new HID.HIDElementDescriptor
- {
- usage = localItemState.GetUsage(i) & 0xFFFF, // Mask off usage page, if set.
- usagePage = globalItemState.GetUsagePage(i, ref localItemState),
- reportType = reportType,
- reportSizeInBits = globalItemState.reportSize.GetValueOrDefault(8),
- reportOffsetInBits = report.currentBitOffset,
- reportId = globalItemState.reportId.GetValueOrDefault(1),
- flags = (HID.HIDElementFlags)flags,
- logicalMin = globalItemState.logicalMinimum.GetValueOrDefault(0),
- logicalMax = globalItemState.logicalMaximum.GetValueOrDefault(0),
- physicalMin = globalItemState.GetPhysicalMin(),
- physicalMax = globalItemState.GetPhysicalMax(),
- unitExponent = globalItemState.unitExponent.GetValueOrDefault(0),
- unit = globalItemState.unit.GetValueOrDefault(0),
- };
- report.currentBitOffset += element.reportSizeInBits;
- elements.Add(element);
- }
- reports[reportIndex] = report;
-
- HIDItemStateLocal.Reset(ref localItemState);
- break;
- }
-
- if (itemSize == 3)
- currentPtr += 4;
- else
- currentPtr += itemSize;
- }
-
- deviceDescriptor.elements = elements.ToArray();
- deviceDescriptor.collections = collections.ToArray();
-
- // Set usage and usage page on device descriptor to what's
- // on the toplevel application collection.
- foreach (var collection in collections)
- {
- if (collection.parent == -1 && collection.type == HID.HIDCollectionType.Application)
- {
- deviceDescriptor.usage = collection.usage;
- deviceDescriptor.usagePage = collection.usagePage;
- break;
- }
- }
-
- return true;
- }
-
- private unsafe static int ReadData(int itemSize, byte* currentPtr, byte* endPtr)
- {
- if (itemSize == 0)
- return 0;
-
- // Read byte.
- if (itemSize == 1)
- {
- if (currentPtr >= endPtr)
- return 0;
- return *currentPtr;
- }
-
- // Read short.
- if (itemSize == 2)
- {
- if (currentPtr + 2 >= endPtr)
- return 0;
- var data1 = *currentPtr;
- var data2 = *(currentPtr + 1);
- return (data2 << 8) | data1;
- }
-
- // Read int.
- if (itemSize == 3) // Item size 3 means 4 bytes!
- {
- if (currentPtr + 4 >= endPtr)
- return 0;
-
- var data1 = *currentPtr;
- var data2 = *(currentPtr + 1);
- var data3 = *(currentPtr + 2);
- var data4 = *(currentPtr + 3);
-
- return (data4 << 24) | (data3 << 24) | (data2 << 8) | data1;
- }
-
- Debug.Assert(false, "Should not reach here");
- return 0;
- }
-
- private struct HIDReportData
- {
- public int reportId;
- public HID.HIDReportType reportType;
- public int currentBitOffset;
-
- public static int FindOrAddReport(int? reportId, HID.HIDReportType reportType, List<HIDReportData> reports)
- {
- var id = 1;
- if (reportId.HasValue)
- id = reportId.Value;
-
- for (var i = 0; i < reports.Count; ++i)
- {
- if (reports[i].reportId == id && reports[i].reportType == reportType)
- return i;
- }
-
- reports.Add(new HIDReportData
- {
- reportId = id,
- reportType = reportType
- });
-
- return reports.Count - 1;
- }
- }
-
- // All types and tags with size bits (low order two bits) masked out (i.e. being 0).
- private enum HIDItemTypeAndTag
- {
- Input = 0x80,
- Output = 0x90,
- Feature = 0xB0,
- Collection = 0xA0,
- EndCollection = 0xC0,
- UsagePage = 0x04,
- LogicalMinimum = 0x14,
- LogicalMaximum = 0x24,
- PhysicalMinimum = 0x34,
- PhysicalMaximum = 0x44,
- UnitExponent = 0x54,
- Unit = 0x64,
- ReportSize = 0x74,
- ReportID = 0x84,
- ReportCount = 0x94,
- Push = 0xA4,
- Pop = 0xB4,
- Usage = 0x08,
- UsageMinimum = 0x18,
- UsageMaximum = 0x28,
- DesignatorIndex = 0x38,
- DesignatorMinimum = 0x48,
- DesignatorMaximum = 0x58,
- StringIndex = 0x78,
- StringMinimum = 0x88,
- StringMaximum = 0x98,
- Delimiter = 0xA8,
- }
-
- // State that needs to be defined for each main item separately.
- // See section 6.2.2.8
- private struct HIDItemStateLocal
- {
- public int? usage;
- public int? usageMinimum;
- public int? usageMaximum;
- public int? designatorIndex;
- public int? designatorMinimum;
- public int? designatorMaximum;
- public int? stringIndex;
- public int? stringMinimum;
- public int? stringMaximum;
-
- public List<int> usageList;
-
- // Wipe state but preserve usageList allocation.
- public static void Reset(ref HIDItemStateLocal state)
- {
- var usageList = state.usageList;
- state = new HIDItemStateLocal();
- if (usageList != null)
- {
- usageList.Clear();
- state.usageList = usageList;
- }
- }
-
- // Usage can be set repeatedly to provide an enumeration of usages.
- public void SetUsage(int value)
- {
- if (usage.HasValue)
- {
- if (usageList == null)
- usageList = new List<int>();
- usageList.Add(usage.Value);
- }
- usage = value;
- }
-
- // Get usage for Nth element in [0-reportCount] list.
- public int GetUsage(int index)
- {
- // If we have minimum and maximum usage, interpolate between that.
- if (usageMinimum.HasValue && usageMaximum.HasValue)
- {
- var min = usageMinimum.Value;
- var max = usageMaximum.Value;
-
- var range = max - min;
- if (range < 0)
- return 0;
- if (index >= range)
- return max;
- return min + index;
- }
-
- // If we have a list of usages, index into that.
- if (usageList != null && usageList.Count > 0)
- {
- var usageCount = usageList.Count;
- if (index >= usageCount)
- return usage.Value;
-
- return usageList[index];
- }
-
- if (usage.HasValue)
- return usage.Value;
-
- ////TODO: min/max
-
- return 0;
- }
- }
-
- // State that is carried over from main item to main item.
- // See section 6.2.2.7
- private struct HIDItemStateGlobal
- {
- public int? usagePage;
- public int? logicalMinimum;
- public int? logicalMaximum;
- public int? physicalMinimum;
- public int? physicalMaximum;
- public int? unitExponent;
- public int? unit;
- public int? reportSize;
- public int? reportCount;
- public int? reportId;
-
- public HID.UsagePage GetUsagePage(int index, ref HIDItemStateLocal localItemState)
- {
- if (!usagePage.HasValue)
- {
- var usage = localItemState.GetUsage(index);
- return (HID.UsagePage)(usage >> 16);
- }
-
- return (HID.UsagePage)usagePage.Value;
- }
-
- public int GetPhysicalMin()
- {
- if (physicalMinimum == null || physicalMaximum == null ||
- (physicalMinimum.Value == 0 && physicalMaximum.Value == 0))
- return logicalMinimum.GetValueOrDefault(0);
- return physicalMinimum.Value;
- }
-
- public int GetPhysicalMax()
- {
- if (physicalMinimum == null || physicalMaximum == null ||
- (physicalMinimum.Value == 0 && physicalMaximum.Value == 0))
- return logicalMaximum.GetValueOrDefault(0);
- return physicalMaximum.Value;
- }
- }
- }
- }
|