• 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 #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