1# Copyright 2021-2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# ----------------------------------------------------------------------------- 16# Imports 17# ----------------------------------------------------------------------------- 18import asyncio 19import logging 20import struct 21import os 22import socket 23import ctypes 24import collections 25 26from .common import Transport, ParserSource 27 28 29# ----------------------------------------------------------------------------- 30# Logging 31# ----------------------------------------------------------------------------- 32logger = logging.getLogger(__name__) 33 34 35# ----------------------------------------------------------------------------- 36async def open_hci_socket_transport(spec): 37 ''' 38 Open an HCI Socket (only available on some platforms). 39 The parameter string is either empty (to use the first/default Bluetooth adapter) 40 or a 0-based integer to indicate the adapter number. 41 ''' 42 43 HCI_CHANNEL_USER = 1 44 45 # Create a raw HCI socket 46 try: 47 hci_socket = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW | socket.SOCK_NONBLOCK, socket.BTPROTO_HCI) 48 except AttributeError: 49 # Not supported on this platform 50 logger.info("HCI sockets not supported on this platform") 51 raise Exception('Bluetooth HCI sockets not supported on this platform') 52 53 # Compute the adapter index 54 if spec is None: 55 adapter_index = 0 56 else: 57 adapter_index = int(spec) 58 59 # Bind the socket 60 # NOTE: since Python doesn't support binding with the required address format (yet), 61 # we need to go directly to the C runtime... 62 try: 63 ctypes.cdll.LoadLibrary('libc.so.6') 64 libc = ctypes.CDLL('libc.so.6', use_errno=True) 65 except OSError: 66 logger.info("HCI sockets not supported on this platform") 67 raise Exception('Bluetooth HCI sockets not supported on this platform') 68 libc.bind.argtypes = (ctypes.c_int, ctypes.POINTER(ctypes.c_char), ctypes.c_int) 69 libc.bind.restype = ctypes.c_int 70 bind_address = struct.pack('<HHH', socket.AF_BLUETOOTH, adapter_index, HCI_CHANNEL_USER) 71 if libc.bind(hci_socket.fileno(), ctypes.create_string_buffer(bind_address), len(bind_address)) != 0: 72 raise IOError(ctypes.get_errno(), os.strerror(ctypes.get_errno())) 73 74 class HciSocketSource(ParserSource): 75 def __init__(self, socket): 76 super().__init__() 77 self.socket = socket 78 asyncio.get_running_loop().add_reader(socket.fileno(), self.recv_until_would_block) 79 80 def recv_until_would_block(self): 81 logger.debug('recv until would block +++') 82 while True: 83 try: 84 packet = self.socket.recv(4096) 85 logger.debug(f'received packet {len(packet)} bytes') 86 self.parser.feed_data(packet) 87 except BlockingIOError: 88 logger.debug('recv would block') 89 break 90 91 def close(self): 92 asyncio.get_running_loop().remove_reader(self.socket.fileno()) 93 94 class HciSocketSink: 95 def __init__(self, socket): 96 self.socket = socket 97 self.packets = collections.deque() 98 self.writer_added = False 99 100 def send_until_would_block(self): 101 logger.debug('send until would block ---') 102 while self.packets: 103 packet = self.packets.pop() 104 logger.debug('sending packet') 105 try: 106 bytes_written = self.socket.send(packet) 107 except BlockingIOError: 108 bytes_written = 0 109 if bytes_written != len(packet): 110 # Note: we assume here that there are no partial writes 111 logger.debug('send would block') 112 break 113 114 if self.packets: 115 # There's still something to send, ensure that we are monitoring the socket 116 if not self.writer_added: 117 asyncio.get_running_loop().add_writer(socket.fileno(), self.send_until_would_block) 118 self.writer_added = True 119 else: 120 # Nothing left to send, stop monitoring the socket 121 if self.writer_added: 122 asyncio.get_running_loop().remove_writer(self.socket.fileno()) 123 self.writer_added = False 124 125 def on_packet(self, packet): 126 self.packets.appendleft(packet) 127 self.send_until_would_block() 128 129 def close(self): 130 if self.writer_added: 131 asyncio.get_running_loop().remove_writer(self.socket.fileno()) 132 133 class HciSocketTransport(Transport): 134 def __init__(self, socket, source, sink): 135 super().__init__(source, sink) 136 self.socket = socket 137 138 async def close(self): 139 logger.debug('closing HCI socket transport') 140 self.source.close() 141 self.sink.close() 142 self.socket.close() 143 144 packet_source = HciSocketSource(hci_socket) 145 packet_sink = HciSocketSink(hci_socket) 146 return HciSocketTransport(hci_socket, packet_source, packet_sink) 147