def pp_service_cls_list(self, seq:ElementTree.Element): '''Parse and print ServiceClassIDList (0x0001). seq - Example: <sequence> <uuid value="0x110e" /> <uuid value="0x110f" /> </sequence> ''' uuids = seq.findall('./uuid') for uuid in uuids: uuid = uuid.attrib['value'] print('\t'+uuid+':', end=' ') try: uuid = int(uuid[2:], base=16) except ValueError: # Full UUID pass self.service_clses.append(uuid) try: if 'Service Class' in service_cls_profile_ids[uuid]['Allowed Usage']: name = service_cls_profile_ids[uuid]['Name'] print(green(name)) else: print(red('unknown')) except KeyError: if uuid == 0x1800: print(green('Generic Access')) elif uuid == 0x1801: print(green('Generic Attribute')) else: print(red('unknown'))
def pp_bt_profile_descp_list(self, seq:ElementTree.Element): '''Parse and print BluetoothProfileDescriptorList (0x0009). seq - Example: <sequence> <sequence> <uuid value="0x1108" /> <uint16 value="0x0102" /> </sequence> </sequence> ''' profiles = seq.findall('./sequence') for profile in profiles: uuid = profile.find('./uuid').attrib['value'] print('\t'+uuid+':', end=' ') try: uuid = int(uuid[2:], base=16) except ValueError: pass try: if 'Profile' in service_cls_profile_ids[uuid]['Allowed Usage']: name = service_cls_profile_ids[uuid]['Name'] print(green(name), end=' ') # print('\t\t', service_cls_profile_ids[uuid]['Specification']) else: print(red('unknown'), end=' ') version = int(profile.find('./uint16').attrib['value'][2:], base=16) print(green('v%d.%d'%(version>>8, version&0xFF))) except KeyError: print(red('unknown'))
def pp_supported_formats_list(self, val: ElementTree.Element): """ val - sequence """ fid_descps = { 0x01: "vCard 2.1", 0x02: "vCard 3.0", 0x03: "vCal 1.0", 0x04: "iCal 2.0", 0x05: "vNote", 0x06: "vMessage", 0xFF: "Any type of object" } format_ids = val.findall('./uint8') for format_id in format_ids: try: id_val = int(format_id.attrib['value'], base=16) print('\t0x%02x' % id_val, ': ', green(fid_descps[id_val]), sep='') except KeyError as e: print('\t0x%02x' % id_val, ': ', red('unknown'), sep='') continue except Exception as e: logger.warning('{}'.format(e)) print(WARNING_INDENT + format_id.attrib['value'])
def pp_supported_features(self, val: int): '''Parse and print SupportedFeatures service attribute. val - Value of SupportedFeatures, Uint16 ''' print('\t0x%04X' % val) for i in range(len(self.supported_features_bitmap)): feature_name = self.supported_features_bitmap[i] print('\t\t' + (green(feature_name) if val >> i & 0x0001 else red(feature_name)))
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)
def pp_supported_msg_types(self, val: int): '''Parse and print SupportedMessageTypes service attribute. val - Value of SupportedMessageTypes, uint8 ''' print('\t0x%02X' % val) for i in range(len(self.supported_msg_types_bitmap)): type_name = self.supported_msg_types_bitmap[i] print('\t\t', end=' ') if i < 5: print(green(type_name) if val >> i & 0x01 else red(type_name)) else: print(val >> i & 0x01, 'RFU')
def pp_map_supported_features(cls, val: int): '''Parse and print MapSupportedFeatures (MAP v1.2 and later). val - Value of MapSupportedFeatures, uint32 ''' print('\t0x%08X'%val) for i in range(len(cls.map_supported_features_bitmap)): feature_name = cls.map_supported_features_bitmap print('\t\t', end=' ') if i < 23: print(green(feature_name) if val >> i & 0x01 else red(feature_name)) else: print(val >> i & 0x01, 'RFU')
def pp_browse_group_list(self, seq:ElementTree.Element): '''Parse and print BrowseGroupList (0x0005). seq - Example: <sequence> <uuid value="0x1002" /> </sequence> ''' uuids = seq.findall('./uuid') for uuid in uuids: uuid = uuid.attrib['value'] print('\t'+uuid+':', end=' ') if uuid == "0x1002": print(green('PublicBrowseRoot')) else: print('Unknown')
def pp_ext_lmp_features(ext_lmp_features: bytes, page_num: int): '''Parse and print Extended LMP Features ext_lmp_features -- when page_num is 0, 8 bytes; when page_num is 1, 1 bytes; when page_num is 2, 2 bytes. ''' if page_num == 0: print('Page 0') pp_lmp_features(ext_lmp_features) elif page_num == 1: b = ext_lmp_features[0] print('Page 1') print(' Secure Simple Pairing (Host Support):', green('True') if b & 0x01 else red('False')) print(' LE Supported (Host):', green('True') if (b >> 1) & 0x01 else red('False')) print(' Simultaneous LE and BR/EDR to Same Device Capable (Host):', green('True') if (b >> 2) & 0x01 else red('False')) print(' Secure Connections (Host Support):', green('True') if (b >> 3) & 0x01 else red('False')) elif page_num == 2: print('Page 2') for i in range(0, 2): b = ext_lmp_features[i] if i == 0: print(' Connectionless Slave Broadcast - Master Operation:', green('True') if b & 0x01 else red('False')) print(' Connectionless Slave Broadcast - Slave Operation:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Synchronization Train:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Synchronization Scan:', green('True') if (b >> 3) & 0x01 else red('False')) print(' HCI_Inquiry_Response_Notification event: ', green('True') if (b >> 4) & 0x01 else red('False')) print(' Generalized interlaced scan:', green('True') if (b >> 5) & 0x01 else red('False')) print(' Coarse Clock Adjustment:', green('True') if (b >> 6) & 0x01 else red('False')) print(' Reserved for future use:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 1: print(' Secure Connections (Controller Support):', green('True') if b & 0x01 else red('False')) print(' Ping:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Slot Availability Mask:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Train nudging:', green('True') if (b >> 3) & 0x01 else red('False')) else: logger.warning('Unknown page number {}'.format(page_num))
def pp_lmp_features(lmp_features: bytes): '''Parse and print LMP Features lmp_features -- 8 bytes ''' for i in range(8): b = lmp_features[i] if i == 0: print(' 3 slot packets:', green('True') if b & 0x01 else red('False')) print(' 5 slot packets:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Encryption:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Slot offset:', green('True') if (b >> 3) & 0x01 else red('False')) print(' Timing accuracy:', green('True') if (b >> 4) & 0x01 else red('False')) print(' Role switch:', green('True') if (b >> 5) & 0x01 else red('False')) print(' Hold mode:', green('True') if (b >> 6) & 0x01 else red('False')) print(' Sniff mode:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 1: print(' Previously used:', green('True') if b & 0x01 else red('False')) print(' Power control requests:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Channel quality driven data rate (CQDDR):', green('True') if (b >> 2) & 0x01 else red('False')) print(' SCO link:', green('True') if (b >> 3) & 0x01 else red('False')) print(' HV2 packets:', green('True') if (b >> 4) & 0x01 else red('False')) print(' HV3 packets:', green('True') if (b >> 5) & 0x01 else red('False')) print(' μ-law log synchronous data:', green('True') if (b >> 6) & 0x01 else red('False')) print(' A-law log synchronous data:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 2: print(' CVSD synchronous data:', green('True') if b & 0x01 else red('False')) print(' Paging parameter negotiation:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Power control:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Transparent synchronous data:', green('True') if (b >> 3) & 0x01 else red('False')) print(' Flow control lag:', (b & 0x70) >> 4) print(' Broadcast Encryption:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 3: print(' Reserved for future use:', green('True') if b & 0x01 else red('False')) print(' Enhanced Data Rate ACL 2 Mb/s mode:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Enhanced Data Rate ACL 2 Mb/s mode:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Enhanced inquiry scan:', green('True') if (b >> 3) & 0x01 else red('False')) print(' Interlaced inquiry scan:', green('True') if (b >> 4) & 0x01 else red('False')) print(' Interlaced page scan:', green('True') if (b >> 5) & 0x01 else red('False')) print(' RSSI with inquiry results:', green('True') if (b >> 6) & 0x01 else red('False')) print(' Extended SCO link (EV3 packets):', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 4: print(' EV4 packets:', green('True') if b & 0x01 else red('False')) print(' EV5 packets:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Reserved for future use:', green('True') if (b >> 2) & 0x01 else red('False')) print(' AFH capable slave:', green('True') if (b >> 3) & 0x01 else red('False')) print(' AFH classification slave:', green('True') if (b >> 4) & 0x01 else red('False')) print(' BR/EDR Not Supported:', green('True') if (b >> 5) & 0x01 else red('False')) print(' LE Supported (Controller):', green('True') if (b >> 6) & 0x01 else red('False')) print(' 3-slot Enhanced Data Rate ACL packets:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 5: print(' 5-slot Enhanced Data Rate ACL packets:', green('True') if b & 0x01 else red('False')) print(' Sniff subrating:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Pause encryption:', green('True') if (b >> 2) & 0x01 else red('False')) print(' AFH capable master:', green('True') if (b >> 3) & 0x01 else red('False')) print(' AFH classification master:', green('True') if (b >> 4) & 0x01 else red('False')) print(' Enhanced Data Rate eSCO 2 Mb/s mode:', green('True') if (b >> 5) & 0x01 else red('False')) print(' Enhanced Data Rate eSCO 3 Mb/s mode:', green('True') if (b >> 6) & 0x01 else red('False')) print(' 3-slot Enhanced Data Rate eSCO packets:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 6: print(' Extended Inquiry Response:', green('True') if b & 0x01 else red('False')) print( ' Simultaneous LE and BR/EDR to Same Device Capable (Controller):', green('True') if (b >> 1) & 0x01 else red('False')) print(' Reserved for future use:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Secure Simple Pairing (Controller Support):', green('True') if (b >> 3) & 0x01 else red('False')) print(' Encapsulated PDU:', green('True') if (b >> 4) & 0x01 else red('False')) print(' Erroneous Data Reporting:', green('True') if (b >> 5) & 0x01 else red('False')) print(' Non-flushable Packet Boundary Flag:', green('True') if (b >> 6) & 0x01 else red('False')) print(' Reserved for future use:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 7: print(' HCI_Link_Supervision_Timeout_Changed event:', green('True') if b & 0x01 else red('False')) print(' Variable Inquiry TX Power Level:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Enhanced Power Control:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Reserved for future use:', green('True') if (b >> 3) & 0x01 else red('False')) print(' Reserved for future use:', green('True') if (b >> 4) & 0x01 else red('False')) print(' Reserved for future use:', green('True') if (b >> 5) & 0x01 else red('False')) print(' Reserved for future use:', green('True') if (b >> 6) & 0x01 else red('False')) print(' Extended features:', green('True') if (b >> 7) & 0x01 else red('False'))
def pp_goep_l2cap_psm(self, val: int): print(green('\t0x%02X' % val))
class AGServiceRecord(ServiceRecord): '''Autio Gateway Service Record See HFP Specification v1.8, Table 5.3: Service Record for the AG ''' service_clses = [{ 'UUID': 0x111F, 'name': 'HandsfreeAudioGateway' }, { 'UUID': 0x1203, 'name': 'GenericAudio' }] NETWORK = 0x0301 SUPPORTED_FEATURES = 0x0311 network = { 0x01: green('Ability to reject a call'), 0x00: red('No ability to reject a call') } # See HFP Specification v1.8, Table 5.4: "SupportedFeatures" attribute bit # mapping for the AG supported_features_bitmap = { 0: 'Three-way calling', # LSB 1: 'EC and/or NR function', 2: 'Voice recognition function', 3: 'In-band ring tone capability', 4: 'Attach a phone number to a voice tag', 5: 'Wide band speech', 6: 'Enhanced Voice Recognition Status', 7: 'Voice Recognition Text' } def __init__(self, record_xml: str): ''' record_xml - Service record XML for Autio Gateway ''' self.attrs = { self.NETWORK: { 'Name': 'Network', 'Parser': self.pp_network }, self.SUPPORTED_FEATURES: { 'Name': 'SupportedFeatures', 'Parser': self.pp_supported_features }, } super().__init__(record_xml) def pp_network(self, val: int): '''Parse and print Network service attribute. val - Value of Network, Uint8 ''' print('\t0x%02X' % val) print('\t\t' + self.network[val]) def pp_supported_features(self, val: int): '''Parse and print SupportedFeatures service attribute. val - Value of SupportedFeatures, Uint16 ''' print('\t0x%04X' % val) for i in range(len(self.supported_features_bitmap)): feature_name = self.supported_features_bitmap[i] print('\t\t' + (green(feature_name) if val >> i & 0x0001 else red(feature_name)))
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))
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
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
def __init__(self, record_xml:str): self.attrs = { self.HID_DEVICE_RELEASE_NUMBER: { 'Name': 'HIDDeviceReleaseNumber (Deprecated)', 'Parser': lambda val: print('\t0x%04X'%val)}, self.HID_PARSER_VERSION: { # HIDP v1.1.1, 5.3.4.2 HIDParserVersion # # Example # <attribute id="0x0201"> # <uint16 value="0x0111" /> # </attribute> 'Name': 'HIDParserVersion', 'Parser': lambda val: print('\t0x%04x:'%val, green('USB HID specification ' + 'v' + \ ('%04x'%val)[:2].lstrip('0') + '.' + ('%04x.'%val)[2] + '.' + \ ('%04x.'%val)[3]))}, self.HID_DEVICE_SUBCLASS: { # HIDP v1.1.1, 5.3.4.3 HIDDeviceSubclass # # Example # <attribute id="0x0203"> # <uint8 value="0x00" /> # </attribute> 'Name': 'HIDDeviceSubclass', 'Parser': lambda val: print('\t0x%02X'%val)}, self.HID_COUNTRY_CODE: { 'Name': 'HIDCountryCode', 'Parser': lambda val: print('\t0x%02X'%val)}, self.HID_VIRTUAL_CABLE: { 'Name': 'HIDVirtualCable', 'Parser': lambda val: print(green('\tTrue') if val == 'true' \ else red('\tFalse'))}, self.HID_RECONNECT_INITIATE:{ 'Name': 'HIDReconnectInitiate', 'Parser': lambda val: print(green('\tTrue') if val == 'true' \ else red('\tFalse'))}, self.HID_DESCRIPTOR_LIST:{ 'Name': 'HIDDescriptorList', 'Parser': lambda val: print('\t', val)}, self.HID_LANGID_BASE_LIST:{ 'Name': 'HIDLANGIDBaseList', 'Parser': lambda val: print('\t', val)}, self.HID_SDP_DISABLE:{ 'Name': 'HIDSDPDisable (Deprecated)', 'Parser': lambda val: print(green('\tTrue') if val == 'true' \ else red('\tFalse'))}, self.HID_BATTERY_POWER:{ 'Name': 'HIDBatteryPower', 'Parser': lambda val: print(green('\tTrue') if val == 'true' \ else red('\tFalse'))}, self.HID_REMOTE_WAKE:{ 'Name': 'HIDRemoteWake', 'Parser': lambda val: print(green('\tTrue') if val == 'true' \ else red('\tFalse'))}, self.HID_PROFILE_VERSION:{ 'Name': 'HIDProfileVersion', 'Parser': lambda val: print('\t0x%04X'%val)}, self.HID_SUPERVISION_TIMEOUT:{ 'Name': 'HIDSupervisionTimeout', 'Parser': lambda val: print('\t0x%04X'%val)}, self.HID_NORMALLY_CONNECTABLE:{ 'Name': 'HIDNormallyConnectable', 'Parser': lambda val: print(green('\tTrue') if val == 'true' \ else red('\tFalse'))}, self.HID_BOOT_DEVICE: { 'Name': 'HIDBootDevice', 'Parser': lambda val: print(green('\tTrue') if val == 'true' \ else red('\tFalse'))}, self.HID_SSR_HOST_MAX_LATENCY:{ 'Name': 'HIDSSRHostMaxLatency', 'Parser': lambda val: print('\t0x%04X'%val)}, self.HID_SSR_HOST_MIN_TIMEOUT:{ 'Name': 'HIDSSRHostMinTimeout', 'Parser': lambda val: print('\t0x%04X'%val) }, # HIDSSRHostMinTimeout 0x0211-0x03FF # Available for HID Language Strings 0x0400-0xFFFF } super().__init__(record_xml)
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()
def pp_le_features(features: bytes): """ features - LE LL features. The Bluetooth specification calls this FeatureSet. 待处理 Valid from Controller to Controller, Masked to Peer, Host Controlled """ for i in range(8): b = features[i] if i == 0: print(' LE Encryption:', green('True') if b & 0x01 else red('False')) print(' Connection Parameters Request Procedure:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Extended Reject Indication:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Slave-initiated Features Exchange:', green('True') if (b >> 3) & 0x01 else red('False')) print(' LE Ping: ', green('True') if (b >> 4) & 0x01 else red('False')) print(' LE Data Packet Length Extension:', green('True') if (b >> 5) & 0x01 else red('False')) print(' LL Privacy:', green('True') if (b >> 6) & 0x01 else red('False')) print(' Extended Scanner Filter Policies:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 1: print(' LE 2M PHY:', green('True') if b & 0x01 else red('False')) print(' Stable Modulation Index - Transmitter:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Stable Modulation Index - Receiver:', green('True') if (b >> 2) & 0x01 else red('False')) print(' LE Coded PHY:', green('True') if (b >> 3) & 0x01 else red('False')) print(' LE Extended Advertising:', green('True') if (b >> 4) & 0x01 else red('False')) print(' LE Periodic Advertising:', green('True') if (b >> 5) & 0x01 else red('False')) print(' Channel Selection Algorithm #2:', green('True') if (b >> 6) & 0x01 else red('False')) print(' LE Power Class 1:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 2: print(' Minimum Number of Used Channels Procedure:', green('True') if b & 0x01 else red('False')) print(' Connection CTE Request:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Connection CTE Response:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Connectionless CTE Transmitter:', green('True') if (b >> 3) & 0x01 else red('False')) print(' Connectionless CTE Receiver:', green('True') if (b >> 4) & 0x01 else red('False')) print(' Antenna Switching During CTE Transmission (AoD):', green('True') if (b >> 5) & 0x01 else red('False')) print(' Antenna Switching During CTE Reception (AoA):', green('True') if (b >> 6) & 0x01 else red('False')) print(' Receiving Constant Tone Extensions:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 3: print(' Periodic Advertising Sync Transfer - Sender:', green('True') if b & 0x01 else red('False')) print(' Periodic Advertising Sync Transfer - Recipient:', green('True') if (b >> 1) & 0x01 else red('False')) print(' Sleep Clock Accuracy Updates:', green('True') if (b >> 2) & 0x01 else red('False')) print(' Remote Public Key Validation:', green('True') if (b >> 3) & 0x01 else red('False')) print(' Connected Isochronous Stream - Master:', green('True') if (b >> 4) & 0x01 else red('False')) print(' Connected Isochronous Stream - Slave:', green('True') if (b >> 5) & 0x01 else red('False')) print(' Isochronous Broadcaster:', green('True') if (b >> 6) & 0x01 else red('False')) print(' Synchronized Receiver:', green('True') if (b >> 7) & 0x01 else red('False')) elif i == 4: print(' Isochronous Channels (Host Support):', green('True') if b & 0x01 else red('False')) print(' LE Power Control Request:', green('True') if (b >> 1) & 0x01 else red('False')) print(' LE Power Change Indication:', green('True') if (b >> 2) & 0x01 else red('False')) print(' LE Path Loss Monitoring:', green('True') if (b >> 3) & 0x01 else red('False'))
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")
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))
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")
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()