Пример #1
0
    def scan_ll_feature(self, paddr, patype, timeout: int=10):
        """LL feature scanning

        paddr   - Peer addresss for scanning LL features.
        patype  - Peer address type, public or random.
        timeout - sec
        """
        spinner = Halo(text="Scanning", spinner={'interval': 200,
                                                 'frames': ['', '.', '.'*2, '.'*3]},
                       placement='right')
        hci = HCI(self.hci)
        logger.info('Scanning LE LL Features of %s, using %s\n'%(blue(paddr), blue(self.hci)))
        
        spinner.start()

        try:
            event_params = hci.le_create_connection(paddr, patype, timeout=timeout)
            logger.debug(event_params)
        except RuntimeError as e:
            logger.error(str(e))
            return
        except TimeoutError as e:
            logger.info("Timeout")
            # logger.error("TimeoutError {}".format(e))
            return

        event_params = hci.le_read_remote_features(event_params['Connection_Handle'])
        spinner.stop()
        logger.debug(event_params)
        print(blue('LE LL Features:'))
        pp_le_features(event_params['LE_Features'])

        event_params = hci.disconnect(event_params['Connection_Handle'], ControllerErrorCodes.REMOTE_USER_TERM_CONN)
        logger.debug(event_params)
        return
Пример #2
0
    def scan(self, remote_bd_addr:str):
        hci = HCI(self.iface)
        event_params = hci.create_connection({
            'BD_ADDR': remote_bd_addr,
            'Packet_Type': 0xcc18,
            'Page_Scan_Repetition_Mode': 0x02,
            'Reserved': 0x00,
            'Clock_Offset': 0x0000,
            'Allow_Role_Switch': 0x01
        })

        if event_params['Status'] != 0:
            print(ERROR, 'Failed to create ACL connection')
            sys.exit(1)

        event_params = hci.read_remote_version_information(cmd_params={
            'Connection_Handle': event_params['Connection_Handle']
        })

        if event_params['Status'] != 0:
            print(ERROR, 'Failed to read remote version')
            sys.exit(1)

        print(blue('Version'))
        print('    Version:')
        print(' '*8+lm_vers[event_params['Version']], '(LMP)')
        print(' '*8+ll_vers[event_params['Version']], '(LL)')
        print('    Manufacturer name:', event_params['Manufacturer_Name'])
        print('    Subversion:', event_params['Subversion'], '\n')

        event_params = hci.read_remote_supported_features({
            'Connection_Handle': event_params['Connection_Handle']
        })
        if event_params['Status'] != 0:
            print(ERROR, 'Failed to read remote supported features')
        else:
            print(blue('LMP features'))
            pp_lmp_features(event_params['LMP_Features'])
            print()

        if not True if (event_params['LMP_Features'][7] >> 7) & 0x01 else False:
            sys.exit(1)

        print(blue('Extended LMP features'))
        event_params = hci.read_remote_extended_features({
            'Connection_Handle': event_params['Connection_Handle'],
            'Page_Number': 0x00
        })
        if event_params['Status'] != 0:
            print(ERROR, 'Failed to read remote extented features')
        else:
            pp_ext_lmp_features(event_params['Extended_LMP_Features'], 0)
            for i in range(1, event_params['Maximum_Page_Number']+1):
                event_params = hci.read_remote_extended_features({
                    'Connection_Handle': event_params['Connection_Handle'],
                    'Page_Number': i})
                if event_params['Status'] != 0:
                    print(ERROR, 'Failed to read remote extented features, page', i)
                else:
                    pp_ext_lmp_features(event_params['Extended_LMP_Features'], i)
Пример #3
0
    def scan_ll_feature(self, paddr, patype):
        """LL feature scanning

        paddr  - Peer addresss for scanning LL features.
        patype - Peer address type, public or random.
        """
        hci = HCI(self.hci)
        logger.info('Scanning LE LL Features of %s, using %s\n' %
                    (blue(paddr), blue(self.hci)))

        try:
            event_params = hci.le_create_connection(
                HCI_Cmd_LE_Create_Connection(paddr=bytes.fromhex(
                    paddr.replace(':', ''))[::-1],
                                             patype=patype))
            logger.debug(event_params)
        except RuntimeError as e:
            logger.error(e)
            return

        event_params = hci.le_read_remote_features(
            HCI_Cmd_LE_Read_Remote_Features(
                handle=event_params['Connection_Handle']))
        logger.debug(event_params)
        print(blue('LE LL Features:'))
        pp_le_features(event_params['LE_Features'])

        event_params = hci.disconnect({
            'Connection_Handle':
            event_params['Connection_Handle'],
            'Reason':
            ERR_REMOTE_USER_TERMINATED_CONNECTION
        })
        logger.debug(event_params)
        return
Пример #4
0
    def detect_pairing_feature(self, paddr, patype, timeout:int=10):
        """
        """
        # TODO Mac OS 会弹窗,需要解决。
        hci = HCI(self.hci)
        logger.info("Detecting SMP pairing feature of %s, using %s\n"%(blue(paddr), blue(self.hci)))

        pairing_req = SM_Hdr(sm_command=btsmp.CmdCode.PAIRING_REQUEST) / \
            SM_Pairing_Request(iocap="NoInputNoOutput", oob='Not Present', 
                authentication=(0b00 << AUTHREQ_RFU_POS) | (0 << CT2_POS) | \
                    (0 << KEYPRESS_POS) | (1 << SC_POS) | (0 << MITM_POS) | \
                    (BONDING << BONDING_FLAGS_POS), max_key_size=16,
                initiator_key_distribution=(0b0000 << INIT_RESP_KEY_DIST_RFU_POS) \
                    | (1 << LINKKEY_POS) | (1 << SIGNKEY_POS) | (1 << IDKEY_POS) \
                    | (1 << ENCKEY_POS),
                responder_key_distribution=(0b0000 << INIT_RESP_KEY_DIST_RFU_POS) \
                    | (1 << LINKKEY_POS) | (1 << SIGNKEY_POS) | (1 << IDKEY_POS) \
                    | (1 << ENCKEY_POS))

        event_params = None
        
        spinner = Halo(text="Scanning", spinner={'interval': 200,
                                                 'frames': ['', '.', '.'*2, '.'*3]},
                       placement='right')
        hci = HCI(self.hci)
        logger.info('Scanning LE LL Features of %s, using %s\n'%(blue(paddr), blue(self.hci)))
        
        spinner.start()
        
        try:
            event_params = hci.le_create_connection(paddr, patype, timeout=timeout)
            logger.debug(event_params)

            result = btsmp.send_pairing_request(event_params['Connection_Handle'], pairing_req, self.hci)
            logger.debug("detect_pairing_feature(), result: {}".format(result))

            rsp = btsmp.recv_pairing_response(timeout, self.hci)
            logger.debug("detect_pairing_feature(), rsp: {}".format(rsp))
            
            spinner.stop()

            pp_smp_pkt(rsp)
        except RuntimeError as e:
            logger.error(str(e))
        except TimeoutError as e:
            output = subprocess.check_output(' '.join(['hciconfig', self.hci, 'reset']), 
                stderr=STDOUT, timeout=60, shell=True)
            event_params = None
            logger.info("Timeout")
            # logger.error("detect_pairing_feature(), TimeoutError {}".format(e))

        if event_params != None:
            hci.disconnect(event_params['Connection_Handle'],
                           ControllerErrorCodes.UNSUPPORTED_REMOTE_FEATURE)

        return
Пример #5
0
    def detect_pairing_feature(self, paddr, patype, timeout: int = 10):
        """ """
        hci = HCI(self.hci)
        logger.info("Detecting SMP pairing feature of %s, using %s\n" %
                    (blue(paddr), blue(self.hci)))

        pairing_req = SM_Hdr(sm_command=btsmp.CmdCode.PAIRING_REQUEST) / \
            SM_Pairing_Request(iocap="NoInputNoOutput", oob='Not Present',
                authentication=(0b00 << AUTHREQ_RFU_POS) | (0 << CT2_POS) | \
                    (0 << KEYPRESS_POS) | (1 << SC_POS) | (0 << MITM_POS) | \
                    (BONDING << BONDING_FLAGS_POS), max_key_size=16,
                initiator_key_distribution=(0b0000 << INIT_RESP_KEY_DIST_RFU_POS) \
                    | (1 << LINKKEY_POS) | (1 << SIGNKEY_POS) | (1 << IDKEY_POS) \
                    | (1 << ENCKEY_POS),
                responder_key_distribution=(0b0000 << INIT_RESP_KEY_DIST_RFU_POS) \
                    | (1 << LINKKEY_POS) | (1 << SIGNKEY_POS) | (1 << IDKEY_POS) \
                    | (1 << ENCKEY_POS))

        event_params = None
        try:
            event_params = hci.le_create_connection(
                HCI_Cmd_LE_Create_Connection(paddr=bytes.fromhex(
                    paddr.replace(':', ''))[::-1],
                                             patype=patype), timeout)
            logger.debug(event_params)

            result = btsmp.send_pairing_request(
                event_params['Connection_Handle'], pairing_req, self.hci)
            logger.debug("detect_pairing_feature(), result: {}".format(result))

            rsp = btsmp.recv_pairing_response(timeout, self.hci)
            logger.debug("detect_pairing_feature(), rsp: {}".format(rsp))

            pp_smp_pkt(rsp)
        except RuntimeError as e:
            logger.error(e)
        except TimeoutError as e:
            output = subprocess.check_output(' '.join(
                ['hciconfig', self.hci, 'reset']),
                                             stderr=STDOUT,
                                             timeout=60,
                                             shell=True)
            event_params = None
            logger.info("Timeout")
            # logger.error("detect_pairing_feature(), TimeoutError {}".format(e))

        if event_params != None:
            hci.disconnect({
                'Connection_Handle':
                event_params['Connection_Handle'],
                'Reason':
                0x1A
            })

        return
