import io import pkg_resources from xml.etree import ElementTree from .ui import DEBUG, INFO, WARNING, ERROR from .ui import blue, green, yellow, red # https://www.bluetooth.com/specifications/assigned-numbers/service-discovery/ # Table 2: Service Class Profile Identifiers # # For historical reasons, some UUIDs in Table 2 are used to identify # profiles in a BluetoothProfileDescriptorList universal attribute as well # as service classes in a ServiceClassIDList universal attribute. However, # for new profiles, Service Class UUIDs shall not be used in a # BluetoothProfileDescriptorList universal attribute and Profile UUIDs # shall not be used in a ServiceClassIDList universal attribute. # # Include both service class UUID (32-bit) and profile UUID (32-bit), and other # information. service_cls_profile_ids_file = pkg_resources.resource_stream(__name__, 'res/service-class-profile-ids.txt') service_cls_profile_ids_file = io.TextIOWrapper( service_cls_profile_ids_file) service_cls_profile_ids = {} # 需要手动编辑的 Service Class 如下: # IrMCSyncCommand # Headset – HS # 同时注意去掉可能出现的 E2 80 8B for line in service_cls_profile_ids_file: items = line.strip().split('\t') if items[0] == 'Service Class Name': continue # print(items) uuid = int(items.pop(1)[2:], base=16) service_cls_profile_ids[uuid] = { 'Name': items[0], 'Specification': items[1], 'Allowed Usage': items[2] } # Only used in the ProfileDescriptorList attribute protocol_ids_file = pkg_resources.resource_stream(__name__, "res/sdp_ProfileDescriptorList_protocol_ids.txt") protocol_ids_file = io.TextIOWrapper(protocol_ids_file) protocol_ids = {} for line in protocol_ids_file: items = line.strip().split('\t') if items[0] == 'Protocol Name': continue # print(items) uuid = items.pop(1).lower() protocol_ids[uuid] = { 'Name': items[0], 'spec': items[1] } class ServiceRecord: '''SDP service record''' # Universal Attributes SERVICE_RECORD_HANDLE = 0x0000 SERVICE_CLASS_ID_LIST = 0x0001 SERVICE_RECORD_STATE = 0x0002 SERVICE_ID = 0x0003 PROTOCOL_DESCRIPTOR_LIST = 0x0004 BROWSE_GROUP_LIST = 0x0005 LANGUAGE_BASE_ATTRIBUTE_ID_LIST = 0x0006 SERVICE_INFO_TIME_TO_LIVE = 0x0007 SERVICE_AVAILABILITY = 0x0008 BLUETOOTH_PROFILE_DESCRIPTOR_LIST = 0x0009 DOCUMENTATION_URL = 0x000a CLIENT_EXECUTABLE_URL = 0x000b ICON_URL = 0x000c ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS = 0x000d universal_attrs = { SERVICE_RECORD_HANDLE: 'ServiceRecordHandle', SERVICE_CLASS_ID_LIST: 'ServiceClassIDList', SERVICE_RECORD_STATE: 'ServiceRecordState', SERVICE_ID: 'ServiceID', PROTOCOL_DESCRIPTOR_LIST: 'ProtocolDescriptorList', BROWSE_GROUP_LIST: 'BrowseGroupList', LANGUAGE_BASE_ATTRIBUTE_ID_LIST: 'LanguageBaseAttributeIDList', SERVICE_INFO_TIME_TO_LIVE: 'ServiceInfoTimeToLive', SERVICE_AVAILABILITY: 'ServiceAvailability', BLUETOOTH_PROFILE_DESCRIPTOR_LIST: 'BluetoothProfileDescriptorList', DOCUMENTATION_URL: 'DocumentationURL', CLIENT_EXECUTABLE_URL: 'ClientExecutableURL', ICON_URL: 'IconURL', ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS: 'AdditionalProtocolDescriptorLists' } # See https://www.bluetooth.com/specifications/assigned-numbers/service-discovery/ # Table 5: Attribute Identifiers and BLUETOOTH CORE SPECIFICATION Version # 5.2 | Vol 3, Part B page 1247 SERVICE_NAME = 0x0000 SERVICE_DESCRIPTION = 0x0001 PROVIDER_NAME = 0x0002 universal_attr_offsets = { SERVICE_NAME: 'ServiceName', SERVICE_DESCRIPTION: 'ServiceDescription', PROVIDER_NAME: 'ProviderName' } def __init__(self, record_xml:str): ''' record_xml - A single service record XML. ''' self.record_xml = record_xml self.service_clses = [] self.attr_id_bases = [] def pp(self): '''Parse and print the service record. xml -- XML for a single service record. ''' attrs = ElementTree.fromstring(self.record_xml).findall('./attribute') for attr in attrs: # Parse service attributes one by one. try: self.pp_universal_attr(attr) except KeyError: self.pp_non_universal_attr(attr) def pp_universal_attr(self, attr:ElementTree.Element): '''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. ''' attr_id = int(attr.attrib['id'][2:], base=16) # Parse three universal attribute ID offsets for base in self.attr_id_bases: if attr_id == base + self.SERVICE_NAME: name = attr.find('./text').attrib['value'] print('0x%04x:'%attr_id, self.universal_attr_offsets[self.SERVICE_NAME]) print('\t' + name) return elif attr_id == base + self.SERVICE_DESCRIPTION: description = attr.find('./text').attrib['value'] print('0x%04x:'%attr_id, self.universal_attr_offsets[self.SERVICE_DESCRIPTION]) print('\t' + description) return elif attr_id == base + self.PROVIDER_NAME: name = attr.find('./text').attrib['value'] print('0x%04x:'%attr_id, self.universal_attr_offsets[self.PROVIDER_NAME]) print('\t' + name) return print('0x%04X:'%attr_id, self.universal_attrs[attr_id]+' ', end='') if attr_id == self.SERVICE_RECORD_HANDLE: print('(uint32)') self.pp_service_record_hdl(attr) elif attr_id == self.SERVICE_CLASS_ID_LIST: print('(sequence)') self.pp_service_cls_list(attr) elif attr_id == self.SERVICE_RECORD_STATE: print('ServiceRecordState (to be parsed)') elif attr_id == self.SERVICE_ID: print('ServiceID (to be parsed)') elif attr_id == self.PROTOCOL_DESCRIPTOR_LIST: print('(sequence)') self.pp_protocol_descp_list(attr) elif attr_id == self.BROWSE_GROUP_LIST: print('(sequence)') self.pp_browse_group_list(attr) elif attr_id == self.LANGUAGE_BASE_ATTRIBUTE_ID_LIST: print('(sequence)') self.pp_lang_base_attr_id_list(attr) elif attr_id == self.SERVICE_INFO_TIME_TO_LIVE: print('ServiceInfoTimeToLive (to be parsed)') elif attr_id == self.SERVICE_AVAILABILITY: print('ServiceAvailability (to be parsed)') elif attr_id == self.BLUETOOTH_PROFILE_DESCRIPTOR_LIST: print('(sequence)') self.pp_bt_profile_descp_list(attr) elif attr_id == self.DOCUMENTATION_URL: print('DocumentationURL (to be parsed)') elif attr_id == self.CLIENT_EXECUTABLE_URL: print('ClientExecutableURL (to be parsed)') elif attr_id == self.ICON_URL: print('IconURL (to be parsed)') elif attr_id == self.ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS: print('(sequence)') self.pp_additional_protocol_descp_lists(attr) def pp_non_universal_attr(self, attr:ElementTree.Element): '''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. ''' attr_id = int(attr.attrib['id'][2:], base=16) if attr_id == 0x0100 + self.SERVICE_NAME: name = attr.find('./text').attrib['value'] print('0x%04x: %s (guess)'%(attr_id, self.universal_attr_offsets[self.SERVICE_NAME])) print('\t' + name) elif attr_id == 0x0100 + self.SERVICE_DESCRIPTION: description = attr.find('./text').attrib['value'] print('0x%04x: %s (guess)'%(attr_id, self.universal_attr_offsets[self.SERVICE_DESCRIPTION])) print('\t' + description) elif attr_id == 0x0100 + self.PROVIDER_NAME: print('0x%04x: %s (guess)'%(attr_id, self.universal_attr_offsets[self.PROVIDER_NAME])) elif self.service_clses[0] == HFServiceRecord.service_classes[0]['UUID']: if attr_id == HFServiceRecord.SUPPORTED_FEATURES: val = int(attr.find('./uint16').attrib['value'][2:], base=16) HFServiceRecord.pp_supported_features(val) elif self.service_clses[0] == AGServiceRecord.service_classes[0]['UUID']: if attr_id == AGServiceRecord.NETWORK: val = int(attr.find('./uint8').attrib['value'][2:], base=16) AGServiceRecord.pp_network(val) if attr_id == AGServiceRecord.SUPPORTED_FEATURES: val = int(attr.find('./uint16').attrib['value'][2:], base=16) AGServiceRecord.pp_supported_features(val) elif self.service_clses[0] == MSEServiceRecord.service_classes[0]['UUID']: if attr_id == MSEServiceRecord.GOEP_L2CAP_PSM: print('0x%04x:'%attr_id, MSEServiceRecord.attrs[attr_id], '(uint16)') val = int(attr.find('./uint16').attrib['value'][2:], base=16) MSEServiceRecord.pp_goep_l2cap_psm(val) elif attr_id == MSEServiceRecord.MAS_INSTANCE_ID: print('0x%04x:'%attr_id, MSEServiceRecord.attrs[attr_id], '(uint8)') val = int(attr.find('./uint8').attrib['value'][2:], base=16) MSEServiceRecord.pp_mas_instance_id(val) elif attr_id == MSEServiceRecord.SUPPORTED_MESSAGE_TYPES: print('0x%04x:'%attr_id, MSEServiceRecord.attrs[attr_id], '(uint8)') val = int(attr.find('./uint8').attrib['value'][2:], base=16) MSEServiceRecord.pp_supported_msg_types(val) elif attr_id == MSEServiceRecord.MAP_SUPPORTED_FEATURES: print('0x%04x:'%attr_id, MSEServiceRecord.attrs[attr_id], '(uint32)') val = int(attr.find('./uint32').attrib['value'][2:], base=16) MSEServiceRecord.pp_map_supported_features(val) elif self.service_clses[0] == MCEServiceRecord.service_classes[0]['UUID']: print('Message Notification Server (to be parsed)') if attr_id == MCEServiceRecord.GOEP_L2CAP_PSM: print('0x%04x:'%attr_id, MCEServiceRecord.attrs[attr_id], '(uint16)') val = int(attr.find('./uint16').attrib['value'][2:], base=16) MSEServiceRecord.pp_goep_l2cap_psm(val) elif attr_id == MCEServiceRecord.MAP_SUPPORTED_FEATURES: print('0x%04x:'%attr_id, MCEServiceRecord.attrs[attr_id], '(uint32)') val = int(attr.find('./uint32').attrib['value'][2:], base=16) MCEServiceRecord.pp_map_supported_features(val) else: print('0x%04x: unknown'%attr_id) 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_service_record_hdl(self, attr:ElementTree.Element): '''Parse and print ServiceRecordHandle (0x0000). XML example: <attribute id="0x0000"> <uint32 value="0x00010001" /> </attribute> ''' handle = attr.find('./uint32') # Value is a attribute of the uint32 element print('\t' + handle.attrib['value']) def pp_service_cls_list(self, attr:ElementTree.Element): '''Parse and print ServiceClassIDList (0x0001). XML example: <attribute id="0x0001"> <sequence> <uuid value="0x110e" /> <uuid value="0x110f" /> </sequence> </attribute> ''' sequence = attr.find('./sequence') uuids = sequence.findall('./uuid') for uuid in uuids: uuid = uuid.attrib['value'] print('\t'+uuid+':', end=' ') try: uuid = int(uuid[2:], base=16) except ValueError: pass try: if 'Service Class' in service_cls_profile_ids[uuid]['Allowed Usage']: self.service_clses.append(uuid) 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_protocol_descp_list(self, attr:ElementTree.Element): '''Parse and print ProtocolDescriptorList (0x0004). XML example: <attribute id="0x0004"> <sequence> <sequence> <uuid value="0x0100" /> <uint16 value="0x001f" /> </sequence> <sequence> <uuid value="0x0007" /> <uint16 value="0x0001" /> <uint16 value="0x0003" /> </sequence> </sequence> </attribute> ''' sequence = attr.find('./sequence') protocols = sequence.findall('./sequence') for protocol in protocols: uuid = protocol.find('./uuid').attrib['value'] print('\t'+uuid+':', end=' ') try: name = protocol_ids[uuid]['Name'] print(name) # print('\t\t', protocol_ids[uuid]['Specification']) if name == 'L2CAP': try: psm = protocol.find('./uint16').attrib['value'] print('\t\t' + 'PSM:', psm) except AttributeError: pass elif name == 'RFCOMM': channel = protocol.find('./uint8').attrib['value'] print('\t\tchannel:', channel) elif name in ('AVDTP', 'AVCTP'): # 音频分发、音频控制 version = int(protocol.find('./uint16').attrib['value'][2:], base=16) print('\t\tv%d.%d'%(version>>8, version&0xFF)) # Stream End Point #print('\t\t' + 'SEP:', sep) elif name in ('BNEP'): version = protocol.find('./uint16').attrib['value'] print('\t\tversion:', version) uint16s = protocol.find('./sequence').findall('./uint16') for val in uint16s: print('\t\tuint16:', val.attrib['value']) else: for elem in list(protocol)[1:]: s = ElementTree.tostring(elem).decode().strip().replace('\t', '').split('\n') for i in s: print('\t\t' + i) except KeyError: print('(Unknown)') def pp_browse_group_list(self, attr:ElementTree.Element): '''Parse and print BrowseGroupList (0x0005). XML example: <attribute id="0x0005"> <sequence> <uuid value="0x1002" /> </sequence> </attribute> ''' sequence = attr.find('./sequence') uuids = sequence.findall('./uuid') for uuid in uuids: uuid = uuid.attrib['value'] print('\t'+uuid+':', end=' ') if uuid == "0x1002": print('PublicBrowseRoot') else: print('Unknown') def pp_lang_base_attr_id_list(self, attr:ElementTree.Element): '''Parse and print LanguageBaseAttributeIDList (0x0006). XML example: <attribute id="0x0006"> <sequence> <uint16 value="0x656e" /> <uint16 value="0x006a" /> <uint16 value="0x0100" /> </sequence> </attribute> ''' values = attr.find('./sequence').findall('./uint16') triplets = [] for i in range(0, len(values), 3): triplets.append(values[i:i+3]) #print(triplets) for triplet in triplets: lang_name = triplet[0].attrib['value'] encoding = triplet[1].attrib['value'] base = triplet[2].attrib['value'] print('\tlanguage name:', lang_name) print('\tencoding:', encoding) print('\tattribute ID base:', base) self.attr_id_bases.append(int(base[2:], base=16)) def pp_bt_profile_descp_list(self, attr:ElementTree.Element): '''Parse and print BluetoothProfileDescriptorList (0x0009). XML example: <attribute id="0x0009"> <sequence> <sequence> <uuid value="0x1108" /> <uint16 value="0x0102" /> </sequence> </sequence> </attribute> ''' sequence = attr.find('./sequence') profiles = sequence.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_additional_protocol_descp_lists(self, attr:ElementTree.Element): '''Parse and print AdditionalProtocolDescriptorLists (0x000D). XML example: <attribute id="0x000d"> <sequence> <sequence> <sequence> <uuid value="0x0100" /> <uint16 value="0x001b" /> </sequence> <sequence> <uuid value="0x0017" /> <uint16 value="0x0103" /> </sequence> </sequence> </sequence> </attribute> ''' sequences = attr.find('./sequence').findall('./sequence') for sequence in sequences: pseudo_attr = ElementTree.Element('attribute') pseudo_attr.append(sequence) self.pp_protocol_descp_list(pseudo_attr) class HFServiceRecord(ServiceRecord): '''Hands-Free unit Service Record HFP Specification v1.8, Table 5.1: Service Record for the HF ''' service_classes = [ {'UUID': 0x111E, 'name': 'Handsfree'}, {'UUID': 0x1203, 'name': 'GenericAudio'} ] SUPPORTED_FEATURES = 0x0311 attrs = { SUPPORTED_FEATURES: 'SupportedFeatures' } # See HFP Specification v1.8, Table 5.2: “SupportedFeatures” attribute bit # mapping for the HF supported_features_bitmap = { 0: 'EC and/or NR function', # LSB 1: 'Call waiting or three-way calling', 2: 'CLI presentation capability', 3: 'Voice recognition activation', 4: 'Remote volume control', 5: 'Wide band speech', 6: 'Enhanced Voice Recognition Status', 7: 'Voice Recognition Text' } @classmethod def pp_supported_features(cls, val:int): '''Parse and print SupportedFeatures service attribute. val - Value of SupportedFeatures, Uint16 ''' print('0x%04x:'%cls.SUPPORTED_FEATURES, cls.attrs[cls.SUPPORTED_FEATURES], '(uint16)') print('\t0x%04X'%val) for i in range(len(cls.supported_features_bitmap)): feature_name = cls.supported_features_bitmap[i] print('\t\t'+(green(feature_name) if val >> i & 0x0001 else red(feature_name))) class AGServiceRecord(ServiceRecord): '''Autio Gateway Service Record See HFP Specification v1.8, Table 5.3: Service Record for the AG ''' service_classes = [ {'UUID': 0x111F, 'name': 'HandsfreeAudioGateway'}, {'UUID': 0x1203, 'name': 'GenericAudio'} ] NETWORK = 0x0301 SUPPORTED_FEATURES = 0x0311 attrs = { NETWORK: 'Network', SUPPORTED_FEATURES: 'SupportedFeatures' } 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' } @classmethod def pp_network(cls, val:int): '''Parse and print Network service attribute. val - Value of Network, Uint8 ''' print('0x%04x:'%cls.NETWORK, cls.attrs[cls.NETWORK], '(uint8)') print('\t0x%02X'%val) print('\t\t'+cls.network[val]) @classmethod def pp_supported_features(cls, val:int): '''Parse and print SupportedFeatures service attribute. val - Value of SupportedFeatures, Uint16 ''' print('0x%04x:'%cls.SUPPORTED_FEATURES, cls.attrs[cls.SUPPORTED_FEATURES], '(uint16)') print('\t0x%04X'%val) for i in range(len(cls.supported_features_bitmap)): feature_name = cls.supported_features_bitmap[i] print('\t\t'+(green(feature_name) if val >> i & 0x0001 else red(feature_name))) class MSEServiceRecord(ServiceRecord): '''Message Server Equipment Service Record See MAP Specification v1.4.2, Table 7.1: SDP Record for the Message Access Service on the MSE Device ''' service_classes = [ {'UUID': 0x1132, 'name': 'Message Access Server'} ] GOEP_L2CAP_PSM = 0x0200 MAS_INSTANCE_ID = 0x0315 SUPPORTED_MESSAGE_TYPES = 0x0316 MAP_SUPPORTED_FEATURES = 0x0317 attrs = { GOEP_L2CAP_PSM: 'GoepL2capPsm', # MAP v1.2 and later MAS_INSTANCE_ID: 'MASInstanceID', SUPPORTED_MESSAGE_TYPES: 'SupportedMessageTypes', MAP_SUPPORTED_FEATURES: 'MapSupportedFeatures' # MAP v1.2 and later } supported_msg_types_bitmap = { 0: 'EMAIL', # LSB 1: 'SMS_GSM', 2: 'SMS_CDMA', 3: 'MMS', 4: 'IM', 5: 'Reserved for Future Use', 6: 'Reserved for Future Use', 7: 'Reserved for Future Use' } map_supported_features_bitmap = { 0: 'Notification Registration Feature', # LSB 1: 'Notification Feature', 2: 'Browsing Feature', 3: 'Uploading Feature', 4: 'Delete Feature', 5: 'Instance Information Feature', 6: 'Extended Event Report 1.1', 7: 'Event Report Version 1.2', 8: 'Message Format Version 1.1', 9: 'MessagesListing Format Version 1.1', 10: 'Persistent Message Handles', 11: 'Database Identifier', 12: 'Folder Version Counter', 13: 'Conversation Version Counters', 14: 'Participant Presence Change Notification', 15: 'Participant Chat State Change Notification', 16: 'PBAP Contact Cross Reference', 17: 'Notification Filtering', 18: 'UTC Offset Timestamp Format', # The only difference from MapSupportedFeatures of MSE 19: 'MapSupportedFeatures in Connect Request', 20: 'Conversation listing', 21: 'Owner Status', 22: 'Message Forwarding', 23: 'Reserved for Furture Use', 24: 'Reserved for Furture Use', 25: 'Reserved for Furture Use', 26: 'Reserved for Furture Use', 27: 'Reserved for Furture Use', 28: 'Reserved for Furture Use', 29: 'Reserved for Furture Use', 30: 'Reserved for Furture Use', 31: 'Reserved for Furture Use' } @classmethod def pp_goep_l2cap_psm(cls, val:int): '''Parser and print GoepL2capPsm (MAP v1.2 and later) val - Value of GoepL2capPsm, uint16 ''' print('\t0x%04X'%val) @classmethod def pp_mas_instance_id(cls, val:int): '''Parse and print MASInstanceID service attribute val - Value of MASInstanceID, uint8 ''' print('\t0x%02X'%val) @classmethod def pp_supported_msg_types(cls, val:int): '''Parse and print SupportedMessageTypes service attribute. val - Value of SupportedMessageTypes, uint8 ''' print('\t0x%02X'%val) for i in range(len(cls.supported_msg_types_bitmap)): type_name = cls.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') @classmethod def pp_map_supported_features(cls, val: int): '''Parse and print MapSupportedFeatures (MAP v1.2 and later) service attribute. 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[i] 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') class MCEServiceRecord(ServiceRecord): '''Message Client Equipment Service Record See MAP Specification v1.4.2, Table 7.2: SDP Record for the Message Notification Service on the MCE Device ''' service_classes = [ {'UUID': 0x1133, 'name': 'Message Notification Server'} ] GOEP_L2CAP_PSM = 0x0200 MAP_SUPPORTED_FEATURES = 0x0317 attrs = { GOEP_L2CAP_PSM: 'GoepL2capPsm', # MAP v1.2 and later MAP_SUPPORTED_FEATURES: 'MapSupportedFeatures' # MAP v1.2 and later } map_supported_features_bitmap = { 0: 'Notification Registration Feature', # LSB 1: 'Notification Feature', 2: 'Browsing Feature', 3: 'Uploading Feature', 4: 'Delete Feature', 5: 'Instance Information Feature', 6: 'Extended Event Report 1.1', 7: 'Event Report Version 1.2', 8: 'Message Format Version 1.1', 9: 'MessagesListing Format Version 1.1', 10: 'Persistent Message Handles', 11: 'Database Identifier', 12: 'Folder Version Counter', 13: 'Conversation Version Counters', 14: 'Participant Presence Change Notification', 15: 'Participant Chat State Change Notification', 16: 'PBAP Contact Cross Reference', 17: 'Notification Filtering', 18: 'UTC Offset Timestamp Format', 19: 'Reserved', # The only difference from MapSupportedFeatures of MSE 20: 'Conversation listing', 21: 'Owner Status', 22: 'Message Forwarding', 23: 'Reserved for Furture Use', 24: 'Reserved for Furture Use', 25: 'Reserved for Furture Use', 26: 'Reserved for Furture Use', 27: 'Reserved for Furture Use', 28: 'Reserved for Furture Use', 29: 'Reserved for Furture Use', 30: 'Reserved for Furture Use', 31: 'Reserved for Furture Use' } @classmethod def pp_goep_l2cap_psm(cls, val:int): '''Parser and print GoepL2capPsm (MAP v1.2 and later) val - Value of GoepL2capPsm, uint16 ''' print('\t0x%04X'%val) @classmethod 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 __test(): record_xml = '''<?xml version="1.0" encoding="UTF-8" ?> <record> <attribute id="0x0000"> <uint32 value="0x00010009" /> </attribute> <attribute id="0x0001"> <sequence> <uuid value="0x1132" /> </sequence> </attribute> <attribute id="0x0004"> <sequence> <sequence> <uuid value="0x0100" /> </sequence> <sequence> <uuid value="0x0003" /> <uint8 value="0x1a" /> </sequence> <sequence> <uuid value="0x0008" /> </sequence> </sequence> </attribute> <attribute id="0x0005"> <sequence> <uuid value="0x1002" /> </sequence> </attribute> <attribute id="0x0009"> <sequence> <sequence> <uuid value="0x1134" /> <uint16 value="0x0102" /> </sequence> </sequence> </attribute> <attribute id="0x0100"> <text value="SMS/MMS " /> </attribute> <attribute id="0x0200"> <uint16 value="0x1029" /> </attribute> <attribute id="0x0315"> <uint8 value="0x00" /> </attribute> <attribute id="0x0316"> <uint8 value="0x0e" /> </attribute> <attribute id="0x0317"> <uint32 value="0x0000007f" /> </attribute> </record> ''' ServiceRecord(record_xml).pp() if __name__ == '__main__': __test()