• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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