diff options
-rw-r--r-- | avatar/bumble_server/__init__.py | 3 | ||||
-rw-r--r-- | avatar/bumble_server/asha.py | 42 | ||||
-rw-r--r-- | avatar/bumble_server/host.py | 6 | ||||
-rw-r--r-- | avatar/bumble_server/security.py | 39 | ||||
-rw-r--r-- | avatar/pandora_client.py | 23 |
5 files changed, 40 insertions, 73 deletions
diff --git a/avatar/bumble_server/__init__.py b/avatar/bumble_server/__init__.py index c903154..184968f 100644 --- a/avatar/bumble_server/__init__.py +++ b/avatar/bumble_server/__init__.py @@ -22,11 +22,9 @@ import grpc.aio import logging from avatar.bumble_device import BumbleDevice -from avatar.bumble_server.asha import ASHAService from avatar.bumble_server.host import HostService from avatar.bumble_server.security import SecurityService, SecurityStorageService from bumble.smp import PairingDelegate -from pandora.asha_grpc_aio import add_ASHAServicer_to_server from pandora.host_grpc_aio import add_HostServicer_to_server from pandora.security_grpc_aio import add_SecurityServicer_to_server, add_SecurityStorageServicer_to_server from typing import Callable, List, Optional @@ -54,7 +52,6 @@ async def serve_bumble(bumble: BumbleDevice, grpc_server: Optional[grpc.aio.Serv add_HostServicer_to_server(HostService(server, bumble.device), server) add_SecurityServicer_to_server(SecurityService(bumble.device, io_capability), server) add_SecurityStorageServicer_to_server(SecurityStorageService(bumble.device), server) - add_ASHAServicer_to_server(ASHAService(bumble.device), server) # call hooks if any. for hook in _SERVICERS_HOOKS: diff --git a/avatar/bumble_server/asha.py b/avatar/bumble_server/asha.py deleted file mode 100644 index fdea220..0000000 --- a/avatar/bumble_server/asha.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2022 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. - -import grpc -import logging - -from . import utils -from bumble.device import Device -from bumble.profiles.asha_service import AshaService -from google.protobuf.empty_pb2 import Empty # pytype: disable=pyi-error -from pandora.asha_grpc_aio import ASHAServicer -from pandora.asha_pb2 import RegisterRequest -from typing import Optional - - -class ASHAService(ASHAServicer): - device: Device - asha_service: Optional[AshaService] - - def __init__(self, device: Device) -> None: - self.log = utils.BumbleServerLoggerAdapter(logging.getLogger(), {'service_name': 'Asha', 'device': device}) - self.device = device - self.asha_service = None - - @utils.rpc - async def Register(self, request: RegisterRequest, context: grpc.ServicerContext) -> Empty: - self.log.info('Register') - # asha service from bumble profile - self.asha_service = AshaService(request.capability, request.hisyncid, self.device) - self.device.add_service(self.asha_service) # type: ignore[no-untyped-call] - return Empty() diff --git a/avatar/bumble_server/host.py b/avatar/bumble_server/host.py index 5f84d41..d9d6e4a 100644 --- a/avatar/bumble_server/host.py +++ b/avatar/bumble_server/host.py @@ -520,14 +520,14 @@ class HostService(HostServicer): ad_structures.append( ( AdvertisingData.INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS, - b''.join([uuid128_from_str(uuid) for uuid in uuids]) + b''.join([uuid128_from_str(uuid) for uuid in uuids]), ) ) if uuids := dt.complete_service_class_uuids128: ad_structures.append( ( AdvertisingData.COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS, - b''.join([uuid128_from_str(uuid) for uuid in uuids]) + b''.join([uuid128_from_str(uuid) for uuid in uuids]), ) ) if dt.HasField('include_shortened_local_name'): @@ -583,7 +583,7 @@ class HostService(HostServicer): ad_structures.append( ( AdvertisingData.LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS, - b''.join([uuid128_from_str(uuid) for uuid in uuids]) + b''.join([uuid128_from_str(uuid) for uuid in uuids]), ) ) if datas := dt.service_data_uuid16: diff --git a/avatar/bumble_server/security.py b/avatar/bumble_server/security.py index 4b8cf1b..000d49f 100644 --- a/avatar/bumble_server/security.py +++ b/avatar/bumble_server/security.py @@ -85,46 +85,67 @@ class PairingDelegate(BasePairingDelegate): async def confirm(self) -> bool: self.log.info(f"Pairing event: `just_works` (io_capability: {self.io_capability})") - if not self.service.event_queue or not self.service.event_answer: + if self.service.event_queue is None or self.service.event_answer is None: return True event = self.add_origin(PairingEvent(just_works=empty_pb2.Empty())) self.service.event_queue.put_nowait(event) answer = await anext(self.service.event_answer) # pytype: disable=name-error assert answer.event == event - assert answer.confirm + assert answer.answer_variant() == 'confirm' and answer.confirm is not None return answer.confirm async def compare_numbers(self, number: int, digits: int = 6) -> bool: self.log.info(f"Pairing event: `numeric_comparison` (io_capability: {self.io_capability})") - if not self.service.event_queue or not self.service.event_answer: + if self.service.event_queue is None or self.service.event_answer is None: raise RuntimeError('security: unhandled number comparison request') event = self.add_origin(PairingEvent(numeric_comparison=number)) self.service.event_queue.put_nowait(event) answer = await anext(self.service.event_answer) # pytype: disable=name-error assert answer.event == event - assert answer.confirm + assert answer.answer_variant() == 'confirm' and answer.confirm is not None return answer.confirm - async def get_number(self) -> int: + async def get_number(self) -> Optional[int]: self.log.info(f"Pairing event: `passkey_entry_request` (io_capability: {self.io_capability})") - if not self.service.event_queue or not self.service.event_answer: + if self.service.event_queue is None or self.service.event_answer is None: raise RuntimeError('security: unhandled number request') event = self.add_origin(PairingEvent(passkey_entry_request=empty_pb2.Empty())) self.service.event_queue.put_nowait(event) answer = await anext(self.service.event_answer) # pytype: disable=name-error assert answer.event == event - assert answer.passkey is not None + assert answer.answer_variant() == 'passkey' return answer.passkey + async def get_string(self, max_length: int) -> Optional[str]: + self.log.info(f"Pairing event: `pin_code_request` (io_capability: {self.io_capability})") + + if self.service.event_queue is None or self.service.event_answer is None: + raise RuntimeError('security: unhandled pin_code request') + + event = self.add_origin(PairingEvent(pin_code_request=empty_pb2.Empty())) + self.service.event_queue.put_nowait(event) + answer = await anext(self.service.event_answer) # pytype: disable=name-error + assert answer.event == event + assert answer.answer_variant() == 'pin' + + if answer.pin is None: + return None + + pin = answer.pin.decode('utf-8') + if not pin or len(pin) > max_length: + raise ValueError(f'Pin must be utf-8 encoded up to {max_length} bytes') + + return pin + async def display_number(self, number: int, digits: int = 6) -> None: self.log.info(f"Pairing event: `passkey_entry_notification` (io_capability: {self.io_capability})") - if not self.service.event_queue: + if self.service.event_queue is None: raise RuntimeError('security: unhandled number display request') event = self.add_origin(PairingEvent(passkey_entry_notification=number)) @@ -181,7 +202,7 @@ class SecurityService(SecurityServicer): ) -> AsyncGenerator[PairingEvent, None]: self.log.info('OnPairing') - if self.event_queue: + if self.event_queue is not None: raise RuntimeError('already streaming pairing events') if len(self.device.connections): diff --git a/avatar/pandora_client.py b/avatar/pandora_client.py index cdeec07..548e6d1 100644 --- a/avatar/pandora_client.py +++ b/avatar/pandora_client.py @@ -26,7 +26,7 @@ import logging from avatar.bumble_device import BumbleDevice from bumble.hci import Address as BumbleAddress from dataclasses import dataclass -from pandora import asha_grpc, asha_grpc_aio, host_grpc, host_grpc_aio, security_grpc, security_grpc_aio +from pandora import host_grpc, host_grpc_aio, security_grpc, security_grpc_aio from typing import Any, Dict, MutableMapping, Optional, Tuple, Union @@ -110,11 +110,12 @@ class PandoraClient: ) return except grpc.aio.AioRpcError as e: - if attempts <= max_attempts and e.code() == grpc.StatusCode.UNAVAILABLE: - self.log.debug(f'Server unavailable, retry [{attempts}/{max_attempts}].') - attempts += 1 - continue - self.log.exception(f'Server still unavailable after {attempts} attempts, abort.') + if e.code() in (grpc.StatusCode.UNAVAILABLE, grpc.StatusCode.DEADLINE_EXCEEDED): + if attempts <= max_attempts: + self.log.debug(f'Server unavailable, retry [{attempts}/{max_attempts}].') + attempts += 1 + continue + self.log.exception(f'Server still unavailable after {attempts} attempts, abort.') raise e @property @@ -143,11 +144,6 @@ class PandoraClient: """Returns the Pandora SecurityStorage gRPC interface.""" return security_grpc.SecurityStorage(self.channel) - @property - def asha(self) -> asha_grpc.ASHA: - """Returns the Pandora ASHA gRPC interface.""" - return asha_grpc.ASHA(self.channel) - @dataclass class Aio: channel: grpc.aio.Channel @@ -167,11 +163,6 @@ class PandoraClient: """Returns the Pandora SecurityStorage gRPC interface.""" return security_grpc_aio.SecurityStorage(self.channel) - @property - def asha(self) -> asha_grpc_aio.ASHA: - """Returns the Pandora ASHA gRPC interface.""" - return asha_grpc_aio.ASHA(self.channel) - @property def aio(self) -> 'PandoraClient.Aio': if not self._aio: |