#
# This file is part of usb_protocol.
#
""" Convenience emitters Microsoft OS 1.0 Descriptors. """
import unittest
from contextlib import contextmanager
from .. import emitter_for_format
from ..descriptor import ComplexDescriptorEmitter
from ...types.descriptors.microsoft10 import (
ExtendedCompatIDDescriptor,
ExtendedCompatIDDescriptorFunction,
ExtendedPropertiesDescriptor,
ExtendedPropertiesDescriptorSection,
RegistryTypes
)
# Create our basic emitters...
ExtendedCompatIDDescriptorFunctionEmitter = emitter_for_format(ExtendedCompatIDDescriptorFunction)
# ... and complex emitters.
[docs]
class ExtendedCompatIDDescriptorEmitter(ComplexDescriptorEmitter):
""" Emitter that creates a ExtendedCompatIDDescriptor """
DESCRIPTOR_FORMAT = ExtendedCompatIDDescriptor
[docs]
@contextmanager
def Function(self):
""" Context manager that allows addition of a function section to the descriptor.
It can be used with a `with` statement; and yields an ExtendedCompatIDDescriptorFunctionEmitter
that can be populated:
with d.Function() as f:
f.bFirstInterfaceNumber = 0
f.compatibleID = 'WINUSB'
This adds the relevant descriptor, automatically.
"""
descriptor = ExtendedCompatIDDescriptorFunctionEmitter()
yield descriptor
self.add_subordinate_descriptor(descriptor)
def _pre_emit(self):
self.bCount = len(self._subordinates)
[docs]
class ExtendedPropertiesDescriptorEmitter(ComplexDescriptorEmitter):
""" Emitter that creates a ExtendedPropertiesDescriptor """
DESCRIPTOR_FORMAT = ExtendedPropertiesDescriptor
[docs]
@contextmanager
def Property(self):
""" Context manager that allows addition of a property section to the descriptor.
It can be used with a `with` statement; and yields an ExtendedPropertiesDescriptorSectionEmitter
that can be populated:
with d.Property() as p:
p.dwPropertyDataType = RegistryTypes.REG_EXPAND_SZ
p.PropertyName = "Icons"
p.PropertyData = "%SystemRoot%\\system32\\shell32.dll,-233"
This adds the relevant descriptor, automatically.
"""
descriptor = ExtendedPropertiesDescriptorSectionEmitter()
yield descriptor
self.add_subordinate_descriptor(descriptor)
def _pre_emit(self):
self.wCount = len(self._subordinates)
self.dwLength = 10 + sum(len(s) for s in self._subordinates)
[docs]
class ExtendedPropertiesDescriptorSectionEmitter(ComplexDescriptorEmitter):
""" Emitter that creates a ExtendedPropertiesDescriptorSection """
DESCRIPTOR_FORMAT = ExtendedPropertiesDescriptorSection
def _pre_emit(self):
if self.dwPropertyDataType in (RegistryTypes.REG_SZ, RegistryTypes.REG_EXPAND_SZ, RegistryTypes.REG_LINK):
self.PropertyData = self.PropertyData.encode('utf_16_le') + b'\0\0'
elif self.dwPropertyDataType == RegistryTypes.REG_DWORD_LITTLE_ENDIAN:
self.PropertyData = self.PropertyData.to_bytes(4, 'little')
elif self.dwPropertyDataType == RegistryTypes.REG_DWORD_BIG_ENDIAN:
self.PropertyData = self.PropertyData.to_bytes(4, 'big')
elif self.dwPropertyDataType == RegistryTypes.REG_MULTI_SZ:
strings = b''
for string in self.PropertyData:
strings += string.encode('utf_16_le') + b'\0\0'
self.PropertyData = strings
[docs]
class MicrosoftOS10DescriptorCollection:
""" Object that builds a full collection of Microsoft OS 1.0 descriptors. """
def __init__(self):
self._descriptors = {}
[docs]
def add_descriptor(self, descriptor, index=None):
""" Adds a descriptor to our collection.
Parameters:
descriptor -- The descriptor to be added.
index -- The index of the relevant descriptor. Defaults to None.
"""
# If this is an emitter rather than a descriptor itself, convert it.
if hasattr(descriptor, 'emit'):
descriptor = descriptor.emit()
# Figure out the index for this descriptor...
if index is None:
index = (descriptor[7] << 8) | descriptor[6]
# ... and store it.
self._descriptors[index] = descriptor
[docs]
@contextmanager
def ExtendedCompatIDDescriptor(self):
descriptor = ExtendedCompatIDDescriptorEmitter()
yield descriptor
self.add_descriptor(descriptor)
[docs]
@contextmanager
def ExtendedPropertiesDescriptor(self):
descriptor = ExtendedPropertiesDescriptorEmitter()
yield descriptor
self.add_descriptor(descriptor)
def __iter__(self):
return ((index, descriptor) for index, descriptor in self._descriptors.items())
[docs]
class MicrosoftOS10EmitterTests(unittest.TestCase):
[docs]
def test_compat_id_descriptor(self):
# From Extended Compat ID OS Feature Descriptor Specification, Annex 1
descriptor = bytes([
0x58, 0x00, 0x00, 0x00, # Descriptor length
0x00, 0x01, # Version 1.00
0x04, 0x00, # Extended compat ID descriptor
0x03, # Number of function sections
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # Reserved / padding
# Function Section 1
0x00, # First interface number
0x01, # Reserved
0x52, 0x4e, 0x44, 0x49, 0x53, 0x00, 0x00, 0x00, # compatibleID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # subCompatibleID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # Reserved / padding
# Function Section 2
0x02, # First interface number
0x01, # Reserved
0x4d, 0x54, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, # compatibleID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # subCompatibleID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # Reserved / padding
# Function section 3
0x03, # First interface number
0x01, # Reserved
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # compatibleID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # subCompatibleID
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 # Reserved / padding
])
# Create a trivial configuration descriptor...
collection = MicrosoftOS10DescriptorCollection()
with collection.ExtendedCompatIDDescriptor() as d:
with d.Function() as i:
i.bFirstInterfaceNumber = 0
i.compatibleID = 'RNDIS'
with d.Function() as i:
i.bFirstInterfaceNumber = 2
i.compatibleID = 'MTP'
with d.Function() as i:
i.bFirstInterfaceNumber = 3
# ... and validate that it maches our reference descriptor.
results = list(collection)
self.assertEqual(results[0], (4, descriptor))
[docs]
def test_extended_properties_descriptor(self):
# Based on the Appendix from Extended Properties OS Feature Descriptor Specification
descriptor = bytes([
# Header
0xa2, 0x00, 0x00, 0x00, # total descriptor length
0x00, 0x01, # bcdVersion
0x05, 0x00, # wIndex
0x02, 0x00, # Number of custom properties
# Custom property 1
0x68, 0x00, 0x00, 0x00, # Size of this custom property section
0x02, 0x00, 0x00, 0x00, # Property data format
0x0c, 0x00, # Property name length
0x49, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x00, 0x00, # Property name
0x4e, 0x00, 0x00, 0x00, # Length of the property data
0x25, 0x00, 0x53, 0x00, 0x79, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, # Property data
0x6d, 0x00, 0x52, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x25, 0x00,
0x5c, 0x00, 0x73, 0x00, 0x79, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00,
0x6d, 0x00, 0x33, 0x00, 0x32, 0x00, 0x5c, 0x00, 0x73, 0x00, 0x68, 0x00,
0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x33, 0x00, 0x32, 0x00, 0x2e, 0x00,
0x64, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x2c, 0x00, 0x2d, 0x00, 0x32, 0x00,
0x33, 0x00, 0x33, 0x00, 0x00, 0x00,
# Custom property 2
0x30, 0x00, 0x00, 0x00, # Size of this custom property section
0x01, 0x00, 0x00, 0x00, # Property data format
0x0c, 0x00, # Property name length
0x4c, 0x00, 0x61, 0x00, 0x62, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x00, 0x00, # Property name
0x16, 0x00, 0x00, 0x00, # Length of the property data
0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x20, 0x00, 0x44, 0x00, 0x65, 0x00, # Property data
0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x00, 0x00,
])
# Create a trivial configuration descriptor...
collection = MicrosoftOS10DescriptorCollection()
with collection.ExtendedPropertiesDescriptor() as d:
with d.Property() as p:
p.dwPropertyDataType = RegistryTypes.REG_EXPAND_SZ
p.PropertyName = "Icons"
p.PropertyData = "%SystemRoot%\\system32\\shell32.dll,-233"
with d.Property() as p:
p.dwPropertyDataType = RegistryTypes.REG_SZ
p.PropertyName = "Label"
p.PropertyData = "XYZ Device"
# ... and validate that it maches our reference descriptor.
results = list(collection)
self.assertEqual(results[0], (5, descriptor))
if __name__ == "__main__":
unittest.main()