1 // Copyright 2025 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // 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, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth/snoop.h"
16
17 #include <algorithm>
18 #include <chrono>
19 #include <cstdint>
20 #include <mutex>
21
22 #include "pw_assert/check.h"
23 #include "pw_bluetooth/emboss_util.h"
24 #include "pw_bluetooth/snoop.emb.h"
25 #include "pw_bluetooth_proxy/h4_packet.h"
26 #include "pw_bytes/span.h"
27 #include "pw_chrono/system_clock.h"
28 #include "pw_containers/inline_var_len_entry_queue.h"
29 #include "pw_hex_dump/hex_dump.h"
30 #include "pw_log/log.h"
31 #include "pw_result/result.h"
32 #include "pw_span/span.h"
33 #include "pw_status/status.h"
34 #include "pw_sync/mutex.h"
35
36 namespace pw::bluetooth {
37
38 // Dump the snoop log to the log as a hex string
DumpToLog()39 Status Snoop::DumpToLog() {
40 PW_LOG_INFO("Snoop Log Start");
41 PW_LOG_INFO("Step 1: Copy and paste the hex data into a text file");
42 PW_LOG_INFO("Step 2: Remove any extra text (e.g. file, timestamp, etc)");
43 PW_LOG_INFO("Step 3: $ xxd -r -p input.hex output.snoop");
44 PW_LOG_INFO("Step 4: $ wireshark output.snoop");
45 Status status = Dump([](ConstByteSpan data) {
46 std::array<char, 80> temp;
47 pw::dump::FormattedHexDumper hex_dumper(temp);
48 hex_dumper.flags.prefix_mode =
49 pw::dump::FormattedHexDumper::AddressMode::kDisabled;
50 hex_dumper.flags.show_ascii = false;
51 hex_dumper.flags.bytes_per_line = 32;
52 hex_dumper.flags.group_every = 32;
53 hex_dumper.flags.show_header = false;
54 Status hex_dumper_status = hex_dumper.BeginDump(data);
55 if (!hex_dumper_status.ok()) {
56 return hex_dumper_status;
57 }
58 while (hex_dumper.DumpLine().ok()) {
59 PW_LOG_INFO("%s", temp.data());
60 }
61 return pw::OkStatus();
62 });
63 PW_LOG_INFO("Snoop Log End");
64 return status;
65 }
66
67 /// Dump the snoop log via callback without locking
68 ///
69 /// The callback will be invoked multiple times as the circular buffer is
70 /// traversed.
71 ///
72 /// Note: this function does NOT lock the snoop log. Do not invoke it unless
73 /// the snoop log is not being used. For example, use this API to read the
74 /// snoop log in a crash handler where mutexes are not allowed to be taken.
75 ///
76 /// @param callback callback to invoke
DumpUnlocked(const Function<Status (ConstByteSpan data)> & callback)77 Status Snoop::DumpUnlocked(
78 const Function<Status(ConstByteSpan data)>& callback) {
79 Status status = DumpSnoopLogFileHeader(callback);
80 if (!status.ok()) {
81 return status;
82 }
83
84 for (const auto& entry : queue_) {
85 std::pair<ConstByteSpan, ConstByteSpan> data = entry.contiguous_data();
86 status = callback(data.first);
87 if (!status.ok()) {
88 return status;
89 }
90 if (!data.second.empty()) {
91 status = callback(data.second);
92 if (!status.ok()) {
93 return status;
94 }
95 }
96 }
97 return OkStatus();
98 }
99
AddEntry(emboss::snoop_log::PacketFlags emboss_packet_flag,proxy::H4PacketInterface & hci_packet)100 void Snoop::AddEntry(emboss::snoop_log::PacketFlags emboss_packet_flag,
101 proxy::H4PacketInterface& hci_packet) {
102 std::lock_guard lock(queue_lock_);
103
104 size_t hci_packet_length_to_include = std::min(
105 hci_packet.GetHciSpan().size(),
106 static_cast<size_t>(scratch_buffer_.size() -
107 emboss::snoop_log::EntryHeader::MaxSizeInBytes() -
108 /* hci type*/ 1));
109 size_t total_entry_size = hci_packet_length_to_include + /* hci type*/ 1 +
110 emboss::snoop_log::EntryHeader::MaxSizeInBytes();
111 // Ensure the scratch buffer can fit the entire entry
112 PW_CHECK_INT_GE(scratch_buffer_.size(), total_entry_size);
113
114 pw::Result<emboss::snoop_log::EntryWriter> result =
115 MakeEmbossWriter<emboss::snoop_log::EntryWriter>(scratch_buffer_);
116 PW_CHECK_OK(result);
117 emboss::snoop_log::EntryWriter writer = result.value();
118 writer.header().original_length().Write(hci_packet.GetHciSpan().size() +
119 /* hci type*/ 1);
120 writer.header().included_length().Write(hci_packet_length_to_include +
121 /* hci type*/ 1);
122 writer.header().packet_flags().Write(emboss_packet_flag);
123 writer.header().cumulative_drops().Write(0);
124 writer.header().timestamp_us().Write(static_cast<int64_t>(
125 std::chrono::time_point_cast<std::chrono::microseconds>(
126 system_clock_.now())
127 .time_since_epoch()
128 .count()));
129
130 // write h4 type
131 writer.packet_h4_type().Write(static_cast<uint8_t>(hci_packet.GetH4Type()));
132
133 // write hci packet
134 pw::span<uint8_t> hci_packet_trimmed{hci_packet.GetHciSpan().data(),
135 hci_packet_length_to_include};
136 PW_CHECK(TryToCopyToEmbossStruct(/*emboss_dest=*/writer.packet_hci_data(),
137 /*src=*/hci_packet_trimmed));
138
139 // save the entry!
140 queue_.push_overwrite(
141 as_bytes(span{scratch_buffer_.data(), total_entry_size}));
142 }
143
144 /// Generates the snoop log file header
145 ///
146 /// @returns the file header
DumpSnoopLogFileHeader(const Function<Status (ConstByteSpan data)> & callback)147 Status Snoop::DumpSnoopLogFileHeader(
148 const Function<Status(ConstByteSpan data)>& callback) {
149 std::array<uint8_t, 16> file_header_data;
150 pw::Result<emboss::snoop_log::FileHeaderWriter> result =
151 MakeEmbossWriter<emboss::snoop_log::FileHeaderWriter>(file_header_data);
152 if (!result.ok()) {
153 return result.status();
154 }
155 emboss::snoop_log::FileHeaderWriter writer = result.value();
156 constexpr std::array<uint8_t, 8> kBtSnoopIdentificationPatternData = {
157 0x62, 0x74, 0x73, 0x6E, 0x6F, 0x6F, 0x70, 0x00};
158
159 auto identification_pattern_storage =
160 writer.identification_pattern().BackingStorage();
161 PW_ASSERT(kBtSnoopIdentificationPatternData.size() ==
162 identification_pattern_storage.SizeInBytes());
163 std::copy(kBtSnoopIdentificationPatternData.begin(),
164 kBtSnoopIdentificationPatternData.end(),
165 identification_pattern_storage.begin());
166 writer.version_number().Write(kEmbossFileVersion);
167 writer.datalink_type().Write(emboss::snoop_log::DataLinkType::HCI_UART_H4);
168 return callback(as_bytes(span(file_header_data)));
169 }
170
171 } // namespace pw::bluetooth
172