Пример #6
0
    def inquiry(self, inquiry_len=0x08):
        logger.info('BR scanning on ' + blue("hci%d"%self.devid) + \
              ' with timeout ' + blue("%.2f sec\n"%(inquiry_len*1.28))+'\n')

        self.scanned_dev = []
        self.remote_name_req_flag = True
        hci = HCI(self.iface)

        def inquiry_result_handler(result: bytes):
            event_code = result[0]

            logger.debug("Entered inquiry(), inquiry_result_handler()\n"
                         "{}".format(HciEventCodes[event_code].name))

            if event_code == HCI_Inquiry_Result.evt_code:
                self.pp_inquiry_result(result[2:])
            elif event_code == HCI_Inquiry_Result_with_RSSI.evt_code:
                self.pp_inquiry_result_with_rssi(result[2:])
            elif event_code == HCI_Extended_Inquiry_Result.evt_code:
                self.pp_extended_inquiry_result(result[2:])
            else:
                logger.warning('Unknow inquiry result: {}'.format(result))

        try:
            hci.inquiry(inquiry_len=inquiry_len,
                        inquiry_result_handler=inquiry_result_handler)
            logger.info('Inquiry completed\n')

            if self.remote_name_req_flag and len(self.scanned_dev) != 0:
                logger.info('Requesting the name of the scanned devices...')
                for bd_addr in self.scanned_dev:
                    try:
                        name = hci.remote_name_request({
                            'BD_ADDR':
                            bytes.fromhex(bd_addr.replace(':', '')),
                            'Page_Scan_Repetition_Mode':
                            0x01,
                            'Reserved':
                            0x00,
                            'Clock_Offset':
                            0x0000
                        })['Remote_Name'].decode().strip()
                    except Exception as e:
                        print(e)
                        name = ''

                    print(bd_addr + ':', blue(name))
        except HciRuntimeError as e:
            logger.error("{}".format(e))
        except KeyboardInterrupt as e:
            logger.info('BR/EDR devices scan canceled\n')
            hci.inquiry_cancel()

        hci.close()
Пример #7
0
    def scan_lmp_feature(self, paddr):
        hci = HCI(self.iface)
        conn_complete_evt = hci.create_connection(
            paddr, page_scan_repetition_mode=0x02)

        if conn_complete_evt.status != 0:
            logger.error('Failed to create ACL connection')
            sys.exit(1)

        event_params = hci.read_remote_version_information(
            cmd_params={'Connection_Handle': conn_complete_evt.conn_handle})

        if event_params['Status'] != 0:
            logger.error('Failed to read remote version')
            sys.exit(1)

        print(blue('Version'))
        print('    Version:')
        print(' ' * 8 + lmp_vers[event_params['Version']], '(LMP)')
        print(' ' * 8 + ll_vers[event_params['Version']], '(LL)')
        print('    Manufacturer name:',
              green(company_identfiers[event_params['Manufacturer_Name']]))
        print('    Subversion:', event_params['Subversion'], '\n')

        complete_evt = hci.read_remote_supported_features(
            event_params['Connection_Handle'])
        if complete_evt.status != ControllerErrorCodes.SUCCESS:
            logger.error('Failed to read remote supported features')
        else:
            print(blue('LMP features'))
            pp_lmp_features(complete_evt.lmp_features)
            print()

        if not True if (complete_evt.lmp_features[7] >> 7) & 0x01 else False:
            sys.exit(1)

        print(blue('Extended LMP features'))
        complete_evt = hci.read_remote_extended_features(
            event_params['Connection_Handle'], 0x00)
        if complete_evt.status != ControllerErrorCodes.SUCCESS:
            logger.error('Failed to read remote extented features')
        else:
            pp_ext_lmp_features(complete_evt.ext_lmp_features, 0)
            for i in range(1, complete_evt.max_page_num + 1):
                complete_evt = hci.read_remote_extended_features(
                    event_params['Connection_Handle'], i)
                if complete_evt.status != ControllerErrorCodes.SUCCESS:
                    logger.error(
                        'Failed to read remote extented features, page {}'.
                        format(i))
                else:
                    pp_ext_lmp_features(complete_evt.ext_lmp_features, i)
Пример #8
0
    def pp_extended_inquiry_result(self, params):
        '''Parse and print HCI_Extended_Inquiry_Result'''
        num_rsp = params[0]
        if num_rsp != 1:
            logger.info('Num_Responses in HCI_Extended_Inquiry_Result is %d.' %
                        num_rsp)
            logger.debug('HCI_Extended_Inquiry_Result: {}'.format(params))
            return

        bd_addr, page_scan_repetition_mode, reserved, cod, \
            clk_offset, rssi, ext_inq_rsp = struct.unpack(
                '<6sBB3sHb240s', params[1:])

        bd_addr = ':'.join(['%02X' % b for b in bd_addr[::-1]])
        if bd_addr in self.scanned_dev:
            return

        print('Addr:', blue(bd_addr))
        # print('name:', blue(name.decode()))
        print('Page scan repetition mode: ', end='')
        pp_page_scan_repetition_mode(page_scan_repetition_mode)
        print('Reserved: 0x%02x' % reserved)

        cod = int.from_bytes(cod, byteorder='little')
        print('CoD: 0x%06X' % cod)
        pp_cod(cod)

        print('Clock offset: 0x%04X' % clk_offset)
        print('RSSI: %d' % rssi)
        pp_ext_inquiry_rsp(ext_inq_rsp)
        print('\n')

        self.scanned_dev.append(bd_addr)
Пример #9
0
    def pp_inquiry_result_with_rssi(self, params):
        '''Parse and print HCI_Inquiry_Result_with_RSSI.'''
        num_rsp = params[0]
        if num_rsp != 1:
            print(
                INFO, 'Num_Responses in HCI_Inquiry_Result_with_RSSI is %d.' %
                num_rsp)
            print(DEBUG, 'HCI_Inquiry_Result_with_RSSI:', params)
            return

        bd_addr, page_scan_repetition_mode, reserved, cod, clk_offset, rssi = \
            struct.unpack('<6sBB3sHb', params[1:])

        bd_addr = ':'.join(['%02X' % b for b in bd_addr[::-1]])
        if bd_addr in self.scanned_dev:
            return

        print('Addr:', blue(bd_addr))
        # print('name:', blue(name.decode()))
        print('Page scan repetition mode: ', end='')
        pp_page_scan_repetition_mode(page_scan_repetition_mode)
        print('Reserved: 0x%02x' % reserved)

        cod = int.from_bytes(cod, byteorder='little')
        print('CoD: 0x%06X' % cod)
        pp_cod(cod)

        print('Clock offset: 0x%04X' % clk_offset)
        print('RSSI: %d' % rssi)
        print('\n')

        self.scanned_dev.append(bd_addr)
Пример #10
0
    def pp_inquiry_result(self, params):
        '''Parse and print HCI_Inquiry_Result.'''
        num_rsp = params[0]
        if num_rsp != 1:
            logger.info('Num_Responses in HCI_Inquiry_Result is %d.' % num_rsp)
            logger.debug('HCI_Inquiry_Result: {}'.format(params))
            return

        bd_addr, page_scan_repetition_mode, reserved, cod, clk_offset = \
           struct.unpack('<6sBB3sH', params[1:])

        bd_addr = ':'.join(['%02X' % b for b in bd_addr[::-1]])
        if bd_addr in self.scanned_dev:
            return

        print('Addr:', blue(bd_addr))
        print('Page scan repetition mode: ', end='')
        pp_page_scan_repetition_mode(page_scan_repetition_mode)
        print('Reserved: 0x%02x' % reserved)

        cod = int.from_bytes(cod, byteorder='little')
        print('CoD: 0x%06X' % cod)
        pp_cod(cod)

        print('Clock offset: 0x%04X' % clk_offset)

        # HCI(self.iface).read_remote_name_req()
        print('\n')

        self.scanned_dev.append(bd_addr)
Пример #11
0
    def inquiry(self, lap=0x9e8b33, inquiry_len=0x08, num_rsp=0x00):
        print(INFO, "BR scanning on " + blue("hci%d"%self.devid) + \
              " with timeout " + blue("%.2f sec\n"%(inquiry_len*1.28))+'\n')

        self.scanned_dev = []

        cmd_params = lap.to_bytes(3, 'little') + \
            inquiry_len.to_bytes(1, 'little') + num_rsp.to_bytes(1, 'little')

        # If no filter is set, we can't receive any inquiry result.
        flt = hci_filter_new()
        hci_filter_set_ptype(flt, HCI_EVENT_PKT)
        hci_filter_all_events(flt)

        dd = hci_open_dev(self.devid)
        dd.setsockopt(SOL_HCI, HCI_FILTER, flt)

        hci_send_cmd(dd, OGF_LINK_CTL, OCF_INQUIRY, cmd_params)

        try:
            while True:
                data = dd.recv(300)
                if len(data) >= 4:
                    event_code = data[1]
                    if event_code == EVT_CMD_STATUS:
                        # print(DEBUG, 'HCI_Command_Status')
                        pass
                    elif event_code == EVT_INQUIRY_RESULT:
                        print(DEBUG, 'HCI_Inquiry_Result')
                        self.pp_inquiry_result(data[3:])
                    elif event_code == EVT_INQUIRY_RESULT_WITH_RSSI:
                        # print(DEBUG, 'HCI_Inquiry_Result_with_RSSI')
                        self.pp_inquiry_result_with_rssi(data[3:])
                    elif event_code == EVT_EXTENDED_INQUIRY_RESULT:
                        # print(DEBUG, 'HCI_Extended_Inquiry_Result')
                        self.pp_extended_inquiry_result(data[3:])
                    elif event_code == EVT_INQUIRY_COMPLETE:
                        # print(DEBUG, 'HCI_Inquiry_Complete')
                        print(INFO, 'Inquiry completed')
                        break
                    else:
                        print(DEBUG, "Unknow:", data)
        except KeyboardInterrupt as e:
            print(INFO, "BR/EDR devices scan canceled\n")
            HCI(self.iface).inquiry_cancel()

        hci_close_dev(dd.fileno())
