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
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):
DESCRIPTOR_FORMAT = HIDDescriptorType
@@ -32,7 +32,7 @@ class HIDDescriptor(ComplexDescriptorEmitter):
*report_data -- Additional bytes-like report item data.
Valid lengths are 1, 2, or 4 bytes.
"""
hid_report = ReportDescriptorEmitter()
hid_report = ReportDescriptorItemEmitter()
report_len = _hid_item_length.index(len(report_data))
hid_report.bHeader = {
"prefix": report_prefix,
@@ -41,31 +41,25 @@ class HIDDescriptor(ComplexDescriptorEmitter):
hid_report.data = report_data
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.
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,
array_variable = True,
absolute_relative = False,
@@ -73,21 +67,25 @@ class HIDDescriptor(ComplexDescriptorEmitter):
linear = False,
preferred = True,
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,
"array_variable": array_variable,
"absolute_relative": absolute_relative,
"wrap": wrap,
"linear": linear,
"nPreferred": ~preferred,
"nLinear": not linear,
"nPreferred": not preferred,
"null": null,
"volatile": volatile,
})
self.add_report_item(HIDPrefix.OUTPUT, ord(item_flags))
self.add_report_item(item, item_flags)

def __init__(self, parent_descriptor):
super().__init__()
@@ -106,4 +104,28 @@ class HIDDescriptor(ComplexDescriptorEmitter):
report_descriptor = b"".join(report_descriptor)
descriptor_len = len(report_descriptor)
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

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

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

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

@unique
class HIDPrefix(IntEnum):
# Main items
@@ -53,33 +62,185 @@ HIDDescriptor = DescriptorFormat(
"bcdHID" / DescriptorField("HID Protocol Version", default=1.11),
"bCountryCode" / DescriptorField("HID Device Language", default=0),
"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")
# 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:
# valA_valB: valA when 0, valB when 1
# flag: Flag disabled when 0, flag enabled 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,
"null" / construct.Flag,
"nPreferred" / construct.Flag,
"linear" / construct.Flag,
"nLinear" / construct.Flag,
"wrap" / construct.Flag,
"absolute_relative" / construct.Flag,
"array_variable" / 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