You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

104 lines
2.7 KiB

  1. #
  2. # This file is part of usb-protocol.
  3. #
  4. """ Helpers for creating construct-related emitters. """
  5. import unittest
  6. import construct
  7. class ConstructEmitter:
  8. """ Class that creates a simple emitter based on a construct struct.
  9. For example, if we have a construct format that looks like the following:
  10. MyStruct = struct(
  11. "a" / Int8
  12. "b" / Int8
  13. )
  14. We could create emit an object like follows:
  15. emitter = ConstructEmitter(MyStruct)
  16. emitter.a = 0xab
  17. emitter.b = 0xcd
  18. my_bytes = emitter.emit() # "\xab\xcd"
  19. """
  20. def __init__(self, struct):
  21. """
  22. Parmeters:
  23. construct_format -- The format for which to create an emitter.
  24. """
  25. self.__dict__['format'] = struct
  26. self.__dict__['fields'] = {}
  27. def _format_contains_field(self, field_name):
  28. """ Returns True iff the given format has a field with the provided name.
  29. Parameters:
  30. format_object -- The Construct format to work with. This includes e.g. most descriptor types.
  31. field_name -- The field name to query.
  32. """
  33. return any(f.name == field_name for f in self.format.subcons)
  34. def __setattr__(self, name, value):
  35. """ Hook that we used to set our fields. """
  36. # If the field starts with a '_', don't handle it, as it's an internal field.
  37. if name.startswith('_'):
  38. super().__setattr__(name, value)
  39. return
  40. if not self._format_contains_field(name):
  41. raise AttributeError(f"emitter specification contains no field {name}")
  42. self.fields[name] = value
  43. def emit(self):
  44. """ Emits the stream of bytes associated with this object. """
  45. try:
  46. return self.format.build(self.fields)
  47. except KeyError as e:
  48. raise KeyError(f"missing necessary field: {e}")
  49. def __getattr__(self, name):
  50. """ Retrieves an emitter field, if possible. """
  51. if name in self.fields:
  52. return self.fields[name]
  53. else:
  54. raise AttributeError(f"descriptor emitter has no property {name}")
  55. class ConstructEmitterTest(unittest.TestCase):
  56. def test_simple_emitter(self):
  57. test_struct = construct.Struct(
  58. "a" / construct.Int8ul,
  59. "b" / construct.Int8ul
  60. )
  61. emitter = ConstructEmitter(test_struct)
  62. emitter.a = 0xab
  63. emitter.b = 0xcd
  64. self.assertEqual(emitter.emit(), b"\xab\xcd")
  65. def emitter_for_format(construct_format):
  66. """ Creates a factory method for the relevant construct format. """
  67. def _factory():
  68. return ConstructEmitter(construct_format)
  69. return _factory
  70. if __name__ == "__main__":
  71. unittest.main()