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