#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#
# This application is simply a python only Bluetooth LE Scan command with
# decoding of advertised packets
#
# Copyright (c) 2017 François Wautier
#
# Note large part of this code was taken from scapy and other opensource software
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies
# or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
# IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE

import socket, platform, asyncio
from struct import pack, unpack, calcsize


# A little bit of HCI
HCI_COMMAND = 0x01
HCI_ACL_DATA = 0x02
HCI_SCO_DATA = 0x03
HCI_EVENT = 0x04
HCI_VENDOR = 0x05

PRINT_INDENT = "    "

CMD_SCAN_REQUEST = 0x200C  # mixing the OGF in with that HCI shift

#
EDDY_UUID = b"\xfe\xaa"  # Google UUID

# Generated from https://www.uuidgenerator.net/ 906ed6ab-6785-4eab-9847-bf9889c098ae alternative is 668997f8-4acd-48ea-b35b-749e54215860
MY_UUID = b"\x90\x6e\xd6\xab\x67\x85\x4e\xab\x98\x47\xbf\x98\x89\xc0\x98\xae"
# MY_UUID = b'\x66\x89\x97\xf8\x4a\xcd\x48\xea\xb3\x5b\x74\x9e\x54\x21\x58\x60'
#
# Let's define some useful types
#
class MACAddr:
    """Class representing a MAC address.

    :param name: The name of the instance
    :type name: str
    :param mac: the mac address.
    :type mac: str
    :returns: MACAddr instance.
    :rtype: MACAddr

    """

    def __init__(self, name, mac="00:00:00:00:00:00"):
        self.name = name
        self.val = mac.lower()

    def encode(self):
        """Encode the MAC address to a byte array.

        :returns: The encoded version of the MAC address
        :rtype: bytes
        """
        return int(self.val.replace(":", ""), 16).to_bytes(6, "little")

    def decode(self, data):
        """Decode the MAC address from a byte array.

        This will take the first 6 bytes from data and transform them into a MAC address
        string representation. This will be assigned to the attribute "val". It then returns
        the data stream minus the bytes consumed

            :param data: The data stream containing the value to decode at its head
            :type data: bytes
            :returns: The datastream minus the bytes consumed
            :rtype: bytes
        """
        self.val = ":".join("%02x" % x for x in reversed(data[:6]))
        return data[6:]

    def __len__(self):
        return 6

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class Bool:
    """Class representing a boolean value.

    :param name: The name of the instance
    :type name: str
    :param val: the boolean value.
    :type mac: bool
    :returns: Bool instance.
    :rtype: Bool

    """

    def __init__(self, name, val=True):
        self.name = name
        self.val = val

    def encode(self):
        return b"\x01" if self.val else b"\x00"

    def decode(self, data):
        self.val = data[:1] == b"\x01"
        return data[1:]

    def __len__(self):
        return 1

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class Byte:
    """Class representing a single byte value.

    :param name: The name of the instance
    :type name: str
    :param val: the single byte value.
    :type val: byte
    :returns: Byte instance.
    :rtype: Byte

    """

    def __init__(self, name, val=b"\0"):
        self.name = name
        self.val = val

    def encode(self):
        val = pack("<c", self.val)
        return val

    def decode(self, data):
        self.val = unpack("<c", data[:1])[0]
        return data[1:]

    def __len__(self):
        return 1

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print(
            "{}{}".format(
                PRINT_INDENT * (depth + 1),
                ":".join(map(lambda b: format(b, "02x"), self.val)),
            )
        )


class EnumByte:
    """Class representing a single byte value from a limited set of value

    :param name: The name of the instance
    :type name: str
    :param val: the single byte value.
    :type val: byte
    :param loval: the list of possible values.
    :type loval: dict
    :returns: EnumByte instance.
    :rtype: EnumByte

    """

    def __init__(self, name, val=0, loval={0: "Undef"}):
        self.name = name
        self.val = val
        self.loval = loval

    def encode(self):
        val = pack(">B", self.val)
        return val

    def decode(self, data):
        self.val = unpack(">B", data[:1])[0]
        return data[1:]

    @property
    def strval(self):
        if self.val in self.loval:
            return self.loval[self.val]
        else:
            return str(self.val)

    def __len__(self):
        return 1

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        if self.val in self.loval:
            print("{}{}".format(PRINT_INDENT * (depth + 1), self.loval[self.val]))
        else:
            print("{}Undef".format(PRINT_INDENT * (depth + 1)))


class BitFieldByte:
    """Class representing a single byte value as a bit field.

    :param name: The name of the instance
    :type name: str
    :param val: the single byte value.
    :type val: byte
    :param loval: the list defining the name of the property represented by each bit.
    :type loval: list
    :returns: BitFieldByte instance.
    :rtype: BitFieldByte

    """

    def __init__(self, name, val=0, loval=["Undef"] * 8):
        self.name = name
        self._val = val
        self.loval = loval

    def encode(self):
        val = pack(">B", self._val)
        return val

    def decode(self, data):
        self._val = unpack(">B", data[:1])[0]
        return data[1:]

    def __len__(self):
        return 1

    @property
    def val(self):
        resu = {}
        mybit = 1 << (len(self.loval) - 1)
        for x in self.loval:
            if x not in ["Undef", "Reserv"]:
                resu[x] = (self._val & mybit) > 0
            mybit = mybit >> 1
        return resu

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        mybit = 1 << (len(self.loval) - 1)
        for x in self.loval:
            if x not in ["Undef", "Reserv"]:
                print(
                    "{}{}: {}".format(
                        PRINT_INDENT * (depth + 1),
                        x,
                        ((self._val & mybit) and "True") or False,
                    )
                )
            mybit = mybit >> 1


