bumble 0.0.213__py3-none-any.whl → 0.0.215__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. bumble/_version.py +16 -3
  2. bumble/a2dp.py +15 -16
  3. bumble/apps/auracast.py +14 -38
  4. bumble/apps/bench.py +10 -15
  5. bumble/apps/ble_rpa_tool.py +1 -0
  6. bumble/apps/console.py +22 -25
  7. bumble/apps/controller_info.py +20 -25
  8. bumble/apps/controller_loopback.py +6 -10
  9. bumble/apps/controllers.py +2 -3
  10. bumble/apps/device_info.py +4 -5
  11. bumble/apps/gatt_dump.py +3 -3
  12. bumble/apps/gg_bridge.py +7 -8
  13. bumble/apps/hci_bridge.py +4 -3
  14. bumble/apps/l2cap_bridge.py +5 -5
  15. bumble/apps/lea_unicast/app.py +16 -26
  16. bumble/apps/pair.py +30 -43
  17. bumble/apps/pandora_server.py +5 -4
  18. bumble/apps/player/player.py +20 -24
  19. bumble/apps/rfcomm_bridge.py +4 -10
  20. bumble/apps/scan.py +17 -8
  21. bumble/apps/show.py +4 -5
  22. bumble/apps/speaker/speaker.py +23 -27
  23. bumble/apps/unbond.py +3 -3
  24. bumble/apps/usb_probe.py +2 -4
  25. bumble/att.py +241 -246
  26. bumble/audio/io.py +5 -9
  27. bumble/avc.py +2 -2
  28. bumble/avctp.py +6 -7
  29. bumble/avdtp.py +19 -22
  30. bumble/avrcp.py +1097 -589
  31. bumble/codecs.py +2 -0
  32. bumble/controller.py +142 -35
  33. bumble/core.py +567 -248
  34. bumble/crypto/__init__.py +2 -2
  35. bumble/crypto/builtin.py +1 -1
  36. bumble/crypto/cryptography.py +2 -4
  37. bumble/data_types.py +1025 -0
  38. bumble/device.py +319 -267
  39. bumble/drivers/__init__.py +3 -2
  40. bumble/drivers/intel.py +3 -4
  41. bumble/drivers/rtk.py +26 -9
  42. bumble/gap.py +4 -4
  43. bumble/gatt.py +3 -2
  44. bumble/gatt_adapters.py +3 -11
  45. bumble/gatt_client.py +69 -81
  46. bumble/gatt_server.py +124 -124
  47. bumble/hci.py +114 -18
  48. bumble/helpers.py +19 -26
  49. bumble/hfp.py +10 -21
  50. bumble/hid.py +22 -16
  51. bumble/host.py +191 -103
  52. bumble/keys.py +5 -3
  53. bumble/l2cap.py +138 -104
  54. bumble/link.py +18 -19
  55. bumble/logging.py +65 -0
  56. bumble/pairing.py +7 -6
  57. bumble/pandora/__init__.py +9 -8
  58. bumble/pandora/config.py +3 -1
  59. bumble/pandora/device.py +3 -2
  60. bumble/pandora/host.py +38 -36
  61. bumble/pandora/l2cap.py +22 -21
  62. bumble/pandora/security.py +15 -15
  63. bumble/pandora/utils.py +5 -3
  64. bumble/profiles/aics.py +11 -11
  65. bumble/profiles/ams.py +403 -0
  66. bumble/profiles/ancs.py +6 -7
  67. bumble/profiles/ascs.py +14 -9
  68. bumble/profiles/asha.py +8 -12
  69. bumble/profiles/bap.py +11 -23
  70. bumble/profiles/bass.py +2 -7
  71. bumble/profiles/battery_service.py +3 -4
  72. bumble/profiles/cap.py +1 -2
  73. bumble/profiles/csip.py +2 -6
  74. bumble/profiles/device_information_service.py +2 -2
  75. bumble/profiles/gap.py +4 -4
  76. bumble/profiles/gatt_service.py +1 -4
  77. bumble/profiles/gmap.py +5 -5
  78. bumble/profiles/hap.py +62 -59
  79. bumble/profiles/heart_rate_service.py +5 -4
  80. bumble/profiles/le_audio.py +3 -1
  81. bumble/profiles/mcp.py +3 -7
  82. bumble/profiles/pacs.py +3 -6
  83. bumble/profiles/pbp.py +2 -0
  84. bumble/profiles/tmap.py +2 -3
  85. bumble/profiles/vcs.py +2 -8
  86. bumble/profiles/vocs.py +8 -8
  87. bumble/rfcomm.py +11 -14
  88. bumble/rtp.py +1 -0
  89. bumble/sdp.py +10 -8
  90. bumble/smp.py +151 -159
  91. bumble/snoop.py +5 -5
  92. bumble/tools/generate_company_id_list.py +1 -0
  93. bumble/tools/intel_fw_download.py +3 -3
  94. bumble/tools/intel_util.py +5 -4
  95. bumble/tools/rtk_fw_download.py +6 -3
  96. bumble/tools/rtk_util.py +26 -8
  97. bumble/transport/__init__.py +19 -15
  98. bumble/transport/android_emulator.py +8 -13
  99. bumble/transport/android_netsim.py +19 -18
  100. bumble/transport/common.py +12 -15
  101. bumble/transport/file.py +1 -1
  102. bumble/transport/hci_socket.py +4 -6
  103. bumble/transport/pty.py +5 -6
  104. bumble/transport/pyusb.py +7 -10
  105. bumble/transport/serial.py +2 -1
  106. bumble/transport/tcp_client.py +2 -2
  107. bumble/transport/tcp_server.py +11 -14
  108. bumble/transport/udp.py +3 -3
  109. bumble/transport/unix.py +67 -1
  110. bumble/transport/usb.py +6 -6
  111. bumble/transport/vhci.py +0 -1
  112. bumble/transport/ws_client.py +2 -1
  113. bumble/transport/ws_server.py +3 -2
  114. bumble/utils.py +20 -5
  115. bumble/vendor/android/hci.py +1 -2
  116. bumble/vendor/zephyr/hci.py +0 -1
  117. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/METADATA +4 -2
  118. bumble-0.0.215.dist-info/RECORD +183 -0
  119. bumble-0.0.213.dist-info/RECORD +0 -180
  120. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/WHEEL +0 -0
  121. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/entry_points.txt +0 -0
  122. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/licenses/LICENSE +0 -0
  123. {bumble-0.0.213.dist-info → bumble-0.0.215.dist-info}/top_level.txt +0 -0
