Source code for usb_protocol.emitters.descriptors.standard

# -*- coding: utf-8 -*-
#
# This file is part of usb_protocol.
#
""" Convenience emitters for simple, standard descriptors. """

from contextlib import contextmanager

from ...types import LanguageIDs
from ...types.descriptors.standard import *
from .. import emitter_for_format
from ..descriptor import ComplexDescriptorEmitter

# Create our basic emitters...
DeviceDescriptorEmitter         = emitter_for_format(DeviceDescriptor)
StringDescriptorEmitter         = emitter_for_format(StringDescriptor)
StringLanguageDescriptorEmitter = emitter_for_format(StringLanguageDescriptor)
DeviceQualifierDescriptor       = emitter_for_format(DeviceQualifierDescriptor)

# ... our basic superspeed emitters ...
USB2ExtensionDescriptorEmitter                  = emitter_for_format(USB2ExtensionDescriptor)
SuperSpeedUSBDeviceCapabilityDescriptorEmitter  = emitter_for_format(SuperSpeedUSBDeviceCapabilityDescriptor)
SuperSpeedEndpointCompanionDescriptorEmitter    = emitter_for_format(SuperSpeedEndpointCompanionDescriptor)

# ... convenience functions ...
[docs] def get_string_descriptor(string): """ Generates a string descriptor for the relevant string. """ emitter = StringDescriptorEmitter() emitter.bString = string return emitter.emit()
# ... and complex emitters.
[docs] class EndpointDescriptorEmitter(ComplexDescriptorEmitter): """ Emitter that creates an InterfaceDescriptor. """ DESCRIPTOR_FORMAT = EndpointDescriptor
[docs] @contextmanager def SuperSpeedCompanion(self): """ Context manager that allows addition of a SuperSpeed Companion to this endpoint descriptor. It can be used with a `with` statement; and yields an SuperSpeedEndpointCompanionDescriptorEmitter that can be populated:: with endpoint.SuperSpeedEndpointCompanion() as d: d.bMaxBurst = 1 This adds the relevant descriptor, automatically. """ descriptor = SuperSpeedEndpointCompanionDescriptorEmitter() yield descriptor self.add_subordinate_descriptor(descriptor)
[docs] class InterfaceDescriptorEmitter(ComplexDescriptorEmitter): """ Emitter that creates an InterfaceDescriptor. """ DESCRIPTOR_FORMAT = InterfaceDescriptor
[docs] @contextmanager def EndpointDescriptor(self, *, add_default_superspeed=False): """ Context manager that allows addition of a subordinate endpoint descriptor. It can be used with a `with` statement; and yields an EndpointDesriptorEmitter that can be populated:: with interface.EndpointDescriptor() as d: d.bEndpointAddress = 0x01 d.bmAttributes = 0x80 d.wMaxPacketSize = 64 d.bInterval = 0 This adds the relevant descriptor, automatically. """ descriptor = EndpointDescriptorEmitter() yield descriptor # If we're adding a default SuperSpeed extension, do so. if add_default_superspeed: with descriptor.SuperSpeedCompanion(): pass self.add_subordinate_descriptor(descriptor)
def _pre_emit(self): # Count our endpoints, and update our internal count. self.bNumEndpoints = self._type_counts[StandardDescriptorNumbers.ENDPOINT] # Ensure that our interface string is an index, if we can. if self._collection and hasattr(self, 'iInterface'): self.iInterface = self._collection.ensure_string_field_is_index(self.iInterface)
[docs] class ConfigurationDescriptorEmitter(ComplexDescriptorEmitter): """ Emitter that creates a configuration descriptor. """ DESCRIPTOR_FORMAT = ConfigurationDescriptor
[docs] @contextmanager def InterfaceDescriptor(self): """ Context manager that allows addition of a subordinate interface descriptor. It can be used with a `with` statement; and yields an InterfaceDescriptorEmitter that can be populated:: with interface.InterfaceDescriptor() as d: d.bInterfaceNumber = 0x01 [snip] This adds the relevant descriptor, automatically. Note that populating derived fields such as bNumEndpoints aren't necessary; they'll be populated automatically. """ descriptor = InterfaceDescriptorEmitter(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. self.bNumInterfaces = len(set([subordinate[2] for subordinate in self._subordinates if (subordinate[1] == StandardDescriptorNumbers.INTERFACE)])) # Figure out our total length. subordinate_length = sum(len(sub) for sub in self._subordinates) self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() # Ensure that our configuration string is an index, if we can. if self._collection and hasattr(self, 'iConfiguration'): self.iConfiguration = self._collection.ensure_string_field_is_index(self.iConfiguration)
[docs] class DeviceDescriptorCollection: """ Object that builds a full collection of descriptors related to a given USB device. """ # Most systems seem happiest with en_US (ugh), so default to that. DEFAULT_SUPPORTED_LANGUAGES = [LanguageIDs.ENGLISH_US] def __init__(self, automatic_language_descriptor=True): """ :automatic_language_descriptor: If set or not provided, a language descriptor will automatically be added if none exists. """ self._automatic_language_descriptor = automatic_language_descriptor # Create our internal descriptor tracker. # Keys are a tuple of (type, index). self._descriptors = {} # Track string descriptors as they're created. self._next_string_index = 1 self._index_for_string = {}
[docs] def ensure_string_field_is_index(self, field_value): """ Processes the given field value; if it's not an string index, converts it to one. Non-index-fields are converted to indices using `get_index_for_string`, which automatically adds the relevant fields to our string descriptor collection. """ if isinstance(field_value, str): return self.get_index_for_string(field_value) else: return field_value
[docs] def get_index_for_string(self, string): """ Returns an string descriptor index for the given string. If a string descriptor already exists for the given string, its index is returned. Otherwise, a string descriptor is created. """ # If we already have a descriptor for this string, return it. if string in self._index_for_string: return self._index_for_string[string] # Otherwise, create one: # Allocate an index... index = self._next_string_index self._index_for_string[string] = index self._next_string_index += 1 # ... store our string descriptor with it ... identifier = StandardDescriptorNumbers.STRING, index self._descriptors[identifier] = get_string_descriptor(string) # ... and return our index. return index
[docs] def add_descriptor(self, descriptor, index=0, descriptor_type=None): """ Adds a descriptor to our collection. :param descriptor: The descriptor to be added. :param index: The index of the relevant descriptor. Defaults to 0. :param descriptor_type: The type of the descriptor to be added. If `None`, this is automatically derived from the descriptor contents. """ # If this is an emitter rather than a descriptor itself, convert it. if hasattr(descriptor, 'emit'): descriptor = descriptor.emit() # Figure out the identifier (type + index) for this descriptor... if (descriptor_type is None): descriptor_type = descriptor[1] identifier = descriptor_type, index # ... and store it. self._descriptors[identifier] = descriptor
[docs] def add_language_descriptor(self, supported_languages=None): """ Adds a language descriptor to the list of device descriptors. :param supported_languages: A list of languages supported by the device. """ if supported_languages is None: supported_languages = self.DEFAULT_SUPPORTED_LANGUAGES descriptor = StringLanguageDescriptorEmitter() descriptor.wLANGID = supported_languages self.add_descriptor(descriptor)
[docs] @contextmanager def DeviceDescriptor(self): """ Context manager that allows addition of a device descriptor. It can be used with a `with` statement; and yields an DeviceDescriptorEmitter that can be populated:: with collection.DeviceDescriptor() as d: d.idVendor = 0xabcd d.idProduct = 0x1234 [snip] This adds the relevant descriptor, automatically. """ descriptor = DeviceDescriptorEmitter() yield descriptor # If we have any string fields, ensure that they're indices before continuing. for field in ('iManufacturer', 'iProduct', 'iSerialNumber'): if hasattr(descriptor, field): value = getattr(descriptor, field) index = self.ensure_string_field_is_index(value) setattr(descriptor, field, index) self.add_descriptor(descriptor)
[docs] @contextmanager def ConfigurationDescriptor(self): """ Context manager that allows addition of a configuration descriptor. It can be used with a `with` statement; and yields an ConfigurationDescriptorEmitter that can be populated:: with collection.ConfigurationDescriptor() as d: d.bConfigurationValue = 1 [snip] This adds the relevant descriptor, automatically. Note that populating derived fields such as bNumInterfaces aren't necessary; they'll be populated automatically. """ descriptor = ConfigurationDescriptorEmitter(collection=self) yield descriptor self.add_descriptor(descriptor)
def _ensure_has_language_descriptor(self): """ Ensures that we have a language descriptor; adding one if necessary.""" # if we're not automatically adding a language descriptor, we shouldn't do anything, # and we'll just ignore this. if not self._automatic_language_descriptor: return # if we don't have a language descriptor, add our default one. if (StandardDescriptorNumbers.STRING, 0) not in self._descriptors: self.add_language_descriptor()
[docs] def get_descriptor_bytes(self, type_number: int, index: int = 0): """ Returns the raw, binary descriptor for a given descriptor type/index. :param type_number: The descriptor type number. :param index: The index of the relevant descriptor, if relevant. """ # If this is a request for a language descriptor, return one. if (type_number, index) == (StandardDescriptorNumbers.STRING, 0): self._ensure_has_language_descriptor() return self._descriptors[(type_number, index)]
def __iter__(self): """ Allow iterating over each of our descriptors; yields (index, value, descriptor). """ self._ensure_has_language_descriptor() return ((number, index, desc) for ((number, index), desc) in self._descriptors.items())
[docs] class BinaryObjectStoreDescriptorEmitter(ComplexDescriptorEmitter): """ Emitter that creates a BinaryObjectStore descriptor. """ DESCRIPTOR_FORMAT = BinaryObjectStoreDescriptor
[docs] @contextmanager def USB2Extension(self): """ Context manager that allows addition of a USB 2.0 Extension to this Binary Object Store. It can be used with a `with` statement; and yields an USB2ExtensionDescriptorEmitter that can be populated:: with bos.USB2Extension() as e: e.bmAttributes = 1 This adds the relevant descriptor, automatically. """ descriptor = USB2ExtensionDescriptorEmitter() yield descriptor self.add_subordinate_descriptor(descriptor)
[docs] @contextmanager def SuperSpeedUSBDeviceCapability(self): """ Context manager that allows addition of a SS Device Capability to this Binary Object Store. It can be used with a `with` statement; and yields an SuperSpeedUSBDeviceCapabilityDescriptorEmitter that can be populated:: with bos.SuperSpeedUSBDeviceCapability() as e: e.wSpeedSupported = 0b1110 e.bFunctionalitySupport = 1 This adds the relevant descriptor, automatically. """ descriptor = SuperSpeedUSBDeviceCapabilityDescriptorEmitter() yield descriptor self.add_subordinate_descriptor(descriptor)
def _pre_emit(self): # Figure out the total length of our descriptor, including subordinates. subordinate_length = sum(len(sub) for sub in self._subordinates) self.wTotalLength = subordinate_length + self.DESCRIPTOR_FORMAT.sizeof() # Count our subordinate descriptors, and update our internal count. self.bNumDeviceCaps = len(self._subordinates)
[docs] class SuperSpeedDeviceDescriptorCollection(DeviceDescriptorCollection): """ Object that builds a full collection of descriptors related to a given USB3 device. """ def __init__(self, automatic_descriptors=True): """ :param automatic_descriptors: If set or not provided, certain required descriptors will be be added if none exists. """ self._automatic_descriptors = automatic_descriptors super().__init__(automatic_language_descriptor=automatic_descriptors)
[docs] @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)
[docs] def add_default_bos_descriptor(self): """ Adds a default, empty BOS descriptor. """ # Create an empty BOS descriptor... descriptor = BinaryObjectStoreDescriptorEmitter() # ... populate our default required descriptors... descriptor.add_subordinate_descriptor(USB2ExtensionDescriptorEmitter()) descriptor.add_subordinate_descriptor(SuperSpeedUSBDeviceCapabilityDescriptorEmitter()) # ... and add it to our overall BOS descriptor. self.add_descriptor(descriptor)
def _ensure_has_bos_descriptor(self): """ Ensures that we have a BOS descriptor; adding one if necessary.""" # If we're not automatically adding a language descriptor, we shouldn't do anything, # and we'll just ignore this. if not self._automatic_descriptors: return # If we don't have a language descriptor, add our default one. if (StandardDescriptorNumbers.BOS, 0) not in self._descriptors: self.add_default_bos_descriptor() def __iter__(self): """ Allow iterating over each of our descriptors; yields (index, value, descriptor). """ self._ensure_has_bos_descriptor() return super().__iter__()