class IntByte:
    """Class representing a single byte as a signed integer.

    :param name: The name of the instance
    :type name: str
    :param val: the integer value.
    :type val: int
    :returns: IntByte instance.
    :rtype: IntByte

    """

    def __init__(self, name, val=0):
        self.name = name
        self.val = val

    def encode(self):
        val = pack(">b", self.val)
        return val

    def decode(self, data):
        self.val = unpack(">b", data[:1])[0]
        return data[1:]

    def __len__(self):
        return 1

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class UIntByte:
    """Class representing a single byte as an unsigned integer.

    :param name: The name of the instance
    :type name: str
    :param val: the integer value.
    :type val: int
    :returns: UIntByte instance.
    :rtype: UIntByte

    """

    def __init__(self, name, val=0):
        self.name = name
        self.val = val

    def encode(self):
        val = pack(">B", self.val)
        return val

    def decode(self, data):
        self.val = unpack(">B", data[:1])[0]
        return data[1:]

    def __len__(self):
        return 1

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class ShortInt:
    """Class representing 2 bytes as a signed integer.

    :param name: The name of the instance
    :type name: str
    :param val: the integer value.
    :type val: int
    :param endian: Endianess of the bytes. "big" or no "big" (i.e. "little")
    :type endian: str
    :returns: ShortInt instance.
    :rtype: ShortInt

    """

    def __init__(self, name, val=0, endian="big"):
        self.name = name
        self.val = val
        self.endian = endian

    def encode(self):
        if self.endian == "big":
            val = pack(">h", self.val)
        else:
            val = pack("<h", self.val)
        return val

    def decode(self, data):
        if self.endian == "big":
            self.val = unpack(">h", data[:2])[0]
        else:
            self.val = unpack("<h", data[:2])[0]
        return data[2:]

    def __len__(self):
        return 2

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class UShortInt:
    """Class representing 2 bytes as an unsigned integer.

    :param name: The name of the instance
    :type name: str
    :param val: the integer value.
    :type val: int
    :param endian: Endianess of the bytes. "big" or no "big" (i.e. "little")
    :type endian: str
    :returns: UShortInt instance.
    :rtype: UShortInt

    """

    def __init__(self, name, val=0, endian="big"):
        self.name = name
        self.val = val
        self.endian = endian

    def encode(self):
        if self.endian == "big":
            val = pack(">H", self.val)
        else:
            val = pack("<H", self.val)
        return val

    def decode(self, data):
        if self.endian == "big":
            self.val = unpack(">H", data[:2])[0]
        else:
            self.val = unpack("<H", data[:2])[0]
        return data[2:]

    def __len__(self):
        return 2

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class LongInt:
    """Class representing 4 bytes as a signed integer.

    :param name: The name of the instance
    :type name: str
    :param val: the integer value.
    :type val: int
    :param endian: Endianess of the bytes. "big" or no "big" (i.e. "little")
    :type endian: str
    :returns: LongInt instance.
    :rtype: LongInt

    """

    def __init__(self, name, val=0, endian="big"):
        self.name = name
        self.val = val
        self.endian = endian

    def encode(self):
        if self.endian == "big":
            val = pack(">l", self.val)
        else:
            val = pack("<l", self.val)
        return val

    def decode(self, data):
        if self.endian == "big":
            self.val = unpack(">l", data[:4])[0]
        else:
            self.val = unpack("<l", data[:4])[0]
        return data[4:]

    def __len__(self):
        return 4

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class ULongInt:
    """Class representing 4 bytes as an unsigned integer.

    :param name: The name of the instance
    :type name: str
    :param val: the integer value.
    :type val: int
    :param endian: Endianess of the bytes. "big" or no "big" (i.e. "little")
    :type endian: str
    :returns: ULongInt instance.
    :rtype: ULongInt

    """

    def __init__(self, name, val=0, endian="big"):
        self.name = name
        self.val = val
        self.endian = endian

    def encode(self):
        if self.endian == "big":
            val = pack(">L", self.val)
        else:
            val = pack("<L", self.val)
        return val

    def decode(self, data):
        if self.endian == "big":
            self.val = unpack(">L", data[:4])[0]
        else:
            self.val = unpack("<L", data[:4])[0]
        return data[4:]

    def __len__(self):
        return 4

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class OgfOcf:
    """Class representing the 2 bytes that specify the command in an HCI command packet.

    :param name: The name of the instance
    :type name: str
    :param ogf: the Op-code Group (6 bits).
    :type ogf: bytes
    :param ocf: the Op-code Command (10 bits).
    :type ocf: bytes
    :returns: OgfOcf instance.
    :rtype: OgfOcf

    """

    def __init__(self, name, ogf=b"\x00", ocf=b"\x00"):
        self.name = name
        self.ogf = ogf
        self.ocf = ocf

    def encode(self):
        val = pack("<H", (ord(self.ogf) << 10) | ord(self.ocf))
        return val

    def decode(self, data):
        val = unpack("<H", data[: len(self)])[0]
        self.ogf = val >> 10
        self.ocf = int(val - (self.ogf << 10)).to_bytes(1, "big")
        self.ogf = int(self.ogf).to_bytes(1, "big")
        return data[len(self) :]

    def __len__(self):
        return calcsize("<H")

    def show(self, depth=0):
        print("{}Cmd Group:".format(PRINT_INDENT * depth))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.ogf))
        print("{}Cmd Code:".format(PRINT_INDENT * depth))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.ocf))


class Itself:
    """Class representing a byte array that need no manipulation.

    :param name: The name of the instance
    :type name: str
    :returns: Itself instance.
    :rtype: Itself

    """

    def __init__(self, name):
        self.name = name
        self.val = b""

    def encode(self):
        val = pack(">%ds" % len(self.val), self.val)
        return val

    def decode(self, data):
        self.val = unpack(">%ds" % len(data), data)[0]
        return b""

    def __len__(self):
        return len(self.val)

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print(
            "{}{}".format(
                PRINT_INDENT * (depth + 1),
                ":".join(map(lambda b: format(b, "02x"), self.val)),
            )
        )