bumble/att.py CHANGED
@@ -24,24 +24,26 @@
24
24
  # -----------------------------------------------------------------------------
25
25
  from __future__ import annotations
26
26
 
27
+ import dataclasses
27
28
  import enum
28
29
  import functools
29
30
  import inspect
30
31
  import struct
31
32
  from typing import (
33
+ TYPE_CHECKING,
32
34
  Awaitable,
33
35
  Callable,
36
+ ClassVar,
34
37
  Generic,
38
+ Optional,
35
39
  TypeVar,
36
40
  Union,
37
- TYPE_CHECKING,
38
41
  )
39
42
 
40
-
41
- from bumble import utils
42
- from bumble.core import UUID, name_or_number, InvalidOperationError, ProtocolError
43
- from bumble.hci import HCI_Object, key_with_value
43
+ from bumble import hci, utils
44
44
  from bumble.colors import color
45
+ from bumble.core import UUID, InvalidOperationError, ProtocolError
46
+ from bumble.hci import HCI_Object
45
47
 
46
48
  # -----------------------------------------------------------------------------
47
49
  # Typing
@@ -60,96 +62,66 @@ _T = TypeVar('_T')
60
62
  ATT_CID = 0x04
61
63
  ATT_PSM = 0x001F
62
64
 
63
- ATT_ERROR_RESPONSE = 0x01
64
- ATT_EXCHANGE_MTU_REQUEST = 0x02
65
- ATT_EXCHANGE_MTU_RESPONSE = 0x03
66
- ATT_FIND_INFORMATION_REQUEST = 0x04
67
- ATT_FIND_INFORMATION_RESPONSE = 0x05
68
- ATT_FIND_BY_TYPE_VALUE_REQUEST = 0x06
69
- ATT_FIND_BY_TYPE_VALUE_RESPONSE = 0x07
70
- ATT_READ_BY_TYPE_REQUEST = 0x08
71
- ATT_READ_BY_TYPE_RESPONSE = 0x09
72
- ATT_READ_REQUEST = 0x0A
73
- ATT_READ_RESPONSE = 0x0B
74
- ATT_READ_BLOB_REQUEST = 0x0C
75
- ATT_READ_BLOB_RESPONSE = 0x0D
76
- ATT_READ_MULTIPLE_REQUEST = 0x0E
77
- ATT_READ_MULTIPLE_RESPONSE = 0x0F
78
- ATT_READ_BY_GROUP_TYPE_REQUEST = 0x10
79
- ATT_READ_BY_GROUP_TYPE_RESPONSE = 0x11
80
- ATT_WRITE_REQUEST = 0x12
81
- ATT_WRITE_RESPONSE = 0x13
82
- ATT_WRITE_COMMAND = 0x52
83
- ATT_SIGNED_WRITE_COMMAND = 0xD2
84
- ATT_PREPARE_WRITE_REQUEST = 0x16
85
- ATT_PREPARE_WRITE_RESPONSE = 0x17
86
- ATT_EXECUTE_WRITE_REQUEST = 0x18
87
- ATT_EXECUTE_WRITE_RESPONSE = 0x19
88
- ATT_HANDLE_VALUE_NOTIFICATION = 0x1B
89
- ATT_HANDLE_VALUE_INDICATION = 0x1D
90
- ATT_HANDLE_VALUE_CONFIRMATION = 0x1E
91
-
92
- ATT_PDU_NAMES = {
93
- ATT_ERROR_RESPONSE: 'ATT_ERROR_RESPONSE',
94
- ATT_EXCHANGE_MTU_REQUEST: 'ATT_EXCHANGE_MTU_REQUEST',
95
- ATT_EXCHANGE_MTU_RESPONSE: 'ATT_EXCHANGE_MTU_RESPONSE',
96
- ATT_FIND_INFORMATION_REQUEST: 'ATT_FIND_INFORMATION_REQUEST',
97
- ATT_FIND_INFORMATION_RESPONSE: 'ATT_FIND_INFORMATION_RESPONSE',
98
- ATT_FIND_BY_TYPE_VALUE_REQUEST: 'ATT_FIND_BY_TYPE_VALUE_REQUEST',
99
- ATT_FIND_BY_TYPE_VALUE_RESPONSE: 'ATT_FIND_BY_TYPE_VALUE_RESPONSE',
100
- ATT_READ_BY_TYPE_REQUEST: 'ATT_READ_BY_TYPE_REQUEST',
101
- ATT_READ_BY_TYPE_RESPONSE: 'ATT_READ_BY_TYPE_RESPONSE',
102
- ATT_READ_REQUEST: 'ATT_READ_REQUEST',
103
- ATT_READ_RESPONSE: 'ATT_READ_RESPONSE',
104
- ATT_READ_BLOB_REQUEST: 'ATT_READ_BLOB_REQUEST',
105
- ATT_READ_BLOB_RESPONSE: 'ATT_READ_BLOB_RESPONSE',
106
- ATT_READ_MULTIPLE_REQUEST: 'ATT_READ_MULTIPLE_REQUEST',
107
- ATT_READ_MULTIPLE_RESPONSE: 'ATT_READ_MULTIPLE_RESPONSE',
108
- ATT_READ_BY_GROUP_TYPE_REQUEST: 'ATT_READ_BY_GROUP_TYPE_REQUEST',
109
- ATT_READ_BY_GROUP_TYPE_RESPONSE: 'ATT_READ_BY_GROUP_TYPE_RESPONSE',
110
- ATT_WRITE_REQUEST: 'ATT_WRITE_REQUEST',
111
- ATT_WRITE_RESPONSE: 'ATT_WRITE_RESPONSE',
112
- ATT_WRITE_COMMAND: 'ATT_WRITE_COMMAND',
113
- ATT_SIGNED_WRITE_COMMAND: 'ATT_SIGNED_WRITE_COMMAND',
114
- ATT_PREPARE_WRITE_REQUEST: 'ATT_PREPARE_WRITE_REQUEST',
115
- ATT_PREPARE_WRITE_RESPONSE: 'ATT_PREPARE_WRITE_RESPONSE',
116
- ATT_EXECUTE_WRITE_REQUEST: 'ATT_EXECUTE_WRITE_REQUEST',
117
- ATT_EXECUTE_WRITE_RESPONSE: 'ATT_EXECUTE_WRITE_RESPONSE',
118
- ATT_HANDLE_VALUE_NOTIFICATION: 'ATT_HANDLE_VALUE_NOTIFICATION',
119
- ATT_HANDLE_VALUE_INDICATION: 'ATT_HANDLE_VALUE_INDICATION',
120
- ATT_HANDLE_VALUE_CONFIRMATION: 'ATT_HANDLE_VALUE_CONFIRMATION'
121
- }
65
+ class Opcode(hci.SpecableEnum):
66
+ ATT_ERROR_RESPONSE = 0x01
67
+ ATT_EXCHANGE_MTU_REQUEST = 0x02
68
+ ATT_EXCHANGE_MTU_RESPONSE = 0x03
69
+ ATT_FIND_INFORMATION_REQUEST = 0x04
70
+ ATT_FIND_INFORMATION_RESPONSE = 0x05
71
+ ATT_FIND_BY_TYPE_VALUE_REQUEST = 0x06
72
+ ATT_FIND_BY_TYPE_VALUE_RESPONSE = 0x07
73
+ ATT_READ_BY_TYPE_REQUEST = 0x08
74
+ ATT_READ_BY_TYPE_RESPONSE = 0x09
75
+ ATT_READ_REQUEST = 0x0A
76
+ ATT_READ_RESPONSE = 0x0B
77
+ ATT_READ_BLOB_REQUEST = 0x0C
78
+ ATT_READ_BLOB_RESPONSE = 0x0D
79
+ ATT_READ_MULTIPLE_REQUEST = 0x0E
80
+ ATT_READ_MULTIPLE_RESPONSE = 0x0F
81
+ ATT_READ_BY_GROUP_TYPE_REQUEST = 0x10
82
+ ATT_READ_BY_GROUP_TYPE_RESPONSE = 0x11
83
+ ATT_WRITE_REQUEST = 0x12
84
+ ATT_WRITE_RESPONSE = 0x13
85
+ ATT_WRITE_COMMAND = 0x52
86
+ ATT_SIGNED_WRITE_COMMAND = 0xD2
87
+ ATT_PREPARE_WRITE_REQUEST = 0x16
88
+ ATT_PREPARE_WRITE_RESPONSE = 0x17
89
+ ATT_EXECUTE_WRITE_REQUEST = 0x18
90
+ ATT_EXECUTE_WRITE_RESPONSE = 0x19
91
+ ATT_HANDLE_VALUE_NOTIFICATION = 0x1B
92
+ ATT_HANDLE_VALUE_INDICATION = 0x1D
93
+ ATT_HANDLE_VALUE_CONFIRMATION = 0x1E
122
94
 
