bumble 0.0.210__py3-none-any.whl → 0.0.212__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 (44) hide show
  1. bumble/_version.py +2 -2
  2. bumble/apps/bench.py +8 -4
  3. bumble/apps/console.py +2 -2
  4. bumble/apps/pair.py +185 -32
  5. bumble/att.py +13 -12
  6. bumble/avctp.py +2 -2
  7. bumble/avdtp.py +122 -68
  8. bumble/avrcp.py +11 -5
  9. bumble/core.py +13 -7
  10. bumble/{crypto.py → crypto/__init__.py} +11 -95
  11. bumble/crypto/builtin.py +652 -0
  12. bumble/crypto/cryptography.py +84 -0
  13. bumble/device.py +365 -185
  14. bumble/drivers/intel.py +3 -0
  15. bumble/gatt.py +3 -5
  16. bumble/gatt_client.py +5 -3
  17. bumble/gatt_server.py +8 -6
  18. bumble/hci.py +81 -4
  19. bumble/hfp.py +44 -20
  20. bumble/hid.py +24 -12
  21. bumble/host.py +24 -0
  22. bumble/keys.py +64 -48
  23. bumble/l2cap.py +19 -9
  24. bumble/pandora/host.py +11 -11
  25. bumble/pandora/l2cap.py +2 -2
  26. bumble/pandora/security.py +72 -56
  27. bumble/profiles/aics.py +3 -5
  28. bumble/profiles/ancs.py +3 -1
  29. bumble/profiles/ascs.py +11 -5
  30. bumble/profiles/asha.py +11 -6
  31. bumble/profiles/csip.py +1 -3
  32. bumble/profiles/gatt_service.py +1 -3
  33. bumble/profiles/hap.py +16 -33
  34. bumble/profiles/mcp.py +12 -9
  35. bumble/profiles/vcs.py +5 -5
  36. bumble/profiles/vocs.py +6 -9
  37. bumble/rfcomm.py +17 -8
  38. bumble/smp.py +14 -8
  39. {bumble-0.0.210.dist-info → bumble-0.0.212.dist-info}/METADATA +4 -4
  40. {bumble-0.0.210.dist-info → bumble-0.0.212.dist-info}/RECORD +44 -42
  41. {bumble-0.0.210.dist-info → bumble-0.0.212.dist-info}/WHEEL +1 -1
  42. {bumble-0.0.210.dist-info → bumble-0.0.212.dist-info}/entry_points.txt +0 -0
  43. {bumble-0.0.210.dist-info → bumble-0.0.212.dist-info}/licenses/LICENSE +0 -0
  44. {bumble-0.0.210.dist-info → bumble-0.0.212.dist-info}/top_level.txt +0 -0
bumble/profiles/ancs.py CHANGED
@@ -250,6 +250,8 @@ class AncsClient(utils.EventEmitter):
250
250
  _expected_response_tuples: int
251
251
  _response_accumulator: bytes
252
252
 
253
+ EVENT_NOTIFICATION = "notification"
254
+
253
255
  def __init__(self, ancs_proxy: AncsProxy) -> None:
254
256
  super().__init__()
255
257
  self._ancs_proxy = ancs_proxy
@@ -284,7 +286,7 @@ class AncsClient(utils.EventEmitter):
284
286
 
285
287
  def _on_notification(self, notification: Notification) -> None:
286
288
  logger.debug(f"ANCS NOTIFICATION: {notification}")
287
- self.emit("notification", notification)
289
+ self.emit(self.EVENT_NOTIFICATION, notification)
288
290
 
289
291
  def _on_data(self, data: bytes) -> None:
290
292
  logger.debug(f"ANCS DATA: {data.hex()}")
bumble/profiles/ascs.py CHANGED
@@ -276,6 +276,8 @@ class AseStateMachine(gatt.Characteristic):
276
276
  DISABLING = 0x05
277
277
  RELEASING = 0x06
278
278
 
279
+ EVENT_STATE_CHANGE = "state_change"
280
+
279
281
  cis_link: Optional[device.CisLink] = None
280
282
 
281
283
  # Additional parameters in CODEC_CONFIGURED State
@@ -329,8 +331,12 @@ class AseStateMachine(gatt.Characteristic):
329
331
  value=gatt.CharacteristicValue(read=self.on_read),
