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 24 25import base64 26import fileinput 27import struct 28import sys 29import 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. 35TYPE_IN_EVT = 0x10 36TYPE_IN_ACL = 0x11 37TYPE_IN_SCO = 0x12 38TYPE_OUT_CMD = 0x20 39TYPE_OUT_ACL = 0x21 40TYPE_OUT_SCO = 0x22 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]: 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 67 68def 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 89def 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 116def 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 143def 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 166if __name__ == '__main__': 167 main() 168