123
95
  ATT_REQUESTS = [
124
- ATT_EXCHANGE_MTU_REQUEST,
125
- ATT_FIND_INFORMATION_REQUEST,
126
- ATT_FIND_BY_TYPE_VALUE_REQUEST,
127
- ATT_READ_BY_TYPE_REQUEST,
128
- ATT_READ_REQUEST,
129
- ATT_READ_BLOB_REQUEST,
130
- ATT_READ_MULTIPLE_REQUEST,
131
- ATT_READ_BY_GROUP_TYPE_REQUEST,
132
- ATT_WRITE_REQUEST,
133
- ATT_PREPARE_WRITE_REQUEST,
134
- ATT_EXECUTE_WRITE_REQUEST
96
+ Opcode.ATT_EXCHANGE_MTU_REQUEST,
97
+ Opcode.ATT_FIND_INFORMATION_REQUEST,
98
+ Opcode.ATT_FIND_BY_TYPE_VALUE_REQUEST,
99
+ Opcode.ATT_READ_BY_TYPE_REQUEST,
100
+ Opcode.ATT_READ_REQUEST,
101
+ Opcode.ATT_READ_BLOB_REQUEST,
102
+ Opcode.ATT_READ_MULTIPLE_REQUEST,
103
+ Opcode.ATT_READ_BY_GROUP_TYPE_REQUEST,
104
+ Opcode.ATT_WRITE_REQUEST,
105
+ Opcode.ATT_PREPARE_WRITE_REQUEST,
106
+ Opcode.ATT_EXECUTE_WRITE_REQUEST
135
107
  ]
136
108
 