class String:
    """Class representing a string.

    :param name: The name of the instance
    :type name: str
    :returns: String instance.
    :rtype: String

    """

    def __init__(self, name):
        self.name = name
        self.val = ""

    def encode(self):
        if isinstance(self.val, str):
            self.val = self.val.encode()
        val = pack(">%ds" % len(self.val), self.val)
        return val

    def decode(self, data):
        self.val = data
        return b""

    def __len__(self):
        return len(self.val)

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class NBytes:
    """Class representing a byte string.

    :param name: The name of the instance
    :type name: str
    :param length: The length
    :type length: int
    :returns: NBytes instance.
    :rtype: NBytes

    """

    def __init__(self, name, length=2):
        self.name = name
        self.length = length
        self.val = b""

    def encode(self):
        return pack(">%ds" % self.length, self.val)

    def decode(self, data):
        self.val = unpack(">%ds" % self.length, data[: self.length])[0][::-1]
        return data[self.length :]

    def __len__(self):
        return self.length

    def show(self, depth=0):
        if self.name:
            print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print(
            "{}{}".format(
                PRINT_INDENT * (depth + 1),
                ":".join(map(lambda b: format(b, "02x"), self.val)),
            )
        )

    def __eq__(self, b):
        return self.val == b


class NBytes_List:
    """Class representing a list of bytes string.

    :param name: The name of the instance
    :type name: str
    :param bytes: Length of the bytes strings (2, 4 or 16)
    :type nbytes: int
    :returns: NBytes_List instance.
    :rtype: NBytes_List

    """

    def __init__(self, name, nbytes=2):
        # Bytes should be one of 2, 4 or 16
        self.name = name
        self.length = nbytes
        self.lonbytes = []

    def decode(self, data):
        while data:
            mynbyte = NBytes("", self.length)
            data = mynbyte.decode(data)
            self.lonbytes.append(mynbyte)
        return data

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        for x in self.lonbytes:
            x.show(depth + 1)

    def __len__(self):
        return len(self.lonbytes) + self.length

    def __contains__(self, b):
        for x in self.lonbytes:
            if b == x:
                return True

        return False


class Float88:
    """Class representing a 8.8 fixed point quantity.

    :param name: The name of the instance
    :type name: str
    :returns: Float88 instance.
    :rtype: Float88

    """

    def __init__(self, name):
        self.name = name
        self.val = 0.0

    def encode(self):
        val = pack(">h", int(self.val * 256))
        return val

    def decode(self, data):
        self.val = unpack(">h", data[:2])[0] / 256.0
        return data[2:]

    def __len__(self):
        return 2

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        print("{}{}".format(PRINT_INDENT * (depth + 1), self.val))


class EmptyPayload:
    def __init__(self):
        pass

    def encode(self):
        return b""

    def decode(self, data):
        return data

    def __len__(self):
        return 0

    def show(self, depth=0):
        return


#
# Bluetooth starts here
#


class Packet:
    """Class representing a generic HCI packet.

    :param header: The packet header.
    :type header: bytes
    :returns: Packet instance.
    :rtype: Packet

    """

    """A generic packet that will be build fromparts"""

    def __init__(self, header="\x00", fmt=">B"):
        self.header = header
        self.fmt = fmt
        self.payload = []
        self.raw_data = None

    def encode(self):
        return pack(self.fmt, self.header)

    def decode(self, data):
        try:
            if unpack(self.fmt, data[: calcsize(self.fmt)])[0] == self.header:
                self.raw_data = data
                return data[calcsize(self.fmt) :]
        except:
            pass
        return None

    def retrieve(self, aclass):
        """Look for a specifc class/name in the packet"""
        resu = []
        for x in self.payload:
            try:
                if isinstance(aclass, str):
                    if x.name == aclass:
                        resu.append(x)
                else:
                    if isinstance(x, aclass):
                        resu.append(x)

                resu += x.retrieve(aclass)
            except:
                pass
        return resu


#
# Commands
#


class HCI_Command(Packet):
    """Class representing a command HCI packet.

    :param ogf: the Op-code Group (6 bits).
    :type ogf: bytes
    :param ocf: the Op-code Command (10 bits).
    :type ocf: bytes
    :returns: HCI_Command instance.
    :rtype: HCI_Command

    """

    def __init__(self, ogf, ocf):
        super().__init__(HCI_COMMAND)
        self.cmd = OgfOcf("command", ogf, ocf)
        self.payload = []

    def encode(self):
        pld = b""
        for x in self.payload:
            pld += x.encode()
        plen = len(pld)
        pld = b"".join([super().encode(), self.cmd.encode(), pack(">B", plen), pld])
        return pld

    def show(self, depth=0):
        self.cmd.show(depth)
        for x in self.payload:
            x.show(depth + 1)


class RepeatedField(Packet):
    def __init__(self, name, subfield_cls, length_field_cls=UIntByte):
        super().__init__()
        self.name = name
        self.subfield_cls = subfield_cls
        self.length_field = length_field_cls("count of " + name)
        self.payload = []

    def decode(self, data):
        self.payload = []
        data = self.length_field.decode(data)
        for x in range(self.length_field.val):
            field = self.subfield_cls()
            data = field.decode(data)
            self.payload.append(field)

        return data

    def show(self, depth=0):
        print("{}{}: {}".format(PRINT_INDENT * depth, self.name, self.length_field.val))
        for field in self.payload:
            field.show(depth + 1)


class HCI_Cmd_Read_Local_Supported_Commands(HCI_Command):
    """Class representing a HCI command to read commands supported by local
    controller.
    """

    def __init__(self):
        super().__init__(b"\x04", b"\x02")