Пример #12
0
def pp_cod(cod: int):
    '''Print and parse Class of Device.'''
    #print(DEBUG, 'br_scan.py pp_cod()')
    if cod > 0xFFFFFF:
        print(WARNING, "CoD's Format Type is not format #1")
        return
    elif cod & 0x000003 != 0:
        print(WARNING, "CoD's Format Type is not format #1")
        return

    print('    Service Class: %s' % bin(cod >> 13))
    information = lambda b: (b >> 23) & 1
    telephony = lambda b: (b >> 22) & 1
    audio = lambda b: (b >> 21) & 1
    object_transfer = lambda b: (b >> 20) & 1
    capturing = lambda b: (b >> 19) & 1
    rendering = lambda b: (b >> 18) & 1
    networking = lambda b: (b >> 17) & 1
    positioning = lambda b: (b >> 16) & 1
    limited_discoverable_mode = lambda b: (b >> 13) & 1

    # Parse Service Class Field
    if information(cod):
        print(' ' * 8 + 'Information (WEB-server, WAP-server, ...)')

    if telephony(cod):
        print(' ' * 8 +
              'Telephony (Cordless telephony, Modem, Headset service, ...)')

    if audio(cod):
        print(' ' * 8 +
              'Audio (Cordless telephony, Modem, Headset service, ...)')

    if object_transfer(cod):
        print(' ' * 8 + 'Object Transfer (v-Inbox, v-Folder, ...)')

    if capturing(cod):
        print(' ' * 8 + 'Capturing (Scanner, Microphone, ...)')

    if rendering(cod):
        print(' ' * 8 + 'Rendering (Printing, Speaker, ...)')

    if networking(cod):
        print(' ' * 8 + 'Networking (LAN, Ad hoc, ...)')

    if positioning(cod):
        print(' ' * 8 + 'Positioning (Location identification)')

    if limited_discoverable_mode(cod):
        print(' ' * 8 + 'Limited Discoverable Mode')

    # Parse Major Device Class
    major_dev_cls = (cod >> 8) & 0x001F
    print('    Major Device Class: %s,' % bin(major_dev_cls),
          blue(major_dev_clses[major_dev_cls]))

    # Parse Minor Device class
    pp_minor_dev_cls((cod >> 8) & 0x0000, major_dev_cls)
Пример #13
0
def bdaddr_to_company_name(addr: str):
    company_id = addr.replace(':', '-').upper()[0:8]

    logger.debug("bdaddr_to_company_name(), addr: " + addr)
    logger.debug(company_id)

    try:
        return blue(oui_company_names[company_id])
    except KeyError:
        return red('Unknown')
Пример #14
0
def pp_cod(cod: int):
    '''Print and parse the Class of Device.'''
    logger.debug('Entered br_scan.py, pp_cod()')
    if cod > 0xFFFFFF or cod & 0x000003 != 0:
        logger.warning('CoD\'s Format Type is not format #1')
        return

    print(INDENT + 'Service Class: %s' % bin(cod >> 13))
    information = lambda b: (b >> 23) & 1
    telephony = lambda b: (b >> 22) & 1
    audio = lambda b: (b >> 21) & 1
    object_transfer = lambda b: (b >> 20) & 1
    capturing = lambda b: (b >> 19) & 1
    rendering = lambda b: (b >> 18) & 1
    networking = lambda b: (b >> 17) & 1
    positioning = lambda b: (b >> 16) & 1
    limited_discoverable_mode = lambda b: (b >> 13) & 1

    # Parse Service Class field
    if information(cod):
        print(INDENT * 2 + 'Information')  # (WEB-server, WAP-server, ...)

    if telephony(cod):
        print(INDENT * 2 +
              'Telephony')  # (Cordless telephony, Modem, Headset service, ...)

    if audio(cod):
        print(INDENT * 2 +
              'Audio')  # (Cordless telephony, Modem, Headset service, ...)

    if object_transfer(cod):
        print(INDENT * 2 + 'Object Transfer')  # (v-Inbox, v-Folder, ...)

    if capturing(cod):
        print(INDENT * 2 + 'Capturing')  # (Scanner, Microphone, ...)

    if rendering(cod):
        print(INDENT * 2 + 'Rendering')  # (Printing, Speaker, ...)

    if networking(cod):
        print(INDENT * 2 + 'Networking')  # (LAN, Ad hoc, ...)

    if positioning(cod):
        print(INDENT * 2 + 'Positioning')  # (Location identification)

    if limited_discoverable_mode(cod):
        print(INDENT * 2 + 'Limited Discoverable Mode')

    # Parse Major Device Class
    major_dev_cls = (cod >> 8) & 0x001F
    print(INDENT + 'Major Device Class: %s,' % bin(major_dev_cls),
          blue(major_dev_clses[major_dev_cls]))

    # Parse Minor Device class
    pp_minor_dev_cls((cod >> 8) & 0x0000, major_dev_cls)
Пример #15
0
def find_rfkill_devid(dev='hci0') -> int:
    exitcode, output = subprocess.getstatusoutput('rfkill -rno ID,DEVICE')

    for line in output.splitlines():
        id_dev = line.split()
        if len(id_dev) == 2 and id_dev[1] == dev:
            return int(id_dev[0])
        else:
            continue
    
    raise RuntimeError("Can't find the ID of %s in rfkill" % blue(dev))
Пример #16
0
def pp_smp_pkt(pkt:bytes):
    logger.debug("pp_smp_pkt(), pkt: {}".format(pkt))
    code = pkt[0]

    if code == btsmp.CmdCode.PAIRING_RESPONSE:
        print(blue("Pairing Response"))
        iocap, oob, auth_req, max_enc_key_size, init_key_distr, rsp_key_distr = struct.unpack('BBBBBB', pkt[1:])
        print("    IO Capability: 0x%02x - %s" % (iocap, green(IOCapability[iocap].hname)))
        print("    OOB data flag: 0x%02x - %s" % (oob, OOBDataFlag[oob].hname))
        print("    AuthReq: 0x%02x" % auth_req)
        bonding_flag = (auth_req & BONDING_FLAGS_MSK) >> BONDING_FLAGS_POS
        mitm = (auth_req & MITM_MSK) >> MITM_POS
        sc = (auth_req & SC_MSK) >> SC_POS
        keypress = (auth_req & KEYPRESS_MSK) >> KEYPRESS_POS
        ct2 = (auth_req & CT2_MSK) >> CT2_POS
        rfu = (auth_req & AUTHREQ_RFU_MSK) >> AUTHREQ_RFU_POS
        print("        Bonding Flag:      0b{:02b} - {}".format(bonding_flag, BondingFlag[bonding_flag].hname))
        print("        MitM:              {}".format(green("True") if mitm else red("False")))
        print("        Secure Connection: {}".format(green("True") if sc else red("False")))
        print("        Keypress:          {}".format(green("True") if keypress else red("False")))
        print("        CT2:               {}".format(green("True") if ct2 else red("False")))
        print("        RFU:               0b{:02b}".format(rfu))
        print("    Maximum Encryption Key Size: %d" % max_enc_key_size)
        print("    Initiator Key Distribution: 0x%02x" % init_key_distr)
        enckey = (init_key_distr & ENCKEY_MSK) >> ENCKEY_POS
        idkey = (init_key_distr & IDKEY_MSK) >> IDKEY_POS
        signkey = (init_key_distr & SIGNKEY_MSK) >> SIGNKEY_POS
        linkkey = (init_key_distr & LINKKEY_MSK) >> LINKKEY_POS
        print("        EncKey:  {}".format(green("True") if enckey else red("False")))
        print("        IdKey:   {}".format(green("True") if idkey else red("False")))
        print("        SignKey: {}".format(green("True") if signkey else red("False")))
        print("        LinkKey: {}".format(green("True") if linkkey else red("False")))
        print("        RFU:     0b{:04b}".format((init_key_distr & INIT_RESP_KEY_DIST_RFU_MSK) >> INIT_RESP_KEY_DIST_RFU_POS))
        print("    Responder Key Distribution: 0x%02x" % rsp_key_distr)
        enckey = (rsp_key_distr & ENCKEY_MSK) >> ENCKEY_POS
        idkey = (rsp_key_distr & IDKEY_MSK) >> IDKEY_POS
        signkey = (rsp_key_distr & SIGNKEY_MSK) >> SIGNKEY_POS
        linkkey = (rsp_key_distr & LINKKEY_MSK) >> LINKKEY_POS
        print("        EncKey:  {}".format(green("True") if enckey else red("False")))
        print("        IdKey:   {}".format(green("True") if idkey else red("False")))
        print("        SignKey: {}".format(green("True") if signkey else red("False")))
        print("        LinkKey: {}".format(green("True") if linkkey else red("False")))
        print("        RFU:     0b{:04b}".format((rsp_key_distr & INIT_RESP_KEY_DIST_RFU_MSK) >> INIT_RESP_KEY_DIST_RFU_POS))
    elif code == btsmp.CmdCode.PAIRING_FAILED:
        reason = pkt[1]
        print(red("Pairing Failed"))
        print("    Reason: 0x%02x (%s)"%(reason, PairingFailedReason[reason].hname))
        print("            %s"%PairingFailedReason[reason].desc)
    else:
        logger.warning("unknown SMP packet: {}".format(pkt))
