Home | History | Annotate | Download | only in scripts
      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