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 #pragma once 16 17 #include <cstdint> 18 #include <mutex> 19 20 #include "pw_bluetooth/snoop.emb.h" 21 #include "pw_bluetooth_proxy/h4_packet.h" 22 #include "pw_bytes/span.h" 23 #include "pw_chrono/system_clock.h" 24 #include "pw_containers/inline_var_len_entry_queue.h" 25 #include "pw_result/result.h" 26 #include "pw_span/span.h" 27 #include "pw_status/status.h" 28 #include "pw_sync/lock_annotations.h" 29 #include "pw_sync/mutex.h" 30 #include "pw_sync/virtual_basic_lockable.h" 31 32 namespace pw::bluetooth { 33 34 /// Snoop will record Rx & Tx transactions in a circular buffer. The most 35 /// recent transactions are saved when the buffer is full. 36 /// 37 /// @param system_clock system clock to use 38 /// @param queue queue to hold all records 39 /// @param lock lock to hold while accessing queue 40 /// @param scratch_buffer buffer used for generation of each record. If a record 41 /// is larger than the scratch buffer, the record will be truncated. 42 class Snoop { 43 public: Create(chrono::VirtualSystemClock & system_clock,InlineVarLenEntryQueue<> & queue,pw::sync::VirtualBasicLockable & queue_lock,span<uint8_t> scratch_buffer)44 static pw::Result<Snoop> Create(chrono::VirtualSystemClock& system_clock, 45 InlineVarLenEntryQueue<>& queue, 46 pw::sync::VirtualBasicLockable& queue_lock, 47 span<uint8_t> scratch_buffer) { 48 if (scratch_buffer.size() < 49 emboss::snoop_log::EntryHeader::MaxSizeInBytes()) { 50 return Status::FailedPrecondition(); 51 } 52 return Snoop(system_clock, queue, queue_lock, scratch_buffer); 53 } 54 55 // Calculate the size of the scratch buffer. 56 /// 57 /// @param hci_payload_size The number of bytes of the hci packet to save NeededScratchBufferSize(size_t hci_payload_size)58 static constexpr size_t NeededScratchBufferSize(size_t hci_payload_size) { 59 return emboss::snoop_log::EntryHeader::MaxSizeInBytes() + /* hci type */ 1 + 60 hci_payload_size; 61 } 62 63 // Dump the snoop log to the log as a hex string 64 Status DumpToLog(); 65 66 /// Dump the snoop log via callback 67 /// 68 /// The callback will be invoked multiple times as the circular buffer is 69 /// traversed. The data returned in the callback should be saved directly to 70 /// a file. Each callback will contain part of the file. The number of 71 /// callbacks is not known ahead of time. 72 /// 73 /// @param callback callback to invoke Dump(const Function<Status (ConstByteSpan data)> & callback)74 Status Dump(const Function<Status(ConstByteSpan data)>& callback) { 75 std::lock_guard lock(queue_lock_); 76 return DumpUnlocked(callback); 77 } 78 79 /// Dump the snoop log via callback without locking 80 /// 81 /// The callback will be invoked multiple times as the circular buffer is 82 /// traversed. 83 /// 84 /// Note: this function does NOT lock the snoop log. Do not invoke it unless 85 /// the snoop log is not being used. For example, use this API to read the 86 /// snoop log in a crash handler where mutexes are not allowed to be taken. 87 /// 88 /// @param callback callback to invoke 89 Status DumpUnlocked(const Function<Status(ConstByteSpan data)>& callback) 90 PW_NO_LOCK_SAFETY_ANALYSIS; 91 92 /// Add a Tx transaction 93 /// 94 /// @param packet Packet to save to snoop log AddTx(proxy::H4PacketInterface & packet)95 void AddTx(proxy::H4PacketInterface& packet) { 96 AddEntry(emboss::snoop_log::PacketFlags::SENT, packet); 97 } 98 99 // Add an Rx transaction 100 /// 101 /// @param packet Packet to save to snoop log AddRx(proxy::H4PacketInterface & packet)102 void AddRx(proxy::H4PacketInterface& packet) { 103 AddEntry(emboss::snoop_log::PacketFlags::RECEIVED, packet); 104 } 105 106 protected: 107 /// @param system_clock system clock to use 108 /// @param queue queue to hold all records 109 /// @param lock lock to hold while accessing queue 110 /// @param scratch_buffer buffer used for generation of each record. If a 111 /// record is larger than the scratch buffer, the record will be truncated. Snoop(chrono::VirtualSystemClock & system_clock,InlineVarLenEntryQueue<> & queue,pw::sync::VirtualBasicLockable & queue_lock,span<uint8_t> scratch_buffer)112 Snoop(chrono::VirtualSystemClock& system_clock, 113 InlineVarLenEntryQueue<>& queue, 114 pw::sync::VirtualBasicLockable& queue_lock, 115 span<uint8_t> scratch_buffer) 116 : system_clock_(system_clock), 117 queue_(queue), 118 scratch_buffer_(scratch_buffer), 119 queue_lock_(queue_lock) {} 120 121 private: 122 /// Add an entry to the snoop log 123 /// 124 /// @param packet_flag Packet flags (rx/tx) 125 /// @param packet Packet to save to snoop log 126 /// @param scratch_entry scratch buffer used to assemble the entry 127 void AddEntry(emboss::snoop_log::PacketFlags emboss_packet_flag, 128 proxy::H4PacketInterface& hci_packet); 129 130 /// Generates the snoop log file header and sends it to the callback 131 /// 132 /// @param callback callback to invoke 133 Status DumpSnoopLogFileHeader( 134 const Function<Status(ConstByteSpan data)>& callback); 135 136 constexpr static uint32_t kEmbossFileVersion = 1; 137 chrono::VirtualSystemClock& system_clock_; 138 InlineVarLenEntryQueue<>& queue_ PW_GUARDED_BY(queue_lock_); 139 span<uint8_t> scratch_buffer_ PW_GUARDED_BY(queue_lock_); 140 pw::sync::VirtualBasicLockable& queue_lock_; 141 }; 142 143 /// SnoopBuffer is a buffer backed snoop log. 144 /// 145 /// @param kTotalSize total size of the snoop log 146 /// @param kMaxHciPacketSize max size of an hci packet to record 147 template <size_t kTotalSize, size_t kMaxHciPacketSize> 148 class SnoopBuffer : public Snoop { 149 public: SnoopBuffer(chrono::VirtualSystemClock & system_clock)150 SnoopBuffer(chrono::VirtualSystemClock& system_clock) 151 : Snoop(system_clock, queue_buffer_, queue_mutex_, scratch_buffer_) {} 152 153 private: 154 std::array<uint8_t, Snoop::NeededScratchBufferSize(kMaxHciPacketSize)> 155 scratch_buffer_{}; 156 InlineVarLenEntryQueue<kTotalSize> queue_buffer_; 157 sync::VirtualMutex queue_mutex_; 158 }; 159 160 } // namespace pw::bluetooth 161