Пример #17
0
    def pp_sdptool_output(cls, output:str):
        '''Split the string output by sdptool into individual servcie records 
        and processes them separately.'''
        # print(DEBUG, 'parse_sdptool_output')
        pattern = r'Failed to connect to SDP server on[\da-zA-Z :]*'
        pattern = re.compile(pattern)
        result = pattern.findall(output)
        for i in result:
            output = output.replace(i, '')

        record_xmls = output.split('<?xml version="1.0" encoding="UTF-8" ?>\n\n')[1:]
        print('Number of service records:', len(record_xmls), '\n\n')
        for record_xml in record_xmls:
            print(blue('Service Record'))
            try:
                sr = ServiceRecord(record_xml)
                sr.pp()
            except ElementTree.ParseError as e:
                print(record_xml)
            print('\n')
Пример #18
0
    def scan(self, bdaddr, addr_type, include_descriptor: bool):
        """
        bdaddr - Remote BD_ADDR
        """
        def run_mainloop():
            mainloop.run()

        mainloop_thread = threading.Thread(target=run_mainloop, args=[])
        mainloop_thread.start()

        try:
            try:
                target = Peripheral(bdaddr,
                                    iface=self.devid,
                                    addrType=addr_type)
            except BTLEDisconnectError as e:
                logger.error("BTLEDisconnectError")
                print(ERROR_INDENT, e, sep='')
                return

            services = target.getServices()
            print("Number of services: %s\n\n" % len(services))

            # Show service
            for service in services:
                logger.debug('Start handle: {}'.format(service.hndStart))
                logger.debug('End handle: {}'.format(service.hndEnd))

                try:
                    characteristics = []
                    characteristics = service.getCharacteristics()
                except BTLEException as e:
                    logger.warning("BTLEException")
                    print(WARNING_INDENT, e, sep='')
                    # continue

                print(
                    blue('Service'), '(0x%04x - 0x%04x, %s characteristics)' %
                    (service.hndStart, service.hndEnd, len(characteristics)))
                print(
                    indent + 'Handle: 0x%04x' % service.hndStart
                )  # ", "\"attr handle\" by using gatttool -b <BD_ADDR> --primary
                print(indent + 'Type: (May be primary service 0x2800)')
                print(indent + 'Value (Service UUID): ',
                      blue(str(service.uuid).replace(sig_uuid_suffix, '')),
                      end=' ')
                try:
                    print(
                        '(' +
                        services_spec['0x' +
                                      ("%s" %
                                       service.uuid)[4:8].upper()]['Name'] +
                        ')', '\x1B[0m')
                except KeyError:
                    print('(' + red('unknown') + ')', '\x1B[0m')
                print(
                    indent +
                    'Permission: Read Only, No Authentication, No Authorization\n'
                )

                # Show characteristic
                for characteristic in characteristics:
                    descriptors = []
                    # 对每个 characteristic 都获取 descriptor 会很耗时
                    # 有些设备会因此断开连接。于是这里提供了一个是否获取 descriptor 的选项
                    if include_descriptor:
                        descriptors = characteristic.getDescriptors()

                    try:
                        print(indent + yellow('Characteristic'),
                              '(%s descriptors)' % len(descriptors))
                        #print('-'*8)
                        print(indent * 2 + 'Handle: %#06x' %
                              (characteristic.getHandle() - 1))
                        print(indent * 2 + 'Type: 0x2803 (Characteristic)')
                        print(indent * 2 + 'Value:')
                        print(indent * 3 + 'Characteristic properties:',
                              green(characteristic.propertiesToString()))
                        print(indent * 3 +
                              'Characteristic value handle: %#06x' %
                              characteristic.getHandle())
                        print(
                            indent * 3 + 'Characteristic UUID: ',
                            green(
                                str(characteristic.uuid).replace(
                                    sig_uuid_suffix, '')),
                            end=' '
                        )  # This UUID is also the type field of characteristic value declaration attribute.
                        try:
                            print('(' + characteristics_spec['0x' + (
                                "%s" %
                                characteristic.uuid)[4:8].upper()]['Name'] +
                                  ')')
                        except KeyError:
                            print('(' + red('unknown') + ')')
                        print(
                            indent * 3 +
                            'Permission: Read Only, No Authentication, No Authorization'
                        )

                        if characteristic.supportsRead():
                            print(indent + yellow('Characteristic value'))
                            print(indent * 2 + 'Handle:',
                                  green('%#06x' % characteristic.getHandle()))
                            print(
                                indent * 2 + 'Type:',
                                str(characteristic.uuid).replace(
                                    sig_uuid_suffix, ''))
                            print(indent * 2 + 'Value:',
                                  green(str(characteristic.read())))
                            print(
                                indent * 2 +
                                'Permission: Higher layer profile or implementation-specific'
                            )
                    except BTLEException as e:
                        print('        ' + str(e))

                    # Show descriptor
                    for descriptor in descriptors:
                        try:
                            print(indent + yellow('Descriptor'))
                            print(indent * 2 + 'Handle:',
                                  green('%#06x' % descriptor.handle))
                            print(indent * 2 + 'Type:',
                                  str(descriptor.uuid).replace(
                                      sig_uuid_suffix, ''),
                                  end=' ')
                            try:
                                print('(' + descriptors_spec['0x' + (
                                    "%s" %
                                    descriptor.uuid)[4:8].upper()]['Name'] +
                                      ')')
                            except KeyError:
                                print('(Unknown descriptor)')
                            print(indent * 2 + 'Value:',
                                  green(str(descriptor.read())))
                            print(indent * 2 + 'Permissions:')
                        except BTLEException as e:
                            print(indent * 2 + str(e))
                    print()
                print()

            # Set remote device untursted
            output = subprocess.check_output(' '.join(
                ['bluetoothctl', 'untrust', bdaddr]),
                                             stderr=STDOUT,
                                             timeout=60,
                                             shell=True)
            logger.info(output.decode())

            # output = subprocess.check_output(
            #     ' '.join(['sudo', 'systemctl', 'stop', 'bluetooth.service']),
            #     stderr=STDOUT, timeout=60, shell=True)

            # output = subprocess.check_output(
            #     ' '.join(['sudo', 'rm', '-rf', '/var/lib/bluetooth/' + \
            #               self.hci_bdaddr + '/' + bdaddr.upper()]),
            #     stderr=STDOUT, timeout=60, shell=True)

            # output = subprocess.check_output(
            #     ' '.join(['sudo', 'systemctl', 'start', 'bluetooth.service']),
            #     stderr=STDOUT, timeout=60, shell=True)
        finally:
            if self.agent_registered:
                self.agent_mgr_1_iface.UnregisterAgent(
                    ObjectPath(self.bluescan_agent.path))
                logger.info('Unregistered Agent object')

                mainloop.quit()
