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_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 parse_cmdline() -> dict: args = docopt(__doc__, version="v" + VERSION, options_first=True) logger.debug("parse_cmdline, args: {}".format(args)) try: if args['-m'] is not None: args['-m'] = args['-m'].lower() if (args['-m'] == 'sdp' or args['-m'] == 'gatt') and args['BD_ADDR'] is None: raise ValueError( "The argument BD_ADDR is {}, please provide it.".format( args['BD_ADDR'])) args['--inquiry-len'] = int(args['--inquiry-len']) args['--timeout'] = int(args['--timeout']) args['--sort'] = args['--sort'].lower() if args['--channel'] is not None: args['--channel'] = [int(n) for n in args['--channel'].split(',')] args['--channel'] = set(args['--channel']) if args['--channel'].issubset({37, 38, 39}): args['--channel'] = list(args['--channel']) else: raise ValueError("Invalid channel {}, ".format(args['--channel']) \ + "must a subset of {37, 38, 39}") if args['BD_ADDR'] is not None: args['BD_ADDR'] = args['BD_ADDR'].lower() if not valid_bdaddr(args['BD_ADDR']): raise ValueError("Invalid BD_ADDR: " + red(args['BD_ADDR'])) if args['--scan-type'] is not None: args['--scan-type'] = args['--scan-type'].lower() if args['--scan-type'] not in ('active', 'passive'): raise ValueError("Invalid scan type %s, " % \ red(args['--scan-type']) + "must be active or passive.") if args['--addr-type'] is not None: args['--addr-type'] = args['--addr-type'].lower() if args['--addr-type'] not in ('public', 'random'): raise ValueError("Invalid address type %s, " % \ args['--addr-type'] + "must be public or random.") if args['--io-capability'] not in [ 'DisplayOnly', 'DisplayYesNo', 'KeyboardOnly', 'NoInputNoOutput', 'KeyboardDisplay', 'KeyboardOnly' ]: raise ValueError("Invalid IO capability %s" % args['--io-capability']) except ValueError as e: logger.error(str(e)) exit(1) return args
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_page_scan_repetition_mode(val): print(val, end=' ') if val == 0x00: print('(R0)') elif val == 0x01: print('(R1)') elif val == 0x02: print('(R2)') else: print(red('RFU'))
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 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')
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_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_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: print(WARNING, 'Unknown page number', page_num)
def parse_cmdline() -> dict: args = docopt(__doc__, version='v0.4.1', options_first=True) logger.debug("ui.parse_cmdline, args: {}".format(args)) args['-m'] = args['-m'].lower() args['--inquiry-len'] = int(args['--inquiry-len']) args['--timeout'] = int(args['--timeout']) args['--sort'] = args['--sort'].lower() if args['--channel'] is not None: args['--channel'] = [int(n) for n in args['--channel'].split(',')] args['--channel'] = set(args['--channel']) if args['--channel'].issubset({37, 38, 39}): args['--channel'] = list(args['--channel']) else: raise ValueError("Invalid channel {}, ".format(args['--channel']) \ + "must a subset of {37, 38, 39}") if args['BD_ADDR'] is not None: args['BD_ADDR'] = args['BD_ADDR'].lower() if not valid_bdaddr(args['BD_ADDR']): raise ValueError("Invalid BD_ADDR: " + red(args['BD_ADDR'])) if args['--scan-type'] is not None: args['--scan-type'] = args['--scan-type'].lower() if args['--scan-type'] not in ('active', 'passive'): raise ValueError("Invalid scan type %s, " % \ red(args['--scan-type']) + "must be active or passive.") if args['--addr-type'] is not None: args['--addr-type'] = args['--addr-type'].lower() if args['--addr-type'] not in ('public', 'random'): raise ValueError("Invalid address type %s, " % \ args['--addr-type'] + "must be public or random.") return args
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(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 __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 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 attr_permissions2str(permissions: dict): permi_str = '' read_flags = [] write_flags = [] if permissions is None: return red("Unknown") if permissions['read']['enable']: permi_str += 'Read' if permissions['read']['authen'] or permissions['read'][ 'author'] or permissions['read']['higher']: permi_str += "(" if permissions['read']['authen']: read_flags.append("authen") if permissions['read']['author']: read_flags.append("author") if permissions['read']['higher']: read_flags.append("higher") permi_str += ' '.join(read_flags) permi_str += ")" elif not (permissions['write']['enable'] or permissions['encrypt'] or permissions['higher']): return "Read Only, No Authentication, No Authorization" permi_str += ' ' if permissions['write']['enable']: permi_str += 'Write' if permissions['write']['authen'] or permissions['write'][ 'author'] or permissions['write']['higher']: permi_str += "(" if permissions['write']['authen']: write_flags.append("authen") if permissions['write']['author']: write_flags.append("author") if permissions['write']['higher']: write_flags.append("higher") permi_str += ' '.join(write_flags) permi_str += ")" permi_str += ' ' if permissions['encrypt']: permi_str += 'Encrypt' permi_str += ' ' if permissions['higher']: permi_str += 'Higher layer profile or implementation specific' permi_str += ' ' return permi_str
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 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 pp_attr(self, attr:ElementTree.Element): '''Parse and print the service attribute. attr - Example: <attribute id="0x????"> ... ... </attribute> ''' attr_id = int(attr.attrib['id'][2:], base=16) val_type = attr.find('./').tag if 'uint' in val_type: val = int(attr.find('./'+val_type).attrib['value'][2:], base=16) elif val_type == 'sequence': val = attr.find('./'+val_type) else: val = attr.find('./'+val_type).attrib['value'] # Parse and print three universal attribute ID offsets for base in self.attr_id_bases: offset = attr_id - base try: print('0x%04x:'%attr_id, self.universal_attr_offsets[offset]['Name'], '(%s)'%val_type) self.universal_attr_offsets[offset]['Parser'](val) return except KeyError: continue try: # The code for parsing a service attribute is divided into two parts. # One of the part is parsing the universal attribute and the other part is # parsing the non universal attribute. This is the first part. print('0x%04x:'%attr_id, self.universal_attrs[attr_id]['Name'], '(%s)'%val_type) self.universal_attrs[attr_id]['Parser'](val) except KeyError: # The code that parses a service attribute is divided into two parts. # One of the part is parsing the universal attribute and the other part is # parsing the non universal attribute. This is the second part. from .ag_service_record import AGServiceRecord from .hf_service_record import HFServiceRecord from .hid_service_record import HIDServiceRecord from .mce_service_record import MCEServiceRecord from .mse_service_record import MSEServiceRecord from .op_service_record import ObjPushServiceRecord # Guess if the attribute is one of the universal attribute offsets. offset = attr_id - 0x0100 try: print('0x%04x:'%attr_id, self.universal_attr_offsets[offset]['Name'], '(guess)', '(%s)'%val_type) self.universal_attr_offsets[offset]['Parser'](val) return except KeyError: pass if self.service_clses[0] == HFServiceRecord.service_clses[0]['UUID']: hfsr = HFServiceRecord(self.record_xml) print('0x%04x:'%attr_id, hfsr.attrs[attr_id]['Name'], '(%s)'%val_type) hfsr.attrs[attr_id]['Parser'](val) elif self.service_clses[0] == AGServiceRecord.service_clses[0]['UUID']: agsr = AGServiceRecord(self.record_xml) print('0x%04x:'%attr_id, agsr.attrs[attr_id]['Name'], '(%s)'%val_type) agsr.attrs[attr_id]['Parser'](val) elif self.service_clses[0] == MSEServiceRecord.service_clses[0]['UUID']: msesr = MSEServiceRecord(self.record_xml) print('0x%04x:'%attr_id, msesr.attrs[attr_id]['Name'], '(%s)'%val_type) msesr.attrs[attr_id]['Parser'](val) elif self.service_clses[0] == MCEServiceRecord.service_clses[0]['UUID']: mcesr = MCEServiceRecord(self.record_xml) print('0x%04x:'%attr_id, mcesr.attrs[attr_id]['Name'], '(%s)'%val_type) mcesr.attrs[attr_id]['Parser'](val) elif self.service_clses[0] == HIDServiceRecord.service_clses[0]['UUID']: hidsr = HIDServiceRecord(self.record_xml) print('0x%04x:'%attr_id, hidsr.attrs[attr_id]['Name'], '(%s)'%val_type) hidsr.attrs[attr_id]['Parser'](val) elif self.service_clses[0] == ObjPushServiceRecord.service_clses[0]['UUID']: opsr = ObjPushServiceRecord(self.record_xml) print('0x%04x:'%attr_id, opsr.attrs[attr_id]['Name'], '(guess)', '(%s)'%val_type) opsr.attrs[attr_id]['Parser'](val) else: print('0x%04x:'%attr_id, red('unknown')) for elem in list(attr): s = [i.strip() for i in ElementTree.tostring(elem).decode().strip().replace('\t', '').split('\n')] # print('DEBUG', s) for i in s: print('\t' + i)
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 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='')
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 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()