class HCI_Cmd_LE_Read_Local_Supported_Features(HCI_Command):
    """Class representing a HCI command to read LE features."""

    def __init__(self):
        super().__init__(b"\x08", b"\x03")


class HCI_Cmd_LE_Scan_Enable(HCI_Command):
    """Class representing a command HCI command to enable/disable BLE scanning.

    :param enable: enable/disable scanning.
    :type enable: bool
    :param filter_dups: filter duplicates.
    :type filter_dups: bool
    :returns: HCI_Cmd_LE_Scan_Enable instance.
    :rtype: HCI_Cmd_LE_Scan_Enable

    """

    def __init__(self, enable=True, filter_dups=True):
        super().__init__(b"\x08", b"\x0c")
        self.payload.append(Bool("enable", enable))
        self.payload.append(Bool("filter", filter_dups))


class HCI_Cmd_LE_Set_Scan_Params(HCI_Command):
    """Class representing an HCI command to set the scanning parameters.

    This will set a number of parameters related to the scanning functions. For the
    interval and window, it will always silently enforce the Specs that says it should be >= 2.5 ms
    and <= 10.24s. It will also silently enforce window <= interval

        :param scan_type: Type of scanning. 0 => Passive (default)
                                            1 => Active
        :type scan_type: int
        :param interval: Time in ms between the start of a scan and the next scan start. Default 10
        :type interval: int/float
        :param window: maximum advertising interval in ms. Default 10
        :type window: int.float
        :param oaddr_type: Type of own address Value 0 => public (default)
                                                     1 => Random
                                                     2 => Private with public fallback
                                                     3 => Private with random fallback
        :type oaddr_type: int
        :param nfilter: How white list filter is applied. 0 => No filter (Default)
                                                          1 => sender must be in white list
                                                          2 => Similar to 0. Some directed advertising may be received.
                                                          3 => Similar to 1. Some directed advertising may be received.
        :type nfilter: int
        :returns: HCI_Cmd_LE_Set_Scan_Params instance.
        :rtype: HCI_Cmd_LE_Set_Scan_Params

    """

    def __init__(self, scan_type=0x0, interval=10, window=750, oaddr_type=0, nfilter=0):

        super().__init__(b"\x08", b"\x0b")
        self.payload.append(
            EnumByte("scan type", scan_type, {0: "Passive", 1: "Active"})
        )
        self.payload.append(
            UShortInt(
                "Interval",
                int(round(min(10240, max(2.5, interval)) / 0.625)),
                endian="little",
            )
        )
        self.payload.append(
            UShortInt(
                "Window",
                int(round(min(10240, max(2.5, min(interval, window))) / 0.625)),
                endian="little",
            )
        )
        self.payload.append(
            EnumByte(
                "own addresss type",
                oaddr_type,
                {
                    0: "Public",
                    1: "Random",
                    2: "Private IRK or Public",
                    3: "Private IRK or Random",
                },
            )
        )
        self.payload.append(
            EnumByte(
                "filter policy",
                nfilter,
                {
                    0: "None",
                    1: "Sender In White List",
                    2: "Almost None",
                    3: "SIWL and some",
                },
            )
        )


class HCI_Cmd_LE_Advertise(HCI_Command):
    """Class representing a command HCI command to enable/disable BLE advertising.

    :param enable: enable/disable advertising.
    :type enable: bool
    :returns: HCI_Cmd_LE_Scan_Enable instance.
    :rtype: HCI_Cmd_LE_Scan_Enable

    """

    def __init__(self, enable=True):
        super().__init__(b"\x08", b"\x0a")
        self.payload.append(Bool("enable", enable))


class HCI_Cmd_LE_Set_Advertised_Msg(HCI_Command):
    """Class representing an HCI command to set the advertised content.

    :param enable: enable/disable advertising.
    :type enable: bool
    :returns: HCI_Cmd_LE_Scan_Enable instance.
    :rtype: HCI_Cmd_LE_Scan_Enable

    """

    def __init__(self, msg=EmptyPayload()):
        super().__init__(b"\x08", b"\x08")
        self.payload.append(msg)