Пример #19
0
    def print(self):
        if self.addr is None or self.addr_type is None:
            return

        print("Number of services: {}".format(len(self.services)))
        print()
        print()  # Two empty lines before Service Group

        # Prints each service group
        for service in self.services:
            uuid_str_for_show = self.uuid2str_for_show(service.declar.value)

            try:
                service_name = green(ServiceUuids[service.declar.value].name)
            except KeyError:
                service_name = red("Unknown")

            print(
                blue("Service"),
                "(0x{:04x} - 0x{:04x}, {} characteristics)".format(
                    service.start_handle, service.end_handle,
                    len(service.get_characts())))
            print(INDENT + blue("Declaration"))
            print(INDENT + "Handle: 0x{:04x}".format(service.start_handle))
            print(INDENT + "Type:   {:04X} ({})".format(
                service.declar.type.int16, service.declar.type.name))
            print(INDENT + "Value:  {} ({})".format(green(uuid_str_for_show),
                                                    service_name))
            print(INDENT + "Permissions:", service.declar.permissions_desc)
            print()  # An empty line before Characteristic Group

            # Prints each Gharacteristic group
            for charact in service.characts:
                uuid_str_for_show = self.uuid2str_for_show(
                    charact.declar.value.uuid)

                # if type(uuid) is int:
                #     uuid = "0x{:04X}".format(uuid)

                # value_declar_uuid = charact.value_declar.type
                # if type(value_declar_uuid) is int:
                #     value_declar_uuid = "0x{:04X}".format(value_declar_uuid)

                # if charact.declar.value.uuid != charact.value_declar.type:
                #     pass
                #     logger.warning("charact.declar.value['UUID'] != charact.value_declar.type")

                try:
                    charact_name = green(
                        GattAttrTypes[charact.declar.value.uuid].name)
                    # charact_name = green(charact_names[charact.declar.value.uuid])
                except KeyError:
                    charact_name = red("Unknown")

                print(INDENT + yellow("Characteristic"),
                      '({} descriptors)'.format(len(charact.descriptors)))
                print(INDENT * 2 + yellow("Declaration"))
                print(INDENT * 2 +
                      "Handle: 0x{:04x}".format(charact.declar.handle))
                print(INDENT * 2 + "Type:   {:04X} ({})".format(
                    charact.declar.type.int16, charact.declar.type.name))
                print(INDENT * 2 + "Value:")
                print(INDENT * 3 + "Properties: {}".format(
                    green(', '.join(charact.declar.get_property_names()))))
                print(INDENT * 3 + "Handle:    ",
                      green("0x{:04x}".format(charact.declar.value.handle)))
                print(INDENT * 3 + "UUID:       {} ({})".format(
                    green(uuid_str_for_show), charact_name))
                print(INDENT * 2 + "Permissions: {}\n".format(
                    charact.declar.permissions_desc))

                if charact.value_declar is not None:
                    try:
                        value_declar_name = GattAttrTypes[
                            charact.value_declar.type].name
                    except KeyError:
                        value_declar_name = "Unknown"

                    type_str_for_show = self.uuid2str_for_show(
                        charact.value_declar.type)

                    error = charact.value_declar.get_read_error()
                    if error != None:
                        value_print = red(error.desc)
                    elif charact.value_declar.value is None:
                        value_print = red('Unknown')
                    else:
                        value_print = green(str(charact.value_declar.value))

                    print(INDENT * 2 + yellow("Value declaration"))
                    print(
                        INDENT * 2 +
                        "Handle: 0x{:04x}".format(charact.value_declar.handle))
                    print(INDENT * 2 + "Type:   {} ({})".format(
                        type_str_for_show, value_declar_name))
                    print(INDENT * 2 + "Value:  {}".format(value_print))
                    print(INDENT * 2 + "Permissions: {}\n".format(
                        charact.value_declar.permissions_desc))

                # Prints each Characteristic Descriptor
                for descriptor in charact.get_descriptors():
                    uuid = descriptor.type
                    if type(uuid) is int:
                        uuid = "0x{:04X}".format(uuid)

                    error = descriptor.get_read_error()
                    if error != None:
                        value_print = red(error.desc)
                    else:
                        if descriptor.value is None:
                            value_print = red('Unknown')
                        else:
                            value_print = green(str(descriptor.value))

                    print(INDENT * 2 + yellow("Descriptor"))
                    print(INDENT * 2 + "Handle: {}".format(
                        green('0x{:04x}'.format(descriptor.handle))))
                    print(INDENT * 2 + "Type:   {} ({})".format(
                        green("{:04X}".format(descriptor.type.int16)),
                        yellow(descriptor.type.name)))
                    print(INDENT * 2 + "Value: ", value_print)
                    print(INDENT * 2 + "Permissions: {}\n".format(
                        descriptor.permissions_desc))
Пример #20
0
    def scan_devs(self, timeout=8, scan_type='active', sort='rssi'):
        """LE devices scanning

        scan_type  - Indicate the type of LE scan:active, passive, adv or 
                     features.
        """
        if scan_type == 'adv':
            return

        scanner = Scanner(self.devid).withDelegate(LEDelegate())
        #print("[Debug] timeout =", timeout)

        # scan() 返回的 devs 是 dictionary view。
        if scan_type == 'active':  # Active scan 会在 LL 发送 SCAN_REQ PDU
            logger.warning(
                'Before doing an active scan, make sure you spoof your BD_ADDR.'
            )
            logger.info('LE active scanning on %s with timeout %d sec\n' % \
                (blue('hci%d'%self.devid), timeout))
            devs = scanner.scan(timeout)
        elif scan_type == 'passive':
            logger.info('LE passive scanning on %s with timeout %d sec\n' % \
                (blue('hci%d'%self.devid), timeout))
            devs = scanner.scan(timeout, passive=True)
        else:
            logger.error('Unknown LE scan type')
            return

        if sort == 'rssi':
            devs = list(devs)  # 将 dictionary view 转换为 list
            devs.sort(key=lambda d: d.rssi)

        for dev in devs:
            print('Addr:       ', blue(dev.addr.upper()))
            print('Addr type:  ', blue(dev.addrType))
            print('Connectable:',
                  green('True') if dev.connectable else red('False'))
            print("RSSI:        %d dB" % dev.rssi)
            print("General Access Profile:")
            for (adtype, desc, val) in dev.getScanData():
                # 打印当前 remote LE dev 透露的所有 GAP 数据(AD structure)。
                #
                # 如果 bluepy.scan() 执行的是 active scan,那么这些 GAP 数据
                # 可能同时包含 AdvData 与 ScanRspData。其中 AdvData 由 remote LE
                # dev 主动返回,ScanRspData 由 remote BLE dev 响应 SCAN_REQ 返回。
                #
                # 虽然 LL 分开定义了 Advertising PDUs (ADV_IND, ADV_DIRECT_IND...)
                # 和 Scanning PDUs (SCAN_REQ, SCAN_RSP)。但它们分别包含的 AdvData
                # 与 ScanRspData 到了 HCI 层都被放在了 HCI_LE_Advertising_Report
                # event 中。HCI_LE_Advertising_Report 的 Event_Type 标识了这些数
                # 据具体来源于哪个 LL 层的 PDU。另外 ScanRspData 与 AdvData 的格式
                # 完全相同,都是 GAP 协议标准定义的 AD structure。
                #
                # 在 LL 定义的 Advertising PDUs 中 ADV_DIRECT_IND 一定不会包含
                # AdvData。其余的 ADV_IND,ADV_NONCONN_IND 以及 ADV_SCAN_IND 都
                # 可能包含 AdvData。
                #
                # 另外 getScanData() 返回的 desc 还可以通过 ScanEntry.getDescription()
                # 单独获取;val 还可以通过 ScanEntry.getValueText() 单独获取;
                # adtype 表示当前一条 GAP 数据(AD structure)的类型。
                print('\t' + desc + ': ', end='')
                if adtype == COMPLETE_16_BIT_SERVICE_CLS_UUID_LIST:
                    print()
                    for uuid in val.split(','):
                        if len(uuid) == 36:
                            # 这里拿到的是完整的 128-bit uuid,但我们需要 16-bit uuid。
                            print('\t\t' + blue(uuid[4:8]))
                        else:
                            print('\t\t' + blue(uuid))
                    continue
                elif adtype == COMPLETE_32_BIT_SERVICE_CLS_UUID_LIST:
                    print()
                    for uuid in val.split(','):
                        if len(uuid) == 36:
                            # 这里拿到的是完整的 128-bit uuid,但我们需要 32-bit uuid。
                            print('\t\t' + blue(uuid[0:8]))
                        else:
                            print('\t\t' + blue(uuid))
                    continue
                elif adtype == MANUFACTURER_SPECIFIC_DATA:
                    val = bytes.fromhex(val)
                    if len(val) > 2:
                        print()
                        print(
                            '\t\tCompany ID:', '0x%04x' %
                            int.from_bytes(val[0:2], 'little', signed=False))
                        print('\t\tData:', val[2:])
                    else:
                        print(val)
                    continue
                print(val)
            print("\n")
Пример #21
0
def main():
    try:
        args = parse_cmdline()
        logger.debug("main(), args: {}".format(args))

        if not args['--adv']:
            # 在不使用 microbit 的情况下,我们需要将选中的 hci 设备配置到一个干净的状态。

            if args['-i'] == 'The default HCI device':
                # 当 user 没有显示指明 hci 设备情况下,我们需要自动获取一个可用的 hci
                # 设备。注意这个设备不一定是 hci0。因为系统中可能只有 hci1,而没有 hci0。
                try:
                    args['-i'] = HCI.get_default_hcistr()
                except IndexError:
                    logger.error('No available HCI device')
                    exit(-1)

            init_hci(args['-i'])

        scan_result = None
        if args['-m'] == 'br':
            br_scanner = BRScanner(args['-i'])
            if args['--lmp-feature']:
                br_scanner.scan_lmp_feature(args['BD_ADDR'])
            else:
                br_scanner = BRScanner(args['-i'])
                br_scanner.inquiry(inquiry_len=args['--inquiry-len'])
        elif args['-m'] == 'le':
            if args['--adv']:
                dev_paths = get_microbit_devpaths()
                LeScanner(microbit_devpaths=dev_paths).sniff_adv(
                    args['--channel'])
            elif args['--ll-feature']:
                LeScanner(args['-i']).scan_ll_feature(args['BD_ADDR'],
                                                      args['--addr-type'],
                                                      args['--timeout'])
            elif args['--smp-feature']:
                LeScanner(args['-i']).detect_pairing_feature(
                    args['BD_ADDR'], args['--addr-type'], args['--timeout'])
            else:
                scan_result = LeScanner(args['-i']).scan_devs(
                    args['--timeout'], args['--scan-type'], args['--sort'])
        elif args['-m'] == 'sdp':
            SDPScanner(args['-i']).scan(args['BD_ADDR'])
        elif args['-m'] == 'gatt':
            scan_result = GattScanner(args['-i'],
                                      args['--io-capability']).scan(
                                          args['BD_ADDR'], args['--addr-type'])
        # elif args['-m'] == 'stack':
        #     StackScanner(args['-i']).scan(args['BD_ADDR'])
        elif args['--clean']:
            BlueScanner(args['-i'])
            clean(BlueScanner(args['-i']).hci_bdaddr, args['BD_ADDR'])
        else:
            logger.error('Invalid scan mode')

        # Prints scan result
        if scan_result is not None:
            print()
            print()
            print(
                blue("----------------" + scan_result.type + " Scan Result" +
                     "----------------"))
            scan_result.print()
            scan_result.store()
    # except (RuntimeError, ValueError, BluetoothError) as e:
    except (RuntimeError, ValueError) as e:
        logger.error("{}: {}".format(e.__class__.__name__, e))
        traceback.print_exc()
        exit(1)
    except (BTLEException) as e:
        logger.error(
            str(e) +
            ("\nNo BLE adapter or missing sudo?" if 'le on' in str(e) else ""))
    except KeyboardInterrupt:
        if args != None and args['-i'] != None:
            output = subprocess.check_output(' '.join(
                ['hciconfig', args['-i'], 'reset']),
                                             stderr=STDOUT,
                                             timeout=60,
                                             shell=True)
        print()
        logger.info("Canceled\n")
