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