blob: f849e3a71c85270c960e3d0fb0b4e4dbfc36ca26 [file] [log] [blame]
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -07001# Copyright 2021-2022 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15# -----------------------------------------------------------------------------
16# Imports
17# -----------------------------------------------------------------------------
18import struct
19import click
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -070020
uaeld21da782023-02-23 20:16:33 +000021from bumble.colors import color
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -070022from bumble import hci
Gilles Boccon-Gibod86ded3f2022-05-29 16:30:05 -070023from bumble.transport.common import PacketReader
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -070024from bumble.helpers import PacketTracer
25
26
27# -----------------------------------------------------------------------------
28class SnoopPacketReader:
29 '''
Gilles Boccon-Gibodc2959da2022-12-10 09:29:51 -080030 Reader that reads HCI packets from a "snoop" file (based on RFC 1761, but not
31 exactly the same...)
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -070032 '''
33
Gilles Boccon-Gibod135df0d2022-12-10 08:53:51 -080034 DATALINK_H1 = 1001
35 DATALINK_H4 = 1002
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -070036 DATALINK_BSCP = 1003
Gilles Boccon-Gibod135df0d2022-12-10 08:53:51 -080037 DATALINK_H5 = 1004
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -070038
39 def __init__(self, source):
40 self.source = source
41
42 # Read the header
43 identification_pattern = source.read(8)
44 if identification_pattern.hex().lower() != '6274736e6f6f7000':
Gilles Boccon-Gibod135df0d2022-12-10 08:53:51 -080045 raise ValueError(
46 'not a valid snoop file, unexpected identification pattern'
47 )
48 (self.version_number, self.data_link_type) = struct.unpack(
49 '>II', source.read(8)
50 )
Gilles Boccon-Gibodc2959da2022-12-10 09:29:51 -080051 if self.data_link_type not in (self.DATALINK_H4, self.DATALINK_H1):
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -070052 raise ValueError(f'datalink type {self.data_link_type} not supported')
53
54 def next_packet(self):
55 # Read the record header
56 header = self.source.read(24)
57 if len(header) < 24:
58 return (0, None)
59 (
60 original_length,
61 included_length,
62 packet_flags,
Gilles Boccon-Gibodc2959da2022-12-10 09:29:51 -080063 _cumulative_drops,
64 _timestamp_seconds,
65 _timestamp_microsecond,
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -070066 ) = struct.unpack('>IIIIII', header)
67
68 # Abort on truncated packets
69 if original_length != included_length:
70 return (0, None)
71
72 if self.data_link_type == self.DATALINK_H1:
73 # The packet is un-encapsulated, look at the flags to figure out its type
74 if packet_flags & 1:
75 # Controller -> Host
76 if packet_flags & 2:
77 packet_type = hci.HCI_EVENT_PACKET
78 else:
79 packet_type = hci.HCI_ACL_DATA_PACKET
80 else:
81 # Host -> Controller
82 if packet_flags & 2:
83 packet_type = hci.HCI_COMMAND_PACKET
84 else:
85 packet_type = hci.HCI_ACL_DATA_PACKET
86
Gilles Boccon-Gibod135df0d2022-12-10 08:53:51 -080087 return (
88 packet_flags & 1,
89 bytes([packet_type]) + self.source.read(included_length),
90 )
Gilles Boccon-Gibodc2959da2022-12-10 09:29:51 -080091
92 return (packet_flags & 1, self.source.read(included_length))
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -070093
94
95# -----------------------------------------------------------------------------
96# Main
97# -----------------------------------------------------------------------------
98@click.command()
Gilles Boccon-Gibod135df0d2022-12-10 08:53:51 -080099@click.option(
100 '--format',
101 type=click.Choice(['h4', 'snoop']),
102 default='h4',
103 help='Format of the input file',
104)
Gilles Boccon-Gibodae77e452023-08-31 16:35:21 -0700105@click.option(
106 '--vendors',
Michael Mogensonda02f6a2023-09-08 12:32:28 -0400107 type=click.Choice(['android', 'zephyr']),
Gilles Boccon-Gibodae77e452023-08-31 16:35:21 -0700108 multiple=True,
109 help='Support vendor-specific commands (list one or more)',
110)
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -0700111@click.argument('filename')
Gilles Boccon-Gibodc2959da2022-12-10 09:29:51 -0800112# pylint: disable=redefined-builtin
Gilles Boccon-Gibodae77e452023-08-31 16:35:21 -0700113def main(format, vendors, filename):
114 for vendor in vendors:
115 if vendor == 'android':
116 import bumble.vendor.android.hci
Michael Mogensonda02f6a2023-09-08 12:32:28 -0400117 elif vendor == 'zephyr':
118 import bumble.vendor.zephyr.hci
Gilles Boccon-Gibodae77e452023-08-31 16:35:21 -0700119
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -0700120 input = open(filename, 'rb')
121 if format == 'h4':
122 packet_reader = PacketReader(input)
123
124 def read_next_packet():
Gilles Boccon-Gibodc2959da2022-12-10 09:29:51 -0800125 return (0, packet_reader.next_packet())
Gilles Boccon-Gibod135df0d2022-12-10 08:53:51 -0800126
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -0700127 else:
128 packet_reader = SnoopPacketReader(input)
129 read_next_packet = packet_reader.next_packet
130
131 tracer = PacketTracer(emit_message=print)
132
133 while True:
134 try:
135 (direction, packet) = read_next_packet()
136 if packet is None:
137 break
138 tracer.trace(hci.HCI_Packet.from_bytes(packet), direction)
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -0700139 except Exception as error:
140 print(color(f'!!! {error}', 'red'))
Gilles Boccon-Gibod6ac91f72022-05-16 19:42:31 -0700141
142
143# -----------------------------------------------------------------------------
144if __name__ == '__main__':
Gilles Boccon-Gibodc2959da2022-12-10 09:29:51 -0800145 main() # pylint: disable=no-value-for-parameter