• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 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 #include <cpp-type/member_pointer_traits.h>
17 #include <cpp-type/to_std_array.h>
18 
19 #include <array>
20 #include <cstdint>
21 #include <cstring>
22 #include <limits>
23 #include <memory>
24 #include <string>
25 #include <string_view>
26 #include <tuple>
27 #include <type_traits>
28 #include <vector>
29 
30 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
31 #include "pw_bluetooth_sapphire/internal/host/common/macros.h"
32 #include "pw_span/span.h"
33 
34 namespace bt {
35 
36 class BufferView;
37 class MutableBufferView;
38 class MutableByteBuffer;
39 
40 // Interface for buffer implementations with various allocation schemes.
41 class ByteBuffer {
42  public:
43   using const_iterator = const uint8_t*;
44   using iterator = const_iterator;
45   using value_type = uint8_t;
46 
47   virtual ~ByteBuffer() = default;
48 
49   // Returns a pointer to the beginning of this buffer. The return value is
50   // undefined if the buffer has size 0.
51   virtual const uint8_t* data() const = 0;
52 
53   // Returns the number of bytes contained in this buffer.
54   virtual size_t size() const = 0;
55 
56   // Returns a BufferView that points to the region of this buffer starting at
57   // |pos| of |size| bytes. If |size| is larger than the size of this BufferView
58   // then the returned region will contain all bytes in this buffer starting at
59   // |pos|.
60   //
61   // For example:
62   //
63   //  // Get a view of all of |my_buffer|.
64   //  const BufferView view = my_buffer.view();
65   //
66   //  // Get a view of the first 5 bytes in |my_buffer| (assuming |my_buffer| is
67   //  // large enough).
68   //  view = my_buffer.view(0, 5);
69   //
70   //  // Get a view of |my_buffer| starting at the second byte.
71   //  view = my_buffer.view(2);
72   //
73   //
74   // WARNING:
75   //
76   // A BufferView is only valid as long as the buffer that it points to is
77   // valid. Care should be taken to ensure that a BufferView does not outlive
78   // its backing buffer.
79   BufferView view(size_t pos = 0,
80                   size_t size = std::numeric_limits<std::size_t>::max()) const;
81 
82   // Same as view(), but returns a span instead of a BufferView.
83   pw::span<const std::byte> subspan(
84       size_t pos = 0,
85       size_t size = std::numeric_limits<std::size_t>::max()) const;
86 
87   // Copies all bytes of this buffer into |out_buffer|. |out_buffer| must be
88   // large enough to accommodate the result of this operation.
89   void Copy(MutableByteBuffer* out_buffer) const;
90 
91   // Copies |size| bytes of this buffer into |out_buffer| starting at offset
92   // |pos|. |out_buffer| must be large enough to accommodate the result of this
93   // operation.
94   void Copy(MutableByteBuffer* out_buffer, size_t pos, size_t size) const;
95 
96   // Creates a new std::string that contains a printable representation of a
97   // range of this buffer starting at |pos|. The string is checked to see if it
98   // is UTF-8. If not, each byte in the range to be converted is checked to see
99   // if it is printable ASCII. If so, the character is used as is. If not, it is
100   // replaced by '.'. The returned std::string will have size |size| + 1 to fit
101   // a terminating '\0'.
102   std::string Printable(size_t pos, size_t size) const;
103 
104   // Iterator functions.
begin()105   iterator begin() const { return cbegin(); }
end()106   iterator end() const { return cend(); }
107   virtual const_iterator cbegin() const = 0;
108   virtual const_iterator cend() const = 0;
109 
110   // Read-only random access operator.
111   inline const uint8_t& operator[](size_t pos) const {
112     BT_ASSERT_MSG(pos < size(), "invalid offset (pos = %zu)", pos);
113     return data()[pos];
114   }
115 
116   // Creates an object of type T with the first sizeof(T) bytes of the buffer as
117   // its representation (per definition at ISO/IEC 14882:2017(E) § 6.9
118   // [basic.types] ¶ 4.4). The user is responsible for checking that the first
119   // sizeof(T) bytes represent a valid instance of T. If T is an array type, the
120   // return value will be a std::array with the same element type and extents.
121   //
122   // This or ReadMember should always be used in place of reinterpret_cast on
123   // raw pointers because of dangerous UB related to object lifetimes and
124   // alignment issues (see fxbug.dev/42123294). Moreover, this will perform
125   // bounds checking on the data being read.
126   template <typename T>
To()127   [[nodiscard]] auto To() const {
128     static_assert(std::is_trivially_copyable_v<T>,
129                   "unsafe to copy representation");
130     static_assert(std::is_default_constructible_v<T>);
131     using OutType = std::remove_cv_t<bt_lib_cpp_type::ToStdArrayT<T>>;
132 
133     // This is value-initialized in order to construct objects that have const
134     // members. The consideration for modifying the object through its
135     // representation even if the constituent types are cv-qualified is based on
136     // the potent rules for memcpy'ing "underlying bytes" at ISO/IEC
137     // 14882:2017(E) § 6.9 [basic.types] ¶ 4.2–4.3.
138     OutType out{};
139     CopyRaw(/*dst_data=*/std::addressof(out),
140             /*dst_capacity=*/sizeof(out),
141             /*src_offset=*/0,
142             /*copy_size=*/sizeof(out));
143     return out;
144   }
145 
146   // Given a pointer to a member of a class, interpret the underlying buffer as
147   // a representation of the class and return a copy of the member, with bounds
148   // checking for reading the representation. Array elements (including
149   // multi-dimensional) will be returned as std::array. The buffer is allowed to
150   // be larger than T. The user is responsible for checking that the first
151   // sizeof(T) bytes represent a valid instance of T.
152   //
153   // Example:
154   //   struct Foo { float bar[3]; int baz; char qux[]; };
155   //   buffer.ReadMember<&Foo::bar>();  // OK, returns std::array<float, 3>
156   //   buffer.ReadMember<&Foo::baz>();  // OK, returns int
157   //   buffer.ReadMember<&Foo::qux>();  // Asserts, use
158   //   ReadMember<&Foo::qux>(index) instead
159   //
160   // This functions similarly to C-style type punning at address
161   //   |buffer.data() + offsetof(Foo, bar)|
162   template <auto PointerToMember>
ReadMember()163   auto ReadMember() const {
164     using ClassT = typename bt_lib_cpp_type::MemberPointerTraits<
165         PointerToMember>::ClassType;
166     BT_ASSERT_MSG(sizeof(ClassT) <= this->size(),
167                   "insufficient buffer (class size: %zu, buffer size: %zu)",
168                   sizeof(ClassT),
169                   this->size());
170     using MemberT = typename bt_lib_cpp_type::MemberPointerTraits<
171         PointerToMember>::MemberType;
172     if constexpr (std::is_array_v<MemberT>) {
173       static_assert(
174           std::extent_v<MemberT> > 0,
175           "use indexed overload of ReadMember for flexible array members");
176     }
177     using ReturnType = std::remove_cv_t<bt_lib_cpp_type::ToStdArrayT<MemberT>>;
178 
179     // std::array is required to be an aggregate that's list-initialized per
180     // ISO/IEC 14882:2017(E) § 26.3.7.1 [array.overview] ¶ 2, so its layout's
181     // initial run is identical to a raw array.
182     static_assert(sizeof(MemberT) <= sizeof(ReturnType));
183     static_assert(std::is_trivially_copyable_v<MemberT>,
184                   "unsafe to copy representation");
185     static_assert(std::is_trivially_copyable_v<ReturnType>,
186                   "unsafe to copy representation");
187     ReturnType out{};
188     const size_t offset =
189         bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::offset();
190     CopyRaw(/*dst_data=*/std::addressof(out),
191             /*dst_capacity=*/sizeof(out),
192             /*src_offset=*/offset,
193             /*copy_size=*/sizeof(MemberT));
194     return out;
195   }
196 
197   // Given a pointer to an array (or smart array) member of a class, interpret
198   // the underlying buffer as a representation of the class and return a copy of
199   // the member's |index - 1|-th element, with bounds checking for the indexing
200   // and reading representation bytes. Multi-dimensional arrays will return
201   // array elements as std::array. The buffer is allowed to be larger than T.
202   // The user is responsible for checking that the first sizeof(T) bytes
203   // represent a valid instance of T.
204   //
205   // Example:
206   //   struct Foo { float bar[3]; int baz; char qux[]; };
207   //   buffer.ReadMember<&Foo::bar>(2);  // OK
208   //   buffer.ReadMember<&Foo::qux>(3);  // OK, checked against buffer.size()
209   //   buffer.ReadMember<&Foo::bar>(3);  // Asserts because out-of-bounds on
210   //   Foo::bar
211   //
212   // This functions similarly to C-style type punning at address
213   //   |buffer.data() + offsetof(Foo, bar) + index * sizeof(bar[0])|
214   // but performs bounds checking and returns a valid type-punned object.
215   template <auto PointerToMember>
ReadMember(size_t index)216   auto ReadMember(size_t index) const {
217     // From the ReadMember<&Foo::bar>(2) example, ClassT = Foo
218     using ClassT = typename bt_lib_cpp_type::MemberPointerTraits<
219         PointerToMember>::ClassType;
220     BT_ASSERT_MSG(sizeof(ClassT) <= this->size(),
221                   "insufficient buffer (class size: %zu, buffer size: %zu)",
222                   sizeof(ClassT),
223                   this->size());
224 
225     // From the ReadMember<&Foo::bar>(2) example, MemberT = float[3]
226     using MemberT = typename bt_lib_cpp_type::MemberPointerTraits<
227         PointerToMember>::MemberType;
228     static_assert(std::is_trivially_copyable_v<MemberT>,
229                   "unsafe to copy representation");
230 
231     // From the ReadMember<&Foo::bar>(2) example, MemberAsStdArrayT =
232     // std::array<float, 3>
233     using MemberAsStdArrayT = bt_lib_cpp_type::ToStdArrayT<MemberT>;
234 
235     // Check array bounds
236     constexpr size_t kArraySize = std::tuple_size_v<MemberAsStdArrayT>;
237     const size_t base_offset =
238         bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::offset();
239     if constexpr (kArraySize > 0) {
240       // std::array is required to be an aggregate that's list-initialized per
241       // ISO/IEC 14882:2017(E) § 26.3.7.1 [array.overview] ¶ 2, so we can rely
242       // on the initial run of its layout, but in the technically possible but
243       // unlikely case that it contains additional bytes, we can't use its size
244       // for array indexing calculations.
245       static_assert(sizeof(MemberAsStdArrayT) == sizeof(MemberT));
246       BT_ASSERT_MSG(index < kArraySize,
247                     "index past array bounds (index: %zu, array size: %zu)",
248                     index,
249                     kArraySize);
250     } else {
251       // Allow flexible array members (at the end of structs) that have zero
252       // length
253       BT_ASSERT_MSG(base_offset == sizeof(ClassT),
254                     "read from zero-length array");
255     }
256 
257     // From the ReadMember<&Foo::bar>(2) example, ElementT = float
258     using ElementT = std::remove_cv_t<typename MemberAsStdArrayT::value_type>;
259     static_assert(std::is_trivially_copyable_v<ElementT>,
260                   "unsafe to copy representation");
261     const size_t offset = base_offset + index * sizeof(ElementT);
262     ElementT element{};
263     CopyRaw(/*dst_data=*/std::addressof(element),
264             /*dst_capacity=*/sizeof(ElementT),
265             /*src_offset=*/offset,
266             /*copy_size=*/sizeof(ElementT));
267     return element;
268   }
269 
270   bool operator==(const ByteBuffer& other) const {
271     if (size() != other.size()) {
272       return false;
273     }
274     return (memcmp(data(), other.data(), size()) == 0);
275   }
276 
277   // Returns the contents of this buffer as a C++ string-like object without
278   // copying its contents.
279   std::string_view AsString() const;
280 
281   // Returns the contents of this buffer as a C++ string after copying its
282   // contents.
283   std::string ToString() const;
284 
285   // Returns a copy of the contents of this buffer in a std::vector.
286   std::vector<uint8_t> ToVector() const;
287 
288  private:
289   void CopyRaw(void* dst_data,
290                size_t dst_capacity,
291                size_t src_offset,
292                size_t copy_size) const;
293 };
294 
295 using ByteBufferPtr = std::unique_ptr<ByteBuffer>;
296 
297 // Mutable extension to the ByteBuffer interface. This provides methods that
298 // allows direct mutable access to the underlying buffer.
299 class MutableByteBuffer : public ByteBuffer {
300  public:
301   ~MutableByteBuffer() override = default;
302 
303   // Returns a pointer to the beginning of this buffer. The return value is
304   // undefined if the buffer has size 0.
305   virtual uint8_t* mutable_data() = 0;
306 
307   // Random access operator that allows mutations.
308   inline uint8_t& operator[](size_t pos) {
309     BT_ASSERT_MSG(pos < size(), "invalid offset (pos = %zu)", pos);
310     return mutable_data()[pos];
311   }
312 
313   // Read-only random access operator. Required because there is no overload
314   // resolution from derived to base classes - without this, |const
315   // MutableByteBuffer|s cannot use operator[].
316   uint8_t operator[](size_t pos) const { return ByteBuffer::operator[](pos); }
317 
318   // Converts the underlying buffer to a mutable reference to the given type,
319   // with bounds checking. The buffer is allowed to be larger than T. The user
320   // is responsible for checking that the first sizeof(T) bytes represents a
321   // valid instance of T.
322   template <typename T>
AsMutable()323   T* AsMutable() {
324     static_assert(std::is_trivially_copyable_v<T>);
325     BT_ASSERT(size() >= sizeof(T));
326     return reinterpret_cast<T*>(mutable_data());
327   }
328 
329   // Writes the contents of |data| into this buffer starting at |pos|.
330   inline void Write(const ByteBuffer& data, size_t pos = 0) {
331     Write(data.data(), data.size(), pos);
332   }
333 
334   // Writes |size| octets of data starting from |data| into this buffer starting
335   // at |pos|. |data| must point to a valid piece of memory if |size| is
336   // non-zero. If |size| is zero, then this operation is a NOP.
337   void Write(const uint8_t* data, size_t size, size_t pos = 0);
338 
339   // Writes the byte interpretation of |data| at |pos|, overwriting the octets
340   // from pos to pos + sizeof(T).
341   // There must be enough space in the buffer to write T.
342   // If T is an array of known bounds, the entire array will be written.
343   template <typename T>
344   void WriteObj(const T& data, size_t pos = 0) {
345     // ByteBuffers are (mostly?) not TriviallyCopyable, but check this first for
346     // the error to be useful.
347     static_assert(!std::is_base_of_v<ByteBuffer, T>,
348                   "ByteBuffer passed to WriteObj; use Write");
349     static_assert(!std::is_pointer_v<T>,
350                   "Pointer passed to WriteObj, deref or use Write");
351     static_assert(std::is_trivially_copyable_v<T>,
352                   "Unsafe to peek byte representation");
353     Write(reinterpret_cast<const uint8_t*>(&data), sizeof(T), pos);
354   }
355 
356   // Behaves exactly like ByteBuffer::View but returns the result in a
357   // MutableBufferView instead.
358   //
359   // WARNING:
360   //
361   // A BufferView is only valid as long as the buffer that it points to is
362   // valid. Care should be taken to ensure that a BufferView does not outlive
363   // its backing buffer.
364   MutableBufferView mutable_view(
365       size_t pos = 0, size_t size = std::numeric_limits<std::size_t>::max());
366 
367   // Same as mutable_view(), but returns a mutable span instead of a
368   // MutableBufferView.
369   pw::span<std::byte> mutable_subspan(
370       size_t pos = 0, size_t size = std::numeric_limits<std::size_t>::max());
371 
372   // Sets the contents of the buffer to 0s.
SetToZeros()373   void SetToZeros() { Fill(0); }
374 
375   // Fills the contents of the buffer with the given value.
376   virtual void Fill(uint8_t value) = 0;
377 };
378 
379 using MutableByteBufferPtr = std::unique_ptr<MutableByteBuffer>;
380 
381 // A ByteBuffer with static storage duration. Instances of this class are
382 // copyable. Due to the static buffer storage duration, move semantics work the
383 // same way as copy semantics, i.e. moving an instance will copy the buffer
384 // contents.
385 template <size_t BufferSize>
386 class StaticByteBuffer : public MutableByteBuffer {
387  public:
388   // Create a buffer of size |BufferSize|. The buffer bytes will be initialized
389   // to 0x00.
StaticByteBuffer()390   StaticByteBuffer() {
391     static_assert(BufferSize, "|BufferSize| must be non-zero");
392   }
393   ~StaticByteBuffer() override = default;
394 
395   // Variadic template constructor to initialize a StaticByteBuffer using a
396   // parameter pack e.g.:
397   //
398   //   StaticByteBuffer foo(0x00, 0x01, 0x02);
399   //   StaticByteBuffer<3> foo(0x00, 0x01, 0x02);
400   //
401   // The class's |BufferSize| template parameter, if explicitly provided, will
402   // be checked against the number of initialization elements provided.
403   //
404   // All types castable to uint8_t can be used without casting (including class
405   // enums) for brevity but care must be taken not to exceed uint8_t range
406   // limits.
407   //
408   //   StaticByteBuffer foo(-257);  // -257 has type int and will likely convert
409   //   to uint8_t{0xff}
410   template <typename... T>
StaticByteBuffer(T...bytes)411   constexpr explicit StaticByteBuffer(T... bytes)
412       : buffer_{{static_cast<uint8_t>(bytes)...}} {
413     static_assert(BufferSize, "|BufferSize| must be non-zero");
414     static_assert(BufferSize == sizeof...(T),
415                   "|BufferSize| must match initializer list count");
416 
417     // Check that arguments are within byte range. Restrict checking to smaller
418     // inputs to limit compile time impact and because clang considers fold
419     // expressions "nested" (i.e. subject to a default 256 depth limit).
420     if constexpr (sizeof...(bytes) <= 256) {
421       constexpr auto is_byte_storable = [](auto value) {
422         if constexpr (sizeof(value) > sizeof(uint8_t)) {
423           return static_cast<std::make_unsigned_t<decltype(value)>>(value) <=
424                  std::numeric_limits<uint8_t>::max();
425         }
426         return true;
427       };
428 
429       // This is a runtime assert because this class was written to work with
430       // non-constant values but most uses of StaticByteBuffer are in tests so
431       // this is an acceptable cost.
432       BT_DEBUG_ASSERT((is_byte_storable(bytes) && ...));
433     }
434   }
435 
436   // ByteBuffer overrides
data()437   const uint8_t* data() const override { return buffer_.data(); }
size()438   size_t size() const override { return buffer_.size(); }
cbegin()439   const_iterator cbegin() const override { return buffer_.data(); }
cend()440   const_iterator cend() const override {
441     return buffer_.data() + buffer_.size();
442   }
443 
444   // MutableByteBuffer overrides:
mutable_data()445   uint8_t* mutable_data() override { return buffer_.data(); }
Fill(uint8_t value)446   void Fill(uint8_t value) override { buffer_.fill(value); }
447 
448  private:
449   // Value-initialize to 0.
450   std::array<uint8_t, BufferSize> buffer_{};
451 };
452 
453 // Template deduction guide for the |BufferSize| class template parameter using
454 // the number of parameters passed into the templated parameter pack
455 // constructor. This allows |BufferSize| to be omitted when it should be deduced
456 // from the initializer:
457 //
458 //   StaticByteBuffer buffer(0x00, 0x01, 0x02);
459 //
460 template <typename... T>
461 StaticByteBuffer(T... bytes) -> StaticByteBuffer<sizeof...(T)>;
462 
463 // A ByteBuffer with dynamic storage duration. The underlying buffer is
464 // allocated using malloc. Instances of this class are move-only.
465 class DynamicByteBuffer : public MutableByteBuffer {
466  public:
467   // The default constructor creates an empty buffer with size 0.
468   DynamicByteBuffer();
469   ~DynamicByteBuffer() override = default;
470 
471   // Allocates a new buffer with |buffer_size| bytes. The buffer bytes will be
472   // initialized to 0x00.
473   explicit DynamicByteBuffer(size_t buffer_size);
474 
475   // Copies the contents of |buffer|.
476   explicit DynamicByteBuffer(const ByteBuffer& buffer);
477   DynamicByteBuffer(const DynamicByteBuffer& buffer);
478   // Copies the contensts of |string|.
479   explicit DynamicByteBuffer(const std::string& buffer);
480 
481   // Takes ownership of |buffer| and avoids allocating a new buffer. Since this
482   // constructor performs a simple assignment, the caller must make sure that
483   // the buffer pointed to by |buffer| actually contains |buffer_size| bytes.
484   DynamicByteBuffer(size_t buffer_size, std::unique_ptr<uint8_t[]> buffer);
485 
486   // Move constructor and assignment operator
487   DynamicByteBuffer(DynamicByteBuffer&& other);
488   DynamicByteBuffer& operator=(DynamicByteBuffer&& other);
489 
490   // Copy assignment is prohibited.
491   DynamicByteBuffer& operator=(const DynamicByteBuffer&) = delete;
492 
493   // ByteBuffer overrides:
494   const uint8_t* data() const override;
495   size_t size() const override;
496   const_iterator cbegin() const override;
497   const_iterator cend() const override;
498 
499   // MutableByteBuffer overrides:
500   uint8_t* mutable_data() override;
501   void Fill(uint8_t value) override;
502 
503  private:
504   // Pointer to the underlying buffer, which is owned and managed by us.
505   size_t buffer_size_ = 0u;
506   std::unique_ptr<uint8_t[]> buffer_;
507 };
508 
509 // A ByteBuffer that does not own the memory that it points to but rather
510 // provides an immutable view over it.
511 //
512 // WARNING:
513 //
514 // A BufferView is only valid as long as the buffer that it points to is
515 // valid. Care should be taken to ensure that a BufferView does not outlive
516 // its backing buffer.
517 class BufferView final : public ByteBuffer {
518  public:
519   BufferView(const void* bytes, size_t size);
520   ~BufferView() override = default;
521 
522   explicit BufferView(const ByteBuffer& buffer,
523                       size_t size = std::numeric_limits<std::size_t>::max());
524   explicit BufferView(std::string_view string);
525   explicit BufferView(const std::vector<uint8_t>& vec);
526   explicit BufferView(pw::span<const std::byte> bytes);
527 
528   // The default constructor initializes this to an empty buffer.
529   BufferView();
530 
531   // ByteBuffer overrides:
532   const uint8_t* data() const override;
533   size_t size() const override;
534   const_iterator cbegin() const override;
535   const_iterator cend() const override;
536 
537  private:
538   size_t size_ = 0u;
539   const uint8_t* bytes_ = nullptr;
540 };
541 
542 // A ByteBuffer that does not own the memory that it points to but rather
543 // provides a mutable view over it.
544 //
545 // WARNING:
546 //
547 // A BufferView is only valid as long as the buffer that it points to is
548 // valid. Care should be taken to ensure that a BufferView does not outlive
549 // its backing buffer.
550 class MutableBufferView final : public MutableByteBuffer {
551  public:
552   explicit MutableBufferView(MutableByteBuffer* buffer);
553   MutableBufferView(void* bytes, size_t size);
554   ~MutableBufferView() override = default;
555 
556   // The default constructor initializes this to an empty buffer.
557   MutableBufferView();
558 
559   // ByteBuffer overrides:
560   const uint8_t* data() const override;
561   size_t size() const override;
562   const_iterator cbegin() const override;
563   const_iterator cend() const override;
564 
565   // MutableByteBuffer overrides:
566   uint8_t* mutable_data() override;
567   void Fill(uint8_t value) override;
568 
569  private:
570   size_t size_ = 0u;
571   uint8_t* bytes_ = nullptr;
572 };
573 
574 }  // namespace bt
575