• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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