class HCI_Cmd_LE_Set_Advertised_Params(HCI_Command):
    """Class representing an HCI command to set the advertised parameters.

    This will set a number of parameters relted to the advertising functions. For the
    min and max intervals, it will always silently enforce the Specs that says it should be >= 20ms
    and <= 10.24s. It will also silently enforce interval_max >= interval_min

        :param interval_min: minimum advertising interval in ms. Default 500
        :type interval_min: int/float
        :param interval_max: maximum advertising interval in ms. Default 750
        :type interval_max: int/float
        :param adv_type: Type of advertising. Value 0 +> Connectable, Scanable advertising
                                                    1 => Connectable directed advertising (High duty cycle)
                                                    2 => Scannable Undirected advertising
                                                    3 => Non connectable undirected advertising (default)
        :type adv_type: int
        :param oaddr_type: Type of own address Value 0 => public (default)
                                                     1 => Random
                                                     2 => Private with public fallback
                                                     3 => Private with random fallback
        :type oaddr_type: int
        :param paddr_type: Type of peer address Value 0 => public (default)
                                                      1 => Random
        :type paddr_type: int
        :param peer_addr: Peer MAC address Default 00:00:00:00:00:00
        :type peer_addr: str
        :param cmap: Channel map. A bit field dfined as  "Channel 37","Channel 38","Channel 39","RFU","RFU","RFU","RFU","RFU"
        Default value is 0x7. The value 0x0 is RFU.
        :type cmap: int
        :param nfilter: How white list filter is applied. 0 => No filter (Default)
                                                          1 => scan are filtered
                                                          2 => Connection are filtered
                                                          3 => scan and connection are filtered
        :type nfilter: int
        :returns: HCI_Cmd_LE_Scan_Params instance.
        :rtype: HCI_Cmd_LE_Scan_Params

    """

    def __init__(
        self,
        interval_min=500,
        interval_max=750,
        adv_type=0x3,
        oaddr_type=0,
        paddr_type=0,
        peer_addr="00:00:00:00:00:00",
        cmap=0x7,
        nfilter=0,
    ):

        super().__init__(b"\x08", b"\x06")
        self.payload.append(
            UShortInt(
                "Adv minimum",
                int(round(min(10240, max(20, interval_min)) / 0.625)),
                endian="little",
            )
        )
        self.payload.append(
            UShortInt(
                "Adv maximum",
                int(
                    round(min(10240, max(20, max(interval_min, interval_max))) / 0.625)
                ),
                endian="little",
            )
        )
        self.payload.append(
            EnumByte(
                "adv type",
                adv_type,
                {
                    0: "ADV_IND",
                    1: "ADV_DIRECT_IND high",
                    2: "ADV_SCAN_IND",
                    3: "ADV_NONCONN_IND",
                    4: "ADV_DIRECT_IND low",
                },
            )
        )
        self.payload.append(
            EnumByte(
                "own addresss type",
                paddr_type,
                {
                    0: "Public",
                    1: "Random",
                    2: "Private IRK or Public",
                    3: "Private IRK or Random",
                },
            )
        )
        self.payload.append(
            EnumByte("peer addresss type", oaddr_type, {0: "Public", 1: "Random"})
        )
        self.payload.append(MACAddr("peer", mac=peer_addr))
        self.payload.append(
            BitFieldByte(
                "Channels",
                cmap,
                [
                    "Channel 37",
                    "Channel 38",
                    "Channel 39",
                    "RFU",
                    "RFU",
                    "RFU",
                    "RFU",
                    "RFU",
                ],
            )
        )

        self.payload.append(
            EnumByte(
                "filter policy",
                nfilter,
                {0: "None", 1: "Scan", 2: "Connection", 3: "Scan and Connection"},
            )
        )


class HCI_Cmd_LE_Set_Extended_Scan_Enable(HCI_Command):
    """Class representing a HCI command to enable/disable BLE scanning.

    :param enable: enable/disable scanning.
    :type enable: bool
    :param filter_dups: filter duplicates.
    :param filter_dups: filter duplicates 0 => Duplicate filtering disabled
                                          1 => Duplicate filtering enabled (default)
                                          2 => Duplicate filtering enabled, reset for each scan period
    :type filter_dups: int
    :param duration: scan duration in ms. 0 as scan continuously until explicitly disable. Default 0.
    :type duration: int
    :param period: scan period in ms. 0 as scan continuously. Default 0.
    :type period: int
    :returns: HCI_Cmd_LE_Set_Extended_Scan_Enable instance.
    :rtype: HCI_Cmd_LE_Set_Extended_Scan_Enable

    """

    def __init__(self, enable=True, filter_dups=1, duration=0, period=0):
        super().__init__(b"\x08", b"\x42")
        self.payload.append(Bool("enable", enable))
        self.payload.append(
            EnumByte(
                "filter",
                filter_dups,
                {0: "Disable", 1: "Enable", 2: "Eanble with reset"},
            )
        )
        self.payload.append(
            UShortInt(
                "Duration", int(round(min(0xFFFF * 10, duration) / 10)), endian="little"
            )
        )
        self.payload.append(
            UShortInt(
                "Period",
                int(round(min(0xFFFF * 1280, max(period, duration)) / 1280)),
                endian="little",
            )
        )


class HCI_Cmd_LE_Set_Extended_Scan_Params(HCI_Command):
    """Class representing an HCI command to set the scanning parameters.

    This will set a number of parameters related to the scanning functions. For the
    interval and window, it will always silently enforce the Specs that says it should be >= 2.5 ms
    and <= 10.24s. It will also silently enforce window <= interval

        :param oaddr_type: Type of own address Value 0 => public (default)
                                                     1 => Random
                                                     2 => Private with public fallback
                                                     3 => Private with random fallback
        :type oaddr_type: int
        :param filter: How white list filter is applied. 0 => No filter (Default)
                                                         1 => sender must be in white list
                                                         2 => Similar to 0. Some directed advertising may be received.
                                                         3 => Similar to 1. Some directed advertising may be received.
        :type filter: int
        :param phys: Scanning PHYs bit field mask as bit 0 => LE 1M PHY (default)
                                                     bit 2 => LE Coded PHY
        :type phys: int
        :param scan_type: Type of scanning. 0 => Passive (default)
                                            1 => Active
        :type scan_type: int
        :param interval: Time in ms between the start of a scan and the next scan start. Default 10
        :type interval: int/float
        :param window: maximum advertising interval in ms. Default 10
        :type window: int.float
        :returns: HCI_Cmd_LE_Set_Extended_Scan_Params instance.
        :rtype: HCI_Cmd_LE_Set_Extended_Scan_Params

    """

    def __init__(
        self,
        oaddr_type=0,
        nfilter=0,
        phys=1,
        scan_type=[0] * 8,
        interval=[10] * 8,
        window=[750] * 8,
    ):

        super().__init__(b"\x08", b"\x41")
        self.payload.append(
            EnumByte(
                "own addresss type",
                oaddr_type,
                {
                    0: "Public",
                    1: "Random",
                    2: "Private IRK or Public",
                    3: "Private IRK or Random",
                },
            )
        )
        self.payload.append(
            EnumByte(
                "filter policy",
                nfilter,
                {
                    0: "None",
                    1: "Sender In White List",
                    2: "Almost None",
                    3: "SIWL and some",
                },
            )
        )
        self.payload.append(
            BitFieldByte(
                "PHYs",
                phys,
                [
                    "LE 1M",
                    "Reserv",
                    "LE Coded",
                    "Reserv",
                    "Reserv",
                    "Reserv",
                    "Reserv",
                    "Reserv",
                ],
            )
        )

        i = 0
        mask = 0x01
        while i < 8:
            if phys & mask:
                self.payload.append(
                    EnumByte("scan type", scan_type[i], {0: "Passive", 1: "Active"})
                )
                self.payload.append(
                    UShortInt(
                        "Interval",
                        int(round(min(10240, max(2.5, interval[i])) / 0.625)),
                        endian="little",
                    )
                )
                self.payload.append(
                    UShortInt(
                        "Window",
                        int(
                            round(
                                min(10240, max(2.5, min(interval[i], window[i])))
                                / 0.625
                            )
                        ),
                        endian="little",
                    )
                )
            mask <<= 1
            i += 1


