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: A BufferView is only valid as long as the buffer that it points to 75 // is valid. Care should be taken to ensure that a BufferView does not outlive 76 // its backing buffer. 77 BufferView view(size_t pos = 0, 78 size_t size = std::numeric_limits<std::size_t>::max()) const; 79 80 // Same as view(), but returns a span instead of a BufferView. 81 pw::span<const std::byte> subspan( 82 size_t pos = 0, 83 size_t size = std::numeric_limits<std::size_t>::max()) const; 84 85 // Copies all bytes of this buffer into |out_buffer|. |out_buffer| must be 86 // large enough to accommodate the result of this operation. 87 void Copy(MutableByteBuffer* out_buffer) const; 88 89 // Copies |size| bytes of this buffer into |out_buffer| starting at offset 90 // |pos|. |out_buffer| must be large enough to accommodate the result of this 91 // operation. 92 void Copy(MutableByteBuffer* out_buffer, size_t pos, size_t size) const; 93 94 // Creates a new std::string that contains a printable representation of a 95 // range of this buffer starting at |pos|. The string is checked to see if it 96 // is UTF-8. If not, each byte in the range to be converted is checked to see 97 // if it is printable ASCII. If so, the character is used as is. If not, it is 98 // replaced by '.'. The returned std::string will have size |size| + 1 to fit 99 // a terminating '\0'. 100 std::string Printable(size_t pos, size_t size) const; 101 102 // Iterator functions. begin()103 iterator begin() const { return cbegin(); } end()104 iterator end() const { return cend(); } 105 virtual const_iterator cbegin() const = 0; 106 virtual const_iterator cend() const = 0; 107 108 // Read-only random access operator. 109 inline const uint8_t& operator[](size_t pos) const { 110 PW_CHECK(pos < size(), "invalid offset (pos = %zu)", pos); 111 return data()[pos]; 112 } 113 114 // Creates an object of type T with the first sizeof(T) bytes of the buffer as 115 // its representation (per definition at ISO/IEC 14882:2017(E) § 6.9 116 // [basic.types] ¶ 4.4). The user is responsible for checking that the first 117 // sizeof(T) bytes represent a valid instance of T. If T is an array type, the 118 // return value will be a std::array with the same element type and extents. 119 // 120 // This or ReadMember should always be used in place of reinterpret_cast on 121 // raw pointers because of dangerous UB related to object lifetimes and 122 // alignment issues (see fxbug.dev/42123294). Moreover, this will perform 123 // bounds checking on the data being read. 124 template <typename T> To()125 [[nodiscard]] auto To() const { 126 static_assert(std::is_trivially_copyable_v<T>, 127 "unsafe to copy representation"); 128 static_assert(std::is_default_constructible_v<T>); 129 using OutType = std::remove_cv_t<bt_lib_cpp_type::ToStdArrayT<T>>; 130 131 // This is value-initialized in order to construct objects that have const 132 // members. The consideration for modifying the object through its 133 // representation even if the constituent types are cv-qualified is based on 134 // the potent rules for memcpy'ing "underlying bytes" at ISO/IEC 135 // 14882:2017(E) § 6.9 [basic.types] ¶ 4.2–4.3. 136 OutType out{}; 137 CopyRaw(/*dst_data=*/std::addressof(out), 138 /*dst_capacity=*/sizeof(out), 139 /*src_offset=*/0, 140 /*copy_size=*/sizeof(out)); 141 return out; 142 } 143 144 // Given a pointer to a member of a class, interpret the underlying buffer as 145 // a representation of the class and return a copy of the member, with bounds 146 // checking for reading the representation. Array elements (including 147 // multi-dimensional) will be returned as std::array. The buffer is allowed to 148 // be larger than T. The user is responsible for checking that the first 149 // sizeof(T) bytes represent a valid instance of T. 150 // 151 // Example: 152 // struct Foo { float bar[3]; int baz; char qux[]; }; 153 // buffer.ReadMember<&Foo::bar>(); // OK, returns std::array<float, 3> 154 // buffer.ReadMember<&Foo::baz>(); // OK, returns int 155 // buffer.ReadMember<&Foo::qux>(); // Asserts, use 156 // ReadMember<&Foo::qux>(index) instead 157 // 158 // This functions similarly to C-style type punning at address 159 // |buffer.data() + offsetof(Foo, bar)| 160 template <auto PointerToMember> ReadMember()161 auto ReadMember() const { 162 using ClassT = typename bt_lib_cpp_type::MemberPointerTraits< 163 PointerToMember>::ClassType; 164 PW_CHECK(sizeof(ClassT) <= this->size(), 165 "insufficient buffer (class size: %zu, buffer size: %zu)", 166 sizeof(ClassT), 167 this->size()); 168 using MemberT = typename bt_lib_cpp_type::MemberPointerTraits< 169 PointerToMember>::MemberType; 170 if constexpr (std::is_array_v<MemberT>) { 171 static_assert( 172 std::extent_v<MemberT> > 0, 173 "use indexed overload of ReadMember for flexible array members"); 174 } 175 using ReturnType = std::remove_cv_t<bt_lib_cpp_type::ToStdArrayT<MemberT>>; 176 177 // std::array is required to be an aggregate that's list-initialized per 178 // ISO/IEC 14882:2017(E) § 26.3.7.1 [array.overview] ¶ 2, so its layout's 179 // initial run is identical to a raw array. 180 static_assert(sizeof(MemberT) <= sizeof(ReturnType)); 181 static_assert(std::is_trivially_copyable_v<MemberT>, 182 "unsafe to copy representation"); 183 static_assert(std::is_trivially_copyable_v<ReturnType>, 184 "unsafe to copy representation"); 185 ReturnType out{}; 186 const size_t offset = 187 bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::offset(); 188 CopyRaw(/*dst_data=*/std::addressof(out), 189 /*dst_capacity=*/sizeof(out), 190 /*src_offset=*/offset, 191 /*copy_size=*/sizeof(MemberT)); 192 return out; 193 } 194 195 // Given a pointer to an array (or smart array) member of a class, interpret 196 // the underlying buffer as a representation of the class and return a copy of 197 // the member's |index - 1|-th element, with bounds checking for the indexing 198 // and reading representation bytes. Multi-dimensional arrays will return 199 // array elements as std::array. The buffer is allowed to be larger than T. 200 // The user is responsible for checking that the first sizeof(T) bytes 201 // represent a valid instance of T. 202 // 203 // Example: 204 // struct Foo { float bar[3]; int baz; char qux[]; }; 205 // buffer.ReadMember<&Foo::bar>(2); // OK 206 // buffer.ReadMember<&Foo::qux>(3); // OK, checked against buffer.size() 207 // buffer.ReadMember<&Foo::bar>(3); // Asserts because out-of-bounds on 208 // Foo::bar 209 // 210 // This functions similarly to C-style type punning at address 211 // |buffer.data() + offsetof(Foo, bar) + index * sizeof(bar[0])| 212 // but performs bounds checking and returns a valid type-punned object. 213 template <auto PointerToMember> ReadMember(size_t index)214 auto ReadMember(size_t index) const { 215 // From the ReadMember<&Foo::bar>(2) example, ClassT = Foo 216 using ClassT = typename bt_lib_cpp_type::MemberPointerTraits< 217 PointerToMember>::ClassType; 218 PW_CHECK(sizeof(ClassT) <= this->size(), 219 "insufficient buffer (class size: %zu, buffer size: %zu)", 220 sizeof(ClassT), 221 this->size()); 222 223 // From the ReadMember<&Foo::bar>(2) example, MemberT = float[3] 224 using MemberT = typename bt_lib_cpp_type::MemberPointerTraits< 225 PointerToMember>::MemberType; 226 static_assert(std::is_trivially_copyable_v<MemberT>, 227 "unsafe to copy representation"); 228 229 // From the ReadMember<&Foo::bar>(2) example, MemberAsStdArrayT = 230 // std::array<float, 3> 231 using MemberAsStdArrayT = bt_lib_cpp_type::ToStdArrayT<MemberT>; 232 233 // Check array bounds 234 constexpr size_t kArraySize = std::tuple_size_v<MemberAsStdArrayT>; 235 const size_t base_offset = 236 bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::offset(); 237 if constexpr (kArraySize > 0) { 238 // std::array is required to be an aggregate that's list-initialized per 239 // ISO/IEC 14882:2017(E) § 26.3.7.1 [array.overview] ¶ 2, so we can rely 240 // on the initial run of its layout, but in the technically possible but 241 // unlikely case that it contains additional bytes, we can't use its size 242 // for array indexing calculations. 243 static_assert(sizeof(MemberAsStdArrayT) == sizeof(MemberT)); 244 PW_CHECK(index < kArraySize, 245 "index past array bounds (index: %zu, array size: %zu)", 246 index, 247 kArraySize); 248 } else { 249 // Allow flexible array members (at the end of structs) that have zero 250 // length 251 PW_CHECK(base_offset == sizeof(ClassT), "read from zero-length array"); 252 } 253 254 // From the ReadMember<&Foo::bar>(2) example, ElementT = float 255 using ElementT = std::remove_cv_t<typename MemberAsStdArrayT::value_type>; 256 static_assert(std::is_trivially_copyable_v<ElementT>, 257 "unsafe to copy representation"); 258 const size_t offset = base_offset + index * sizeof(ElementT); 259 ElementT element{}; 260 CopyRaw(/*dst_data=*/std::addressof(element), 261 /*dst_capacity=*/sizeof(ElementT), 262 /*src_offset=*/offset, 263 /*copy_size=*/sizeof(ElementT)); 264 return element; 265 } 266 267 bool operator==(const ByteBuffer& other) const { 268 if (size() != other.size()) { 269 return false; 270 } 271 return (memcmp(data(), other.data(), size()) == 0); 272 } 273 274 // Returns the contents of this buffer as a C++ string-like object without 275 // copying its contents. 276 std::string_view AsString() const; 277 278 // Returns a string in hexadecimal format. 279 std::string AsHexadecimal() const; 280 281 // Returns the contents of this buffer as a C++ string after copying its 282 // contents. 283 std::string ToString(bool as_hex = false) 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 PW_CHECK(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 PW_CHECK(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: A BufferView is only valid as long as the buffer that it points to 360 // is valid. Care should be taken to ensure that a BufferView does not outlive 361 // its backing buffer. 362 MutableBufferView mutable_view( 363 size_t pos = 0, size_t size = std::numeric_limits<std::size_t>::max()); 364 365 // Same as mutable_view(), but returns a mutable span instead of a 366 // MutableBufferView. 367 pw::span<std::byte> mutable_subspan( 368 size_t pos = 0, size_t size = std::numeric_limits<std::size_t>::max()); 369 370 // Sets the contents of the buffer to 0s. SetToZeros()371 void SetToZeros() { Fill(0); } 372 373 // Fills the contents of the buffer with the given value. 374 virtual void Fill(uint8_t value) = 0; 375 }; 376 377 using MutableByteBufferPtr = std::unique_ptr<MutableByteBuffer>; 378 379 // A ByteBuffer with static storage duration. Instances of this class are 380 // copyable. Due to the static buffer storage duration, move semantics work the 381 // same way as copy semantics, i.e. moving an instance will copy the buffer 382 // contents. 383 template <size_t BufferSize> 384 class StaticByteBuffer : public MutableByteBuffer { 385 public: 386 // Create a buffer of size |BufferSize|. The buffer bytes will be initialized 387 // to 0x00. StaticByteBuffer()388 StaticByteBuffer() { 389 static_assert(BufferSize, "|BufferSize| must be non-zero"); 390 } 391 ~StaticByteBuffer() override = default; 392 393 // Variadic template constructor to initialize a StaticByteBuffer using a 394 // parameter pack e.g.: 395 // 396 // StaticByteBuffer foo(0x00, 0x01, 0x02); 397 // StaticByteBuffer<3> foo(0x00, 0x01, 0x02); 398 // 399 // The class's |BufferSize| template parameter, if explicitly provided, will 400 // be checked against the number of initialization elements provided. 401 // 402 // All types castable to uint8_t can be used without casting (including class 403 // enums) for brevity but care must be taken not to exceed uint8_t range 404 // limits. 405 // 406 // StaticByteBuffer foo(-257); // -257 has type int and will likely convert 407 // to uint8_t{0xff} 408 template <typename... T> StaticByteBuffer(T...bytes)409 constexpr explicit StaticByteBuffer(T... bytes) 410 : buffer_{{static_cast<uint8_t>(bytes)...}} { 411 static_assert(BufferSize, "|BufferSize| must be non-zero"); 412 static_assert(BufferSize == sizeof...(T), 413 "|BufferSize| must match initializer list count"); 414 415 // Check that arguments are within byte range. Restrict checking to smaller 416 // inputs to limit compile time impact and because clang considers fold 417 // expressions "nested" (i.e. subject to a default 256 depth limit). 418 if constexpr (sizeof...(bytes) <= 256) { 419 constexpr auto is_byte_storable = [](auto value) { 420 if constexpr (sizeof(value) > sizeof(uint8_t)) { 421 return static_cast<std::make_unsigned_t<decltype(value)>>(value) <= 422 std::numeric_limits<uint8_t>::max(); 423 } 424 return true; 425 }; 426 427 // This is a runtime assert because this class was written to work with 428 // non-constant values but most uses of StaticByteBuffer are in tests so 429 // this is an acceptable cost. 430 PW_DASSERT((is_byte_storable(bytes) && ...)); 431 } 432 } 433 434 // ByteBuffer overrides data()435 const uint8_t* data() const override { return buffer_.data(); } size()436 size_t size() const override { return buffer_.size(); } cbegin()437 const_iterator cbegin() const override { return &*buffer_.cbegin(); } cend()438 const_iterator cend() const override { return &*buffer_.cend(); } 439 440 // MutableByteBuffer overrides: mutable_data()441 uint8_t* mutable_data() override { return buffer_.data(); } Fill(uint8_t value)442 void Fill(uint8_t value) override { buffer_.fill(value); } 443 444 private: 445 // Value-initialize to 0. 446 std::array<uint8_t, BufferSize> buffer_{}; 447 }; 448 449 // Template deduction guide for the |BufferSize| class template parameter using 450 // the number of parameters passed into the templated parameter pack 451 // constructor. This allows |BufferSize| to be omitted when it should be deduced 452 // from the initializer: 453 // 454 // StaticByteBuffer buffer(0x00, 0x01, 0x02); 455 // 456 template <typename... T> 457 StaticByteBuffer(T... bytes) -> StaticByteBuffer<sizeof...(T)>; 458 459 // A ByteBuffer with dynamic storage duration. The underlying buffer is 460 // allocated using malloc. Instances of this class are move-only. 461 class DynamicByteBuffer : public MutableByteBuffer { 462 public: 463 // The default constructor creates an empty buffer with size 0. 464 DynamicByteBuffer(); 465 ~DynamicByteBuffer() override = default; 466 467 // Allocates a new buffer with |buffer_size| bytes. The buffer bytes will be 468 // initialized to 0x00. 469 explicit DynamicByteBuffer(size_t buffer_size); 470 471 // Copies the contents of |buffer|. 472 DynamicByteBuffer(const DynamicByteBuffer& buffer); 473 explicit DynamicByteBuffer(const ByteBuffer& buffer); 474 explicit DynamicByteBuffer(const std::string& buffer); 475 476 // Takes ownership of |buffer| and avoids allocating a new buffer. Since this 477 // constructor performs a simple assignment, the caller must make sure that 478 // the buffer pointed to by |buffer| actually contains |buffer_size| bytes. 479 DynamicByteBuffer(size_t buffer_size, std::unique_ptr<uint8_t[]> buffer); 480 481 // Move constructor and assignment operator 482 DynamicByteBuffer(DynamicByteBuffer&& other); 483 DynamicByteBuffer& operator=(DynamicByteBuffer&& other); 484 485 // Copy assignment is prohibited. 486 DynamicByteBuffer& operator=(const DynamicByteBuffer&) = delete; 487 488 // ByteBuffer overrides: 489 const uint8_t* data() const override; 490 size_t size() const override; 491 const_iterator cbegin() const override; 492 const_iterator cend() const override; 493 494 // MutableByteBuffer overrides: 495 uint8_t* mutable_data() override; 496 void Fill(uint8_t value) override; 497 498 // Allocates a new buffer of the given size and copies all underlying data to 499 // the new buffer. This method is meant only to grow the underlying buffer to 500 // fit in more data. Returns false if the new buffer size is less than the 501 // current buffer size. 502 bool expand(size_t new_buffer_size); 503 504 private: 505 // Pointer to the underlying buffer, which is owned and managed by us. 506 size_t buffer_size_ = 0u; 507 std::unique_ptr<uint8_t[]> buffer_; 508 }; 509 510 // A ByteBuffer that does not own the memory that it points to but rather 511 // provides an immutable view over it. 512 // 513 // WARNING: A BufferView is only valid as long as the buffer that it points to 514 // is valid. Care should be taken to ensure that a BufferView does not outlive 515 // its backing buffer. 516 class BufferView final : public ByteBuffer { 517 public: 518 BufferView(const void* bytes, size_t size); 519 ~BufferView() override = default; 520 521 explicit BufferView(const ByteBuffer& buffer, 522 size_t size = std::numeric_limits<std::size_t>::max()); 523 explicit BufferView(std::string_view string); 524 explicit BufferView(const std::vector<uint8_t>& vec); 525 explicit BufferView(pw::span<const std::byte> bytes); 526 527 // The default constructor initializes this to an empty buffer. 528 BufferView(); 529 530 // ByteBuffer overrides: 531 const uint8_t* data() const override; 532 size_t size() const override; 533 const_iterator cbegin() const override; 534 const_iterator cend() const override; 535 536 private: 537 size_t size_ = 0u; 538 const uint8_t* bytes_ = nullptr; 539 }; 540 541 // A ByteBuffer that does not own the memory that it points to but rather 542 // provides a mutable view over it. 543 // 544 // WARNING: A BufferView is only valid as long as the buffer that it points to 545 // is valid. Care should be taken to ensure that a BufferView does not outlive 546 // its backing buffer. 547 class MutableBufferView final : public MutableByteBuffer { 548 public: 549 explicit MutableBufferView(MutableByteBuffer* buffer); 550 MutableBufferView(void* bytes, size_t size); 551 ~MutableBufferView() override = default; 552 553 // The default constructor initializes this to an empty buffer. 554 MutableBufferView(); 555 556 // ByteBuffer overrides: 557 const uint8_t* data() const override; 558 size_t size() const override; 559 const_iterator cbegin() const override; 560 const_iterator cend() const override; 561 562 // MutableByteBuffer overrides: 563 uint8_t* mutable_data() override; 564 void Fill(uint8_t value) override; 565 566 private: 567 size_t size_ = 0u; 568 uint8_t* bytes_ = nullptr; 569 }; 570 571 } // namespace bt 572