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