137
109
  ATT_RESPONSES = [
138
- ATT_ERROR_RESPONSE,
139
- ATT_EXCHANGE_MTU_RESPONSE,
140
- ATT_FIND_INFORMATION_RESPONSE,
141
- ATT_FIND_BY_TYPE_VALUE_RESPONSE,
142
- ATT_READ_BY_TYPE_RESPONSE,
143
- ATT_READ_RESPONSE,
144
- ATT_READ_BLOB_RESPONSE,
145
- ATT_READ_MULTIPLE_RESPONSE,
146
- ATT_READ_BY_GROUP_TYPE_RESPONSE,
147
- ATT_WRITE_RESPONSE,
148
- ATT_PREPARE_WRITE_RESPONSE,
149
- ATT_EXECUTE_WRITE_RESPONSE
110
+ Opcode.ATT_ERROR_RESPONSE,
111
+ Opcode.ATT_EXCHANGE_MTU_RESPONSE,
112
+ Opcode.ATT_FIND_INFORMATION_RESPONSE,
113
+ Opcode.ATT_FIND_BY_TYPE_VALUE_RESPONSE,
114
+ Opcode.ATT_READ_BY_TYPE_RESPONSE,
115
+ Opcode.ATT_READ_RESPONSE,
116
+ Opcode.ATT_READ_BLOB_RESPONSE,
117
+ Opcode.ATT_READ_MULTIPLE_RESPONSE,
118
+ Opcode.ATT_READ_BY_GROUP_TYPE_RESPONSE,
119
+ Opcode.ATT_WRITE_RESPONSE,
120
+ Opcode.ATT_PREPARE_WRITE_RESPONSE,
121
+ Opcode.ATT_EXECUTE_WRITE_RESPONSE
150
122
  ]
151
123
 
152
- class ErrorCode(utils.OpenIntEnum):
124
+ class ErrorCode(hci.SpecableEnum):
153
125
  '''
154
126
  See
155
127
 
@@ -204,10 +176,6 @@ ATT_INSUFFICIENT_RESOURCES_ERROR = ErrorCode.INSUFFICIENT_RESOURCES
204
176
  ATT_DEFAULT_MTU = 23
205
177
 
206
178
  HANDLE_FIELD_SPEC = {'size': 2, 'mapper': lambda x: f'0x{x:04X}'}
207
- # pylint: disable-next=unnecessary-lambda-assignment,unnecessary-lambda
208
- UUID_2_16_FIELD_SPEC = lambda x, y: UUID.parse_uuid(x, y)
209
- # pylint: disable-next=unnecessary-lambda-assignment,unnecessary-lambda
210
- UUID_2_FIELD_SPEC = lambda x, y: UUID.parse_uuid_2(x, y) # noqa: E731
211
179
 
212
180
  # fmt: on
213
181
  # pylint: enable=line-too-long
@@ -227,7 +195,7 @@ class ATT_Error(ProtocolError):
227
195
  super().__init__(
228
196
  error_code,
229
197
  error_namespace='att',
230
- error_name=ATT_PDU.error_name(error_code),
198
+ error_name=ErrorCode(error_code).name,
231
199
  )
232
200
  self.att_handle = att_handle
233
201
  self.message = message
@@ -242,61 +210,45 @@ class ATT_Error(ProtocolError):
242
210
  # -----------------------------------------------------------------------------
243
211
  # Attribute Protocol
244
212
  # -----------------------------------------------------------------------------
213
+ @dataclasses.dataclass
245
214
  class ATT_PDU:
246
215
  '''
247
216
  See Bluetooth spec @ Vol 3, Part F - 3.3 ATTRIBUTE PDU
