blob: f5329e9a977b1624769517e3b255344c779e85ff [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_COMMON_BYTE_BUFFER_H_
#define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_COMMON_BYTE_BUFFER_H_
#include <zircon/assert.h>
#include <zircon/syscalls.h>
#include <array>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include <fbl/macros.h>
#include "src/connectivity/bluetooth/lib/cpp-type/member_pointer_traits.h"
#include "src/connectivity/bluetooth/lib/cpp-type/to_std_array.h"
namespace bt {
class BufferView;
class MutableBufferView;
class MutableByteBuffer;
// Interface for buffer implementations with various allocation schemes.
class ByteBuffer {
public:
using const_iterator = const uint8_t*;
using iterator = const_iterator;
virtual ~ByteBuffer() = default;
// Returns a pointer to the beginning of this buffer. The return value is
// undefined if the buffer has size 0.
virtual const uint8_t* data() const = 0;
// Returns the number of bytes contained in this packet.
virtual size_t size() const = 0;
// Returns a BufferView that points to the region of this buffer starting at
// |pos| of |size| bytes. If |size| is larger than the size of this BufferView
// then the returned region will contain all bytes in this buffer starting at
// |pos|.
//
// For example:
//
// // Get a view of all of |my_buffer|.
// const BufferView view = my_buffer.view();
//
// // Get a view of the first 5 bytes in |my_buffer| (assuming |my_buffer| is
// // large enough).
// view = my_buffer.view(0, 5);
//
// // Get a view of |my_buffer| starting at the second byte.
// view = my_buffer.view(2);
//
//
// WARNING:
//
// A BufferView is only valid as long as the buffer that it points to is
// valid. Care should be taken to ensure that a BufferView does not outlive
// its backing buffer.
BufferView view(size_t pos = 0, size_t size = std::numeric_limits<std::size_t>::max()) const;
// Copies all bytes of this buffer into |out_buffer|. |out_buffer| must be large enough to
// accommodate the result of this operation.
void Copy(MutableByteBuffer* out_buffer) const;
// Copies |size| bytes of this buffer into |out_buffer| starting at offset |pos|. |out_buffer|
// must be large enough to accommodate the result of this operation.
void Copy(MutableByteBuffer* out_buffer, size_t pos, size_t size) const;
// Iterator functions.
iterator begin() const { return cbegin(); }
iterator end() const { return cend(); }
virtual const_iterator cbegin() const = 0;
virtual const_iterator cend() const = 0;
// Read-only random access operator.
inline const uint8_t& operator[](size_t pos) const {
ZX_ASSERT_MSG(pos < size(), "invalid offset (pos = %zu)", pos);
return data()[pos];
}
// Converts the underlying buffer to the given type with bounds checking. The buffer is allowed
// to be larger than T. The user is responsible for checking that the first sizeof(T) bytes
// represent a valid instance of T.
template <typename T>
const T& As() const {
// std::is_trivial_v would be a stronger guarantee that the buffer contains a valid T object,
// but would disallow casting to types that have useful constructors, which might instead cause
// uninitialized field(s) bugs for data encoding/decoding structs.
static_assert(std::is_trivially_copyable_v<T>, "Can not reinterpret bytes");
ZX_ASSERT(size() >= sizeof(T));
return *reinterpret_cast<const T*>(data());
}
// Given a pointer to a member of a class, interpret the underlying buffer as a representation of
// the class and return a copy of the member, with bounds checking for reading the representation.
// Array elements (including multi-dimensional) will be returned as std::array. The buffer is
// allowed to be larger than T. The user is responsible for checking that the first sizeof(T)
// bytes represent a valid instance of T.
//
// Example:
// struct Foo { float bar[3]; int baz; char qux[]; };
// buffer.ReadMember<&Foo::bar>(); // OK, returns std::array<float, 3>
// buffer.ReadMember<&Foo::baz>(); // OK, returns int
// buffer.ReadMember<&Foo::qux>(); // Asserts, use ReadMember<&Foo::qux>(index) instead
//
// This functions similarly to C-style type punning at address
// |buffer.data() + offsetof(Foo, bar)|
template <auto PointerToMember>
auto ReadMember() const {
using ClassT = typename bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::ClassType;
ZX_ASSERT_MSG(sizeof(ClassT) <= this->size(),
"insufficient buffer (class size: %zu, buffer size: %zu)", sizeof(ClassT),
this->size());
using MemberT = typename bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::MemberType;
if constexpr (std::is_array_v<MemberT>) {
static_assert(std::extent_v<MemberT> > 0,
"use indexed overload of ReadMember for flexible array members");
}
using ReturnType = std::remove_cv_t<bt_lib_cpp_type::ToStdArrayT<MemberT>>;
// std::array is required to be an aggregate that's list-initialized per ISO/IEC 14882:2017(E)
// § 26.3.7.1 [array.overview] ¶ 2, so its layout's initial run is identical to a raw array.
static_assert(sizeof(MemberT) <= sizeof(ReturnType));
static_assert(std::is_trivially_copyable_v<MemberT>, "unsafe to copy representation");
static_assert(std::is_trivially_copyable_v<ReturnType>, "unsafe to copy representation");
ReturnType out{};
const size_t offset = bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::offset();
CopyRaw(/*dst_data=*/std::addressof(out), /*dst_capacity=*/sizeof(out), /*src_offset=*/offset,
/*copy_size=*/sizeof(MemberT));
return out;
}
// Given a pointer to an array (or smart array) member of a class, interpret the underlying buffer
// as a representation of the class and return a copy of the member's |index - 1|-th element, with
// bounds checking for the indexing and reading representation bytes. Multi-dimensional arrays
// will return array elements as std::array. The buffer is allowed to be larger than T. The user
// is responsible for checking that the first sizeof(T) bytes represent a valid instance of T.
//
// Example:
// struct Foo { float bar[3]; int baz; char qux[]; };
// buffer.ReadMember<&Foo::bar>(2); // OK
// buffer.ReadMember<&Foo::qux>(3); // OK, checked against buffer.size()
// buffer.ReadMember<&Foo::bar>(3); // Asserts because out-of-bounds on Foo::bar
//
// This functions similarly to C-style type punning at address
// |buffer.data() + offsetof(Foo, bar) + index * sizeof(bar[0])|
// but performs bounds checking and returns a valid type-punned object.
template <auto PointerToMember>
auto ReadMember(size_t index) const {
// From the ReadMember<&Foo::bar>(2) example, ClassT = Foo
using ClassT = typename bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::ClassType;
ZX_ASSERT_MSG(sizeof(ClassT) <= this->size(),
"insufficient buffer (class size: %zu, buffer size: %zu)", sizeof(ClassT),
this->size());
// From the ReadMember<&Foo::bar>(2) example, MemberT = float[3]
using MemberT = typename bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::MemberType;
static_assert(std::is_trivially_copyable_v<MemberT>, "unsafe to copy representation");
// From the ReadMember<&Foo::bar>(2) example, MemberAsStdArrayT = std::array<float, 3>
using MemberAsStdArrayT = bt_lib_cpp_type::ToStdArrayT<MemberT>;
// Check array bounds
constexpr size_t kArraySize = std::tuple_size_v<MemberAsStdArrayT>;
const size_t base_offset = bt_lib_cpp_type::MemberPointerTraits<PointerToMember>::offset();
if constexpr (kArraySize > 0) {
// std::array is required to be an aggregate that's list-initialized per ISO/IEC 14882:2017(E)
// § 26.3.7.1 [array.overview] ¶ 2, so we can rely on the initial run of its layout, but in
// the technically possible but unlikely case that it contains additional bytes, we can't use
// its size for array indexing calculations.
static_assert(sizeof(MemberAsStdArrayT) == sizeof(MemberT));
ZX_ASSERT_MSG(index < kArraySize, "index past array bounds (index: %zu, array size: %zu)",
index, kArraySize);
} else {
// Allow flexible array members (at the end of structs) that have zero length
ZX_ASSERT_MSG(base_offset == sizeof(ClassT), "read from zero-length array");
}
// From the ReadMember<&Foo::bar>(2) example, ElementT = float
using ElementT = std::remove_cv_t<typename MemberAsStdArrayT::value_type>;
static_assert(std::is_trivially_copyable_v<ElementT>, "unsafe to copy representation");
const size_t offset = base_offset + index * sizeof(ElementT);
ElementT element{};
CopyRaw(/*dst_data=*/std::addressof(element), /*dst_capacity=*/sizeof(ElementT),
/*src_offset=*/offset, /*copy_size=*/sizeof(ElementT));
return element;
}
bool operator==(const ByteBuffer& other) const {
if (size() != other.size()) {
return false;
}
return (memcmp(data(), other.data(), size()) == 0);
}
// Returns the contents of this buffer as a C++ string-like object without
// copying its contents.
std::string_view AsString() const;
// Returns the contents of this buffer as a C++ string after copying its
// contents.
std::string ToString() const;
// Returns a copy of the contents of this buffer in a std::vector.
std::vector<uint8_t> ToVector() const;
private:
void CopyRaw(void* dst_data, size_t dst_capacity, size_t src_offset, size_t copy_size) const;
};
using ByteBufferPtr = std::unique_ptr<ByteBuffer>;
// Mutable extension to the ByteBuffer interface. This provides methods that
// allows direct mutable access to the underlying buffer.
class MutableByteBuffer : public ByteBuffer {
public:
~MutableByteBuffer() override = default;
// Returns a pointer to the beginning of this buffer. The return value is
// undefined if the buffer has size 0.
virtual uint8_t* mutable_data() = 0;
// Random access operator that allows mutations.
inline uint8_t& operator[](size_t pos) {
ZX_ASSERT_MSG(pos < size(), "invalid offset (pos = %zu)", pos);
return mutable_data()[pos];
}
// Converts the underlying buffer to a mutable reference to the given type, with bounds checking.
// The buffer is allowed to be larger than T. The user is responsible for checking that the first
// sizeof(T) bytes represents a valid instance of T.
template <typename T>
T& AsMutable() {
static_assert(std::is_trivially_copyable_v<T>, "Can not reinterpret bytes");
ZX_ASSERT(size() >= sizeof(T));
return *reinterpret_cast<T*>(mutable_data());
}
// Writes the contents of |data| into this buffer starting at |pos|.
inline void Write(const ByteBuffer& data, size_t pos = 0) {
Write(data.data(), data.size(), pos);
}
// Writes |size| octets of data starting from |data| into this buffer starting
// at |pos|. |data| must point to a valid piece of memory if |size| is
// non-zero. If |size| is zero, then this operation is a NOP.
void Write(const uint8_t* data, size_t size, size_t pos = 0);
// Writes the byte interpretation of |data| at |pos|, overwriting the octets
// from pos to pos + sizeof(T).
// There must be enough space in the buffer to write T.
// If T is an array of known bounds, the entire array will be written.
template <typename T>
void WriteObj(const T& data, size_t pos = 0) {
// ByteBuffers are (mostly?) not TriviallyCopyable, but check this first for the error to be
// useful.
static_assert(!std::is_base_of_v<ByteBuffer, T>, "ByteBuffer passed to WriteObj; use Write");
static_assert(!std::is_pointer_v<T>, "Pointer passed to WriteObj, deref or use Write");
static_assert(std::is_trivially_copyable_v<T>, "Unsafe to peek byte representation");
Write(reinterpret_cast<const uint8_t*>(&data), sizeof(T), pos);
}
// Behaves exactly like ByteBuffer::View but returns the result in a
// MutableBufferView instead.
//
// WARNING:
//
// A BufferView is only valid as long as the buffer that it points to is
// valid. Care should be taken to ensure that a BufferView does not outlive
// its backing buffer.
MutableBufferView mutable_view(size_t pos = 0,
size_t size = std::numeric_limits<std::size_t>::max());
// Sets the contents of the buffer to 0s.
void SetToZeros() { Fill(0); }
// Fills the contents of the buffer with random bytes.
void FillWithRandomBytes();
// Fills the contents of the buffer with the given value.
virtual void Fill(uint8_t value) = 0;
};
using MutableByteBufferPtr = std::unique_ptr<MutableByteBuffer>;
// A ByteBuffer with static storage duration. Instances of this class are
// copyable. Due to the static buffer storage duration, move semantics work the
// same way as copy semantics, i.e. moving an instance will copy the buffer
// contents.
template <size_t BufferSize>
class StaticByteBuffer : public MutableByteBuffer {
public:
StaticByteBuffer() { static_assert(BufferSize, "|BufferSize| must be non-zero"); }
~StaticByteBuffer() override = default;
// Variadic template constructor to initialize a StaticByteBuffer using a parameter pack e.g.:
//
// StaticByteBuffer<3> foo{0x00, 0x01, 0x02};
// StaticByteBuffer<3> bar({0x00, 0x01, 0x02});
//
// The class's |BufferSize| template parameter, if explicitly provided, will be checked against
// the number of initialization elements provided.
template <typename... T>
explicit StaticByteBuffer(T... bytes) : buffer_{{static_cast<uint8_t>(bytes)...}} {
static_assert(BufferSize, "|BufferSize| must be non-zero");
static_assert(BufferSize == sizeof...(T), "|BufferSize| must match initializer list count");
}
// ByteBuffer overrides
const uint8_t* data() const override { return buffer_.data(); }
size_t size() const override { return buffer_.size(); }
const_iterator cbegin() const override { return buffer_.cbegin(); }
const_iterator cend() const override { return buffer_.cend(); }
// MutableByteBuffer overrides:
uint8_t* mutable_data() override { return buffer_.data(); }
void Fill(uint8_t value) override { buffer_.fill(value); }
private:
std::array<uint8_t, BufferSize> buffer_;
};
// Template deduction guide for the |BufferSize| class template parameter using the number of
// parameters passed into the templated parameter pack constructor. This allows |BufferSize| to be
// omitted when it should be deduced from the initializer:
//
// StaticByteBuffer buffer(0x00, 0x01, 0x02);
//
template <typename... T>
StaticByteBuffer(T... bytes) -> StaticByteBuffer<sizeof...(T)>;
// Wrapper for the variadic template StaticByteBuffer constructor that deduces
// the value of the |BufferSize| template parameter from the given input. This
// way one can construct a StaticByteBuffer without hard-coding the size of the
// buffer like so:
//
// auto buffer = CreateStaticByteBuffer(0x01, 0x02, 0x03);
//
template <typename... T>
StaticByteBuffer<sizeof...(T)> CreateStaticByteBuffer(T... bytes) {
return StaticByteBuffer<sizeof...(T)>{bytes...};
}
// A ByteBuffer with dynamic storage duration. The underlying buffer is
// allocated using malloc. Instances of this class are move-only.
class DynamicByteBuffer : public MutableByteBuffer {
public:
// The default constructor creates an empty buffer with size 0.
DynamicByteBuffer();
~DynamicByteBuffer() override = default;
// Allocates a new buffer with |buffer_size| bytes.
explicit DynamicByteBuffer(size_t buffer_size);
// Copies the contents of |buffer|.
explicit DynamicByteBuffer(const ByteBuffer& buffer);
DynamicByteBuffer(const DynamicByteBuffer& buffer);
// Takes ownership of |buffer| and avoids allocating a new buffer. Since this
// constructor performs a simple assignment, the caller must make sure that
// the buffer pointed to by |buffer| actually contains |buffer_size| bytes.
DynamicByteBuffer(size_t buffer_size, std::unique_ptr<uint8_t[]> buffer);
// Move constructor and assignment operator
DynamicByteBuffer(DynamicByteBuffer&& other);
DynamicByteBuffer& operator=(DynamicByteBuffer&& other);
// Copy assignment is prohibited.
DynamicByteBuffer& operator=(const DynamicByteBuffer&) = delete;
// ByteBuffer overrides:
const uint8_t* data() const override;
size_t size() const override;
const_iterator cbegin() const override;
const_iterator cend() const override;
// MutableByteBuffer overrides:
uint8_t* mutable_data() override;
void Fill(uint8_t value) override;
private:
// Pointer to the underlying buffer, which is owned and managed by us.
size_t buffer_size_ = 0u;
std::unique_ptr<uint8_t[]> buffer_;
};
// A ByteBuffer that does not own the memory that it points to but rather
// provides an immutable view over it.
//
// WARNING:
//
// A BufferView is only valid as long as the buffer that it points to is
// valid. Care should be taken to ensure that a BufferView does not outlive
// its backing buffer.
class BufferView final : public ByteBuffer {
public:
BufferView(const void* bytes, size_t size);
~BufferView() override = default;
explicit BufferView(const ByteBuffer& buffer,
size_t size = std::numeric_limits<std::size_t>::max());
explicit BufferView(std::string_view string);
explicit BufferView(const std::vector<uint8_t>& vec);
// The default constructor initializes this to an empty buffer.
BufferView();
// ByteBuffer overrides:
const uint8_t* data() const override;
size_t size() const override;
const_iterator cbegin() const override;
const_iterator cend() const override;
private:
size_t size_ = 0u;
const uint8_t* bytes_ = nullptr;
};
// A ByteBuffer that does not own the memory that it points to but rather
// provides a mutable view over it.
//
// WARNING:
//
// A BufferView is only valid as long as the buffer that it points to is
// valid. Care should be taken to ensure that a BufferView does not outlive
// its backing buffer.
class MutableBufferView final : public MutableByteBuffer {
public:
explicit MutableBufferView(MutableByteBuffer* buffer);
MutableBufferView(void* bytes, size_t size);
~MutableBufferView() override = default;
// The default constructor initializes this to an empty buffer.
MutableBufferView();
// ByteBuffer overrides:
const uint8_t* data() const override;
size_t size() const override;
const_iterator cbegin() const override;
const_iterator cend() const override;
// MutableByteBuffer overrides:
uint8_t* mutable_data() override;
void Fill(uint8_t value) override;
private:
size_t size_ = 0u;
uint8_t* bytes_ = nullptr;
};
} // namespace bt
#endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_COMMON_BYTE_BUFFER_H_