330
332
  )
331
333
 
332
- self.service.device.on('cis_request', self.on_cis_request)
333
- self.service.device.on('cis_establishment', self.on_cis_establishment)
334
+ self.service.device.on(
335
+ self.service.device.EVENT_CIS_REQUEST, self.on_cis_request
336
+ )
337
+ self.service.device.on(
338
+ self.service.device.EVENT_CIS_ESTABLISHMENT, self.on_cis_establishment
339
+ )
334
340
 
335
341
  def on_cis_request(
336
342
  self,
@@ -356,7 +362,7 @@ class AseStateMachine(gatt.Characteristic):
356
362
  and cis_link.cis_id == self.cis_id
357
363
  and self.state == self.State.ENABLING
358
364
  ):
359
- cis_link.on('disconnection', self.on_cis_disconnection)
365
+ cis_link.on(cis_link.EVENT_DISCONNECTION, self.on_cis_disconnection)
360
366
 
361
367
  async def post_cis_established():
362
368
  await cis_link.setup_data_path(direction=self.role)
@@ -525,7 +531,7 @@ class AseStateMachine(gatt.Characteristic):
525
531
  def state(self, new_state: State) -> None:
526
532
  logger.debug(f'{self} state change -> {colors.color(new_state.name, "cyan")}')
527
533
  self._state = new_state
528
- self.emit('state_change')
534
+ self.emit(self.EVENT_STATE_CHANGE)
529
535
 
530
536
  @property
531
537
  def value(self):
@@ -584,7 +590,7 @@ class AseStateMachine(gatt.Characteristic):
584
590
  # Readonly. Do nothing in the setter.
585
591
  pass
586
592
 
587
- def on_read(self, _: Optional[device.Connection]) -> bytes:
593
+ def on_read(self, _: device.Connection) -> bytes:
588
594
  return self.value
589
595
 
590
596
  def __str__(self) -> str:
bumble/profiles/asha.py CHANGED
@@ -88,6 +88,11 @@ class AudioStatus(utils.OpenIntEnum):
88
88
  class AshaService(gatt.TemplateService):
89
89
  UUID = gatt.GATT_ASHA_SERVICE
90
90
 
91
+ EVENT_STARTED = "started"
92
+ EVENT_STOPPED = "stopped"
93
+ EVENT_DISCONNECTED = "disconnected"
94
+ EVENT_VOLUME_CHANGED = "volume_changed"
95
+
91
96
  audio_sink: Optional[Callable[[bytes], Any]]
92
97
  active_codec: Optional[Codec] = None
93
98
  audio_type: Optional[AudioType] = None
@@ -195,7 +200,7 @@ class AshaService(gatt.TemplateService):
195
200
 
196
201
  # Handler for audio control commands
197
202
  async def _on_audio_control_point_write(
198
- self, connection: Optional[Connection], value: bytes
203
+ self, connection: Connection, value: bytes
199
204
  ) -> None:
200
205
  _logger.debug(f'--- AUDIO CONTROL POINT Write:{value.hex()}')
201
206
  opcode = value[0]
@@ -211,14 +216,14 @@ class AshaService(gatt.TemplateService):
211
216
  f'volume={self.volume}, '
212
217
  f'other_state={self.other_state}'
213
218
  )
214
- self.emit('started')
219
+ self.emit(self.EVENT_STARTED)
215
220
  elif opcode == OpCode.STOP:
216
221
  _logger.debug('### STOP')
217
222
  self.active_codec = None
218
223
  self.audio_type = None
219
224
  self.volume = None
220
225
  self.other_state = None
221
- self.emit('stopped')
226
+ self.emit(self.EVENT_STOPPED)
222
227
  elif opcode == OpCode.STATUS:
223
228
  _logger.debug('### STATUS: %s', PeripheralStatus(value[1]).name)
224
229
 
@@ -231,7 +236,7 @@ class AshaService(gatt.TemplateService):
231
236
  self.audio_type = None
232
237
  self.volume = None
233
238
  self.other_state = None
234
- self.emit('disconnected')
239
+ self.emit(self.EVENT_DISCONNECTED)
235
240
 
236
241
  connection.once('disconnection', on_disconnection)