class HCI_Cmd_Reset(HCI_Command):
    """Class representing an HCI command to reset the adapater.


    :returns: HCI_Cmd_Reset instance.
    :rtype: HCI_Cmd_Reset

    """

    def __init__(self):
        super().__init__(b"\x03", b"\x03")


####
# HCI EVents
####


class HCI_Event(Packet):
    def __init__(self, code=0, payload=[]):
        super().__init__(HCI_EVENT)
        self.payload.append(Byte("code"))
        self.payload.append(UIntByte("length"))

    def decode(self, data):
        data = super().decode(data)
        if data is None:
            return None

        for x in self.payload:
            x.decode(data[: len(x)])
            data = data[len(x) :]
        code = self.payload[0]
        length = self.payload[1].val
        if code.val == b"\x0e":
            ev = HCI_CC_Event()
            data = ev.decode(data)
            self.payload.append(ev)
        elif code.val == b"\x3e":
            ev = HCI_LE_Meta_Event()
            data = ev.decode(data)
            self.payload.append(ev)
        else:
            ev = Itself("Payload")
            data = ev.decode(data)
            self.payload.append(ev)
        return data

    def show(self, depth=0):
        print("{}HCI Event:".format(PRINT_INDENT * depth))
        for x in self.payload:
            x.show(depth + 1)


class HCI_CC_Event(Packet):
    """Command Complete event"""

    def __init__(self):
        self.name = "Command Completed"
        self.payload = [UIntByte("allow pkt"), OgfOcf("cmd"), Itself("resp code")]

    def decode(self, data):
        for x in self.payload:
            data = x.decode(data)
        return data

    def show(self, depth=0):
        for x in self.payload:
            x.show(depth + 1)


class HCI_LE_Meta_Event(Packet):
    def __init__(self):
        self.name = "LE Meta"
        self.payload = [Byte("code")]

    def decode(self, data):
        for x in self.payload:
            data = x.decode(data)
        code = self.payload[0]
        if code.val == b"\x02":
            ev = RepeatedField("Adv Report", HCI_LEM_Adv_Report)
        elif code.val == b"\x0d":
            ev = RepeatedField("Ext Adv Report", HCI_LEM_Ext_Adv_Report)
        else:
            ev = Itself("Payload")

        data = ev.decode(data)
        self.payload.append(ev)
        return data

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        for x in self.payload:
            x.show(depth + 1)


class HCI_LEM_Adv_Report(Packet):
    def __init__(self):
        self.name = "Adv Report"
        self.payload = [
            EnumByte(
                "ev type", 0, {0: "generic adv", 3: "no connection adv", 4: "scan rsp"}
            ),
            EnumByte("addr type", 0, {0: "public", 1: "random"}),
            MACAddr("peer"),
            UIntByte("length"),
        ]

    def decode(self, data):

        for x in self.payload:
            data = x.decode(data)
        # Now we have a sequence of len, type data with possibly a RSSI byte at the end
        while len(data) > 1:
            ad = AD_Structure()
            data = ad.decode(data)
            self.payload.append(ad)

        if data:
            myinfo = IntByte("rssi")
            data = myinfo.decode(data)
            self.payload.append(myinfo)
        return data

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        for x in self.payload:
            x.show(depth + 1)


class HCI_LEM_Ext_Adv_Report(Packet):
    def __init__(self):
        self.name = "Ext Adv Report"
        self.payload = [
            BitFieldByte(
                "ev type",
                0,
                [
                    "Connectable",
                    "Scannable",
                    "Directed",
                    "Scan Response",
                    "Legacy",
                    "Incomplete/more",
                    "Incomplete/truncated",
                    "RFU",
                ],
            ),
            UIntByte("unused"),
            EnumByte(
                "addr type",
                0,
                {
                    0: "public device",
                    1: "random device",
                    2: "public identity",
                    3: "random identity",
                    0xFF: "anonymous",
                },
            ),
            MACAddr("peer"),
            EnumByte("primary phy", 1, {1: "LE 1M", 3: "LE Coded"}),
            EnumByte(
                "secondary phy", 0, {0: "N/A", 1: "LE 1M", 2: "LE 2M", 3: "LE Coded"}
            ),
            EnumByte(
                "adv sid",
                255,
                {
                    0: "0x00",
                    1: "0x01",
                    2: "0x02",
                    3: "0x03",
                    4: "0x04",
                    5: "0x05",
                    6: "0x06",
                    7: "0x07",
                    8: "0x08",
                    9: "0x09",
                    10: "0x0A",
                    11: "0x0B",
                    12: "0x0C",
                    13: "0x0D",
                    14: "0x0E",
                    15: "0x0F",
                    0xFF: "N/A",
                },
            ),
            IntByte("tx power"),
            IntByte("rssi"),
            UShortInt("adv interval", endian="little"),
            EnumByte(
                "direct addr type",
                0,
                {
                    0: "public device",
                    1: "random device",
                    2: "public identity",
                    3: "random identity",
                    0xFE: "random device",
                },
            ),
            MACAddr("direct addr"),
            UIntByte("data len"),
        ]

    def decode(self, data):

        for x in self.payload:
            data = x.decode(data)

        datalength = self.payload[-1].val
        while datalength > 0:
            ad = AD_Structure()
            data = ad.decode(data)
            self.payload.append(ad)

            datalength -= len(ad)
        return data

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        for x in self.payload:
            x.show(depth + 1)


