Scott James Remnant | 96927a4 | 2013-07-17 18:27:57 -0700 | [diff] [blame] | 1 | # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | import logging |
| 6 | import socket |
| 7 | |
| 8 | import _btsocket |
Scott James Remnant | fc5a3f7 | 2013-08-01 14:39:40 -0700 | [diff] [blame] | 9 | import constants |
Scott James Remnant | 96927a4 | 2013-07-17 18:27:57 -0700 | [diff] [blame] | 10 | |
| 11 | class BluetoothSocket(socket._socketobject): |
| 12 | """Socket wrapper class for Bluetooth sockets. |
| 13 | |
| 14 | Wraps the Python socket class to implement the necessary pieces to be able |
| 15 | to bind to the HCI monitor and control sockets as well as receive messages |
| 16 | with ancillary data from them. |
| 17 | |
Scott James Remnant | ffa6f74 | 2013-11-19 12:10:07 -0800 | [diff] [blame] | 18 | The bind() and connect() functions match the behavior of those of the |
| 19 | built-in socket methods and accept a tuple defining the address, the |
| 20 | contents of which depend on the protocol of the socket. |
| 21 | |
| 22 | For BTPROTO_HCI this is: |
| 23 | @param dev: Device index, or HCI_DEV_NONE. |
| 24 | @param channel: Channel, e.g. HCI_CHANNEL_RAW. |
| 25 | |
| 26 | For BTPROTO_L2CAP this is: |
| 27 | @param address: Address of device in string format. |
| 28 | @param psm: PSM of L2CAP service. |
| 29 | |
| 30 | For BTPROTO_RFCOMM this is: |
| 31 | @param address: Address of device in string format. |
| 32 | @param cid: Channel ID of RFCOMM service. |
| 33 | |
| 34 | For BTPROTO_SCO this is: |
| 35 | @param address: Address of device in string format. |
| 36 | |
Scott James Remnant | 96927a4 | 2013-07-17 18:27:57 -0700 | [diff] [blame] | 37 | """ |
| 38 | |
Scott James Remnant | fc5a3f7 | 2013-08-01 14:39:40 -0700 | [diff] [blame] | 39 | def __init__(self, |
| 40 | family=constants.AF_BLUETOOTH, |
| 41 | type=socket.SOCK_RAW, |
| 42 | proto=constants.BTPROTO_HCI): |
| 43 | super(BluetoothSocket, self).__init__(family, type, proto) |
Scott James Remnant | 96927a4 | 2013-07-17 18:27:57 -0700 | [diff] [blame] | 44 | |
| 45 | def bind(self, *args): |
Scott James Remnant | ffa6f74 | 2013-11-19 12:10:07 -0800 | [diff] [blame] | 46 | """Bind the socket to a local address.""" |
Scott James Remnant | fc5a3f7 | 2013-08-01 14:39:40 -0700 | [diff] [blame] | 47 | if self.family == constants.AF_BLUETOOTH: |
| 48 | return _btsocket.bind(self, self.proto, *args) |
Scott James Remnant | 96927a4 | 2013-07-17 18:27:57 -0700 | [diff] [blame] | 49 | else: |
| 50 | return super(BluetoothSocket, self).bind(*args) |
| 51 | |
Scott James Remnant | ffa6f74 | 2013-11-19 12:10:07 -0800 | [diff] [blame] | 52 | def connect(self, *args): |
| 53 | """Connect the socket to a remote address.""" |
| 54 | if self.family == constants.AF_BLUETOOTH: |
| 55 | return _btsocket.connect(self, self.proto, *args) |
| 56 | else: |
| 57 | return super(BluetoothSocket, self).connect(*args) |
| 58 | |
Scott James Remnant | 96927a4 | 2013-07-17 18:27:57 -0700 | [diff] [blame] | 59 | def recvmsg(self, bufsize, ancbufsize=0, flags=0): |
| 60 | """Receive normal data and ancillary data from the socket. |
| 61 | |
| 62 | @param bufsize size in bytes of buffer for normal data. |
| 63 | @param ancbufsize size in bytes of internal buffer for ancillary data. |
| 64 | @param flags same meaning as socket.recv() |
| 65 | |
| 66 | @return tuple of (data, ancdata, msg_flags, address), |
| 67 | @ancdata is zero or more tuples of (cmsg_level, cmsg_type, cmsg_data). |
| 68 | |
| 69 | """ |
| 70 | buffer = bytearray(bufsize) |
| 71 | (nbytes, ancdata, msg_flags, address) = \ |
| 72 | self.recvmsg_into((buffer,), ancbufsize, flags) |
| 73 | return (bytes(buffer), ancdata, msg_flags, address) |
| 74 | |
| 75 | def recvmsg_into(self, buffers, ancbufsize=0, flags=0): |
| 76 | """Receive normal data and ancillary data from the socket. |
| 77 | |
| 78 | @param buffers iterable of bytearray objects filled with read chunks. |
| 79 | @param ancbufsize size in bytes of internal buffer for ancillary data. |
| 80 | @param flags same meaning as socket.recv() |
| 81 | |
| 82 | @return tuple of (nbytes, ancdata, msg_flags, address), |
| 83 | @ancdata is zero or more tuples of (cmsg_level, cmsg_type, cmsg_data). |
| 84 | |
| 85 | """ |
| 86 | return _btsocket.recvmsg(self, buffers, ancbufsize, flags) |
| 87 | |
Artem Rakhov | 1b65449 | 2015-01-21 00:40:44 -0800 | [diff] [blame] | 88 | |
| 89 | def create_le_gatt_server_socket(): |
| 90 | """Create a socket for incoming BLE connection. |
| 91 | |
| 92 | The function creates an LE socket, then does bind, listen and accept. |
| 93 | The accept call blocks until it receives an incoming connection. |
| 94 | |
| 95 | Note, that resulting socket will be standard Python socket, |
| 96 | not BluetoothSocket. It is already connected, ready for recv() and |
| 97 | send() calls, and there is no need for further setup. |
| 98 | |
| 99 | Before calling this function, the machine must be set up (with appropriate |
| 100 | HCI commands) to be advertising, be powered, and have LE turned on. |
| 101 | |
| 102 | @return tuple of (sock, source_address), |
| 103 | @sock is standard Python socket object, which is able to receive and send. |
| 104 | @source_address is string containing BDADDR of the connected remote. |
| 105 | |
| 106 | """ |
| 107 | file_descriptor, source_address = _btsocket.listen_and_accept() |
| 108 | sock = socket.fromfd(file_descriptor, constants.AF_BLUETOOTH, |
| 109 | socket.SOCK_SEQPACKET, constants.BTPROTO_L2CAP) |
| 110 | return sock, source_address |