248
217
  '''
249
218
 
250
- pdu_classes: dict[int, type[ATT_PDU]] = {}
251
- op_code = 0
252
- name: str
219
+ pdu_classes: ClassVar[dict[int, type[ATT_PDU]]] = {}
220
+ fields: ClassVar[hci.Fields] = ()
221
+ op_code: int = dataclasses.field(init=False)
222
+ name: str = dataclasses.field(init=False)
223
+ _payload: Optional[bytes] = dataclasses.field(default=None, init=False)
253
224
 
254
- @staticmethod
255
- def from_bytes(pdu):
225
+ @classmethod
226
+ def from_bytes(cls, pdu: bytes) -> ATT_PDU:
256
227
  op_code = pdu[0]
257
228
 
258
- cls = ATT_PDU.pdu_classes.get(op_code)
259
- if cls is None:
260
- instance = ATT_PDU(pdu)
261
- instance.name = ATT_PDU.pdu_name(op_code)
229
+ subclass = ATT_PDU.pdu_classes.get(op_code)
230
+ if subclass is None:
231
+ instance = ATT_PDU()
262
232
  instance.op_code = op_code
233
+ instance.payload = pdu[1:]
234
+ instance.name = Opcode(op_code).name
263
235
  return instance
264
- self = cls.__new__(cls)
265
- ATT_PDU.__init__(self, pdu)
266
- if hasattr(self, 'fields'):
267
- self.init_from_bytes(pdu, 1)
268
- return self
236
+ instance = subclass(**HCI_Object.dict_from_bytes(pdu, 1, subclass.fields))
237
+ instance.payload = pdu[1:]
238
+ return instance
269
239
 
270
- @staticmethod
271
- def pdu_name(op_code):
272
- return name_or_number(ATT_PDU_NAMES, op_code, 2)
240
+ _PDU = TypeVar("_PDU", bound="ATT_PDU")
273
241
 
274
242
  @classmethod
275
- def error_name(cls, error_code: int) -> str:
276
- return ErrorCode(error_code).name
277
-
278
- @staticmethod
279
- def subclass(fields):
280
- def inner(cls):
281
- cls.name = cls.__name__.upper()
282
- cls.op_code = key_with_value(ATT_PDU_NAMES, cls.name)
283
- if cls.op_code is None:
284
- raise KeyError(f'PDU name {cls.name} not found in ATT_PDU_NAMES')
285
- cls.fields = fields
286
-
287
- # Register a factory for this class
288
- ATT_PDU.pdu_classes[cls.op_code] = cls
289
-
290
- return cls
243
+ def subclass(cls, subclass: type[_PDU]) -> type[_PDU]:
244
+ subclass.name = subclass.__name__.upper()
245
+ subclass.op_code = Opcode[subclass.name]
246
+ subclass.fields = HCI_Object.fields_from_dataclass(subclass)
291
247
 
292
- return inner
248
+ # Register a factory for this class
249
+ ATT_PDU.pdu_classes[subclass.op_code] = subclass
293
250
 
294
- def __init__(self, pdu=None, **kwargs):
295
- if hasattr(self, 'fields') and kwargs:
296
- HCI_Object.init_from_fields(self, self.fields, kwargs)
297
- if pdu is None:
298
- pdu = bytes([self.op_code]) + HCI_Object.dict_to_bytes(kwargs, self.fields)
299
- self.pdu = pdu
251
+ return subclass
300
252
 
301
253
  def init_from_bytes(self, pdu, offset):
302
254
  return HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
@@ -309,67 +261,91 @@ class ATT_PDU:
309
261
  def has_authentication_signature(self):
310
262
  return ((self.op_code >> 7) & 1) == 1
311
263
 
312
- def __bytes__(self):
313
- return self.pdu
264
+ @property
265
+ def payload(self) -> bytes:
266
+ if self._payload is None:
267
+ self._payload = HCI_Object.dict_to_bytes(self.__dict__, self.fields)
268
+ return self._payload
269
+
270
+ @payload.setter
271
+ def payload(self, value: bytes):
272
+ self._payload = value
273
+
274
+ def __bytes__(self) -> bytes:
275
+ return bytes([self.op_code]) + self.payload
314
276
 
315
277
  def __str__(self):
316
278
  result = color(self.name, 'yellow')
317
279
  if fields := getattr(self, 'fields', None):
318
280
  result += ':\n' + HCI_Object.format_fields(self.__dict__, fields, ' ')
319
281
  else:
320
- if len(self.pdu) > 1:
321
- result += f': {self.pdu.hex()}'
282
+ if self.payload:
283
+ result += f': {self.payload.hex()}'
322
284
  return result
323
285
 
324
286
 
325
287
  # -----------------------------------------------------------------------------
326
- @ATT_PDU.subclass(
327
- [
328
- ('request_opcode_in_error', {'size': 1, 'mapper': ATT_PDU.pdu_name}),
329
- ('attribute_handle_in_error', HANDLE_FIELD_SPEC),
330
- ('error_code', {'size': 1, 'mapper': ATT_PDU.error_name}),
331
- ]
332
- )
288
+ @ATT_PDU.subclass
289
+ @dataclasses.dataclass
333
290
  class ATT_Error_Response(ATT_PDU):
334
291
  '''
335
292
  See Bluetooth spec @ Vol 3, Part F - 3.4.1.1 Error Response
336
293
  '''
337
294
 
295
+ request_opcode_in_error: int = dataclasses.field(metadata=Opcode.type_metadata(1))
296
+ attribute_handle_in_error: int = dataclasses.field(
297
+ metadata=hci.metadata(HANDLE_FIELD_SPEC)
298
+ )
299
+ error_code: int = dataclasses.field(metadata=ErrorCode.type_metadata(1))
300
+
338
301
 
339
302
  # -----------------------------------------------------------------------------
340
- @ATT_PDU.subclass([('client_rx_mtu', 2)])
303
+ @ATT_PDU.subclass
304
+ @dataclasses.dataclass
341
305
  class ATT_Exchange_MTU_Request(ATT_PDU):
342
306
  '''
343
307
  See Bluetooth spec @ Vol 3, Part F - 3.4.2.1 Exchange MTU Request
344
308
  '''
345
309
 
310
+ client_rx_mtu: int = dataclasses.field(metadata=hci.metadata(2))
311
+
346
312
 
347
313
  # -----------------------------------------------------------------------------
348
- @ATT_PDU.subclass([('server_rx_mtu', 2)])
314
+ @ATT_PDU.subclass
315
+ @dataclasses.dataclass
349
316
  class ATT_Exchange_MTU_Response(ATT_PDU):
350
317
  '''
351
318
  See Bluetooth spec @ Vol 3, Part F - 3.4.2.2 Exchange MTU Response
352
319
  '''
353
320
 
321
+ server_rx_mtu: int = dataclasses.field(metadata=hci.metadata(2))
322
+
354
323
 
355
324
  # -----------------------------------------------------------------------------
356
- @ATT_PDU.subclass(
357
- [('starting_handle', HANDLE_FIELD_SPEC), ('ending_handle', HANDLE_FIELD_SPEC)]
358
- )
325
+ @ATT_PDU.subclass
326
+ @dataclasses.dataclass
359
327
  class ATT_Find_Information_Request(ATT_PDU):
360
328
  '''
361
329
  See Bluetooth spec @ Vol 3, Part F - 3.4.3.1 Find Information Request
362
330
  '''
363
331
 
332
+ starting_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
333
+ ending_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
334
+
364
335
 
365
336
  # -----------------------------------------------------------------------------
366
- @ATT_PDU.subclass([('format', 1), ('information_data', '*')])
337
+ @ATT_PDU.subclass
338
+ @dataclasses.dataclass
367
339
  class ATT_Find_Information_Response(ATT_PDU):
368
340
  '''
369
341
  See Bluetooth spec @ Vol 3, Part F - 3.4.3.2 Find Information Response
