from math import ceil
import time
import struct

def hp(b):
    return ' '.join(['{:02X}'.format(x) for x in b])

def hex_read(s):
    return [int(x, 16) for x in s.split()]

def bytes_to_ints(b):
    return [int(x) for x in b]

def hex_to_cmds(s):
    lines = s.split('\n')
    return [hex_read(l) for l in lines]

def truncate(s, lim = 100):
    if len(s) < lim:
        return s

    return s[:lim] + '...'

def LSB128(i):
    return (2**64 - 1) & i

def MSB128(i):
    return i >> 64

def start_btsnoop(f):
    f.write(b'btsnoop\x00')
    f.write(struct.pack('>II', 1, 1002))

def btsnoop_ts(us_since_epoch = None):
    if us_since_epoch is None:
        us_since_epoch = int(round(time.time() * 1000000))

    magic_1 = 0x00E03AB44A676000;
    magic_2 = 1000000 * 86400 * (365 * 23 + 366 * 7);
    us_since_1Jan2000 = us_since_epoch - magic_2
    return us_since_1Jan2000 + magic_1

def pcap_ts(us_since_epoch = None):
    if us_since_epoch is None:
        us_since_epoch = int(round(time.time() * 1000000))

    ns_since_epoch = us_since_epoch * 1000

    return ns_since_epoch // 1000000000, ns_since_epoch % 1000000000

def pcapng_ts(us_since_epoch = None):
    if us_since_epoch is None:
        us_since_epoch = int(round(time.time() * 1000000))

    ns_since_epoch = us_since_epoch * 1000

    return ns_since_epoch >> 32, 0xFFFFFFFF & ns_since_epoch

def build_btsnoop_pkt(H2C, payl, us_since_epoch = None):
    t = btsnoop_ts(us_since_epoch)
    p_len = len(payl)
    if H2C:
        direction = 0
    else:
        direction = 1

    return (struct.pack('>IIII', p_len, p_len, direction, 0) + 
                struct.pack('>II', t >> 32, t & 0xFFFFFFFF) + bytes(payl))

def build_pcap_pkt(H2C, payl, us_since_epoch = None):
    secs, nanoseconds = pcap_ts(us_since_epoch)
    p_len = len(payl) + 4
    if H2C:
        direction = 0
    else:
        direction = 1

    return (struct.pack('<IIII', secs, nanoseconds, p_len, p_len) + struct.pack('>I', direction) 
                 + bytes(payl))

def write_btsnoop_pkt(f, H2C, payl, us_since_epoch = None):
    f.write(build_btsnoop_pkt(H2C, payl, us_since_epoch))
    f.flush()

def pcapng_key_pkt(key, label: str = 'Injected key', addr0 = 0, addr1 = 0):
    l = 40 + 4 * ceil((1 + len(label)) / 4)    
    extra =  l - (len(label) + 1 + 40)
    return (struct.pack('<IIQQIHHIB', 0x80000200, l, LSB128(key), MSB128(key), 
                       0xFFFFFFFF & addr0, addr0 >> 32, 0xFFFF & addr1, addr1 >> 16, len(label)) + 
                    bytes(label, 'utf-8') + bytes(extra) + struct.pack('<I', l))
    
def pcapng_interface_description(link_type = 201, description = ''):
    opts_len = 0
    l = 20 + opts_len
    return struct.pack('<IIHHII', 0x1, l, link_type, 0xFFFF, 0xFFFFFFFF, l)

def pcapng_enhanced_pkt(id, payl, us_since_epoch = None):
    ts_msb, ts_lsb = pcapng_ts(us_since_epoch)

    l = 32 + 4 * ceil(len(payl) / 4)    
    extra =  l - (len(payl) +  32)
    return (struct.pack('<IIIIIII', 0x6, l, id, ts_msb, ts_lsb, len(payl), len(payl)) + 
                    bytes(payl) + bytes(extra) + struct.pack('<I', l))

def pcapng_h4_phdr(id, H2C, payl, us_since_epoch = None):
    if H2C:
        direction = 0
    else:
        direction = 1
    
    payl = struct.pack('>I', direction) + bytes(payl)
    return pcapng_enhanced_pkt(id, payl, us_since_epoch)


def pcap_file_header():
    return struct.pack('<IHHIIII', 0xA1B23C4D, 2, 4, 0, 0, 0xFFFFFFFF, 0xC9)

def bits24(val):
    if val >= 1<<24:
        raise ValueError('val {} is too large for 24-bits'.format(val))

    return struct.pack('3B', 0xFF & val, 0xFF & (val >> 8), 0xFF & (val >> 16))


class HCI_pkt:
    def __init__(self, bytes):
        self.bytes = bytes

    def __repr__(self):
        return hp(self.bytes)

    def type(self):
        return self.bytes[0]

    def hdr_matches(self, hdr):
        for i in range(len(hdr)):
            if i > len(self.bytes) or self.bytes[i] != hdr[i]:
                return False

        return True

    def is_CS_test(self):
        if len(self.bytes) < 3:
            return False

        return self.bytes[0] == 1 and self.bytes[1] == 0x95 and self.bytes[2] == 0x20


def HCI_pkt_len(payl):
    if len(payl) < 3:
        return None

    if payl[0] == 1: # Command
        return payl[3] + 4

    if payl[0] == 2: # ACL
        return payl[3] + (payl[4] << 8) + 5

    if payl[0] == 3: # SCO
        return payl[3] + 4

    if payl[0] == 4: # Event
        return payl[2] + 3

    if payl[0] == 5: # ISO
        return payl[3] + ((0x3F & payl[4]) << 8) + 5