Browse Source

refactor a bit and add tests... incomplete...

I've kinda given up on this as I can't seem to make construct behave
how I think it should.. The ifthenelse isn't bidirectional, and not
sure how to fix it..
hid
John-Mark Gurney 3 years ago
parent
commit
0aa73a849d
2 changed files with 232 additions and 49 deletions
  1. +55
    -33
      usb_protocol/emitters/descriptors/hid.py
  2. +177
    -16
      usb_protocol/types/descriptors/hid.py

+ 55
- 33
usb_protocol/emitters/descriptors/hid.py View File

@@ -8,9 +8,9 @@ from ...types.descriptors.hid import \
HIDDescriptor as HIDDescriptorType HIDDescriptor as HIDDescriptorType
from ...types.descriptors.hid import * from ...types.descriptors.hid import *


ReportDescriptorEmitter = emitter_for_format(ReportDescriptor)
ReportDescriptorItemEmitter = emitter_for_format(ReportDescriptorItem)


_hid_item_length = [ 0, 1, 2, 4 ]
from ...types.descriptors.hid import _hid_item_length, ItemFlags1, ItemFlags2


class HIDDescriptor(ComplexDescriptorEmitter): class HIDDescriptor(ComplexDescriptorEmitter):
DESCRIPTOR_FORMAT = HIDDescriptorType DESCRIPTOR_FORMAT = HIDDescriptorType
@@ -32,7 +32,7 @@ class HIDDescriptor(ComplexDescriptorEmitter):
*report_data -- Additional bytes-like report item data. *report_data -- Additional bytes-like report item data.
Valid lengths are 1, 2, or 4 bytes. Valid lengths are 1, 2, or 4 bytes.
""" """
hid_report = ReportDescriptorEmitter()
hid_report = ReportDescriptorItemEmitter()
report_len = _hid_item_length.index(len(report_data)) report_len = _hid_item_length.index(len(report_data))
hid_report.bHeader = { hid_report.bHeader = {
"prefix": report_prefix, "prefix": report_prefix,
@@ -41,31 +41,25 @@ class HIDDescriptor(ComplexDescriptorEmitter):
hid_report.data = report_data hid_report.data = report_data
self._reports.append(hid_report) self._reports.append(hid_report)


def add_input_item(self,
data_constant = False,
array_variable = True,
absolute_relative = False,
wrap = False,
linear = False,
preferred = True,
null = False,
volatile = False):
def add_input_item(self, *args, **kwargs):
"""Convenience function to add HID input item with preformatted flags. """Convenience function to add HID input item with preformatted flags.
See HID 1.11 section 6.2.2.5 for flag meanings. See HID 1.11 section 6.2.2.5 for flag meanings.

See add_inpout_item for argument names and defaults.
""" """
item_flags = ItemFlags.build({
"data_constant": data_constant,
"array_variable": array_variable,
"absolute_relative": absolute_relative,
"wrap": wrap,
"linear": linear,
"nPreferred": ~preferred,
"null": null,
"volatile": volatile,
})
self.add_report_item(HIDPrefix.INPUT, ord(item_flags))


def add_output_item(self,
return self.add_inpout_item(HIDPrefix.INPUT, *args, **kwargs)

def add_output_item(self, *args, **kwargs):
"""Convenience function to add HID output item with preformatted flags.
See HID 1.11 section 6.2.2.5 for flag meanings.

See add_inpout_item for argument names and defaults.
"""

return self.add_inpout_item(HIDPrefix.OUTPUT, *args, **kwargs)

def add_inpout_item(self, item,
data_constant = False, data_constant = False,
array_variable = True, array_variable = True,
absolute_relative = False, absolute_relative = False,
@@ -73,21 +67,25 @@ class HIDDescriptor(ComplexDescriptorEmitter):
linear = False, linear = False,
preferred = True, preferred = True,
null = False, null = False,
volatile = False):
"""Convenience function to add HID output item with preformatted flags.
See HID 1.11 section 6.2.2.5 for flag meanings.
"""
item_flags = ItemFlags.build({
volatile = False,
bitfield_bufferedbytes = False):

if bitfield_bufferedbytes:
itmf = ItemFlags2
else:
itmf = ItemFlags1

item_flags = itmf.build({
"data_constant": data_constant, "data_constant": data_constant,
"array_variable": array_variable, "array_variable": array_variable,
"absolute_relative": absolute_relative, "absolute_relative": absolute_relative,
"wrap": wrap, "wrap": wrap,
"linear": linear,
"nPreferred": ~preferred,
"nLinear": not linear,
"nPreferred": not preferred,
"null": null, "null": null,
"volatile": volatile, "volatile": volatile,
}) })
self.add_report_item(HIDPrefix.OUTPUT, ord(item_flags))
self.add_report_item(item, item_flags)


def __init__(self, parent_descriptor): def __init__(self, parent_descriptor):
super().__init__() super().__init__()
@@ -106,4 +104,28 @@ class HIDDescriptor(ComplexDescriptorEmitter):
report_descriptor = b"".join(report_descriptor) report_descriptor = b"".join(report_descriptor)
descriptor_len = len(report_descriptor) descriptor_len = len(report_descriptor)
self.wDescriptorLength = descriptor_len self.wDescriptorLength = descriptor_len
self._parent_descriptor.add_descriptor(report_descriptor, descriptor_type=0x22)
self._parent_descriptor.add_descriptor(report_descriptor, descriptor_type=0x22)

from ..descriptors import DeviceDescriptorCollection
import unittest

class TestHIDEmitter(unittest.TestCase):
def test_hidemitter(self):
collection = DeviceDescriptorCollection()

hd = HIDDescriptor(collection)

hd.add_report_item(HIDPrefix.USAGE_PAGE, 1)
hd.add_report_item(HIDPrefix.USAGE, 6)
hd.add_report_item(HIDPrefix.COLLECTION, 1)
hd.add_report_item(HIDPrefix.USAGE_PAGE, 7)
hd.add_report_item(HIDPrefix.USAGE_MIN, 224)
hd.add_report_item(HIDPrefix.USAGE_MAX, 231)
hd.add_report_item(HIDPrefix.LOGICAL_MIN, 0)
hd.add_report_item(HIDPrefix.LOGICAL_MAX, 1)
hd.add_report_item(HIDPrefix.REPORT_SIZE, 1)
hd.add_report_item(HIDPrefix.REPORT_COUNT, 8)
hd.add_input_item(data_constant=False, array_variable=True, absolute_relative=False)

import codecs
print(repr(codecs.encode(hd.emit(), 'hex')))

+ 177
- 16
usb_protocol/types/descriptors/hid.py View File

@@ -7,13 +7,22 @@ import unittest
from enum import IntEnum, unique from enum import IntEnum, unique


import construct import construct
from construct import this, Default
from construct import this, IfThenElse, Default, GreedyRange
from construct import Probe


from .. import LanguageIDs from .. import LanguageIDs
from ..descriptor import \ from ..descriptor import \
DescriptorField, DescriptorNumber, DescriptorFormat, \ DescriptorField, DescriptorNumber, DescriptorFormat, \
BCDFieldAdapter, DescriptorLength BCDFieldAdapter, DescriptorLength


__all__ = [
'HIDPrefix',
'HIDDescriptor',
'ReportDescriptor',
'ReportDescriptorItem',
'ItemFlags',
]

@unique @unique
class HIDPrefix(IntEnum): class HIDPrefix(IntEnum):
# Main items # Main items
@@ -53,33 +62,185 @@ HIDDescriptor = DescriptorFormat(
"bcdHID" / DescriptorField("HID Protocol Version", default=1.11), "bcdHID" / DescriptorField("HID Protocol Version", default=1.11),
"bCountryCode" / DescriptorField("HID Device Language", default=0), "bCountryCode" / DescriptorField("HID Device Language", default=0),
"bNumDescriptors" / DescriptorField("Number of HID Descriptors", default=1), "bNumDescriptors" / DescriptorField("Number of HID Descriptors", default=1),
"bDescriptorType" / DescriptorField("HID Descriptor Type", default=34),
"bRepDescriptorType" / DescriptorField("HID Descriptor Type", default=34),
"wDescriptorLength" / DescriptorField("HID Descriptor Length") "wDescriptorLength" / DescriptorField("HID Descriptor Length")
# bDescriptorType and wDescriptorLength repeat bNumDescriptors times # bDescriptorType and wDescriptorLength repeat bNumDescriptors times
) )


_hid_item_length = [ 0, 1, 2, 4 ]
ReportDescriptor = DescriptorFormat(
"bHeader" / construct.BitStruct(
# prefix technically consists of a 4 byte tag and a 2 byte type,
# however, they're all listed together in the HID spec
"prefix" / construct.Enum(construct.BitsInteger(6), HIDPrefix),
"bSize" / construct.BitsInteger(2),
),
"data" / construct.Byte[lambda ctx: _hid_item_length[ctx.bHeader.bSize]]
)

# Flags for INPUT/OUTPUT/FEATURE items. Named under one of the following conventions: # Flags for INPUT/OUTPUT/FEATURE items. Named under one of the following conventions:
# valA_valB: valA when 0, valB when 1 # valA_valB: valA when 0, valB when 1
# flag: Flag disabled when 0, flag enabled when 1 # flag: Flag disabled when 0, flag enabled when 1
# nFlag: Flag enabled when 0, flag disabled when 1 # nFlag: Flag enabled when 0, flag disabled when 1
ItemFlags = construct.BitStruct(
ItemFlags2 = construct.BitStruct(
"reserved6" / construct.Flag,
"reserved5" / construct.Flag,
"reserved4" / construct.Flag,
"reserved3" / construct.Flag,
"reserved2" / construct.Flag,
"reserved1" / construct.Flag,
"reserved0" / construct.Flag,
"bitfield_bufferedbytes" / construct.Flag,
"volatile" / construct.Flag, "volatile" / construct.Flag,
"null" / construct.Flag, "null" / construct.Flag,
"nPreferred" / construct.Flag, "nPreferred" / construct.Flag,
"linear" / construct.Flag,
"nLinear" / construct.Flag,
"wrap" / construct.Flag, "wrap" / construct.Flag,
"absolute_relative" / construct.Flag, "absolute_relative" / construct.Flag,
"array_variable" / construct.Flag, "array_variable" / construct.Flag,
"data_constant" / construct.Flag, "data_constant" / construct.Flag,
)
)
ItemFlags1 = construct.BitStruct(
"volatile" / construct.Flag,
"null" / construct.Flag,
"nPreferred" / construct.Flag,
"nLinear" / construct.Flag,
"wrap" / construct.Flag,
"absolute_relative" / construct.Flag,
"array_variable" / construct.Flag,
"data_constant" / construct.Flag,
)
_hid_item_flags = dict(enumerate([ construct.Byte[0], ItemFlags1, ItemFlags2, construct.Byte[4] ]))
ItemFlags = construct.Switch(this.bHeader.bSize, _hid_item_flags)

def HasItemFlags(ctx):
# Cannot use in w/ this inline, known issue.
v = ctx.bHeader.prefix
print(type(v))
if not isinstance(v, HIDPrefix):
v = v.intvalue
return v in { HIDPrefix.INPUT, HIDPrefix.OUTPUT, HIDPrefix.FEATURE }

_hid_item_length = [ 0, 1, 2, 4 ]
ReportDescriptorItem = DescriptorFormat(
"bHeader" / construct.BitStruct(
# prefix technically consists of a 4 byte tag and a 2 byte type,
# however, they're all listed together in the HID spec
"prefix" / construct.Enum(construct.BitsInteger(6), HIDPrefix),
"bSize" / construct.BitsInteger(2),
),
"data" / IfThenElse(HasItemFlags, ItemFlags, construct.Byte[lambda ctx: _hid_item_length[ctx.bHeader.bSize]]),
)
ReportDescriptor = GreedyRange(ReportDescriptorItem)

import unittest

class TestHIDDescriptor(unittest.TestCase):
def test_bitstruct(self):
rditem = ReportDescriptor.parse(b'\x81\x02')

self.assertEqual(len(rditem), 1)

ifs = rditem[0].data

self.assertEqual(ifs.volatile, False)
self.assertEqual(ifs.null, False)
self.assertEqual(ifs.nPreferred, False)
self.assertEqual(ifs.nLinear, False)
self.assertEqual(ifs.wrap, False)
self.assertEqual(ifs.absolute_relative, False)
self.assertEqual(ifs.array_variable, True)
self.assertEqual(ifs.data_constant, False)

rditem = ReportDescriptor.parse(b'\x82\x01\x02')

self.assertEqual(len(rditem), 1)

ifs = rditem[0].data

self.assertEqual(ifs.bitfield_bufferedbytes, True)
self.assertEqual(ifs.volatile, False)
self.assertEqual(ifs.null, False)
self.assertEqual(ifs.nPreferred, False)
self.assertEqual(ifs.nLinear, False)
self.assertEqual(ifs.wrap, False)
self.assertEqual(ifs.absolute_relative, False)
self.assertEqual(ifs.array_variable, True)
self.assertEqual(ifs.data_constant, False)

construct.setGlobalPrintFullStrings(True)

rditem = ReportDescriptor.parse(b'\x95\x08')

self.assertEqual(len(rditem), 1)

it = rditem[0]

self.assertEqual(it.bHeader.prefix.intvalue, HIDPrefix.REPORT_COUNT)
self.assertEqual(it.data, [8])

def test_hid_desc(self):
# sample from USB HID E.4
hid_descriptor = bytes([
0x09,
0x21,
0x11, 0x01,
0x00,
0x01,
0x22,
0x3f, 0x00,
])

parsed = HIDDescriptor.parse(hid_descriptor)

self.assertEqual(parsed.bLength, 9)
self.assertEqual(parsed.bDescriptorType, 0x21)
self.assertEqual(parsed.bcdHID, 1.11)
self.assertEqual(parsed.bCountryCode, 0x00)
self.assertEqual(parsed.bNumDescriptors, 0x01)
self.assertEqual(parsed.bRepDescriptorType, 0x22)
self.assertEqual(parsed.wDescriptorLength, 0x3f)

def test_report_desc(self):
# sample from USB HID E.6
report_descriptor = bytes([
0x05, 0x01,
0x09, 0x06,
0xa1, 0x01,
0x05, 0x07,
0x19, 0xe0,
0x29, 0xe7,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x08,
0x81, 0x02,
0x95, 0x01,
0x75, 0x08,
0x81, 0x01,
0x95, 0x05,
0x75, 0x01,
0x05, 0x08,
0x19, 0x01,
0x29, 0x05,
0x91, 0x02,
0x95, 0x01,
0x75, 0x03,
0x91, 0x01,
0x95, 0x06,
0x75, 0x08,
0x15, 0x00,
0x25, 0x65,
0x05, 0x07,
0x19, 0x00,
0x29, 0x65,
0x81, 0x00,
0xc0,
])

parsed = ReportDescriptor.parse(report_descriptor)

#print(repr(parsed))

self.assertEqual(len(parsed), 32)

self.assertEqual(parsed[0].bHeader.prefix.intvalue, HIDPrefix.USAGE_PAGE)
self.assertEqual(parsed[5].bHeader.prefix.intvalue, HIDPrefix.USAGE_MAX)
self.assertEqual(parsed[5].data, [ 231 ])
self.assertEqual(parsed[9].data, [ 8 ])
self.assertEqual(parsed[10].bHeader.prefix.intvalue, HIDPrefix.INPUT)
self.assertEqual(parsed[10].data.data_constant, False)
self.assertEqual(parsed[10].data.array_variable, True)
self.assertEqual(parsed[10].data.absolute_relative, False)
self.assertEqual(parsed[-1].bHeader.prefix.intvalue, HIDPrefix.END_COLLECTION)

#print(repr(parsed))

Loading…
Cancel
Save