370
342
  '''
371
343
 
372
- def parse_information_data(self):
344
+ format: int = dataclasses.field(metadata=hci.metadata(1))
345
+ information_data: bytes = dataclasses.field(metadata=hci.metadata("*"))
346
+ information: list[tuple[int, bytes]] = dataclasses.field(init=False)
347
+
348
+ def __post_init__(self) -> None:
373
349
  self.information = []
374
350
  offset = 0
375
351
  uuid_size = 2 if self.format == 1 else 16
@@ -379,14 +355,6 @@ class ATT_Find_Information_Response(ATT_PDU):
379
355
  self.information.append((handle, uuid))
380
356
  offset += 2 + uuid_size
381
357
 
382
- def __init__(self, *args, **kwargs):
383
- super().__init__(*args, **kwargs)
384
- self.parse_information_data()
385
-
386
- def init_from_bytes(self, pdu, offset):
387
- super().init_from_bytes(pdu, offset)
388
- self.parse_information_data()
389
-
390
358
  def __str__(self):
391
359
  result = color(self.name, 'yellow')
392
360
  result += ':\n' + HCI_Object.format_fields(
@@ -408,28 +376,31 @@ class ATT_Find_Information_Response(ATT_PDU):
408
376
 
409
377
 
410
378
  # -----------------------------------------------------------------------------
411
- @ATT_PDU.subclass(
412
- [
413
- ('starting_handle', HANDLE_FIELD_SPEC),
414
- ('ending_handle', HANDLE_FIELD_SPEC),
415
- ('attribute_type', UUID_2_FIELD_SPEC),
416
- ('attribute_value', '*'),
417
- ]
418
- )
379
+ @ATT_PDU.subclass
380
+ @dataclasses.dataclass
419
381
  class ATT_Find_By_Type_Value_Request(ATT_PDU):
420
382
  '''
421
383
  See Bluetooth spec @ Vol 3, Part F - 3.4.3.3 Find By Type Value Request
422
384
  '''
423
385
 
386
+ starting_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
387
+ ending_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
388
+ attribute_type: UUID = dataclasses.field(metadata=hci.metadata(UUID.parse_uuid_2))
389
+ attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
390
+
424
391
 
425
392
  # -----------------------------------------------------------------------------
426
- @ATT_PDU.subclass([('handles_information_list', '*')])
393
+ @ATT_PDU.subclass
394
+ @dataclasses.dataclass
427
395
  class ATT_Find_By_Type_Value_Response(ATT_PDU):
428
396
  '''
429
397
  See Bluetooth spec @ Vol 3, Part F - 3.4.3.4 Find By Type Value Response
430
398
  '''
431
399
 
432
- def parse_handles_information_list(self):
400
+ handles_information_list: bytes = dataclasses.field(metadata=hci.metadata("*"))
401
+ handles_information: list[tuple[int, int]] = dataclasses.field(init=False)
402
+
403
+ def __post_init__(self) -> None:
433
404
  self.handles_information = []
434
405
  offset = 0
435
406
  while offset + 4 <= len(self.handles_information_list):
@@ -439,14 +410,6 @@ class ATT_Find_By_Type_Value_Response(ATT_PDU):
439
410
  self.handles_information.append((found_attribute_handle, group_end_handle))
440
411
  offset += 4
441
412
 
442
- def __init__(self, *args, **kwargs):
443
- super().__init__(*args, **kwargs)
444
- self.parse_handles_information_list()
445
-
446
- def init_from_bytes(self, pdu, offset):
447
- super().init_from_bytes(pdu, offset)
448
- self.parse_handles_information_list()
449
-
450
413
  def __str__(self):
451
414
  result = color(self.name, 'yellow')
452
415
  result += ':\n' + HCI_Object.format_fields(
@@ -470,27 +433,31 @@ class ATT_Find_By_Type_Value_Response(ATT_PDU):
470
433
 
471
434
 
472
435
  # -----------------------------------------------------------------------------
473
- @ATT_PDU.subclass(
474
- [
475
- ('starting_handle', HANDLE_FIELD_SPEC),
476
- ('ending_handle', HANDLE_FIELD_SPEC),
477
- ('attribute_type', UUID_2_16_FIELD_SPEC),
478
- ]
479
- )
436
+ @ATT_PDU.subclass
437
+ @dataclasses.dataclass
480
438
  class ATT_Read_By_Type_Request(ATT_PDU):
481
439
  '''
482
440
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.1 Read By Type Request
483
441
  '''
484
442
 
443
+ starting_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
444
+ ending_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
445
+ attribute_type: UUID = dataclasses.field(metadata=hci.metadata(UUID.parse_uuid))
446
+
485
447
 
486
448
  # -----------------------------------------------------------------------------
487
- @ATT_PDU.subclass([('length', 1), ('attribute_data_list', '*')])
449
+ @ATT_PDU.subclass
450
+ @dataclasses.dataclass
488
451
  class ATT_Read_By_Type_Response(ATT_PDU):
489
452
  '''
490
453
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.2 Read By Type Response
491
454
  '''
492
455
 
493
- def parse_attribute_data_list(self):
456
+ length: int = dataclasses.field(metadata=hci.metadata(1))
457
+ attribute_data_list: bytes = dataclasses.field(metadata=hci.metadata("*"))
458
+ attributes: list[tuple[int, bytes]] = dataclasses.field(init=False)
459
+
460
+ def __post_init__(self) -> None:
494
461
  self.attributes = []
495
462
  offset = 0
496
463
  while self.length != 0 and offset + self.length <= len(
@@ -505,14 +472,6 @@ class ATT_Read_By_Type_Response(ATT_PDU):
505
472
  self.attributes.append((attribute_handle, attribute_value))
506
473
  offset += self.length
507
474
 
508
- def __init__(self, *args, **kwargs):
509
- super().__init__(*args, **kwargs)
510
- self.parse_attribute_data_list()
511
-
512
- def init_from_bytes(self, pdu, offset):
513
- super().init_from_bytes(pdu, offset)
514
- self.parse_attribute_data_list()
515
-
516
475
  def __str__(self):
517
476
  result = color(self.name, 'yellow')
518
477
  result += ':\n' + HCI_Object.format_fields(
@@ -534,75 +493,100 @@ class ATT_Read_By_Type_Response(ATT_PDU):
534
493
 
535
494
 
536
495
  # -----------------------------------------------------------------------------
537
- @ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC)])
496
+ @ATT_PDU.subclass
497
+ @dataclasses.dataclass
538
498
  class ATT_Read_Request(ATT_PDU):
539
499
  '''
