Skip to content

Commit c1bc2b6

Browse files
authored
hid_parser: handle negative numbers in descriptors (#20)
Some Global descriptor items can have negative data values. Parse these correctly. This also causes the printed output to match the comments in the test descriptors. Also, correctly parse report items that are signed (either Logical Minium or Logical Maximum is negative). Adjust tests accordingly.
1 parent 9ab827f commit c1bc2b6

File tree

4 files changed

+44
-20
lines changed

4 files changed

+44
-20
lines changed

hid_parser/__init__.py

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ class TagGlobal():
6666
REPORT_COUNT = 0b1001
6767
PUSH = 0b1010
6868
POP = 0b1011
69+
# These tags have signed integer data
70+
_signed_tags = (
71+
LOGICAL_MINIMUM,
72+
LOGICAL_MAXIMUM,
73+
PHYSICAL_MINIMUM,
74+
PHYSICAL_MAXIMUM,
75+
UNIT_EXPONENT,
76+
)
6977

7078

7179
class TagLocal():
@@ -81,6 +89,15 @@ class TagLocal():
8189
DELIMITER = 0b1010
8290

8391

92+
def _item_is_signed(tag: int, typ: int) -> bool:
93+
if typ != Type.GLOBAL:
94+
return False
95+
if tag in TagGlobal._signed_tags:
96+
return True
97+
else:
98+
return False
99+
100+
84101
def _data_bit_shift(data: Sequence[int], offset: int, length: int) -> Sequence[int]:
85102
if not length > 0:
86103
raise ValueError(f'Invalid specified length: {length}')
@@ -688,33 +705,36 @@ def _iterate_raw(self) -> Iterable[Tuple[int, int, Optional[int]]]:
688705
i = 0
689706
while i < len(self.data):
690707
prefix = self.data[i]
708+
i += 1
691709
tag = (prefix & 0b11110000) >> 4
692710
typ = (prefix & 0b00001100) >> 2
693711
size = prefix & 0b00000011
694712

695-
if size == 3: # 6.2.2.2
696-
size = 4
697-
698713
if size == 0:
699-
data = None
700-
elif size == 1:
701-
if i + 1 >= len(self.data):
702-
raise InvalidReportDescriptor(f'Invalid size: expecting >={i + 1}, got {len(self.data)}')
703-
data = self.data[i+1]
714+
# 6.2.2.4 Main items with empty data have a value of zero
715+
if typ == Type.MAIN:
716+
data = 0
717+
else:
718+
data = None
719+
elif size > 3:
720+
raise ValueError(f'Invalid item size: {size}')
704721
else:
705-
if i + 1 + size >= len(self.data):
706-
raise InvalidReportDescriptor(f'Invalid size: expecting >={i + 1 + size}, got {len(self.data)}')
707-
if size == 2:
708-
pack_type = 'H'
709-
elif size == 4:
710-
pack_type = 'L'
722+
# Pick an unpack format letter by size and signedness
723+
# Per 6.2.2.2, size of 0b11 means 4
724+
if _item_is_signed(tag, typ):
725+
pack_type = '0bhl'[size]
711726
else:
712-
raise ValueError(f'Invalid item size: {size}')
713-
data = struct.unpack(f'<{pack_type}', bytes(self.data[i+1:i+1+size]))[0]
727+
pack_type = '0BHL'[size]
728+
fmt = f'<{pack_type}'
729+
size = struct.calcsize(fmt)
730+
if i + size > len(self.data):
731+
raise InvalidReportDescriptor(f'Invalid size: expecting >={i + size}, got {len(self.data)}')
732+
733+
data, = struct.unpack(fmt, bytes(self.data[i:i+size]))
714734

715735
yield typ, tag, data
716736

717-
i += size + 1
737+
i += size
718738

719739
def _append_item(
720740
self,

tests/linux-hidpp-print.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ Collection (Application)
4646
Report Size (1)
4747
Input (Data, Variable, Absolute, No Wrap, Linear, Preferred State, No Null position, Bit Field)
4848
Usage Page (Generic Desktop Controls)
49-
Logical Minimum (32769)
49+
Logical Minimum (-32767)
5050
Logical Maximum (32767)
5151
Report Size (16)
5252
Report Count (2)
5353
Usage (X)
5454
Usage (Y)
5555
Input (Data, Variable, Relative, No Wrap, Linear, Preferred State, No Null position, Bit Field)
56-
Logical Minimum (129)
56+
Logical Minimum (-127)
5757
Logical Maximum (127)
5858
Report Size (8)
5959
Report Count (1)

tests/simple-mouse-print.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Collection (Application)
1919
Usage Page (Generic Desktop Controls)
2020
Usage (X)
2121
Usage (Y)
22-
Logical Minimum (129)
22+
Logical Minimum (-127)
2323
Logical Maximum (127)
2424
Report Size (8)
2525
Report Count (2)

tests/test_parse.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,12 +224,16 @@ def test_simple_mouse_items():
224224
assert int(items[4].size) == 8
225225
assert items[4].usage.page == hid_parser.data.UsagePages.GENERIC_DESKTOP_CONTROLS_PAGE
226226
assert items[4].usage.usage == hid_parser.data.GenericDesktopControls.X
227+
assert items[4].logical_min == -127
228+
assert items[4].logical_max == 127
227229

228230
assert isinstance(items[5], hid_parser.VariableItem)
229231
assert int(items[5].offset) == 8*2
230232
assert int(items[5].size) == 8
231233
assert items[5].usage.page == hid_parser.data.UsagePages.GENERIC_DESKTOP_CONTROLS_PAGE
232234
assert items[5].usage.usage == hid_parser.data.GenericDesktopControls.Y
235+
assert items[5].logical_min == -127
236+
assert items[5].logical_max == 127
233237

234238

235239
def test_linux_hidpp_items():

0 commit comments

Comments
 (0)