aboutsummaryrefslogtreecommitdiff
path: root/tools/scripts/btsnooz.py
blob: 622824a470cc7521510f705065d0a3fe061c3d74 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python
"""
This script extracts btsnooz content from bugreports and generates
a valid btsnoop log file which can be viewed using standard tools
like Wireshark.

btsnooz is a custom format designed to be included in bugreports.
It can be described as:

base64 {
  file_header
  deflate {
    repeated {
      record_header
      record_data
    }
  }
}

where the file_header and record_header are modified versions of
the btsnoop headers.
"""

import base64
import fileinput
import struct
import sys
import zlib

# Enumeration of the values the 'type' field can take in a btsnooz
# header. These values come from the Bluetooth stack's internal
# representation of packet types.
TYPE_IN_EVT = 0x10
TYPE_IN_ACL = 0x11
TYPE_IN_SCO = 0x12
TYPE_IN_ISO = 0x17
TYPE_OUT_CMD = 0x20
TYPE_OUT_ACL = 0x21
TYPE_OUT_SCO = 0x22
TYPE_OUT_ISO = 0x2d


def type_to_direction(type):
    """
  Returns the inbound/outbound direction of a packet given its type.
  0 = sent packet
  1 = received packet
  """
    if type in [TYPE_IN_EVT, TYPE_IN_ACL, TYPE_IN_SCO, TYPE_IN_ISO]:
        return 1
    return 0


def type_to_hci(type):
    """
  Returns the HCI type of a packet given its btsnooz type.
  """
    if type == TYPE_OUT_CMD:
        return '\x01'
    if type == TYPE_IN_ACL or type == TYPE_OUT_ACL:
        return '\x02'
    if type == TYPE_IN_SCO or type == TYPE_OUT_SCO:
        return '\x03'
    if type == TYPE_IN_EVT:
        return '\x04'
    if type == TYPE_IN_ISO or type == TYPE_OUT_ISO:
        return '\x05'
    raise RuntimeError("type_to_hci: unknown type (0x{:02x})".format(type))


def decode_snooz(snooz):
    """
  Decodes all known versions of a btsnooz file into a btsnoop file.
  """
    version, last_timestamp_ms = struct.unpack_from('=bQ', snooz)

    if version != 1 and version != 2:
        sys.stderr.write('Unsupported btsnooz version: %s\n' % version)
        exit(1)

    # Oddly, the file header (9 bytes) is not compressed, but the rest is.
    decompressed = zlib.decompress(snooz[9:])

    sys.stdout.write('btsnoop\x00\x00\x00\x00\x01\x00\x00\x03\xea')

    if version == 1:
        decode_snooz_v1(decompressed, last_timestamp_ms)
    elif version == 2:
        decode_snooz_v2(decompressed, last_timestamp_ms)


def decode_snooz_v1(decompressed, last_timestamp_ms):
    """
  Decodes btsnooz v1 files into a btsnoop file.
  """
    # An unfortunate consequence of the file format design: we have to do a
    # pass of the entire file to determine the timestamp of the first packet.
    first_timestamp_ms = last_timestamp_ms + 0x00dcddb30f2f8000
    offset = 0
    while offset < len(decompressed):
        length, delta_time_ms, type = struct.unpack_from('=HIb', decompressed, offset)
        offset += 7 + length - 1
        first_timestamp_ms -= delta_time_ms

    # Second pass does the actual writing out to stdout.
    offset = 0
    while offset < len(decompressed):
        length, delta_time_ms, type = struct.unpack_from('=HIb', decompressed, offset)
        first_timestamp_ms += delta_time_ms
        offset += 7
        sys.stdout.write(struct.pack('>II', length, length))
        sys.stdout.write(struct.pack('>II', type_to_direction(type), 0))
        sys.stdout.write(struct.pack('>II', (first_timestamp_ms >> 32), (first_timestamp_ms & 0xFFFFFFFF)))
        sys.stdout.write(type_to_hci(type))
        sys.stdout.write(decompressed[offset:offset + length - 1])
        offset += length - 1


def decode_snooz_v2(decompressed, last_timestamp_ms):
    """
  Decodes btsnooz v2 files into a btsnoop file.
  """
    # An unfortunate consequence of the file format design: we have to do a
    # pass of the entire file to determine the timestamp of the first packet.
    first_timestamp_ms = last_timestamp_ms + 0x00dcddb30f2f8000
    offset = 0
    while offset < len(decompressed):
        length, packet_length, delta_time_ms, snooz_type = struct.unpack_from('=HHIb', decompressed, offset)
        offset += 9 + length - 1
        first_timestamp_ms -= delta_time_ms

    # Second pass does the actual writing out to stdout.
    offset = 0
    while offset < len(decompressed):
        length, packet_length, delta_time_ms, snooz_type = struct.unpack_from('=HHIb', decompressed, offset)
        first_timestamp_ms += delta_time_ms
        offset += 9
        sys.stdout.write(struct.pack('>II', packet_length, length))
        sys.stdout.write(struct.pack('>II', type_to_direction(snooz_type), 0))
        sys.stdout.write(struct.pack('>II', (first_timestamp_ms >> 32), (first_timestamp_ms & 0xFFFFFFFF)))
        sys.stdout.write(type_to_hci(snooz_type))
        sys.stdout.write(decompressed[offset:offset + length - 1])
        offset += length - 1


def main():
    if len(sys.argv) > 2:
        sys.stderr.write('Usage: %s [bugreport]\n' % sys.argv[0])
        exit(1)

    iterator = fileinput.input()
    found = False
    base64_string = ""
    for line in iterator:
        if found:
            if line.find('--- END:BTSNOOP_LOG_SUMMARY') != -1:
                decode_snooz(base64.standard_b64decode(base64_string))
                sys.exit(0)
            base64_string += line.strip()

        if line.find('--- BEGIN:BTSNOOP_LOG_SUMMARY') != -1:
            found = True

    if not found:
        sys.stderr.write('No btsnooz section found in bugreport.\n')
        sys.exit(1)


if __name__ == '__main__':
    main()