540
500
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.3 Read Request
541
501
  '''
542
502
 
503
+ attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
504
+
543
505
 
544
506
  # -----------------------------------------------------------------------------
545
- @ATT_PDU.subclass([('attribute_value', '*')])
507
+ @ATT_PDU.subclass
508
+ @dataclasses.dataclass
546
509
  class ATT_Read_Response(ATT_PDU):
547
510
  '''
548
511
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.4 Read Response
549
512
  '''
550
513
 
514
+ attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
515
+
551
516
 
552
517
  # -----------------------------------------------------------------------------
553
- @ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('value_offset', 2)])
518
+ @ATT_PDU.subclass
519
+ @dataclasses.dataclass
554
520
  class ATT_Read_Blob_Request(ATT_PDU):
555
521
  '''
556
522
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.5 Read Blob Request
557
523
  '''
558
524
 
525
+ attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
526
+ value_offset: int = dataclasses.field(metadata=hci.metadata(2))
527
+
559
528
 
560
529
  # -----------------------------------------------------------------------------
561
- @ATT_PDU.subclass([('part_attribute_value', '*')])
530
+ @ATT_PDU.subclass
531
+ @dataclasses.dataclass
562
532
  class ATT_Read_Blob_Response(ATT_PDU):
563
533
  '''
564
534
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.6 Read Blob Response
565
535
  '''
566
536
 
537
+ part_attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
538
+
567
539
 
568
540
  # -----------------------------------------------------------------------------
569
- @ATT_PDU.subclass([('set_of_handles', '*')])
541
+ @ATT_PDU.subclass
542
+ @dataclasses.dataclass
570
543
  class ATT_Read_Multiple_Request(ATT_PDU):
571
544
  '''
572
545
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.7 Read Multiple Request
573
546
  '''
574
547
 
548
+ set_of_handles: bytes = dataclasses.field(metadata=hci.metadata("*"))
549
+
575
550
 
576
551
  # -----------------------------------------------------------------------------
577
- @ATT_PDU.subclass([('set_of_values', '*')])
552
+ @ATT_PDU.subclass
553
+ @dataclasses.dataclass
578
554
  class ATT_Read_Multiple_Response(ATT_PDU):
579
555
  '''
580
556
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.8 Read Multiple Response
581
557
  '''
582
558
 
559
+ set_of_values: bytes = dataclasses.field(metadata=hci.metadata("*"))
560
+
583
561
 
584
562
  # -----------------------------------------------------------------------------
585
- @ATT_PDU.subclass(
586
- [
587
- ('starting_handle', HANDLE_FIELD_SPEC),
588
- ('ending_handle', HANDLE_FIELD_SPEC),
589
- ('attribute_group_type', UUID_2_16_FIELD_SPEC),
590
- ]
591
- )
563
+ @ATT_PDU.subclass
564
+ @dataclasses.dataclass
592
565
  class ATT_Read_By_Group_Type_Request(ATT_PDU):
593
566
  '''
594
567
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.9 Read by Group Type Request
595
568
  '''
596
569
 
570
+ starting_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
571
+ ending_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
572
+ attribute_group_type: UUID = dataclasses.field(
573
+ metadata=hci.metadata(UUID.parse_uuid)
574
+ )
575
+
597
576
 
598
577
  # -----------------------------------------------------------------------------
599
- @ATT_PDU.subclass([('length', 1), ('attribute_data_list', '*')])
578
+ @ATT_PDU.subclass
579
+ @dataclasses.dataclass
600
580
  class ATT_Read_By_Group_Type_Response(ATT_PDU):
601
581
  '''
602
582
  See Bluetooth spec @ Vol 3, Part F - 3.4.4.10 Read by Group Type Response
603
583
  '''
604
584
 
605
- def parse_attribute_data_list(self):
585
+ length: int = dataclasses.field(metadata=hci.metadata(1))
586
+ attribute_data_list: bytes = dataclasses.field(metadata=hci.metadata("*"))
587
+ attributes: list[tuple[int, int, bytes]] = dataclasses.field(init=False)
588
+
589
+ def __post_init__(self) -> None:
606
590
  self.attributes = []
607
591
  offset = 0
608
592
  while self.length != 0 and offset + self.length <= len(
@@ -619,14 +603,6 @@ class ATT_Read_By_Group_Type_Response(ATT_PDU):
619
603
  )
620
604
  offset += self.length
621
605
 
622
- def __init__(self, *args, **kwargs):
623
- super().__init__(*args, **kwargs)
624
- self.parse_attribute_data_list()
625
-
626
- def init_from_bytes(self, pdu, offset):
627
- super().init_from_bytes(pdu, offset)
628
- self.parse_attribute_data_list()
629
-
630
606
  def __str__(self):
631
607
  result = color(self.name, 'yellow')
632
608
  result += ':\n' + HCI_Object.format_fields(
@@ -651,15 +627,20 @@ class ATT_Read_By_Group_Type_Response(ATT_PDU):
651
627
 
652
628
 
653
629
  # -----------------------------------------------------------------------------
654
- @ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('attribute_value', '*')])
630
+ @ATT_PDU.subclass
631
+ @dataclasses.dataclass
655
632
  class ATT_Write_Request(ATT_PDU):
656
633
  '''
657
634
  See Bluetooth spec @ Vol 3, Part F - 3.4.5.1 Write Request