class EIR_Hdr(Packet):
    def __init__(self):
        self.type = EnumByte(
            "type",
            0,
            {
                0x01: "flags",
                0x02: "incomplete_list_16_bit_svc_uuids",
                0x03: "complete_list_16_bit_svc_uuids",
                0x04: "incomplete_list_32_bit_svc_uuids",
                0x05: "complete_list_32_bit_svc_uuids",
                0x06: "incomplete_list_128_bit_svc_uuids",
                0x07: "complete_list_128_bit_svc_uuids",
                0x08: "shortened_local_name",
                0x09: "complete_local_name",
                0x0A: "tx_power_level",
                0x0D: "class_of_device",
                0x0E: "simple_pairing_hash",
                0x0F: "simple_pairing_rand",
                0x10: "sec_mgr_tk",
                0x11: "sec_mgr_oob_flags",
                0x12: "slave_conn_intvl_range",
                0x17: "pub_target_addr",
                0x18: "rand_target_addr",
                0x19: "appearance",
                0x1A: "adv_intvl",
                0x1B: "le_addr",
                0x1C: "le_role",
                0x14: "list_16_bit_svc_sollication_uuids",
                0x1F: "list_32_bit_svc_sollication_uuids",
                0x15: "list_128_bit_svc_sollication_uuids",
                0x16: "svc_data_16_bit_uuid",
                0x20: "svc_data_32_bit_uuid",
                0x21: "svc_data_128_bit_uuid",
                0x22: "sec_conn_confirm",
                0x23: "sec_conn_rand",
                0x24: "uri",
                0xFF: "mfg_specific_data",
            },
        )

    def decode(self, data):
        return self.type.decode(data)

    def show(self, depth=0):
        return self.type.show(depth)

    @property
    def val(self):
        return self.type.val

    @property
    def strval(self):
        return self.type.strval

    def __len__(self):
        return len(self.type)


class Adv_Data(Packet):
    def __init__(self, name, length):
        self.name = name
        self.length = length
        self.payload = []

    def decode(self, data):
        myinfo = NBytes("Service Data uuid", self.length)
        data = myinfo.decode(data)
        self.payload.append(myinfo)
        if data:
            myinfo = Itself("Adv Payload")
            data = myinfo.decode(data)
            self.payload.append(myinfo)
        return data

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        for x in self.payload:
            x.show(depth + 1)

    def __len__(self):
        resu = 0
        for x in self.payload:
            resu += len(x)
        return resu


class AD_Structure(Packet):
    def __init__(self):
        self.name = ""
        self.length = 0
        self.payload = []

    def __len__(self):
        return self.length

    def decode(self, data):
        length = UIntByte("sublen")
        data = length.decode(data)
        self.length = len(length) + length.val
        self.payload = []
        if length.val == 0:
            return data

        type = EIR_Hdr()
        data = type.decode(data)

        val = None
        if type.val == 0x01:
            val = BitFieldByte(
                "flags",
                0,
                [
                    "Undef",
                    "Undef",
                    "Simul LE - BR/EDR (Host)",
                    "Simul LE - BR/EDR (Control.)",
                    "BR/EDR Not Supported",
                    "LE General Disc.",
                    "LE Limited Disc.",
                ],
            )
        elif type.val == 0x02:
            val = NBytes_List("Incomplete uuids", 2)
        elif type.val == 0x03:
            val = NBytes_List("Complete uuids", 2)
        elif type.val == 0x04:
            val = NBytes_List("Incomplete uuids", 4)
        elif type.val == 0x05:
            val = NBytes_List("Complete uuids", 4)
        elif type.val == 0x06:
            val = NBytes_List("Incomplete uuids", 16)
        elif type.val == 0x07:
            val = NBytes_List("Complete uuids", 16)
        elif type.val == 0x08:
            val = String("Short Name")
        elif type.val == 0x09:
            val = String("Complete Name")
        elif type.val == 0x14:
            val = NBytes_List("Service Solicitation uuid", 2)
        elif type.val == 0x15:
            val = NBytes_List("Service Solicitation uuid", 16)
        elif type.val == 0x16:
            val = Adv_Data("Advertised Data", 2)
        elif type.val == 0x1F:
            val = NBytes_List("Service Solicitation uuid", 4)
        elif type.val == 0x20:
            val = Adv_Data("Advertised Data", 4)
        elif type.val == 0x21:
            val = Adv_Data("Advertised Data", 16)
        elif type.val == 0xFF:
            val = ManufacturerSpecificData()
        else:
            val = Itself("Payload for %s" % type.strval)

        # Some data type may consume all input data, therefore an copy
        # is passed instead.
        val.decode(data[: length.val - len(type)])

        self.payload.append(type)
        self.payload.append(val)

        return data[length.val - len(type) :]

    def show(self, depth=0):
        for x in self.payload:
            x.show(depth + 1)


class ManufacturerSpecificData(Packet):
    def __init__(self, name="Manufacturer Specific Data"):
        self.name = name
        self.payload = [
            UShortInt("Manufacturer ID", endian="little"),
            Itself("Payload"),
        ]

    def decode(self, data):
        # Warning: Will consume all the data you give it!
        for p in self.payload:
            data = p.decode(data)
        return data

    def show(self, depth=0):
        print("{}{}:".format(PRINT_INDENT * depth, self.name))
        for x in self.payload:
            x.show(depth + 1)


#
# The defs are over. Now the realstuffs
#