Пример #22
0
    def scan_devs(self, timeout=8, scan_type='active', sort='rssi') -> LeDevicesScanResult:
        """Perform LE Devices scanning and return scan reuslt as LeDevicesScanResult

        scan_type  - 'active' or 'passive'
        """
        if scan_type == 'adv':
            return

        scanner = Scanner(self.devid).withDelegate(LEDelegate())
        #print("[Debug] timeout =", timeout)
        
        spinner = Halo(text="Scanning", spinner={'interval': 200,
                                        'frames': ['', '.', '.'*2, '.'*3]},
                       placement='right')

        # scan() 返回的 devs 是 dictionary view。
        if scan_type == 'active': # Active scan 会在 LL 发送 SCAN_REQ PDU
            logger.warning('Before doing an active scan, make sure you spoof your BD_ADDR.')
            logger.info('LE active scanning on %s with timeout %d sec\n' % \
                (blue('hci%d'%self.devid), timeout))
            spinner.start()
            devs = scanner.scan(timeout)
            spinner.stop()
        elif scan_type == 'passive':
            logger.info('LE passive scanning on %s with timeout %d sec\n' % \
                (blue('hci%d'%self.devid), timeout))
            spinner.start()
            devs = scanner.scan(timeout, passive=True)
            spinner.stop()
        else:
            logger.error('Unknown LE scan type')
            return

        if sort == 'rssi':
            devs = list(devs) # 将 dictionary view 转换为 list
            devs.sort(key=lambda d:d.rssi)
        
        for dev in devs:
            dev_info = LeDeviceInfo(dev.addr.upper(), dev.addrType, dev.connectable, dev.rssi)
            self.devs_scan_result.add_device_info(dev_info)
            
            # print('Addr:       ', blue(dev.addr.upper()))
            # print('Addr type:  ', blue(dev.addrType))
            # print('Connectable:', 
            #     green('True') if dev.connectable else red('False'))
            # print("RSSI:        %d dB" % dev.rssi)
            # print("General Access Profile:")
            for (adtype, desc, val) in dev.getScanData():
                ad_struct = AdStruct(adtype, val)
                dev_info.add_ad_structs(ad_struct)
                # 打印当前 remote LE dev 透露的所有 GAP 数据(AD structure)。
                # 
                # 如果 bluepy.scan() 执行的是 active scan,那么这些 GAP 数据
                # 可能同时包含 AdvData 与 ScanRspData。其中 AdvData 由 remote LE 
                # dev 主动返回,ScanRspData 由 remote BLE dev 响应 SCAN_REQ 返回。
                #
                # 虽然 LL 分开定义了 Advertising PDUs (ADV_IND, ADV_DIRECT_IND...)
                # 和 Scanning PDUs (SCAN_REQ, SCAN_RSP)。但它们分别包含的 AdvData 
                # 与 ScanRspData 到了 HCI 层都被放在了 HCI_LE_Advertising_Report 
                # event 中。HCI_LE_Advertising_Report 的 Event_Type 标识了这些数
                # 据具体来源于哪个 LL 层的 PDU。另外 ScanRspData 与 AdvData 的格式
                # 完全相同,都是 GAP 协议标准定义的 AD structure。
                #
                # 在 LL 定义的 Advertising PDUs 中 ADV_DIRECT_IND 一定不会包含 
                # AdvData。其余的 ADV_IND,ADV_NONCONN_IND 以及 ADV_SCAN_IND 都
                # 可能包含 AdvData。
                #
                # 另外 getScanData() 返回的 desc 还可以通过 ScanEntry.getDescription() 
                # 单独获取;val 还可以通过 ScanEntry.getValueText() 单独获取;
                # adtype 表示当前一条 GAP 数据(AD structure)的类型。
            
        return self.devs_scan_result
Пример #23
0
    def inquiry(self, lap=0x9e8b33, inquiry_len=0x08, num_rsp=0x00):
        logger.info('BR scanning on ' + blue("hci%d"%self.devid) + \
              ' with timeout ' + blue("%.2f sec\n"%(inquiry_len*1.28))+'\n')

        self.scanned_dev = []
        self.remote_name_req_flag = True

        cmd_params = lap.to_bytes(3, 'little') + \
            inquiry_len.to_bytes(1, 'little') + num_rsp.to_bytes(1, 'little')

        # If no filter is set, we can't receive any inquiry result.
        flt = hci_filter_new()
        hci_filter_set_ptype(flt, HCI_EVENT_PKT)
        hci_filter_all_events(flt)

        dd = hci_open_dev(self.devid)
        dd.setsockopt(SOL_HCI, HCI_FILTER, flt)

        hci_send_cmd(dd, OGF_LINK_CTL, OCF_INQUIRY, cmd_params)

        try:
            while True:
                data = dd.recv(300)
                if len(data) >= 4:
                    event_code = data[1]
                    if event_code == EVT_CMD_STATUS:
                        logger.debug('HCI_Command_Status')
                        pass
                    elif event_code == EVT_INQUIRY_RESULT:
                        logger.debug('HCI_Inquiry_Result')
                        self.pp_inquiry_result(data[3:])
                    elif event_code == EVT_INQUIRY_RESULT_WITH_RSSI:
                        logger.debug('HCI_Inquiry_Result_with_RSSI')
                        self.pp_inquiry_result_with_rssi(data[3:])
                    elif event_code == EVT_EXTENDED_INQUIRY_RESULT:
                        logger.debug('HCI_Extended_Inquiry_Result')
                        self.pp_extended_inquiry_result(data[3:])
                    elif event_code == EVT_INQUIRY_COMPLETE:
                        logger.debug('HCI_Inquiry_Complete')
                        logger.info('Inquiry completed\n')

                        if self.remote_name_req_flag and len(
                                self.scanned_dev) != 0:
                            logger.info(
                                'Requesting the name of the scanned devices...'
                            )
                            for bd_addr in self.scanned_dev:
                                try:
                                    name = HCI(self.iface).remote_name_request(
                                        {
                                            'BD_ADDR':
                                            bytes.fromhex(
                                                bd_addr.replace(':', '')),
                                            'Page_Scan_Repetition_Mode':
                                            0x01,
                                            'Reserved':
                                            0x00,
                                            'Clock_Offset':
                                            0x0000
                                        })['Remote_Name'].decode().strip()
                                except Exception as e:
                                    print(e)
                                    name = ''

                                print(bd_addr + ':', blue(name))
                        break
                    else:
                        logger.debug('Unknow: {}'.format(data))
        except KeyboardInterrupt as e:
            logger.info('BR/EDR devices scan canceled\n')
            HCI(self.iface).inquiry_cancel()

        hci_close_dev(dd.fileno())
