diff --git a/usb_protocol/contextmgrs/__init__.py b/usb_protocol/contextmgrs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/usb_protocol/contextmgrs/descriptors/__init__.py b/usb_protocol/contextmgrs/descriptors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/usb_protocol/contextmgrs/descriptors/microsoft.py b/usb_protocol/contextmgrs/descriptors/microsoft.py new file mode 100644 index 0000000..80759f4 --- /dev/null +++ b/usb_protocol/contextmgrs/descriptors/microsoft.py @@ -0,0 +1,21 @@ +# +# This file is part of usb-protocol. +# + +from ...emitters.descriptors.microsoft import ( + PlatformDescriptorCollection, PlatformDescriptorEmitter +) +from ...emitters.descriptors.standard import BinaryObjectStoreDescriptorEmitter +from ..manager import DescriptorContextManager + +class PlatformDescriptor(DescriptorContextManager): + ParentDescriptor = BinaryObjectStoreDescriptorEmitter + + def DescriptorEmitter(self) -> PlatformDescriptorEmitter: + return PlatformDescriptorEmitter(platform_collection = self._platform_collection) + + def __init__( + self, parentDesc: ParentDescriptor, platform_collection: PlatformDescriptorCollection + ) -> None: + self._platform_collection = platform_collection + super().__init__(parentDesc) diff --git a/usb_protocol/contextmgrs/manager.py b/usb_protocol/contextmgrs/manager.py new file mode 100644 index 0000000..dc27de8 --- /dev/null +++ b/usb_protocol/contextmgrs/manager.py @@ -0,0 +1,23 @@ +# +# This file is part of usb-protocol. +# + +from ..emitters.descriptor import ComplexDescriptorEmitter + + +class DescriptorContextManager: + ParentDescriptor = ComplexDescriptorEmitter + DescriptorEmitter = None + + def __init__(self, parentDesc: ParentDescriptor) -> None: + self._parent = parentDesc + self._descriptor = self.DescriptorEmitter() + + def __enter__(self): + return self._descriptor + + def __exit__(self, exc_type, exc_value, traceback): + # If an exception was raised, fast exit + if not (exc_type is None and exc_value is None and traceback is None): + return + self._parent.add_subordinate_descriptor(self._descriptor) diff --git a/usb_protocol/emitters/descriptors/microsoft.py b/usb_protocol/emitters/descriptors/microsoft.py new file mode 100644 index 0000000..e9c0540 --- /dev/null +++ b/usb_protocol/emitters/descriptors/microsoft.py @@ -0,0 +1,199 @@ +# +# This file is part of usb-protocol. +# +""" Convenience emitters for Microsoft OS 2.0 descriptors. """ + +from contextlib import contextmanager +from typing import Dict + +from .. import emitter_for_format +from ..descriptor import ComplexDescriptorEmitter + +from ...types.descriptors.microsoft import * + +# Create our basic emitters... +FeatureCompatibleIDEmitter = emitter_for_format(FeatureCompatibleID) +FeatureRegPropertyEmitter = emitter_for_format(FeatureRegProperty) +FeatureMinResumeTimeEmitter = emitter_for_format(FeatureMinResumeTime) +FeatureModelIDEmitter = emitter_for_format(FeatureModelID) +FeatureCCGPDeviceEmitter = emitter_for_format(FeatureCCGPDevice) +FeatureVendorRevisionEmitter = emitter_for_format(FeatureVendorRevision) + +# ... and complex emitters. +class FeatureDescriptorEmitter(ComplexDescriptorEmitter): + """ Abstract base type for things that can hold Feature Descriptors. """ + + @contextmanager + def FeatureCompatibleID(self): + descriptor = FeatureCompatibleIDEmitter() + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + + @contextmanager + def FeatureRegProperty(self): + descriptor = FeatureRegPropertyEmitter() + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + + @contextmanager + def FeatureMinResumeTime(self): + descriptor = FeatureMinResumeTimeEmitter() + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + + @contextmanager + def FeatureModelID(self): + descriptor = FeatureModelIDEmitter() + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + + @contextmanager + def FeatureCCGPDevice(self): + descriptor = FeatureCCGPDeviceEmitter() + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + + @contextmanager + def FeatureVendorRevision(self): + descriptor = FeatureVendorRevisionEmitter() + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + +class SubsetHeaderFunctionEmitter(FeatureDescriptorEmitter): + """ Emitter that creates a SubsetHeaderFunctionEmitter. """ + + DESCRIPTOR_FORMAT = SubsetHeaderFunction + + def _pre_emit(self): + # Figure out our total length. + subordinate_length = sum(len(sub) for sub in self._subordinates) + self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() + + +class SubsetHeaderConfigurationEmitter(FeatureDescriptorEmitter): + """ Emitter that creates a SubsetHeaderConfiguration. """ + + DESCRIPTOR_FORMAT = SubsetHeaderConfiguration + + @contextmanager + def SubsetHeaderFunction(self): + descriptor = SubsetHeaderFunctionEmitter() + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + + def _pre_emit(self): + # Figure out our total length. + subordinate_length = sum(len(sub) for sub in self._subordinates) + self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() + + +class SetHeaderDescriptorEmitter(FeatureDescriptorEmitter): + """ Emitter that creates a SetHeaderDescriptor. """ + + DESCRIPTOR_FORMAT = SetHeaderDescriptor + + @contextmanager + def SubsetHeaderConfiguration(self): + descriptor = SubsetHeaderConfigurationEmitter() + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + + def _pre_emit(self): + # Figure out our total length. + subordinate_length = sum(len(sub) for sub in self._subordinates) + self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() + + +class DescriptorSetInformationEmitter(ComplexDescriptorEmitter): + """ Emitter that creates a DescriptorSetInformation. """ + + DESCRIPTOR_FORMAT = DescriptorSetInformation + + @contextmanager + def SetHeaderDescriptor(self): + assert hasattr(self, 'bMS_VendorCode') + + descriptor = SetHeaderDescriptorEmitter() + yield descriptor + + self._subordinate = descriptor + self._collection.add_descriptor(descriptor, vendor_code = self.bMS_VendorCode) + + + def _pre_emit(self): + # Figure out our total length. + self.wMSOSDescriptorSetTotalLength = self._subordinate.wTotalLength + + +class PlatformDescriptorCollection: + """ Object that holds the OS descriptor sets for windows """ + + def __init__(self): + self._descriptors = {} + + + def add_descriptor(self, descriptor : SetHeaderDescriptorEmitter, vendor_code : int): + """ Adds a descriptor to our collection. + Parameters: + descriptor -- The set header descriptor to be added. + vendor_code -- The vendor request code for this descriptor tree + """ + + assert isinstance(descriptor, SetHeaderDescriptorEmitter) + descriptor = descriptor.emit() + + self._descriptors[vendor_code] = descriptor + + + @property + def descriptors(self) -> Dict[int, bytes]: + return self._descriptors + + +class PlatformDescriptorEmitter(ComplexDescriptorEmitter): + """ Emitter that creates a PlatformDescriptor. """ + + DESCRIPTOR_FORMAT = PlatformDescriptor + + def __init__(self, platform_collection : PlatformDescriptorCollection, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._platform_collection = platform_collection + + + @contextmanager + def DescriptorSetInformation(self): + """ Context manager that allows addition of the information associated with a descriptor set. + It can be used with a `with` statement; and yields a DescriptorSetInformationEmitter + that can be populated: + with platformDescriptor.DescriptorSetInformation() as dsi: + dsi.bMS_VendorCode = 1 + This adds the relevant descriptor, automatically. + """ + + descriptor = DescriptorSetInformationEmitter(collection = self._platform_collection) + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + + def _pre_emit(self): + # Figure out our total length. + subordinate_length = sum(len(sub) for sub in self._subordinates) + self.bLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() diff --git a/usb_protocol/emitters/descriptors/standard.py b/usb_protocol/emitters/descriptors/standard.py index 130e3fd..d6e58ad 100644 --- a/usb_protocol/emitters/descriptors/standard.py +++ b/usb_protocol/emitters/descriptors/standard.py @@ -34,9 +34,19 @@ def get_string_descriptor(string): return emitter.emit() # ... and complex emitters. +class InterfaceAssociationDescriptorEmitter(ComplexDescriptorEmitter): + """ Emitter that creates an InterfaceAssociationDescriptor. """ + + DESCRIPTOR_FORMAT = InterfaceAssociationDescriptor + + def _pre_emit(self): + # Ensure that our function string is an index, if we can. + if self._collection and hasattr(self, 'iFunction'): + self.iFunction = self._collection.ensure_string_field_is_index(self.iFunction) + class EndpointDescriptorEmitter(ComplexDescriptorEmitter): - """ Emitter that creates an InterfaceDescriptor. """ + """ Emitter that creates an EndpointDescriptor. """ DESCRIPTOR_FORMAT = EndpointDescriptor @@ -127,6 +137,25 @@ def InterfaceDescriptor(self): self.add_subordinate_descriptor(descriptor) + @contextmanager + def InterfaceAssociationDescriptor(self): + """ Context manager that allows addition of a subordinate interface association descriptor. + + It can be used with a `with` statement; and yields an InterfaceAssociationDescriptorEmitter + that can be populated: + + with interface.InterfaceAssociationDescriptor() as d: + d.bFirstInterface = 0 + [snip] + + This adds the relevant descriptor, automatically. + """ + descriptor = InterfaceAssociationDescriptorEmitter(collection=self._collection) + yield descriptor + + self.add_subordinate_descriptor(descriptor) + + def _pre_emit(self): # Count our interfaces. Alternate settings of the same interface do not count multiple times. @@ -307,6 +336,25 @@ def ConfigurationDescriptor(self): self.add_descriptor(descriptor) + @contextmanager + def BOSDescriptor(self): + """ Context manager that allows addition of a Binary Object Store descriptor. + + It can be used with a `with` statement; and yields an BinaryObjectStoreDescriptorEmitter + that can be populated: + + with collection.BOSDescriptor() as d: + [snip] + + This adds the relevant descriptor, automatically. Note that populating derived + fields such as bNumDeviceCaps aren't necessary; they'll be populated automatically. + """ + descriptor = BinaryObjectStoreDescriptorEmitter() + yield descriptor + + self.add_descriptor(descriptor) + + def _ensure_has_language_descriptor(self): """ ensures that we have a language descriptor; adding one if necessary.""" diff --git a/usb_protocol/types/descriptor.py b/usb_protocol/types/descriptor.py index 772396f..9ba3f34 100644 --- a/usb_protocol/types/descriptor.py +++ b/usb_protocol/types/descriptor.py @@ -178,6 +178,7 @@ class DescriptorField(construct.Subconstruct): 'id' : construct.Int16ul, 'bm' : construct.Int8ul, 'w' : construct.Int16ul, + 'dw' : construct.Int32ul, } diff --git a/usb_protocol/types/descriptors/microsoft.py b/usb_protocol/types/descriptors/microsoft.py new file mode 100644 index 0000000..cd079db --- /dev/null +++ b/usb_protocol/types/descriptors/microsoft.py @@ -0,0 +1,139 @@ +# +# This file is part of usb-protocol. +# +""" +Structures describing Microsoft OS specific functionality descriptors. Versions that support parsing +incomplete binary data are available as `DescriptorType`.Partial, e.g. `DeviceDescriptor.Partial`, and +are collectively available in the `usb_protocol.types.descriptors.partial.microsoft` module. +""" + +from enum import IntEnum + +import construct +from construct import this, len_ + +from ..descriptor import DescriptorField, DescriptorFormat, DescriptorNumber +from .standard import DeviceCapabilityTypes, StandardDescriptorNumbers + + +class OSDescriptorTypes(IntEnum): + SET_HEADER = 0 + SUBSET_HEADER_CONFIGURATION = 1 + SUBSET_HEADER_FUNCTION = 2 + FEATURE_COMPATIBLE_ID = 3 + FEATURE_REG_PROPERTY = 4 + FEATURE_MIN_RESUME_TIME = 5 + FEATURE_MODEL_ID = 6 + FEATURE_CCGP_DEVICE = 7 + FEATURE_VENDOR_REVISION = 8 + + +class RegistryTypes(IntEnum): + REG_SZ = 1 + REG_EXPAND_SZ = 2 + REG_BINARY = 3 + REG_DWORD_LITTLE_ENDIAN = 4 + REG_DWORD_BIG_ENDIAN = 5 + REG_LINK = 6 + REG_MULTI_SZ = 7 + + +class MicrosoftRequests(IntEnum): + GET_DESCRIPTOR_SET = 7 + SET_ALTERNATE_ENUM = 8 + + +PlatformDescriptor = DescriptorFormat( + "bLength" / DescriptorField("Total length of the platform-specific descriptor block"), + "bDescriptorType" / DescriptorNumber(StandardDescriptorNumbers.DEVICE_CAPABILITY), + "bDevCapabilityType" / construct.Const(DeviceCapabilityTypes.PLATFORM, construct.Int8ul), + "bReserved" / construct.Const(0x00, construct.Int8ul), + "PlatformCapabilityUUID" / construct.Sequence( + construct.Const(0xd8dd60df, construct.Int32ul), + construct.Const(0x4589, construct.Int16ul), + construct.Const(0x4cc7, construct.Int16ul), + construct.Const(0x9cd2, construct.Int16ub), + construct.Const(0x659d, construct.Int16ub), + construct.Const(0x9e648a9f, construct.Int32ub) + ), +) + +DescriptorSetInformation = DescriptorFormat( + "dwWindowsVersion" / DescriptorField("Minimum windows version this set should apply to", default = 0x06030000), + "wMSOSDescriptorSetTotalLength" / DescriptorField("The byte length of the descriptor set"), + "bMS_VendorCode" / DescriptorField("The vendor code to request this descriptor set with"), + "bAltEnumCode" / DescriptorField("A non-zero number if the device may return non-default USB descriptors for enumeration.", default = 0), +) + + +SetHeaderDescriptor = DescriptorFormat( + "wLength" / construct.Const(0x0A, construct.Int16ul), + "wDescriptorType" / construct.Const(OSDescriptorTypes.SET_HEADER, construct.Int16ul), + "dwWindowsVersion" / DescriptorField("Windows version", default = 0x06030000), + "wTotalLength" / DescriptorField("The total length of this descriptor set"), +) + + +SubsetHeaderConfiguration = DescriptorFormat( + "wLength" / construct.Const(0x08, construct.Int16ul), + "wDescriptorType" / construct.Const(OSDescriptorTypes.SUBSET_HEADER_CONFIGURATION, construct.Int16ul), + "bConfigurationValue" / DescriptorField("The configuration ID to which this subset applies"), + "bReserved" / construct.Const(0x00, construct.Int8ul), + "wTotalLength" / DescriptorField("The total length the configuration subset (including header)"), +) + + +SubsetHeaderFunction = DescriptorFormat( + "wLength" / construct.Const(0x08, construct.Int16ul), + "wDescriptorType" / construct.Const(OSDescriptorTypes.SUBSET_HEADER_FUNCTION, construct.Int16ul), + "bFirstInterface" / DescriptorField("The first interface number to which this function subset applies"), + "bReserved" / construct.Const(0x00, construct.Int8ul), + "wTotalLength" / DescriptorField("The total length the function subset (including header)"), +) + + +FeatureCompatibleID = DescriptorFormat( + "wLength" / construct.Const(0x14, construct.Int16ul), + "wDescriptorType" / construct.Const(OSDescriptorTypes.FEATURE_COMPATIBLE_ID, construct.Int16ul), + "CompatibleID" / construct.PaddedString(8, "utf8"), + "SubCompatibleID" / construct.PaddedString(8, "utf8"), +) + + +FeatureRegProperty = DescriptorFormat( + "wLength" / construct.Rebuild(construct.Int16ul, 10 + (2 + 2*len_(this.PropertyName)) + (2 + 2*len_(this.PropertyData))), + "wDescriptorType" / construct.Const(OSDescriptorTypes.FEATURE_REG_PROPERTY, construct.Int16ul), + "wPropertyDataType" / DescriptorField("Data type of the registry property"), + "wPropertyNameLength" / construct.Rebuild(construct.Int16ul, 2 + 2*len_(this.PropertyName)), + "PropertyName" / construct.CString("utf_16_le"), + "wPropertyDataLength" / construct.Rebuild(construct.Int16ul, 2 + 2*len_(this.PropertyData)), + "PropertyData" / construct.CString("utf_16_le"), +) + + +FeatureMinResumeTime = DescriptorFormat( + "wLength" / construct.Const(0x06, construct.Int16ul), + "wDescriptorType" / construct.Const(OSDescriptorTypes.FEATURE_MIN_RESUME_TIME, construct.Int16ul), + "bResumeRecoveryTime" / DescriptorField("Number of milliseconds required to resume the device"), + "bResumeSignalingTime" / DescriptorField("Number of milliseconds device required for resume signaling to be asserted"), +) + + +FeatureModelID = DescriptorFormat( + "wLength" / construct.Const(0x14, construct.Int16ul), + "wDescriptorType" / construct.Const(OSDescriptorTypes.FEATURE_MODEL_ID, construct.Int16ul), + "ModelID" / construct.Bytes(16), +) + + +FeatureCCGPDevice = DescriptorFormat( + "wLength" / construct.Const(0x04, construct.Int16ul), + "wDescriptorType" / construct.Const(OSDescriptorTypes.FEATURE_CCGP_DEVICE, construct.Int16ul), +) + + +FeatureVendorRevision = DescriptorFormat( + "wLength" / construct.Const(0x06, construct.Int16ul), + "wDescriptorType" / construct.Const(OSDescriptorTypes.FEATURE_VENDOR_REVISION, construct.Int16ul), + "VendorRevision" / construct.Int16ul, +)