def create_bt_socket(interface=None):
    exceptions = []
    sock = None
    try:
        sock = socket.socket(
            family=socket.AF_BLUETOOTH, type=socket.SOCK_RAW, proto=socket.BTPROTO_HCI
        )
        sock.setblocking(False)
        if platform.system() == "Linux":
            sock.setsockopt(
                socket.SOL_HCI,
                socket.HCI_FILTER,
                pack("IIIh2x", 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0),
            )  # type mask, event mask, event mask, opcode
            if not interface:
                interface = 0
            try:
                sock.bind((interface,))
            except OSError as exc:
                exc = OSError(
                    exc.errno,
                    "error while attempting to bind on "
                    "interface {!r}: {}".format(interface, exc.strerror),
                )
                exceptions.append(exc)
        elif platform.system() == "FreeBSD":
            import ctypes, ctypes.util

            sock.setsockopt(
                socket.SOL_HCI,
                socket.HCI_FILTER,
                pack("IQ", 0x00000000, 0x2000000000000000),
            )  # type mask, event mask
            if not interface:
                interface = "ubt0"
            libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
            # bind/connect via libc is required due to https://bugs.python.org/issue41130
            class SockaddrHci(ctypes.Structure):
                _fields_ = [
                    ("hci_len", ctypes.c_char),
                    ("hci_family", ctypes.c_char),
                    ("hci_node", ctypes.c_char * 32),
                ]

            adr = SockaddrHci(
                ctypes.sizeof(SockaddrHci),
                socket.AF_BLUETOOTH,
                (interface + "hci").ljust(32, "\0").encode("utf-8"),
            )
            if (
                libc.bind(
                    sock.fileno(), ctypes.pointer(adr), ctypes.sizeof(SockaddrHci)
                )
                != 0
            ):
                raise OSError(ctypes.get_errno(), "bind")
            if (
                libc.connect(
                    sock.fileno(), ctypes.pointer(adr), ctypes.sizeof(SockaddrHci)
                )
                != 0
            ):
                raise OSError(ctypes.get_errno(), "connect")
        else:
            raise OSError("Platform not supported")
    except OSError as exc:
        if sock is not None:
            sock.close()
        exceptions.append(exc)
    except:
        if sock is not None:
            sock.close()
        raise
    if len(exceptions) == 1:
        raise exceptions[0]
    elif len(exceptions) > 1:
        model = str(exceptions[0])
        if all(str(exc) == model for exc in exceptions):
            raise exceptions[0]
        raise OSError(
            "Multiple exceptions: {}".format(", ".join(str(exc) for exc in exceptions))
        )
    return sock


###########


class BLEScanRequester(asyncio.Protocol):
    """Protocol handling the requests"""

    def __init__(self):
        self._supported_commands = None
        self._le_features = None
        self._initialized = asyncio.Event()
        self._uninitialized = True
        self.transport = None
        self.smac = None
        self.sip = None
        self.process = self.default_process

    def _use_ext_scan(self):
        # Bluetooth Core Specification Vol 2, Part E, Section 6.27
        # Use LE extended scan if HCI_Cmd_LE_Set_Extended_Scan_Enable/Params are
        # supported.
        return (self._supported_commands[37] & 0x60) == 0x60

    def connection_made(self, transport):
        self.transport = transport

        command = HCI_Cmd_Read_Local_Supported_Commands()
        self.transport.write(command.encode())

    def connection_lost(self, exc):
        super().connection_lost(exc)

    async def send_scan_request(self, isactivescan=False):
        """Sending LE scan request"""
        await self._initialized.wait()

        if self._use_ext_scan():
            sparam = [int(isactivescan)] * 8
            command = HCI_Cmd_LE_Set_Extended_Scan_Params(scan_type=sparam)
            self._send_command_no_wait(command)

            command = HCI_Cmd_LE_Set_Extended_Scan_Enable(True, 0)
            return self._send_command_no_wait(command)
        else:
            sparam = int(isactivescan)
            command = HCI_Cmd_LE_Set_Scan_Params(scan_type=sparam)
            self._send_command_no_wait(command)

            command = HCI_Cmd_LE_Scan_Enable(True, False)
            return self._send_command_no_wait(command)

    async def stop_scan_request(self):
        """Sending LE scan request"""
        await self._initialized.wait()

        if self._use_ext_scan():
            command = HCI_Cmd_LE_Set_Extended_Scan_Enable(False, 0)
        else:
            command = HCI_Cmd_LE_Scan_Enable(False, False)

        return self._send_command_no_wait(command)

    def _send_command_no_wait(self, command):
        return self.transport.write(command.encode())

    async def send_command(self, command):
        """Sending an arbitrary command"""
        await self._initialized.wait()
        return self._send_command_no_wait(command)

    def data_received(self, packet):
        if self._uninitialized:
            ev = HCI_Event()
            extra_data = ev.decode(packet)
            if ev.payload[0].val == b"\x0e":
                cc = ev.retrieve("Command Completed")[0]
                cmd = cc.retrieve(OgfOcf)[0]
                opcode = (ord(cmd.ogf) << 10) | ord(cmd.ocf)
                resp = cc.retrieve("resp code")[0]
                if opcode == 0x1002:
                    self._handle_cc_read_local_supported_commands(resp)
                elif opcode == 0x2003:
                    self._handle_cc_le_read_local_supported_features(resp)

                return
        self.process(packet)

    def _handle_cc_read_local_supported_commands(self, resp):
        if resp.val[0] == 0:
            self._supported_commands = resp.val[1:]
        else:
            self._supported_commands = [0] * 64

        command = HCI_Cmd_LE_Read_Local_Supported_Features()
        self.transport.write(command.encode())

    def _handle_cc_le_read_local_supported_features(self, resp):
        if resp.val[0] == 0:
            self._le_features = resp.val[1:]
        else:
            self._le_features = [0] * 8

        self._initialized.set()
        self._uninitialized = False

    def default_process(self, data):
        pass
