"""Custom backend for FreeBSD.""" import os import socket import struct import ctypes libc = ctypes.cdll.LoadLibrary('libc.so.7') NG_HCI_EVENT_MASK_LE = 0x2000000000000000 SOL_HCI_RAW = 0x0802 SOL_HCI_RAW_FILTER = 1 class SockaddrHci(ctypes.Structure): """Structure representing a hci socket address.""" _fields_ = [ ('hci_len', ctypes.c_char), ('hci_family', ctypes.c_char), ('hci_node', ctypes.c_char * 32), ] class HciRawFilter(ctypes.Structure): """Structure specifying filter masks.""" _fields_ = [ ('packet_mask', ctypes.c_uint32), ('event_mask', ctypes.c_uint64), ] def open_dev(bt_device_id): """Open hci device socket.""" # pylint: disable=no-member sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, socket.BTPROTO_HCI) # Unlike Linux, FreeBSD has separate numbering depending on hardware # (ubt - USB bluetooth - is the most common, so convert numbers to that) if not isinstance(bt_device_id, str): bt_device_id = 'ubt{}hci'.format(bt_device_id) # Python's BTPROTO_HCI address parsing is busted: https://bugs.python.org/issue41130 adr = SockaddrHci(ctypes.sizeof(SockaddrHci), socket.AF_BLUETOOTH, bt_device_id.ljust(32, '\0').encode('utf-8')) if libc.bind(sock.fileno(), ctypes.pointer(adr), ctypes.sizeof(SockaddrHci)) != 0: raise ConnectionError(ctypes.get_errno(), os.strerror(ctypes.get_errno())) if libc.connect(sock.fileno(), ctypes.pointer(adr), ctypes.sizeof(SockaddrHci)) != 0: raise ConnectionError(ctypes.get_errno(), os.strerror(ctypes.get_errno())) # pylint: enable=no-member fltr = HciRawFilter(0, NG_HCI_EVENT_MASK_LE) if libc.setsockopt(sock.fileno(), SOL_HCI_RAW, SOL_HCI_RAW_FILTER, ctypes.pointer(fltr), ctypes.sizeof(HciRawFilter)) != 0: raise ConnectionError(ctypes.get_errno(), os.strerror(ctypes.get_errno())) return sock def send_cmd(sock, group_field, command_field, data): """Send hci command to device.""" opcode = (((group_field & 0x3f) << 10) | (command_field & 0x3ff)) sock.send(struct.pack('<BHB', 1, opcode, len(data)) + data)