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