658
635
  '''
659
636
 
637
+ attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
638
+ attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
639
+
660
640
 
661
641
  # -----------------------------------------------------------------------------
662
- @ATT_PDU.subclass([])
642
+ @ATT_PDU.subclass
643
+ @dataclasses.dataclass
663
644
  class ATT_Write_Response(ATT_PDU):
664
645
  '''
665
646
  See Bluetooth spec @ Vol 3, Part F - 3.4.5.2 Write Response
@@ -667,65 +648,70 @@ class ATT_Write_Response(ATT_PDU):
667
648
 
668
649
 
669
650
  # -----------------------------------------------------------------------------
670
- @ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('attribute_value', '*')])
651
+ @ATT_PDU.subclass
652
+ @dataclasses.dataclass
671
653
  class ATT_Write_Command(ATT_PDU):
672
654
  '''
673
655
  See Bluetooth spec @ Vol 3, Part F - 3.4.5.3 Write Command
674
656
  '''
675
657
 
658
+ attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
659
+ attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
660
+
676
661
 
677
662
  # -----------------------------------------------------------------------------
678
- @ATT_PDU.subclass(
679
- [
680
- ('attribute_handle', HANDLE_FIELD_SPEC),
681
- ('attribute_value', '*'),
682
- # ('authentication_signature', 'TODO')
683
- ]
684
- )
663
+ @ATT_PDU.subclass
664
+ @dataclasses.dataclass
685
665
  class ATT_Signed_Write_Command(ATT_PDU):
686
666
  '''
687
667
  See Bluetooth spec @ Vol 3, Part F - 3.4.5.4 Signed Write Command
688
668
  '''
689
669
 
670
+ attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
671
+ attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
672
+ # TODO: authentication_signature
673
+
690
674
 
691
675
  # -----------------------------------------------------------------------------
692
- @ATT_PDU.subclass(
693
- [
694
- ('attribute_handle', HANDLE_FIELD_SPEC),
695
- ('value_offset', 2),
696
- ('part_attribute_value', '*'),
697
- ]
698
- )
676
+ @ATT_PDU.subclass
677
+ @dataclasses.dataclass
699
678
  class ATT_Prepare_Write_Request(ATT_PDU):
700
679
  '''
701
680
  See Bluetooth spec @ Vol 3, Part F - 3.4.6.1 Prepare Write Request
702
681
  '''
703
682
 
683
+ attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
684
+ value_offset: int = dataclasses.field(metadata=hci.metadata(2))
685
+ part_attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
686
+
704
687
 
705
688
  # -----------------------------------------------------------------------------
706
- @ATT_PDU.subclass(
707
- [
708
- ('attribute_handle', HANDLE_FIELD_SPEC),
709
- ('value_offset', 2),
710
- ('part_attribute_value', '*'),
711
- ]
712
- )
689
+ @ATT_PDU.subclass
690
+ @dataclasses.dataclass
713
691
  class ATT_Prepare_Write_Response(ATT_PDU):
714
692
  '''
715
693
  See Bluetooth spec @ Vol 3, Part F - 3.4.6.2 Prepare Write Response
716
694
  '''
717
695
 
696
+ attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
697
+ value_offset: int = dataclasses.field(metadata=hci.metadata(2))
698
+ part_attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
699
+
718
700
 
719
701
  # -----------------------------------------------------------------------------
720
- @ATT_PDU.subclass([("flags", 1)])
702
+ @ATT_PDU.subclass
703
+ @dataclasses.dataclass
721
704
  class ATT_Execute_Write_Request(ATT_PDU):
722
705
  '''
723
706
  See Bluetooth spec @ Vol 3, Part F - 3.4.6.3 Execute Write Request
724
707
  '''
725
708
 
709
+ flags: int = dataclasses.field(metadata=hci.metadata(1))
710
+
726
711
 
727
712
  # -----------------------------------------------------------------------------
728
- @ATT_PDU.subclass([])
713
+ @ATT_PDU.subclass
714
+ @dataclasses.dataclass
729
715
  class ATT_Execute_Write_Response(ATT_PDU):
730
716
  '''
731
717
  See Bluetooth spec @ Vol 3, Part F - 3.4.6.4 Execute Write Response
@@ -733,23 +719,32 @@ class ATT_Execute_Write_Response(ATT_PDU):
733
719
 
734
720
 
735
721
  # -----------------------------------------------------------------------------
736
- @ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('attribute_value', '*')])
722
+ @ATT_PDU.subclass
723
+ @dataclasses.dataclass
737
724
  class ATT_Handle_Value_Notification(ATT_PDU):
738
725
  '''
739
726
  See Bluetooth spec @ Vol 3, Part F - 3.4.7.1 Handle Value Notification
740
727
  '''
741
728
 
729
+ attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
730
+ attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
731
+
742
732
 
743
733
  # -----------------------------------------------------------------------------
744
- @ATT_PDU.subclass([('attribute_handle', HANDLE_FIELD_SPEC), ('attribute_value', '*')])
734
+ @ATT_PDU.subclass
735
+ @dataclasses.dataclass
745
736
  class ATT_Handle_Value_Indication(ATT_PDU):
746
737
  '''
747
738
  See Bluetooth spec @ Vol 3, Part F - 3.4.7.2 Handle Value Indication
748
739
  '''
749
740
 
741
+ attribute_handle: int = dataclasses.field(metadata=hci.metadata(HANDLE_FIELD_SPEC))
742
+ attribute_value: bytes = dataclasses.field(metadata=hci.metadata("*"))
743
+
750
744
 
751
745
  # -----------------------------------------------------------------------------
752
- @ATT_PDU.subclass([])
746
+ @ATT_PDU.subclass
747
+ @dataclasses.dataclass
753
748
  class ATT_Handle_Value_Confirmation(ATT_PDU):
754
749
  '''
755
750
  See Bluetooth spec @ Vol 3, Part F - 3.4.7.3 Handle Value Confirmation