1#!/usr/bin/env python 2""" 3This script extracts btsnooz content from bugreports and generates 4a valid btsnoop log file which can be viewed using standard tools 5like Wireshark. 6 7btsnooz is a custom format designed to be included in bugreports. 8It can be described as: 9 10base64 { 11 file_header 12 deflate { 13 repeated { 14 record_header 15 record_data 16 } 17 } 18} 19 20where the file_header and record_header are modified versions of 21the btsnoop headers. 22""" 23 24import base64 25import fileinput 26import struct 27import sys 28import zlib 29 30# Enumeration of the values the 'type' field can take in a btsnooz 31# header. These values come from the Bluetooth stack's internal 32# representation of packet types. 33TYPE_IN_EVT = 0x10 34TYPE_IN_ACL = 0x11 35TYPE_IN_SCO = 0x12 36TYPE_IN_ISO = 0x17 37TYPE_OUT_CMD = 0x20 38TYPE_OUT_ACL = 0x21 39TYPE_OUT_SCO = 0x22 40TYPE_OUT_ISO = 0x2d 41 42 43def type_to_direction(type): 44 """ 45 Returns the inbound/outbound direction of a packet given its type. 46 0 = sent packet 47 1 = received packet 48 """ 49 if type in [TYPE_IN_EVT, TYPE_IN_ACL, TYPE_IN_SCO, TYPE_IN_ISO]: 50 return 1 51 return 0 52 53 54def type_to_hci(type): 55 """ 56 Returns the HCI type of a packet given its btsnooz type. 57 """ 58 if type == TYPE_OUT_CMD: 59 return '\x01' 60 if type == TYPE_IN_ACL or type == TYPE_OUT_ACL: 61 return '\x02' 62 if type == TYPE_IN_SCO or type == TYPE_OUT_SCO: 63 return '\x03' 64 if type == TYPE_IN_EVT: 65 return '\x04' 66 if type == TYPE_IN_ISO or type == TYPE_OUT_ISO: 67 return '\x05' 68 raise RuntimeError("type_to_hci: unknown type (0x{:02x})".format(type)) 69 70 71def decode_snooz(snooz): 72 """ 73 Decodes all known versions of a btsnooz file into a btsnoop file. 74 """ 75 version, last_timestamp_ms = struct.unpack_from('=bQ', snooz) 76 77 if version != 1 and version != 2: 78 sys.stderr.write('Unsupported btsnooz version: %s\n' % version) 79 exit(1) 80 81 # Oddly, the file header (9 bytes) is not compressed, but the rest is. 82 decompressed = zlib.decompress(snooz[9:]) 83 84 sys.stdout.write('btsnoop\x00\x00\x00\x00\x01\x00\x00\x03\xea') 85 86 if version == 1: 87 decode_snooz_v1(decompressed, last_timestamp_ms) 88 elif version == 2: 89 decode_snooz_v2(decompressed, last_timestamp_ms) 90 91 92def decode_snooz_v1(decompressed, last_timestamp_ms): 93 """ 94 Decodes btsnooz v1 files into a btsnoop file. 95 """ 96 # An unfortunate consequence of the file format design: we have to do a 97 # pass of the entire file to determine the timestamp of the first packet. 98 first_timestamp_ms = last_timestamp_ms + 0x00dcddb30f2f8000 99 offset = 0 100 while offset < len(decompressed): 101 length, delta_time_ms, type = struct.unpack_from('=HIb', decompressed, offset) 102 offset += 7 + length - 1 103 first_timestamp_ms -= delta_time_ms 104 105 # Second pass does the actual writing out to stdout. 106 offset = 0 107 while offset < len(decompressed): 108 length, delta_time_ms, type = struct.unpack_from('=HIb', decompressed, offset) 109 first_timestamp_ms += delta_time_ms 110 offset += 7 111 sys.stdout.write(struct.pack('>II', length, length)) 112 sys.stdout.write(struct.pack('>II', type_to_direction(type), 0)) 113 sys.stdout.write(struct.pack('>II', (first_timestamp_ms >> 32), (first_timestamp_ms & 0xFFFFFFFF))) 114 sys.stdout.write(type_to_hci(type)) 115 sys.stdout.write(decompressed[offset:offset + length - 1]) 116 offset += length - 1 117 118 119def decode_snooz_v2(decompressed, last_timestamp_ms): 120 """ 121 Decodes btsnooz v2 files into a btsnoop file. 122 """ 123 # An unfortunate consequence of the file format design: we have to do a 124 # pass of the entire file to determine the timestamp of the first packet. 125 first_timestamp_ms = last_timestamp_ms + 0x00dcddb30f2f8000 126 offset = 0 127 while offset < len(decompressed): 128 length, packet_length, delta_time_ms, snooz_type = struct.unpack_from('=HHIb', decompressed, offset) 129 offset += 9 + length - 1 130 first_timestamp_ms -= delta_time_ms 131 132 # Second pass does the actual writing out to stdout. 133 offset = 0 134 while offset < len(decompressed): 135 length, packet_length, delta_time_ms, snooz_type = struct.unpack_from('=HHIb', decompressed, offset) 136 first_timestamp_ms += delta_time_ms 137 offset += 9 138 sys.stdout.write(struct.pack('>II', packet_length, length)) 139 sys.stdout.write(struct.pack('>II', type_to_direction(snooz_type), 0)) 140 sys.stdout.write(struct.pack('>II', (first_timestamp_ms >> 32), (first_timestamp_ms & 0xFFFFFFFF))) 141 sys.stdout.write(type_to_hci(snooz_type)) 142 sys.stdout.write(decompressed[offset:offset + length - 1]) 143 offset += length - 1 144 145 146def main(): 147 if len(sys.argv) > 2: 148 sys.stderr.write('Usage: %s [bugreport]\n' % sys.argv[0]) 149 exit(1) 150 151 iterator = fileinput.input() 152 found = False 153 base64_string = "" 154 for line in iterator: 155 if found: 156 if line.find('--- END:BTSNOOP_LOG_SUMMARY') != -1: 157 decode_snooz(base64.standard_b64decode(base64_string)) 158 sys.exit(0) 159 base64_string += line.strip() 160 161 if line.find('--- BEGIN:BTSNOOP_LOG_SUMMARY') != -1: 162 found = True 163 164 if not found: 165 sys.stderr.write('No btsnooz section found in bugreport.\n') 166 sys.exit(1) 167 168 169if __name__ == '__main__': 170 main() 171