Пример #24
0
    def print(self):
        for dev_info in self.devices_info:
            print('Addr:       ', blue(dev_info.addr), 
                  "("+bdaddr_to_company_name(dev_info.addr)+")" if dev_info.addr_type == 'public' else "")
            print('Addr type:  ', blue(dev_info.addr_type))
            print('Connectable:', 
                green('True') if dev_info.connectable else red('False'))
            print("RSSI:        {} dBm".format(dev_info.rssi))
            print("General Access Profile:")
            
            # TODO: Unify the gap type name parsings of BR and LE
            for ad in dev_info.ad_structs:
                try:
                    type_names = gap_type_names[ad.type]
                except KeyError:
                    type_names = "0x{:02X} ".format(ad.type)+"("+ red("Unknown")+")"

                print(INDENT+"{}: ".format(type_names), end='')
                # print(INDENT+"0x{:02X} ({}): ".format(ad.type, type_names), end='')
                
                # Parses AD structure based on https://www.bluetooth.com/specifications/specs/
                # -> Core Specification Supplement
                if ad.type == COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS or \
                    ad.type == INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
                    print()
                    for uuid in ad.value.split(','):
                        if len(uuid) == 36:
                            # 这里拿到的是完整的 128-bit uuid,但我们需要 16-bit uuid。
                            print(INDENT*2 + blue("0x"+uuid[4:8].upper()))
                        else:
                            print(INDENT*2 + blue(uuid))
                elif ad.type == COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS or \
                    ad.type == INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS:
                    print()
                    for uuid in ad.value.split(','): 
                        if len(uuid) == 36:
                            # 这里拿到的是完整的 128-bit uuid,但我们需要 32-bit uuid。
                            print(INDENT*2 + blue("0x"+uuid[0:8].upper()))
                        else:
                            print(INDENT*2 + blue(uuid))
                elif ad.type == COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS or \
                    ad.type == INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS:
                        print()
                        for uuid in ad.value.split(','): 
                            print(INDENT*2 + blue(uuid).upper())
                elif ad.type == SERVICE_DATA_16_BIT_UUID:
                    print()
                    print(INDENT*2 + "UUID: 0x{}".format(ad.value[0:2*2].upper()))
                    print(INDENT*2 + "Data:", ad.value[2*2:])
                elif ad.type == SERVICE_DATA_32_BIT_UUID:
                    print()
                    print(INDENT*2 + "UUID: {}".format(ad.value[0:4*2].upper()))
                    print(INDENT*2 + "Data:", ad.value[4*2:])
                elif ad.type == SERVICE_DATA_128_BIT_UUID:
                    print()
                    print(INDENT*2 + "UUID: {}".format(ad.value[0:16*2].upper()))
                    print(INDENT*2 + "Data: ", ad.value[16*2:])
                elif ad.type == FLAGS:
                    print()
                    try:
                        value = bytes.fromhex(ad.value)
                        print(INDENT*2 + "LE Limited Discoverable Mode\n" if value[0] & 0x01 else "", end="")
                        print(INDENT*2 + "LE General Discoverable Mode\n" if value[0] & 0x02 else "", end="")
                        print(INDENT*2 + "BR/EDR Not Supported\n" if value[0] & 0x04 else "", end="") # Bit 37 of LMP Feature Mask Definitions (Page 0)
                        print(INDENT*2 + "Simultaneous LE + BR/EDR to Same Device Capable (Controller)\n" if value[0] & 0x08 else "", end="") # Bit 49 of LMP Feature Mask Definitions (Page 0)
                        print(INDENT*2 + "Simultaneous LE + BR/EDR to Same Device Capable (Host)\n" if value[0] & 0x10 else "", end="") # Bit 66 of LMP Feature Mask Definitions (Page 1)
                    except (ValueError, IndexError) as e:
                        logger.debug("LeDevicesScanResult.print(), parse ad.type == FLAGS")
                        print(ad.value, "("+red("Raw")+")")
                elif ad.type == MANUFACTURER_SPECIFIC_DATA:
                    value = bytes.fromhex(ad.value)
                    company_id = int.from_bytes(value[0:2], 'little', signed=False)
                    try:
                        company_name = blue(company_names[company_id])
                    except KeyError:
                        company_name = red("Unknown")
                    
                    if len(value) >= 2:
                        print()
                        print(INDENT*2+"Company ID:", '0x{:04X} ({})'.format(company_id,company_name))
                        try:
                            
                            print(INDENT*2+'Data:      ', ''.join(["{:02X}".format(b) for b in value[2:]]))
                        except IndexError:
                            print(INDENT*2+'Data:', None)
                    else:
                        print(value)
                elif ad.type == TX_POWER_LEVEL:
                    value = int.from_bytes(bytes.fromhex(ad.value), 'little', signed=True)
                    print(value, "dBm", "(pathloss {} dBm)".format(value - dev_info.rssi))
                else:
                    print(ad.value)

            print()  
            print() # Two empty lines before next LE device information
Пример #25
0
def pp_ext_inquiry_rsp(ext_inq_rsp):
    '''Parse and print Extended Inquiry Response (240 octets)

    https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/
    '''
    print('Extended inquiry response: ', end='')
    if ext_inq_rsp[0] == 0:
        print(red('None'))
        return

    print()

    while ext_inq_rsp[0] != 0:
        length = ext_inq_rsp[0]
        data = ext_inq_rsp[1:1 + length]
        data_type = data[0]
        ext_inq_rsp = ext_inq_rsp[1 + length:]
        print('\t', end='')
        if data_type == COMPLETE_16_BIT_SERVICE_CLS_UUID_LIST:
            print(gap_type_name_pairs[data_type])
            if length - 1 >= 2:
                eir_data = data[1:]
                if len(eir_data) % 2 != 0:
                    print('\t\t' +
                          blue('Invalid EIR data length: %d' % len(eir_data)))
                    continue

                for i in range(0, len(eir_data), 2):
                    uuid = int.from_bytes(eir_data[i:i + 2],
                                          byteorder='little')
                    print('\t\t0x%04x ' % uuid, end='')
                    try:
                        print(blue(service_cls_profile_ids[uuid]['Name']))
                    except KeyError as e:
                        print(red('unknown'))
            else:
                print('\t\t' + red('None'))
        elif data_type == COMPLETE_32_BIT_SERVICE_CLS_UUID_LIST:
            print(gap_type_name_pairs[data_type])
            if length - 1 >= 4:
                eir_data = data[1:]
                if len(eir_data) % 4 != 0:
                    logger.info('\t\tInvalid EIR data length: {} {}'.format(
                        len(eir_data), eir_data))
                    continue
                for i in range(0, len(eir_data), 4):
                    uuid = int.from_bytes(eir_data[i:i + 4],
                                          byteorder='little')
                    print('\t\t0x%08x ' % uuid)
            else:
                print('\t\t' + red('None'))
        elif data_type == COMPLETE_128_BIT_SERVICE_CLS_UUID_LIST:
            print(gap_type_name_pairs[data_type])
            if length - 1 >= 16:
                eir_data = data[1:]
                if len(eir_data) % 16 != 0:
                    logger.info('\t\tInvalid EIR data length: {} {}'.format(
                        len(eir_data), eir_data))
                    continue
                for i in range(0, len(eir_data), 16):
                    uuid = int.from_bytes(eir_data[i:i + 16],
                                          byteorder='little')
                    uuid_str = '%032X' % uuid
                    print('\t\t', end='')
                    print(
                        blue('-'.join([
                            uuid_str[:8], uuid_str[8:12], uuid_str[12:16],
                            uuid_str[16:20], uuid_str[20:32]
                        ])))
            else:
                print('\t\t' + red('None'))
        elif data_type == SHORTENED_LOCAL_NAME or \
            data_type == COMPLETE_LOCAL_NAME:
            print(gap_type_name_pairs[data_type] + ':',
                  blue(data[1:].decode()))
        elif data_type == TX_POWER_LEVEL:
            print(
                gap_type_name_pairs[data_type] + ':',
                blue(
                    str(int.from_bytes(data[1:], byteorder='little')) +
                    ' dBm'))
        else:
            try:
                print(gap_type_name_pairs[data_type])
            except KeyError as e:
                print(red('Unknown, 0x%02x' % data_type))
            print('\t\t', data[1:], sep='')
Пример #26
0
def pp_adv_phych_pdu(pdu: bytes, ch: int) -> list:
    '''Parse and print advertising physical channel PDU

    ref 
    BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 6, Part B page 2871, 
    2.3 ADVERTISING PHYSICAL CHANNEL PDU

    Advertising physical channel PDU
    +------------------+
    | Header | Payload |
    |--------|---------|
    | 16 b   | 1-255 B |
    +------------------+

    Header
    +-------------------------------------------------+
    | PDU Type | RFU | ChSel | TxAdd | RxAdd | Length |
    |----------|-----|-------|-------|-------|--------|
    | 4 b      | 1 b | 1 b   | 1 b   | 1 b   | 8 b    |
    +-------------------------------------------------+
    '''
    header = pdu[:2]
    payload = pdu[2:]

    pdu_type = (header[0] & PDU_TYPE_MSK) >> PDU_TYPE_POS
    rfu = (header[0] & RFU_MSK) >> RFU_POS
    ch_sel = (header[0] & CH_SEL_MSK) >> CH_SEL_POS
    tx_add = (header[0] & TX_ADD_MSK) >> TX_ADD_POS
    rx_add = (header[0] & RX_ADD_MSK) >> RX_ADD_POS

    addrs = []

    print("[{}] ".format(ch), end='')
    if pdu_type == ADV_IND:
        adv_a = payload[:6][::-1]
        addrs = [{
            'BD_ADDR': adv_a,
            'type': 'public' if tx_add == 0b0 else 'random'
        }]

        print("[{}]".format(blue('ADV_IND')))
        print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random',
                                   ':'.join('%02X' % b for b in adv_a)))
        # print("AdvData:", payload[6:])
    elif pdu_type == ADV_DIRECT_IND:
        adv_a = payload[:6][::-1]
        target_a = payload[6:][::-1]
        addrs = [
            {
                'BD_ADDR': adv_a,
                'type': 'public' if tx_add == 0b0 else 'random'
            },
            {
                'BD_ADDR': target_a,
                'type': 'public' if rx_add == 0b0 else 'random'
            },
        ]
        print("[{}]".format(blue('ADV_DIRECT_IND')))
        print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random',
                                   ':'.join('%02X' % b for b in adv_a)))
        print("{} TargetA: {}".format('public' if rx_add == 0b0 else 'random',
                                      ':'.join('%02X' % b for b in target_a)))
    elif pdu_type == ADV_NONCONN_IND:
        adv_a = payload[:6][::-1]
        addrs = [{
            'BD_ADDR': adv_a,
            'type': 'public' if tx_add == 0b0 else 'random'
        }]
        print("[{}]".format(red('ADV_NONCONN_IND')))
        print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random',
                                   ':'.join('%02X' % b for b in adv_a)))
        # print("AdvData:", payload[6:])
    elif pdu_type == ADV_SCAN_IND:
        adv_a = payload[:6][::-1]
        addrs = [{
            'BD_ADDR': adv_a,
            'type': 'public' if tx_add == 0b0 else 'random'
        }]
        print("[{}]".format(blue('ADV_SCAN_IND')))
        print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random',
                                   ':'.join('%02X' % b for b in adv_a)))
        # print("AdvData:", payload[6:])
    elif pdu_type == ADV_EXT_IND:
        print("[{}]".format(yellow('ADV_EXT_IND')))
        print("raw: {}".format(payload))
    elif pdu_type == SCAN_REQ:
        scan_a = payload[:6][::-1]
        adv_a = payload[6:][::-1]
        addrs = [
            {
                'BD_ADDR': scan_a,
                'type': 'public' if tx_add == 0b0 else 'random'
            },
            {
                'BD_ADDR': adv_a,
                'type': 'public' if rx_add == 0b0 else 'random'
            },
        ]
        print("[{}]".format(blue('SCAN_REQ')))
        print("{} ScanA: {}".format('public' if tx_add == 0b0 else 'random',
                                    ':'.join('%02X' % b for b in scan_a)))
        print("{} AdvA: {}".format('public' if rx_add == 0b0 else 'random',
                                   ':'.join('%02X' % b for b in adv_a)))
    elif pdu_type == SCAN_RSP:
        adv_a = payload[:6][::-1]
        addrs = [{
            'BD_ADDR': adv_a,
            'type': 'public' if tx_add == 0b0 else 'random'
        }]
        print("[{}]".format(blue('SCAN_RSP')))
        print("{} AdvA: {}".format('public' if tx_add == 0b0 else 'random',
                                   ':'.join('%02X' % b for b in adv_a)))
        # print("ScanRspData:", payload[6:])
    elif pdu_type == CONNECT_IND:
        init_a = payload[:6]
        adv_a = payload[6:12]
        print('init_a:', ':'.join('%02x' % b for b in init_a))
        print('adv_a:', ':'.join('%02x' % b for b in adv_a))
        addrs = [
            {
                'BD_ADDR': init_a,
                'type': 'public' if tx_add == 0b0 else 'random'
            },
            {
                'BD_ADDR': adv_a,
                'type': 'public' if rx_add == 0b0 else 'random'
            },
        ]
        print("[{}]".format(green('CONNECT_IND')))
        print("{} InitA: {}".format('public' if tx_add == 0b0 else 'random',
                                    ':'.join('%02X' % b for b in init_a)))
        print("{} AdvA: {}".format('public' if rx_add == 0b0 else 'random',
                                   ':'.join('%02X' % b for b in adv_a)))
        # print("LLData:", payload[12:])
    else:
        logger.warning("Unknown PDU type 0x%02x'%pdu_type")

    return addrs
