HIDParser.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. using System;
  2. using System.Collections.Generic;
  3. ////TODO: array support
  4. ////TODO: delimiter support
  5. ////TODO: designator support
  6. #pragma warning disable CS0649
  7. namespace UnityEngine.InputSystem.HID
  8. {
  9. /// <summary>
  10. /// Turns binary HID descriptors into <see cref="HID.HIDDeviceDescriptor"/> instances.
  11. /// </summary>
  12. /// <remarks>
  13. /// For information about the format, see the <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf">
  14. /// Device Class Definition for Human Interface Devices</a> section 6.2.2.
  15. /// </remarks>
  16. internal static class HIDParser
  17. {
  18. /// <summary>
  19. /// Parse a HID report descriptor as defined by section 6.2.2 of the
  20. /// <a href="http://www.usb.org/developers/hidpage/HID1_11.pdf">HID
  21. /// specification</a> and add the elements and collections from the
  22. /// descriptor to the given <paramref name="deviceDescriptor"/>.
  23. /// </summary>
  24. /// <param name="buffer">Buffer containing raw HID report descriptor.</param>
  25. /// <param name="deviceDescriptor">HID device descriptor to complete with the information
  26. /// from the report descriptor. Elements and collections will get added to this descriptor.</param>
  27. /// <returns>True if the report descriptor was successfully parsed.</returns>
  28. /// <remarks>
  29. /// Will also set <see cref="HID.HIDDeviceDescriptor.inputReportSize"/>,
  30. /// <see cref="HID.HIDDeviceDescriptor.outputReportSize"/>, and
  31. /// <see cref="HID.HIDDeviceDescriptor.featureReportSize"/>.
  32. /// </remarks>
  33. public static unsafe bool ParseReportDescriptor(byte[] buffer, ref HID.HIDDeviceDescriptor deviceDescriptor)
  34. {
  35. if (buffer == null)
  36. throw new ArgumentNullException(nameof(buffer));
  37. fixed(byte* bufferPtr = buffer)
  38. {
  39. return ParseReportDescriptor(bufferPtr, buffer.Length, ref deviceDescriptor);
  40. }
  41. }
  42. public unsafe static bool ParseReportDescriptor(byte* bufferPtr, int bufferLength, ref HID.HIDDeviceDescriptor deviceDescriptor)
  43. {
  44. // Item state.
  45. var localItemState = new HIDItemStateLocal();
  46. var globalItemState = new HIDItemStateGlobal();
  47. // Lists where we accumulate the data from the HID items.
  48. var reports = new List<HIDReportData>();
  49. var elements = new List<HID.HIDElementDescriptor>();
  50. var collections = new List<HID.HIDCollectionDescriptor>();
  51. var currentCollection = -1;
  52. // Parse the linear list of items.
  53. var endPtr = bufferPtr + bufferLength;
  54. var currentPtr = bufferPtr;
  55. while (currentPtr < endPtr)
  56. {
  57. var firstByte = *currentPtr;
  58. ////TODO
  59. if (firstByte == 0xFE)
  60. throw new NotImplementedException("long item support");
  61. // Read item header.
  62. var itemSize = (byte)(firstByte & 0x3);
  63. var itemTypeAndTag = (byte)(firstByte & 0xFC);
  64. ++currentPtr;
  65. // Process item.
  66. switch (itemTypeAndTag)
  67. {
  68. // ------------ Global Items --------------
  69. // These set item state permanently until it is reset.
  70. // Usage Page
  71. case (int)HIDItemTypeAndTag.UsagePage:
  72. globalItemState.usagePage = ReadData(itemSize, currentPtr, endPtr);
  73. break;
  74. // Report Count
  75. case (int)HIDItemTypeAndTag.ReportCount:
  76. globalItemState.reportCount = ReadData(itemSize, currentPtr, endPtr);
  77. break;
  78. // Report Size
  79. case (int)HIDItemTypeAndTag.ReportSize:
  80. globalItemState.reportSize = ReadData(itemSize, currentPtr, endPtr);
  81. break;
  82. // Report ID
  83. case (int)HIDItemTypeAndTag.ReportID:
  84. globalItemState.reportId = ReadData(itemSize, currentPtr, endPtr);
  85. break;
  86. // Logical Minimum
  87. case (int)HIDItemTypeAndTag.LogicalMinimum:
  88. globalItemState.logicalMinimum = ReadData(itemSize, currentPtr, endPtr);
  89. break;
  90. // Logical Maximum
  91. case (int)HIDItemTypeAndTag.LogicalMaximum:
  92. globalItemState.logicalMaximum = ReadData(itemSize, currentPtr, endPtr);
  93. break;
  94. // Physical Minimum
  95. case (int)HIDItemTypeAndTag.PhysicalMinimum:
  96. globalItemState.physicalMinimum = ReadData(itemSize, currentPtr, endPtr);
  97. break;
  98. // Physical Maximum
  99. case (int)HIDItemTypeAndTag.PhysicalMaximum:
  100. globalItemState.physicalMaximum = ReadData(itemSize, currentPtr, endPtr);
  101. break;
  102. // Unit Exponent
  103. case (int)HIDItemTypeAndTag.UnitExponent:
  104. globalItemState.unitExponent = ReadData(itemSize, currentPtr, endPtr);
  105. break;
  106. // Unit
  107. case (int)HIDItemTypeAndTag.Unit:
  108. globalItemState.unit = ReadData(itemSize, currentPtr, endPtr);
  109. break;
  110. // ------------ Local Items --------------
  111. // These set the state for the very next elements to be generated.
  112. // Usage
  113. case (int)HIDItemTypeAndTag.Usage:
  114. localItemState.SetUsage(ReadData(itemSize, currentPtr, endPtr));
  115. break;
  116. // Usage Minimum
  117. case (int)HIDItemTypeAndTag.UsageMinimum:
  118. localItemState.usageMinimum = ReadData(itemSize, currentPtr, endPtr);
  119. break;
  120. // Usage Maximum
  121. case (int)HIDItemTypeAndTag.UsageMaximum:
  122. localItemState.usageMaximum = ReadData(itemSize, currentPtr, endPtr);
  123. break;
  124. // ------------ Main Items --------------
  125. // These emit things into the descriptor based on the local and global item state.
  126. // Collection
  127. case (int)HIDItemTypeAndTag.Collection:
  128. // Start new collection.
  129. var parentCollection = currentCollection;
  130. currentCollection = collections.Count;
  131. collections.Add(new HID.HIDCollectionDescriptor
  132. {
  133. type = (HID.HIDCollectionType)ReadData(itemSize, currentPtr, endPtr),
  134. parent = parentCollection,
  135. usagePage = globalItemState.GetUsagePage(0, ref localItemState),
  136. usage = localItemState.GetUsage(0),
  137. firstChild = elements.Count
  138. });
  139. HIDItemStateLocal.Reset(ref localItemState);
  140. break;
  141. // EndCollection
  142. case (int)HIDItemTypeAndTag.EndCollection:
  143. if (currentCollection == -1)
  144. return false;
  145. // Close collection.
  146. var collection = collections[currentCollection];
  147. collection.childCount = elements.Count - collection.firstChild;
  148. collections[currentCollection] = collection;
  149. // Switch back to parent collection (if any).
  150. currentCollection = collection.parent;
  151. HIDItemStateLocal.Reset(ref localItemState);
  152. break;
  153. // Input/Output/Feature
  154. case (int)HIDItemTypeAndTag.Input:
  155. case (int)HIDItemTypeAndTag.Output:
  156. case (int)HIDItemTypeAndTag.Feature:
  157. // Determine report type.
  158. var reportType = itemTypeAndTag == (int)HIDItemTypeAndTag.Input
  159. ? HID.HIDReportType.Input
  160. : itemTypeAndTag == (int)HIDItemTypeAndTag.Output
  161. ? HID.HIDReportType.Output
  162. : HID.HIDReportType.Feature;
  163. // Find report.
  164. var reportIndex = HIDReportData.FindOrAddReport(globalItemState.reportId, reportType, reports);
  165. var report = reports[reportIndex];
  166. // If we have a report ID, then reports start with an 8 byte report ID.
  167. // Shift our offsets accordingly.
  168. if (report.currentBitOffset == 0 && globalItemState.reportId.HasValue)
  169. report.currentBitOffset = 8;
  170. // Add elements to report.
  171. var reportCount = globalItemState.reportCount.GetValueOrDefault(1);
  172. var flags = ReadData(itemSize, currentPtr, endPtr);
  173. for (var i = 0; i < reportCount; ++i)
  174. {
  175. var element = new HID.HIDElementDescriptor
  176. {
  177. usage = localItemState.GetUsage(i) & 0xFFFF, // Mask off usage page, if set.
  178. usagePage = globalItemState.GetUsagePage(i, ref localItemState),
  179. reportType = reportType,
  180. reportSizeInBits = globalItemState.reportSize.GetValueOrDefault(8),
  181. reportOffsetInBits = report.currentBitOffset,
  182. reportId = globalItemState.reportId.GetValueOrDefault(1),
  183. flags = (HID.HIDElementFlags)flags,
  184. logicalMin = globalItemState.logicalMinimum.GetValueOrDefault(0),
  185. logicalMax = globalItemState.logicalMaximum.GetValueOrDefault(0),
  186. physicalMin = globalItemState.GetPhysicalMin(),
  187. physicalMax = globalItemState.GetPhysicalMax(),
  188. unitExponent = globalItemState.unitExponent.GetValueOrDefault(0),
  189. unit = globalItemState.unit.GetValueOrDefault(0),
  190. };
  191. report.currentBitOffset += element.reportSizeInBits;
  192. elements.Add(element);
  193. }
  194. reports[reportIndex] = report;
  195. HIDItemStateLocal.Reset(ref localItemState);
  196. break;
  197. }
  198. if (itemSize == 3)
  199. currentPtr += 4;
  200. else
  201. currentPtr += itemSize;
  202. }
  203. deviceDescriptor.elements = elements.ToArray();
  204. deviceDescriptor.collections = collections.ToArray();
  205. // Set usage and usage page on device descriptor to what's
  206. // on the toplevel application collection.
  207. foreach (var collection in collections)
  208. {
  209. if (collection.parent == -1 && collection.type == HID.HIDCollectionType.Application)
  210. {
  211. deviceDescriptor.usage = collection.usage;
  212. deviceDescriptor.usagePage = collection.usagePage;
  213. break;
  214. }
  215. }
  216. return true;
  217. }
  218. private unsafe static int ReadData(int itemSize, byte* currentPtr, byte* endPtr)
  219. {
  220. if (itemSize == 0)
  221. return 0;
  222. // Read byte.
  223. if (itemSize == 1)
  224. {
  225. if (currentPtr >= endPtr)
  226. return 0;
  227. return *currentPtr;
  228. }
  229. // Read short.
  230. if (itemSize == 2)
  231. {
  232. if (currentPtr + 2 >= endPtr)
  233. return 0;
  234. var data1 = *currentPtr;
  235. var data2 = *(currentPtr + 1);
  236. return (data2 << 8) | data1;
  237. }
  238. // Read int.
  239. if (itemSize == 3) // Item size 3 means 4 bytes!
  240. {
  241. if (currentPtr + 4 >= endPtr)
  242. return 0;
  243. var data1 = *currentPtr;
  244. var data2 = *(currentPtr + 1);
  245. var data3 = *(currentPtr + 2);
  246. var data4 = *(currentPtr + 3);
  247. return (data4 << 24) | (data3 << 24) | (data2 << 8) | data1;
  248. }
  249. Debug.Assert(false, "Should not reach here");
  250. return 0;
  251. }
  252. private struct HIDReportData
  253. {
  254. public int reportId;
  255. public HID.HIDReportType reportType;
  256. public int currentBitOffset;
  257. public static int FindOrAddReport(int? reportId, HID.HIDReportType reportType, List<HIDReportData> reports)
  258. {
  259. var id = 1;
  260. if (reportId.HasValue)
  261. id = reportId.Value;
  262. for (var i = 0; i < reports.Count; ++i)
  263. {
  264. if (reports[i].reportId == id && reports[i].reportType == reportType)
  265. return i;
  266. }
  267. reports.Add(new HIDReportData
  268. {
  269. reportId = id,
  270. reportType = reportType
  271. });
  272. return reports.Count - 1;
  273. }
  274. }
  275. // All types and tags with size bits (low order two bits) masked out (i.e. being 0).
  276. private enum HIDItemTypeAndTag
  277. {
  278. Input = 0x80,
  279. Output = 0x90,
  280. Feature = 0xB0,
  281. Collection = 0xA0,
  282. EndCollection = 0xC0,
  283. UsagePage = 0x04,
  284. LogicalMinimum = 0x14,
  285. LogicalMaximum = 0x24,
  286. PhysicalMinimum = 0x34,
  287. PhysicalMaximum = 0x44,
  288. UnitExponent = 0x54,
  289. Unit = 0x64,
  290. ReportSize = 0x74,
  291. ReportID = 0x84,
  292. ReportCount = 0x94,
  293. Push = 0xA4,
  294. Pop = 0xB4,
  295. Usage = 0x08,
  296. UsageMinimum = 0x18,
  297. UsageMaximum = 0x28,
  298. DesignatorIndex = 0x38,
  299. DesignatorMinimum = 0x48,
  300. DesignatorMaximum = 0x58,
  301. StringIndex = 0x78,
  302. StringMinimum = 0x88,
  303. StringMaximum = 0x98,
  304. Delimiter = 0xA8,
  305. }
  306. // State that needs to be defined for each main item separately.
  307. // See section 6.2.2.8
  308. private struct HIDItemStateLocal
  309. {
  310. public int? usage;
  311. public int? usageMinimum;
  312. public int? usageMaximum;
  313. public int? designatorIndex;
  314. public int? designatorMinimum;
  315. public int? designatorMaximum;
  316. public int? stringIndex;
  317. public int? stringMinimum;
  318. public int? stringMaximum;
  319. public List<int> usageList;
  320. // Wipe state but preserve usageList allocation.
  321. public static void Reset(ref HIDItemStateLocal state)
  322. {
  323. var usageList = state.usageList;
  324. state = new HIDItemStateLocal();
  325. if (usageList != null)
  326. {
  327. usageList.Clear();
  328. state.usageList = usageList;
  329. }
  330. }
  331. // Usage can be set repeatedly to provide an enumeration of usages.
  332. public void SetUsage(int value)
  333. {
  334. if (usage.HasValue)
  335. {
  336. if (usageList == null)
  337. usageList = new List<int>();
  338. usageList.Add(usage.Value);
  339. }
  340. usage = value;
  341. }
  342. // Get usage for Nth element in [0-reportCount] list.
  343. public int GetUsage(int index)
  344. {
  345. // If we have minimum and maximum usage, interpolate between that.
  346. if (usageMinimum.HasValue && usageMaximum.HasValue)
  347. {
  348. var min = usageMinimum.Value;
  349. var max = usageMaximum.Value;
  350. var range = max - min;
  351. if (range < 0)
  352. return 0;
  353. if (index >= range)
  354. return max;
  355. return min + index;
  356. }
  357. // If we have a list of usages, index into that.
  358. if (usageList != null && usageList.Count > 0)
  359. {
  360. var usageCount = usageList.Count;
  361. if (index >= usageCount)
  362. return usage.Value;
  363. return usageList[index];
  364. }
  365. if (usage.HasValue)
  366. return usage.Value;
  367. ////TODO: min/max
  368. return 0;
  369. }
  370. }
  371. // State that is carried over from main item to main item.
  372. // See section 6.2.2.7
  373. private struct HIDItemStateGlobal
  374. {
  375. public int? usagePage;
  376. public int? logicalMinimum;
  377. public int? logicalMaximum;
  378. public int? physicalMinimum;
  379. public int? physicalMaximum;
  380. public int? unitExponent;
  381. public int? unit;
  382. public int? reportSize;
  383. public int? reportCount;
  384. public int? reportId;
  385. public HID.UsagePage GetUsagePage(int index, ref HIDItemStateLocal localItemState)
  386. {
  387. if (!usagePage.HasValue)
  388. {
  389. var usage = localItemState.GetUsage(index);
  390. return (HID.UsagePage)(usage >> 16);
  391. }
  392. return (HID.UsagePage)usagePage.Value;
  393. }
  394. public int GetPhysicalMin()
  395. {
  396. if (physicalMinimum == null || physicalMaximum == null ||
  397. (physicalMinimum.Value == 0 && physicalMaximum.Value == 0))
  398. return logicalMinimum.GetValueOrDefault(0);
  399. return physicalMinimum.Value;
  400. }
  401. public int GetPhysicalMax()
  402. {
  403. if (physicalMinimum == null || physicalMaximum == null ||
  404. (physicalMinimum.Value == 0 && physicalMaximum.Value == 0))
  405. return logicalMaximum.GetValueOrDefault(0);
  406. return physicalMaximum.Value;
  407. }
  408. }
  409. }
  410. }