237
242
 
@@ -242,10 +247,10 @@ class AshaService(gatt.TemplateService):
242
247
  )
243
248
 
244
249
  # Handler for volume control
245
- def _on_volume_write(self, connection: Optional[Connection], value: bytes) -> None:
250
+ def _on_volume_write(self, connection: Connection, value: bytes) -> None:
246
251
  _logger.debug(f'--- VOLUME Write:{value[0]}')
247
252
  self.volume = value[0]
248
- self.emit('volume_changed')
253
+ self.emit(self.EVENT_VOLUME_CHANGED)
249
254
 
250
255
  # Register an L2CAP CoC server
251
256
  def _on_connection(self, channel: l2cap.LeCreditBasedChannel) -> None:
bumble/profiles/csip.py CHANGED
@@ -164,12 +164,10 @@ class CoordinatedSetIdentificationService(gatt.TemplateService):
164
164
 
165
165
  super().__init__(characteristics)
166
166
 
167
- async def on_sirk_read(self, connection: Optional[device.Connection]) -> bytes:
167
+ async def on_sirk_read(self, connection: device.Connection) -> bytes:
168
168
  if self.set_identity_resolving_key_type == SirkType.PLAINTEXT:
169
169
  sirk_bytes = self.set_identity_resolving_key
170
170
  else:
171
- assert connection
172
-
173
171
  if connection.transport == core.PhysicalTransport.LE:
174
172
  key = await connection.device.get_long_term_key(
175
173
  connection_handle=connection.handle, rand=b'', ediv=0
@@ -127,9 +127,7 @@ class GenericAttributeProfileService(gatt.TemplateService):
127
127
 
128
128
  return b''
129
129
 
130
- def get_database_hash(self, connection: device.Connection | None) -> bytes:
131
- assert connection
132
-
130
+ def get_database_hash(self, connection: device.Connection) -> bytes:
133
131
  m = b''.join(
134
132
  [
135
133
  self.get_attribute_data(attribute)
bumble/profiles/hap.py CHANGED
@@ -266,13 +266,13 @@ class HearingAccessService(gatt.TemplateService):
266
266
  # associate the lowest index as the current active preset at startup
267
267
  self.active_preset_index = sorted(self.preset_records.keys())[0]
268
268
 
269
- @device.on('connection') # type: ignore
269
+ @device.on(device.EVENT_CONNECTION)
270
270
  def on_connection(connection: Connection) -> None:
271
- @connection.on('disconnection') # type: ignore
271
+ @connection.on(connection.EVENT_DISCONNECTION)
272
272
  def on_disconnection(_reason) -> None:
273
273
  self.currently_connected_clients.remove(connection)
274
274
 
275
- @connection.on('pairing') # type: ignore
275
+ @connection.on(connection.EVENT_PAIRING)
276
276
  def on_pairing(*_: Any) -> None:
277
277
  self.on_incoming_paired_connection(connection)
278
278
 
@@ -335,9 +335,8 @@ class HearingAccessService(gatt.TemplateService):
335
335
 
336
336
  utils.cancel_on_event(connection, 'disconnection', on_connection_async())
337
337
 
338
- def _on_read_active_preset_index(
339
- self, __connection__: Optional[Connection]
340
- ) -> bytes:
338
+ def _on_read_active_preset_index(self, connection: Connection) -> bytes:
339
+ del connection # Unused
341
340
  return bytes([self.active_preset_index])
342
341
 
343
342
  # TODO this need to be triggered when device is unbonded
@@ -345,18 +344,13 @@ class HearingAccessService(gatt.TemplateService):
345
344
  self.preset_changed_operations_history_per_device.pop(addr)
346
345
 
347
346
  async def _on_write_hearing_aid_preset_control_point(
348
- self, connection: Optional[Connection], value: bytes
347
+ self, connection: Connection, value: bytes
349
348
  ):
350
- assert connection
351
-
352
349
  opcode = HearingAidPresetControlPointOpcode(value[0])
353
350
  handler = getattr(self, '_on_' + opcode.name.lower())
354
351
  await handler(connection, value)
355
352
 
356
- async def _on_read_presets_request(
357
- self, connection: Optional[Connection], value: bytes
358
- ):
359
- assert connection
353
+ async def _on_read_presets_request(self, connection: Connection, value: bytes):
360
354
  if connection.att_mtu < 49: # 2.5. GATT sub-procedure requirements
361
355
  logging.warning(f'HAS require MTU >= 49: {connection}')
362
356
 
@@ -471,10 +465,7 @@ class HearingAccessService(gatt.TemplateService):
471
465
  for connection in self.currently_connected_clients:
472
466
  await self._preset_changed_operation(connection)
473
467
 
474
- async def _on_write_preset_name(
475
- self, connection: Optional[Connection], value: bytes
476
- ):
477
- assert connection
468
+ async def _on_write_preset_name(self, connection: Connection, value: bytes):
478
469
 
479
470
  if self.read_presets_request_in_progress:
480
471
  raise att.ATT_Error(att.ErrorCode.PROCEDURE_ALREADY_IN_PROGRESS)
@@ -522,10 +513,7 @@ class HearingAccessService(gatt.TemplateService):
522
513
  for connection in self.currently_connected_clients:
523
514
  await self.notify_active_preset_for_connection(connection)
524
515
 
525
- async def set_active_preset(
526
- self, connection: Optional[Connection], value: bytes
527
- ) -> None:
528
- assert connection
516
+ async def set_active_preset(self, connection: Connection, value: bytes) -> None:
529
517
  index = value[1]
530
518
  preset = self.preset_records.get(index, None)
531
519
  if (
@@ -542,16 +530,11 @@ class HearingAccessService(gatt.TemplateService):
542
530
  self.active_preset_index = index
543
531
  await self.notify_active_preset()
544
532
 
545
- async def _on_set_active_preset(
546
- self, connection: Optional[Connection], value: bytes
547
- ):
533
+ async def _on_set_active_preset(self, connection: Connection, value: bytes):
548
534
  await self.set_active_preset(connection, value)
549
535
 
550
- async def set_next_or_previous_preset(
551
- self, connection: Optional[Connection], is_previous
552
- ):
536
+ async def set_next_or_previous_preset(self, connection: Connection, is_previous):
553
537
  '''Set the next or the previous preset as active'''
554
- assert connection
555
538
 
556
539
  if self.active_preset_index == 0x00:
557
540
  raise att.ATT_Error(ErrorCode.PRESET_OPERATION_NOT_POSSIBLE)
@@ -581,17 +564,17 @@ class HearingAccessService(gatt.TemplateService):
581
564
  await self.notify_active_preset()
582
565
 
583
566
  async def _on_set_next_preset(
584
- self, connection: Optional[Connection], __value__: bytes
567
+ self, connection: Connection, __value__: bytes
585
568
  ) -> None:
586
569
  await self.set_next_or_previous_preset(connection, False)
587
570
 
588
571
  async def _on_set_previous_preset(
589
- self, connection: Optional[Connection], __value__: bytes
572
+ self, connection: Connection, __value__: bytes
590
573
  ) -> None:
591
574
  await self.set_next_or_previous_preset(connection, True)
592
575
 
593
576
  async def _on_set_active_preset_synchronized_locally(
594
- self, connection: Optional[Connection], value: bytes
577
+ self, connection: Connection, value: bytes
595
578
  ):
596
579
  if (
597
580
  self.server_features.preset_synchronization_support
@@ -602,7 +585,7 @@ class HearingAccessService(gatt.TemplateService):
602
585
  # TODO (low priority) inform other server of the change
603
586
 
604
587
  async def _on_set_next_preset_synchronized_locally(
605
- self, connection: Optional[Connection], __value__: bytes
588
+ self, connection: Connection, __value__: bytes
606
589
  ):
607
590
  if (
608
591
  self.server_features.preset_synchronization_support
@@ -613,7 +596,7 @@ class HearingAccessService(gatt.TemplateService):
613
596
  # TODO (low priority) inform other server of the change
614
597
 
615
598
  async def _on_set_previous_preset_synchronized_locally(
616
- self, connection: Optional[Connection], __value__: bytes
599
+ self, connection: Connection, __value__: bytes
617
600
  ):
618
601
  if (
619
602
  self.server_features.preset_synchronization_support
bumble/profiles/mcp.py CHANGED
@@ -287,11 +287,8 @@ class MediaControlService(gatt.TemplateService):
287
287
  )
288
288
 
289
289
  async def on_media_control_point(
290
- self, connection: Optional[device.Connection], data: bytes
290
+ self, connection: device.Connection, data: bytes
291
291
  ) -> None:
292
- if not connection:
293
- raise core.InvalidStateError()
294
-
295
292
  opcode = MediaControlPointOpcode(data[0])
296
293
 
297
294
  await connection.device.notify_subscriber(
@@ -338,6 +335,12 @@ class MediaControlServiceProxy(
338
335
  'content_control_id': gatt.GATT_CONTENT_CONTROL_ID_CHARACTERISTIC,
339
336
  }
340
337
 
338
+ EVENT_MEDIA_STATE = "media_state"
339
+ EVENT_TRACK_CHANGED = "track_changed"
340
+ EVENT_TRACK_TITLE = "track_title"
341
+ EVENT_TRACK_DURATION = "track_duration"
342
+ EVENT_TRACK_POSITION = "track_position"
343
+
341
344
  media_player_name: Optional[gatt_client.CharacteristicProxy[bytes]] = None
342
345
  media_player_icon_object_id: Optional[gatt_client.CharacteristicProxy[bytes]] = None
343
346
  media_player_icon_url: Optional[gatt_client.CharacteristicProxy[bytes]] = None
@@ -432,20 +435,20 @@ class MediaControlServiceProxy(
432
435
  self.media_control_point_notifications.put_nowait(data)
433
436
 
434
437
  def _on_media_state(self, data: bytes) -> None:
435
- self.emit('media_state', MediaState(data[0]))
438
+ self.emit(self.EVENT_MEDIA_STATE, MediaState(data[0]))
436
439
 
437
440
  def _on_track_changed(self, data: bytes) -> None:
438
441
  del data
439
- self.emit('track_changed')
442
+ self.emit(self.EVENT_TRACK_CHANGED)
440
443
 
441
444
  def _on_track_title(self, data: bytes) -> None:
442
- self.emit('track_title', data.decode("utf-8"))
445
+ self.emit(self.EVENT_TRACK_TITLE, data.decode("utf-8"))
443
446
 
444
447
  def _on_track_duration(self, data: bytes) -> None:
445
- self.emit('track_duration', struct.unpack_from('<i', data)[0])
448
+ self.emit(self.EVENT_TRACK_DURATION, struct.unpack_from('<i', data)[0])
446
449
 
447
450
  def _on_track_position(self, data: bytes) -> None:
448
- self.emit('track_position', struct.unpack_from('<i', data)[0])
451
+ self.emit(self.EVENT_TRACK_POSITION, struct.unpack_from('<i', data)[0])
449
452
 
450
453
 
451
454
  class GenericMediaControlServiceProxy(MediaControlServiceProxy):
bumble/profiles/vcs.py CHANGED
@@ -91,6 +91,8 @@ class VolumeState:
91
91
  class VolumeControlService(gatt.TemplateService):
92
92
  UUID = gatt.GATT_VOLUME_CONTROL_SERVICE
93
93
 
94
+ EVENT_VOLUME_STATE_CHANGE = "volume_state_change"
95
+
94
96
  volume_state: gatt.Characteristic[bytes]
95
97
  volume_control_point: gatt.Characteristic[bytes]
96
98
  volume_flags: gatt.Characteristic[bytes]
@@ -144,14 +146,12 @@ class VolumeControlService(gatt.TemplateService):
144
146
  included_services=list(included_services),
145
147
  )
146
148
 
147
- def _on_read_volume_state(self, _connection: Optional[device.Connection]) -> bytes:
149
+ def _on_read_volume_state(self, _connection: device.Connection) -> bytes:
148
150
  return bytes(VolumeState(self.volume_setting, self.muted, self.change_counter))
149
151
 
150
152
  def _on_write_volume_control_point(
151
- self, connection: Optional[device.Connection], value: bytes
153
+ self, connection: device.Connection, value: bytes
152
154
  ) -> None:
153
- assert connection
154
-
155
155
  opcode = VolumeControlPointOpcode(value[0])
156
156
  change_counter = value[1]
157
157
 
@@ -166,7 +166,7 @@ class VolumeControlService(gatt.TemplateService):
166
166
  'disconnection',
167
167
  connection.device.notify_subscribers(attribute=self.volume_state),
168
168
  )
169
- self.emit('volume_state_change')
169
+ self.emit(self.EVENT_VOLUME_STATE_CHANGE)
170
170
 
171
171
  def _on_relative_volume_down(self) -> bool:
172
172
  old_volume = self.volume_setting
bumble/profiles/vocs.py CHANGED
@@ -86,7 +86,7 @@ class VolumeOffsetState:
86
86
  assert self.attribute is not None
87
87
  await connection.device.notify_subscribers(attribute=self.attribute)
88
88
 
89
- def on_read(self, _connection: Optional[Connection]) -> bytes:
89
+ def on_read(self, _connection: Connection) -> bytes:
90
90
  return bytes(self)
91
91
 
92
92
 
@@ -103,11 +103,10 @@ class VocsAudioLocation:
103
103
  audio_location = AudioLocation(struct.unpack('<I', data)[0])
104
104
  return cls(audio_location)
105
105
 
106
- def on_read(self, _connection: Optional[Connection]) -> bytes:
106
+ def on_read(self, _connection: Connection) -> bytes:
107
107
  return bytes(self)
108
108
 
109
- async def on_write(self, connection: Optional[Connection], value: bytes) -> None:
110
- assert connection
109
+ async def on_write(self, connection: Connection, value: bytes) -> None:
111
110
  assert self.attribute
112
111
 
113
112
  self.audio_location = AudioLocation(int.from_bytes(value, 'little'))
@@ -118,8 +117,7 @@ class VocsAudioLocation:
118
117
  class VolumeOffsetControlPoint:
119
118
  volume_offset_state: VolumeOffsetState
120
119
 
121
- async def on_write(self, connection: Optional[Connection], value: bytes) -> None:
122
- assert connection
120
+ async def on_write(self, connection: Connection, value: bytes) -> None:
123
121
 
124
122
  opcode = value[0]
125
123
  if opcode != SetVolumeOffsetOpCode.SET_VOLUME_OFFSET:
@@ -159,11 +157,10 @@ class AudioOutputDescription:
159
157
  def __bytes__(self) -> bytes:
160
158
  return self.audio_output_description.encode('utf-8')
161
159
 
162
- def on_read(self, _connection: Optional[Connection]) -> bytes:
160
+ def on_read(self, _connection: Connection) -> bytes:
163
161
  return bytes(self)
164
162
 
165
- async def on_write(self, connection: Optional[Connection], value: bytes) -> None:
166
- assert connection
163
+ async def on_write(self, connection: Connection, value: bytes) -> None:
167
164
  assert self.attribute
168
165
 
169
166
  self.audio_output_description = value.decode('utf-8')
bumble/rfcomm.py CHANGED
@@ -442,6 +442,9 @@ class RFCOMM_MCC_MSC:
442
442
 
443
443
  # -----------------------------------------------------------------------------
444
444
  class DLC(utils.EventEmitter):
445
+ EVENT_OPEN = "open"
446
+ EVENT_CLOSE = "close"
447
+
445
448
  class State(enum.IntEnum):
446
449
  INIT = 0x00
447
450
  CONNECTING = 0x01
@@ -529,7 +532,7 @@ class DLC(utils.EventEmitter):
529
532
  self.send_frame(RFCOMM_Frame.uih(c_r=self.c_r, dlci=0, information=mcc))
530
533
 
531
534
  self.change_state(DLC.State.CONNECTED)
532
- self.emit('open')
535
+ self.emit(self.EVENT_OPEN)
533
536
 
534
537
  def on_ua_frame(self, _frame: RFCOMM_Frame) -> None:
535
538
  if self.state == DLC.State.CONNECTING:
@@ -550,7 +553,7 @@ class DLC(utils.EventEmitter):
550
553
  self.disconnection_result.set_result(None)
551
554
  self.disconnection_result = None
552
555
  self.multiplexer.on_dlc_disconnection(self)
553
- self.emit('close')
556
+ self.emit(self.EVENT_CLOSE)
554
557
  else:
555
558
  logger.warning(
556
559
  color(
@@ -733,7 +736,7 @@ class DLC(utils.EventEmitter):
733
736
  self.disconnection_result.cancel()
734
737
  self.disconnection_result = None
735
738
  self.change_state(DLC.State.RESET)
736
- self.emit('close')
739
+ self.emit(self.EVENT_CLOSE)
737
740
 
738
741
  def __str__(self) -> str:
739
742
  return (
@@ -763,6 +766,8 @@ class Multiplexer(utils.EventEmitter):
763
766
  DISCONNECTED = 0x05
764
767
  RESET = 0x06
765
768
 
769
+ EVENT_DLC = "dlc"
770
+
766
771
  connection_result: Optional[asyncio.Future]
767
772
  disconnection_result: Optional[asyncio.Future]
768
773
  open_result: Optional[asyncio.Future]
@@ -785,7 +790,7 @@ class Multiplexer(utils.EventEmitter):
785
790
  # Become a sink for the L2CAP channel
786
791
  l2cap_channel.sink = self.on_pdu
787
792
 
788
- l2cap_channel.on('close', self.on_l2cap_channel_close)
793
+ l2cap_channel.on(l2cap_channel.EVENT_CLOSE, self.on_l2cap_channel_close)
789
794
 
790
795
  def change_state(self, new_state: State) -> None:
791
796
  logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}')
@@ -901,7 +906,7 @@ class Multiplexer(utils.EventEmitter):
901
906
  self.dlcs[pn.dlci] = dlc
902
907
 
903
908
  # Re-emit the handshake completion event
904
- dlc.on('open', lambda: self.emit('dlc', dlc))
909
+ dlc.on(dlc.EVENT_OPEN, lambda: self.emit(self.EVENT_DLC, dlc))
905
910
 
906
911
  # Respond to complete the handshake
907
912
  dlc.accept()
@@ -1076,6 +1081,8 @@ class Client:
1076
1081
 
1077
1082
  # -----------------------------------------------------------------------------
1078
1083
  class Server(utils.EventEmitter):
1084
+ EVENT_START = "start"
1085
+
1079
1086
  def __init__(
1080
1087
  self, device: Device, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
1081
1088
  ) -> None:
@@ -1122,7 +1129,9 @@ class Server(utils.EventEmitter):
1122
1129
 
1123
1130
  def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
1124
1131
  logger.debug(f'+++ new L2CAP connection: {l2cap_channel}')
1125
- l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel))
1132
+ l2cap_channel.on(
1133
+ l2cap_channel.EVENT_OPEN, lambda: self.on_l2cap_channel_open(l2cap_channel)
1134
+ )
1126
1135
 
1127
1136
  def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
1128
1137
  logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}')
@@ -1130,10 +1139,10 @@ class Server(utils.EventEmitter):
1130
1139
  # Create a new multiplexer for the channel
1131
1140
  multiplexer = Multiplexer(l2cap_channel, Multiplexer.Role.RESPONDER)
1132
1141
  multiplexer.acceptor = self.accept_dlc
1133
- multiplexer.on('dlc', self.on_dlc)
1142
+ multiplexer.on(multiplexer.EVENT_DLC, self.on_dlc)
1134
1143
 
1135
1144
  # Notify
1136
- self.emit('start', multiplexer)
1145
+ self.emit(self.EVENT_START, multiplexer)
1137
1146
 
1138
1147
  def accept_dlc(self, channel_number: int) -> Optional[Tuple[int, int]]:
1139
1148
  return self.dlc_configs.get(channel_number)
bumble/smp.py CHANGED
@@ -724,12 +724,13 @@ class Session:
724
724
  self.is_responder = not self.is_initiator
725
725
 
726
726
  # Listen for connection events
727
- connection.on('disconnection', self.on_disconnection)
727
+ connection.on(connection.EVENT_DISCONNECTION, self.on_disconnection)
728
728
  connection.on(
729
- 'connection_encryption_change', self.on_connection_encryption_change
729
+ connection.EVENT_CONNECTION_ENCRYPTION_CHANGE,
730
+ self.on_connection_encryption_change,
730
731
  )
731
732
  connection.on(
732
- 'connection_encryption_key_refresh',
733
+ connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
733
734
  self.on_connection_encryption_key_refresh,
734
735
  )
735
736
 
@@ -1310,12 +1311,15 @@ class Session:
1310
1311
  )
1311
1312
 
1312
1313
  def on_disconnection(self, _: int) -> None:
1313
- self.connection.remove_listener('disconnection', self.on_disconnection)
1314
1314
  self.connection.remove_listener(
1315
- 'connection_encryption_change', self.on_connection_encryption_change
1315
+ self.connection.EVENT_DISCONNECTION, self.on_disconnection
1316
1316
  )
1317
1317
  self.connection.remove_listener(
1318
- 'connection_encryption_key_refresh',
1318
+ self.connection.EVENT_CONNECTION_ENCRYPTION_CHANGE,
1319
+ self.on_connection_encryption_change,
1320
+ )
1321
+ self.connection.remove_listener(
1322
+ self.connection.EVENT_CONNECTION_ENCRYPTION_KEY_REFRESH,
1319
1323
  self.on_connection_encryption_key_refresh,
1320
1324
  )
1321
1325
  self.manager.on_session_end(self)
@@ -1376,8 +1380,10 @@ class Session:
1376
1380
  ediv=self.ltk_ediv,
1377
1381
  rand=self.ltk_rand,
1378
1382
  )
1383
+ if not self.peer_ltk:
1384
+ logger.error("peer_ltk is None")
1379
1385
  peer_ltk_key = PairingKeys.Key(
1380
- value=self.peer_ltk,
1386
+ value=self.peer_ltk or b'',
1381
1387
  authenticated=authenticated,
1382
1388
  ediv=self.peer_ediv,
1383
1389
  rand=self.peer_rand,
@@ -1962,7 +1968,7 @@ class Manager(utils.EventEmitter):
1962
1968
  def on_smp_security_request_command(
1963
1969
  self, connection: Connection, request: SMP_Security_Request_Command
1964
1970
  ) -> None:
1965
- connection.emit('security_request', request.auth_req)
1971
+ connection.emit(connection.EVENT_SECURITY_REQUEST, request.auth_req)
1966
1972
 
1967
1973
  def on_smp_pdu(self, connection: Connection, pdu: bytes) -> None:
1968
1974
  # Parse the L2CAP payload into an SMP Command object
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bumble
3
- Version: 0.0.210
3
+ Version: 0.0.212
4
4
  Summary: Bluetooth Stack for Apps, Emulation, Test and Experimentation
5
5
  Author-email: Google <bumble-dev@google.com>
6
6
  Project-URL: Homepage, https://github.com/google/bumble
@@ -10,8 +10,8 @@ License-File: LICENSE
10
10
  Requires-Dist: aiohttp~=3.8; platform_system != "Emscripten"
11
11
  Requires-Dist: appdirs>=1.4; platform_system != "Emscripten"
12
12
  Requires-Dist: click>=8.1.3; platform_system != "Emscripten"
13
- Requires-Dist: cryptography>=39; platform_system != "Emscripten"
14
- Requires-Dist: cryptography>=39.0; platform_system == "Emscripten"
13
+ Requires-Dist: cryptography>=44.0.3; platform_system != "Emscripten"
14
+ Requires-Dist: cryptography>=44.0.3; platform_system == "Emscripten"
15
15
  Requires-Dist: grpcio>=1.62.1; platform_system != "Emscripten"
16
16
  Requires-Dist: humanize>=4.6.0; platform_system != "Emscripten"
17
17
  Requires-Dist: libusb1>=2.0.1; platform_system != "Emscripten"
@@ -20,7 +20,7 @@ Requires-Dist: platformdirs>=3.10.0; platform_system != "Emscripten"
20
20
  Requires-Dist: prompt_toolkit>=3.0.16; platform_system != "Emscripten"
21
21
  Requires-Dist: prettytable>=3.6.0; platform_system != "Emscripten"
22
22
  Requires-Dist: protobuf>=3.12.4; platform_system != "Emscripten"
23
- Requires-Dist: pyee>=8.2.2
23
+ Requires-Dist: pyee>=13.0.0
24
24
  Requires-Dist: pyserial-asyncio>=0.5; platform_system != "Emscripten"
25
25
  Requires-Dist: pyserial>=3.5; platform_system != "Emscripten"
26
26
  Requires-Dist: pyusb>=1.2; platform_system != "Emscripten"