| # Copyright 2021-2023 Google LLC |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| # ----------------------------------------------------------------------------- |
| # Imports |
| # ----------------------------------------------------------------------------- |
| import enum |
| from typing import Optional, Tuple |
| |
| from .hci import ( |
| Address, |
| HCI_NO_INPUT_NO_OUTPUT_IO_CAPABILITY, |
| HCI_DISPLAY_ONLY_IO_CAPABILITY, |
| HCI_DISPLAY_YES_NO_IO_CAPABILITY, |
| HCI_KEYBOARD_ONLY_IO_CAPABILITY, |
| ) |
| from .smp import ( |
| SMP_NO_INPUT_NO_OUTPUT_IO_CAPABILITY, |
| SMP_KEYBOARD_ONLY_IO_CAPABILITY, |
| SMP_DISPLAY_ONLY_IO_CAPABILITY, |
| SMP_DISPLAY_YES_NO_IO_CAPABILITY, |
| SMP_KEYBOARD_DISPLAY_IO_CAPABILITY, |
| SMP_ENC_KEY_DISTRIBUTION_FLAG, |
| SMP_ID_KEY_DISTRIBUTION_FLAG, |
| SMP_SIGN_KEY_DISTRIBUTION_FLAG, |
| SMP_LINK_KEY_DISTRIBUTION_FLAG, |
| ) |
| |
| |
| # ----------------------------------------------------------------------------- |
| class PairingDelegate: |
| """Abstract base class for Pairing Delegates.""" |
| |
| # I/O Capabilities. |
| # These are defined abstractly, and can be mapped to specific Classic pairing |
| # and/or SMP constants. |
| class IoCapability(enum.IntEnum): |
| NO_OUTPUT_NO_INPUT = SMP_NO_INPUT_NO_OUTPUT_IO_CAPABILITY |
| KEYBOARD_INPUT_ONLY = SMP_KEYBOARD_ONLY_IO_CAPABILITY |
| DISPLAY_OUTPUT_ONLY = SMP_DISPLAY_ONLY_IO_CAPABILITY |
| DISPLAY_OUTPUT_AND_YES_NO_INPUT = SMP_DISPLAY_YES_NO_IO_CAPABILITY |
| DISPLAY_OUTPUT_AND_KEYBOARD_INPUT = SMP_KEYBOARD_DISPLAY_IO_CAPABILITY |
| |
| # Direct names for backward compatibility. |
| NO_OUTPUT_NO_INPUT = IoCapability.NO_OUTPUT_NO_INPUT |
| KEYBOARD_INPUT_ONLY = IoCapability.KEYBOARD_INPUT_ONLY |
| DISPLAY_OUTPUT_ONLY = IoCapability.DISPLAY_OUTPUT_ONLY |
| DISPLAY_OUTPUT_AND_YES_NO_INPUT = IoCapability.DISPLAY_OUTPUT_AND_YES_NO_INPUT |
| DISPLAY_OUTPUT_AND_KEYBOARD_INPUT = IoCapability.DISPLAY_OUTPUT_AND_KEYBOARD_INPUT |
| |
| # Key Distribution [LE only] |
| class KeyDistribution(enum.IntFlag): |
| DISTRIBUTE_ENCRYPTION_KEY = SMP_ENC_KEY_DISTRIBUTION_FLAG |
| DISTRIBUTE_IDENTITY_KEY = SMP_ID_KEY_DISTRIBUTION_FLAG |
| DISTRIBUTE_SIGNING_KEY = SMP_SIGN_KEY_DISTRIBUTION_FLAG |
| DISTRIBUTE_LINK_KEY = SMP_LINK_KEY_DISTRIBUTION_FLAG |
| |
| DEFAULT_KEY_DISTRIBUTION: KeyDistribution = ( |
| KeyDistribution.DISTRIBUTE_ENCRYPTION_KEY |
| | KeyDistribution.DISTRIBUTE_IDENTITY_KEY |
| ) |
| |
| # Default mapping from abstract to Classic I/O capabilities. |
| # Subclasses may override this if they prefer a different mapping. |
| CLASSIC_IO_CAPABILITIES_MAP = { |
| NO_OUTPUT_NO_INPUT: HCI_NO_INPUT_NO_OUTPUT_IO_CAPABILITY, |
| KEYBOARD_INPUT_ONLY: HCI_KEYBOARD_ONLY_IO_CAPABILITY, |
| DISPLAY_OUTPUT_ONLY: HCI_DISPLAY_ONLY_IO_CAPABILITY, |
| DISPLAY_OUTPUT_AND_YES_NO_INPUT: HCI_DISPLAY_YES_NO_IO_CAPABILITY, |
| DISPLAY_OUTPUT_AND_KEYBOARD_INPUT: HCI_DISPLAY_YES_NO_IO_CAPABILITY, |
| } |
| |
| io_capability: IoCapability |
| local_initiator_key_distribution: KeyDistribution |
| local_responder_key_distribution: KeyDistribution |
| |
| def __init__( |
| self, |
| io_capability: IoCapability = NO_OUTPUT_NO_INPUT, |
| local_initiator_key_distribution: KeyDistribution = DEFAULT_KEY_DISTRIBUTION, |
| local_responder_key_distribution: KeyDistribution = DEFAULT_KEY_DISTRIBUTION, |
| ) -> None: |
| self.io_capability = io_capability |
| self.local_initiator_key_distribution = local_initiator_key_distribution |
| self.local_responder_key_distribution = local_responder_key_distribution |
| |
| @property |
| def classic_io_capability(self) -> int: |
| """Map the abstract I/O capability to a Classic constant.""" |
| |
| # pylint: disable=line-too-long |
| return self.CLASSIC_IO_CAPABILITIES_MAP.get( |
| self.io_capability, HCI_NO_INPUT_NO_OUTPUT_IO_CAPABILITY |
| ) |
| |
| @property |
| def smp_io_capability(self) -> int: |
| """Map the abstract I/O capability to an SMP constant.""" |
| |
| # This is just a 1-1 direct mapping |
| return self.io_capability |
| |
| async def accept(self) -> bool: |
| """Accept or reject a Pairing request.""" |
| return True |
| |
| async def confirm(self, auto: bool = False) -> bool: |
| """ |
| Respond yes or no to a Pairing confirmation question. |
| The `auto` parameter stands for automatic confirmation. |
| """ |
| return True |
| |
| # pylint: disable-next=unused-argument |
| async def compare_numbers(self, number: int, digits: int) -> bool: |
| """Compare two numbers.""" |
| return True |
| |
| async def get_number(self) -> Optional[int]: |
| """ |
| Return an optional number as an answer to a passkey request. |
| Returning `None` will result in a negative reply. |
| """ |
| return 0 |
| |
| async def get_string(self, max_length: int) -> Optional[str]: |
| """ |
| Return a string whose utf-8 encoding is up to max_length bytes. |
| """ |
| return None |
| |
| # pylint: disable-next=unused-argument |
| async def display_number(self, number: int, digits: int) -> None: |
| """Display a number.""" |
| |
| # [LE only] |
| async def key_distribution_response( |
| self, peer_initiator_key_distribution: int, peer_responder_key_distribution: int |
| ) -> Tuple[int, int]: |
| """ |
| Return the key distribution response in an SMP protocol context. |
| |
| NOTE: since it is only used by the SMP protocol, this method's input and output |
| are directly as integers, using the SMP constants, rather than the abstract |
| KeyDistribution enums. |
| """ |
| return ( |
| int( |
| peer_initiator_key_distribution & self.local_initiator_key_distribution |
| ), |
| int( |
| peer_responder_key_distribution & self.local_responder_key_distribution |
| ), |
| ) |
| |
| |
| # ----------------------------------------------------------------------------- |
| class PairingConfig: |
| """Configuration for the Pairing protocol.""" |
| |
| class AddressType(enum.IntEnum): |
| PUBLIC = Address.PUBLIC_DEVICE_ADDRESS |
| RANDOM = Address.RANDOM_DEVICE_ADDRESS |
| |
| def __init__( |
| self, |
| sc: bool = True, |
| mitm: bool = True, |
| bonding: bool = True, |
| delegate: Optional[PairingDelegate] = None, |
| identity_address_type: Optional[AddressType] = None, |
| ) -> None: |
| self.sc = sc |
| self.mitm = mitm |
| self.bonding = bonding |
| self.delegate = delegate or PairingDelegate() |
| self.identity_address_type = identity_address_type |
| |
| def __str__(self) -> str: |
| return ( |
| f'PairingConfig(sc={self.sc}, ' |
| f'mitm={self.mitm}, bonding={self.bonding}, ' |
| f'identity_address_type={self.identity_address_type}, ' |
| f'delegate[{self.delegate.io_capability}])' |
| ) |