Пример #27
0
    def scan(self, timeout=8, scan_type='active', sort='rssi'):
        '''        
        scan_type
            指定执行的 LE scan,是 active scan 还是 passive scan。
        '''
        scanner = Scanner(self.devid).withDelegate(LEDelegate())
        #print("[Debug] timeout =", timeout)

        # scan() 返回的 devs 是 dictionary view。
        if scan_type == 'active':  # Active scan 会在 LL 发送 SCAN_REQ PDU
            print(
                WARNING,
                'Before doing an active scan, make sure you spoof your BD_ADDR.'
            )
            print(
                INFO,
                "LE active scanning on \x1B[1;34mhci%d\x1B[0m with timeout %d sec\n"
                % (self.devid, timeout))
            devs = scanner.scan(timeout)
        elif scan_type == 'passive':
            print(
                "LE passive scanning on \x1B[1;34mhci%d\x1B[0m with timeout %d sec\n"
                % (self.deivd, timeout))
            devs = scanner.scan(timeout, passive=True)
        else:
            print(ERROR, "Unknown LE scan type.")
            return

        if sort == 'rssi':
            devs = list(devs)  # 将 dictionary view 转换为 list
            devs.sort(key=lambda d: d.rssi)

        for dev in devs:
            print('Addr:       ', blue(dev.addr.upper()))
            print('Addr type:  ', blue(dev.addrType))
            print('Connectable:',
                  green('True') if dev.connectable else red('False'))
            print("RSSI:        %d dB" % dev.rssi)
            print("General Access Profile:")
            for (adtype, desc, val) in dev.getScanData():
                # 打印当前 remote LE dev 透露的所有 GAP 数据(AD structure)。
                #
                # 如果 bluepy.scan() 执行的是 active scan,那么这些 GAP 数据
                # 可能同时包含 AdvData 与 ScanRspData。其中 AdvData 由 remote LE
                # dev 主动返回,ScanRspData 由 remote BLE dev 响应 SCAN_REQ 返回。
                #
                # 虽然 LL 分开定义了 Advertising PDUs (ADV_IND, ADV_DIRECT_IND...)
                # 和 Scanning PDUs (SCAN_REQ, SCAN_RSP)。但它们分别包含的 AdvData
                # 与 ScanRspData 到了 HCI 层都被放在了 HCI_LE_Advertising_Report
                # event 中。HCI_LE_Advertising_Report 的 Event_Type 标识了这些数
                # 据具体来源于哪个 LL 层的 PDU。另外 ScanRspData 与 AdvData 的格式
                # 完全相同,都是 GAP 协议标准定义的 AD structure。
                #
                # 在 LL 定义的 Advertising PDUs 中 ADV_DIRECT_IND 一定不会包含
                # AdvData。其余的 ADV_IND,ADV_NONCONN_IND 以及 ADV_SCAN_IND 都
                # 可能包含 AdvData。
                #
                # 另外 getScanData() 返回的 desc 还可以通过 ScanEntry.getDescription()
                # 单独获取;val 还可以通过 ScanEntry.getValueText() 单独获取;
                # adtype 表示当前一条 GAP 数据(AD structure)的类型。
                print('\t' + desc + ': ', end='')
                if adtype == COMPLETE_16_BIT_SERVICE_CLS_UUID_LIST:
                    print()
                    for uuid in val.split(','):
                        print('\t\t' + blue(uuid))
                    continue
                print(val)
            print("\n")
Пример #28
0
    def scan(self, bdaddr, addr_type, include_descriptor: bool):
        #print("[Debug] target_addr:", self.target_bdaddr)
        #print("[Debug] iface:", self.iface)
        #print("[Debug] addr_type:", self.addr_type)
        target = Peripheral(bdaddr, iface=self.devid, addrType=addr_type)
        #print("[Debug] target", target)

        services = target.getServices()
        print("Number of services: %s\n\n" % len(services))

        # Show service
        for service in services:
            characteristics = service.getCharacteristics()

            print(blue('Service'),
                  '(%s characteristics)' % len(characteristics))
            print(
                '\tHandle:", "\"attr handle\" by using gatttool -b <BD_ADDR> --primary'
            )
            print('\tType: (May be primary service 0x2800)')
            print('\tValue (Service UUID): ', blue(str(service.uuid)), end=' ')
            try:
                print(
                    '(' +
                    services_spec['0x' +
                                  ("%s" % service.uuid)[4:8].upper()]['Name'] +
                    ')', '\x1B[0m')
            except KeyError:
                print('(' + red('unknown') + ')', '\x1B[0m')
            print(
                '\tPermission: Read Only, No Authentication, No Authorization\n'
            )

            # Show characteristic
            for characteristic in characteristics:
                descriptors = []
                # 对每个 characteristic 都获取 descriptor 会很耗时
                # 有些设备会因此断开连接。于是这里提供了一个是否获取 descriptor 的选项
                if include_descriptor:
                    descriptors = characteristic.getDescriptors()

                try:
                    print(yellow('\tCharacteristic'),
                          '(%s descriptors)' % len(descriptors))
                    #print('-'*8)
                    print('\t\tHandle: %#06x' %
                          (characteristic.getHandle() - 1))
                    print('\t\tType: 0x2803 (tCharacteristic)')
                    print('\t\tValue:')
                    print('\t\t\tCharacteristic properties:',
                          green(characteristic.propertiesToString()))
                    print('\t\t\tCharacteristic value handle: %#06x' %
                          characteristic.getHandle())
                    print(
                        '\t\t\tCharacteristic UUID: ',
                        green(str(characteristic.uuid)),
                        end=' '
                    )  # This UUID is also the type field of characteristic value declaration attribute.
                    try:
                        print('(' + characteristics_spec['0x' + (
                            "%s" % characteristic.uuid)[4:8].upper()]['Name'] +
                              ')')
                    except KeyError:
                        print('(' + red('unknown') + ')')
                    print(
                        '\t\tPermission: Read Only, No Authentication, No Authorization'
                    )

                    if characteristic.supportsRead():
                        print(yellow('\tCharacteristic value'))
                        print('\t\tHandle:',
                              green('%#06x' % characteristic.getHandle()))
                        print('\t\tType:', characteristic.uuid)
                        print('\t\tValue:', green(str(characteristic.read())))
                        print(
                            '\t\tPermission: Higher layer profile or implementation-specific'
                        )
                except BTLEException as e:
                    print('        ' + str(e))

                # Show descriptor
                for descriptor in descriptors:
                    try:
                        print('\tDescriptor')
                        print('\t\tHandle:',
                              green('%#06x' % descriptor.handle))
                        print('\t\tType:', descriptor.uuid, end=' ')
                        try:
                            print('(' + descriptors_spec['0x' + (
                                "%s" % descriptor.uuid)[4:8].upper()]['Name'] +
                                  ')')
                        except KeyError:
                            print('(Unknown descriptor)')
                        print('\t\tValue:', descriptor.read())
                        print('\t\tPermissions:')
                    except BTLEException as e:
                        print('\t\t' + str(e))
                print()
            print()