blob: 163f5eaecee66da6bbf2ad4857a3aab04d68c60d [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.
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
#include <cstddef>
#include <type_traits>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
#pragma clang diagnostic ignored "-Wc99-extensions"
namespace bt {
namespace {
TEST(ByteBufferTest, StaticByteBuffer) {
constexpr size_t kBufferSize = 5;
StaticByteBuffer<kBufferSize> buffer;
// The buffer is initialized to 0.
constexpr std::array<uint8_t, kBufferSize> kExpectedDefault{
{0x00, 0x00, 0x00, 0x00, 0x00}};
EXPECT_TRUE(ContainersEqual(kExpectedDefault, buffer));
EXPECT_EQ(kBufferSize, buffer.size());
buffer.SetToZeros();
buffer[3] = 3;
constexpr std::array<uint8_t, kBufferSize> kExpected{
{0x00, 0x00, 0x00, 0x03, 0x00}};
EXPECT_TRUE(ContainersEqual(kExpected, buffer));
// Moving will result in a copy.
StaticByteBuffer<kBufferSize> buffer_copy =
std::move(buffer); // NOLINT(performance-move-const-arg)
EXPECT_EQ(kBufferSize, buffer.size()); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(kBufferSize, buffer_copy.size());
EXPECT_TRUE(
ContainersEqual(kExpected, buffer)); // NOLINT(bugprone-use-after-move)
EXPECT_TRUE(ContainersEqual(kExpected, buffer_copy));
// Copying should also copy.
StaticByteBuffer<kBufferSize> buffer_copy1 = buffer;
EXPECT_EQ(kBufferSize, buffer.size());
EXPECT_EQ(kBufferSize, buffer_copy1.size());
EXPECT_TRUE(ContainersEqual(kExpected, buffer));
EXPECT_TRUE(ContainersEqual(kExpected, buffer_copy1));
// Const ByteBuffer should still permit operator[] access.
const StaticByteBuffer const_buff(0x10);
EXPECT_EQ(0x10, const_buff[0]);
}
TEST(ByteBufferTest, StaticByteBufferPackConstructor) {
constexpr size_t kBufferSize = 3;
StaticByteBuffer<kBufferSize> buffer0;
buffer0[0] = 0x01;
buffer0[1] = 0x02;
buffer0[2] = 0x03;
StaticByteBuffer<kBufferSize> buffer1{0x01, 0x02, 0x03};
StaticByteBuffer buffer2(0x01, 0x02, 0x03);
StaticByteBuffer buffer3{0x01, 0x02, 0x03};
EXPECT_TRUE(ContainersEqual(buffer0, buffer1));
EXPECT_TRUE(ContainersEqual(buffer0, buffer2));
EXPECT_TRUE(ContainersEqual(buffer1, buffer2));
EXPECT_TRUE(ContainersEqual(buffer1, buffer3));
// The corresponding check is a BT_DEBUG_ASSERT runtime check
EXPECT_DEBUG_DEATH(StaticByteBuffer(-257), "ASSERT");
}
TEST(ByteBufferTest, DynamicByteBuffer) {
constexpr size_t kBufferSize = 5;
DynamicByteBuffer buffer(kBufferSize);
EXPECT_EQ(kBufferSize, buffer.size());
// The buffer is initialized to 0.
constexpr std::array<uint8_t, kBufferSize> kExpectedDefault{
{0x00, 0x00, 0x00, 0x00, 0x00}};
EXPECT_TRUE(ContainersEqual(kExpectedDefault, buffer));
buffer[3] = 3;
constexpr std::array<uint8_t, kBufferSize> kExpected{
{0x00, 0x00, 0x00, 0x03, 0x00}};
EXPECT_TRUE(ContainersEqual(kExpected, buffer));
// Moving will invalidate the source buffer.
DynamicByteBuffer buffer_moved = std::move(buffer);
EXPECT_EQ(0u, buffer.size()); // NOLINT(bugprone-use-after-move)
EXPECT_EQ(kBufferSize, buffer_moved.size());
EXPECT_EQ(nullptr, buffer.data()); // NOLINT(bugprone-use-after-move)
EXPECT_TRUE(ContainersEqual(kExpected, buffer_moved));
// Const ByteBuffer copied from buffer_moved should permit operator[] access.
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
const DynamicByteBuffer const_buff(buffer_moved);
EXPECT_EQ(0x03, const_buff[3]);
}
TEST(ByteBufferTest, DynamicByteBufferZeroSize) {
DynamicByteBuffer buffer;
EXPECT_EQ(0u, buffer.size());
EXPECT_EQ(nullptr, buffer.data());
DynamicByteBuffer zerosize(0);
EXPECT_EQ(0u, zerosize.size());
EXPECT_EQ(nullptr, buffer.data());
}
TEST(ByteBufferTest, DynamicByteBufferConstructFromBuffer) {
constexpr size_t kBufferSize = 3;
StaticByteBuffer<kBufferSize> buffer(1, 2, 3);
DynamicByteBuffer dyn_buffer(buffer);
EXPECT_EQ(kBufferSize, dyn_buffer.size());
EXPECT_TRUE(ContainersEqual(buffer, dyn_buffer));
}
TEST(ByteBufferTest, DynamicByteBufferExplicitCopy) {
DynamicByteBuffer src(1);
src[0] = 'a';
DynamicByteBuffer dst(src);
EXPECT_EQ(1u, dst.size());
EXPECT_TRUE(ContainersEqual(src, dst));
}
TEST(ByteBufferTest, DynamicByteBufferConstructFromBytes) {
constexpr size_t kBufferSize = 3;
std::array<uint8_t, kBufferSize> kExpected{{0, 1, 2}};
auto bytes = std::make_unique<uint8_t[]>(kBufferSize);
std::memcpy(bytes.get(), kExpected.data(), kBufferSize);
DynamicByteBuffer buffer(kBufferSize, std::move(bytes));
EXPECT_EQ(nullptr, bytes.get());
EXPECT_TRUE(ContainersEqual(kExpected, buffer));
}
TEST(ByteBufferTest, DynamicByteBufferConstructFromString) {
constexpr size_t kBufferSize = 3;
std::array<uint8_t, kBufferSize> kExpected{{0x41, 0x42, 0x43}};
auto str = std::string("ABC");
DynamicByteBuffer buffer(str);
EXPECT_EQ(std::string("ABC"), str);
EXPECT_TRUE(ContainersEqual(kExpected, buffer));
}
TEST(ByteBufferTest, DynamicByteBufferShrink) {
std::string data = "abcdef";
DynamicByteBuffer buffer(data);
ASSERT_EQ(buffer.size(), data.size());
std::string new_data = "abcde";
ASSERT_FALSE(buffer.expand(new_data.size()));
EXPECT_EQ(buffer.size(), data.size());
EXPECT_EQ(buffer.ToString(), data);
}
TEST(ByteBufferTest, DynamicByteBufferNewSizeEqual) {
std::string data = "abcdef";
DynamicByteBuffer buffer(data);
ASSERT_EQ(buffer.size(), data.size());
ASSERT_FALSE(buffer.expand(data.size()));
ASSERT_EQ(buffer.size(), data.size());
EXPECT_EQ(buffer.ToString(), data);
}
TEST(ByteBufferTest, DynamicByteBufferExpand) {
std::string data = "abcdef";
DynamicByteBuffer buffer(data);
ASSERT_EQ(buffer.size(), data.size());
ASSERT_TRUE(buffer.expand(data.size() * 2));
ASSERT_EQ(buffer.size(), data.size() * 2);
EXPECT_EQ(buffer.view(0, data.size()).ToString(), data);
}
TEST(ByteBufferTest, BufferViewTest) {
constexpr size_t kBufferSize = 5;
DynamicByteBuffer buffer(kBufferSize);
EXPECT_EQ(kBufferSize, buffer.size());
buffer.SetToZeros();
BufferView view(buffer);
EXPECT_EQ(0x00, buffer[0]);
EXPECT_EQ(0x00, view[0]);
EXPECT_EQ(kBufferSize, buffer.size());
EXPECT_EQ(kBufferSize, view.size());
}
TEST(ByteBufferTest, BufferViewFromVector) {
const std::vector<uint8_t> kData{{1, 2, 3}};
BufferView view(kData);
EXPECT_TRUE(ContainersEqual(kData, view));
}
TEST(ByteBufferTest, MutableBufferViewTest) {
constexpr size_t kBufferSize = 5;
constexpr size_t kViewSize = 3;
DynamicByteBuffer buffer(kBufferSize);
EXPECT_EQ(kBufferSize, buffer.size());
buffer.SetToZeros();
MutableBufferView view(buffer.mutable_data(), kViewSize);
view[0] = 0x01;
EXPECT_EQ(0x01, buffer[0]);
EXPECT_EQ(0x01, view[0]);
EXPECT_EQ(kBufferSize, buffer.size());
EXPECT_EQ(kViewSize, view.size());
MutableBufferView view2(view);
view2[0] = 0x00;
EXPECT_EQ(0x00, buffer[0]);
EXPECT_EQ(0x00, view[0]);
EXPECT_EQ(0x00, view2[0]);
EXPECT_EQ(kBufferSize, buffer.size());
EXPECT_EQ(kViewSize, view.size());
}
TEST(ByteBufferDeathTest, Copy) {
StaticByteBuffer buffer('T', 'e', 's', 't');
BufferView empty_buffer;
// Create a large enough buffer.
StaticByteBuffer<10> target_buffer;
// Copying an empty buffer should copy 0 bytes.
empty_buffer.Copy(&target_buffer);
// Copy all of |buffer|. The first |buffer.size()| octets of |target_buffer|
// should match the contents of |buffer|.
buffer.Copy(&target_buffer);
EXPECT_TRUE(
ContainersEqual(buffer, BufferView(target_buffer, buffer.size())));
// Copy one octet of |buffer| starting at index 2
target_buffer.SetToZeros();
buffer.Copy(&target_buffer, 1, 1);
EXPECT_EQ(buffer[1], target_buffer[0]);
// Zero the buffer and copy its contents for later comparison.
target_buffer.SetToZeros();
auto target_buffer_copy = target_buffer;
// Copy all remaining octets starting just past the end of |buffer|. This
// should copy zero bytes and |target_buffer| should remain unchanged.
buffer.Copy(&target_buffer, buffer.size(), 0);
EXPECT_TRUE(ContainersEqual(target_buffer_copy, target_buffer));
// Copied range must remain within buffer limits
EXPECT_DEATH_IF_SUPPORTED(buffer.Copy(&target_buffer, buffer.size() + 1, 0),
"offset exceeds source range");
EXPECT_DEATH_IF_SUPPORTED(buffer.Copy(&target_buffer, 0, buffer.size() + 1),
"end exceeds source range");
EXPECT_DEATH_IF_SUPPORTED(
buffer.Copy(&target_buffer, buffer.size() / 2 + 1, buffer.size() / 2 + 1),
"end exceeds source range");
StaticByteBuffer<1> insufficient_target_buffer;
EXPECT_DEATH_IF_SUPPORTED(buffer.Copy(&insufficient_target_buffer),
"destination not large enough");
// Range calculation overflow is fatal rather than silently erroneous
MutableBufferView bogus_target_buffer(target_buffer.mutable_data(),
std::numeric_limits<size_t>::max());
EXPECT_DEATH_IF_SUPPORTED(
buffer.Copy(&bogus_target_buffer, 1, std::numeric_limits<size_t>::max()),
"end of source range overflows size_t");
}
TEST(ByteBufferTest, View) {
StaticByteBuffer buffer('T', 'e', 's', 't');
BufferView empty_buffer;
BufferView view = empty_buffer.view();
EXPECT_EQ(0u, view.size());
view = empty_buffer.view(0, 200);
EXPECT_EQ(0u, view.size());
view = buffer.view();
EXPECT_EQ("Test", view.AsString());
view = buffer.view(4);
EXPECT_EQ(0u, view.size());
view = buffer.view(1);
EXPECT_EQ("est", view.AsString());
view = buffer.view(1, 1);
EXPECT_EQ("e", view.AsString());
view = buffer.view(1, 2);
EXPECT_EQ("es", view.AsString());
}
TEST(ByteBufferTest, Span) {
StaticByteBuffer buffer('T', 'e', 's', 't');
BufferView empty_buffer;
pw::span span = empty_buffer.subspan();
EXPECT_EQ(0u, span.size());
span = empty_buffer.subspan(0, 200);
EXPECT_EQ(0u, span.size());
span = buffer.subspan();
EXPECT_THAT(
span,
::testing::ElementsAreArray(
{std::byte{'T'}, std::byte{'e'}, std::byte{'s'}, std::byte{'t'}}));
span = buffer.subspan(4);
EXPECT_EQ(0u, span.size());
span = buffer.subspan(1);
EXPECT_THAT(span,
::testing::ElementsAreArray(
{std::byte{'e'}, std::byte{'s'}, std::byte{'t'}}));
span = buffer.subspan(1, 1);
EXPECT_THAT(span, ::testing::ElementsAreArray({std::byte{'e'}}));
span = buffer.subspan(1, 2);
EXPECT_THAT(span,
::testing::ElementsAreArray({std::byte{'e'}, std::byte{'s'}}));
}
TEST(ByteBufferTest, MutableView) {
StaticByteBuffer buffer('T', 'e', 's', 't');
MutableBufferView empty_buffer;
MutableBufferView view;
view = empty_buffer.mutable_view();
EXPECT_EQ(0u, view.size());
view = empty_buffer.mutable_view(0, 200);
EXPECT_EQ(0u, view.size());
view = buffer.mutable_view();
EXPECT_EQ("Test", view.AsString());
view = buffer.mutable_view(4);
EXPECT_EQ(0u, view.size());
view = buffer.mutable_view(1);
EXPECT_EQ("est", view.AsString());
view = buffer.mutable_view(1, 1);
EXPECT_EQ("e", view.AsString());
view = buffer.mutable_view(1, 2);
EXPECT_EQ("es", view.AsString());
// MutableView can modify the underlying buffer.
view[0] = 'E';
EXPECT_EQ("Es", view.AsString());
EXPECT_EQ("TEst", buffer.AsString());
}
TEST(ByteBufferTest, ByteBufferEqualityFail) {
const StaticByteBuffer kData0('T', 'e', 's', 't');
const StaticByteBuffer kData1('F', 'o', 'o');
EXPECT_FALSE(kData0 == kData1);
}
TEST(ByteBufferTest, ByteBufferEqualitySuccess) {
const StaticByteBuffer kData0('T', 'e', 's', 't');
const StaticByteBuffer kData1('T', 'e', 's', 't');
EXPECT_TRUE(kData0 == kData1);
}
TEST(ByteBufferDeathTest, ByteBufferWithInsufficientBytesAssertsOnTo) {
StaticByteBuffer buffer(0, 0, 0);
ASSERT_GT(sizeof(float), buffer.size());
EXPECT_DEATH_IF_SUPPORTED(
[=] { [[maybe_unused]] auto _ = buffer.To<float>(); }(),
"end exceeds source range");
}
template <typename T>
void TestByteBufferRoundtrip(std::initializer_list<T> values) {
for (auto value : values) {
BufferView value_view(&value, sizeof(value));
auto to_value = value_view.To<T>();
SCOPED_TRACE(value);
EXPECT_EQ(0, std::memcmp(&value, &to_value, sizeof(T)));
}
}
TEST(ByteBufferTest, ByteBufferToRoundtripOnPrimitives) {
// Test values from absl::bit_cast tests
{
SCOPED_TRACE("bool");
TestByteBufferRoundtrip<bool>({true, false});
}
{
SCOPED_TRACE("array 1 of const bool");
TestByteBufferRoundtrip<const bool[1]>({{true}, {false}});
}
{
SCOPED_TRACE("int32_t");
TestByteBufferRoundtrip<int32_t>(
{0, 1, 100, 2147483647, -1, -100, -2147483647, -2147483647 - 1});
}
{
SCOPED_TRACE("int64_t");
TestByteBufferRoundtrip<int64_t>({0, 1, 1LL << 40, -1, -(1LL << 40)});
}
{
SCOPED_TRACE("uint64_t");
TestByteBufferRoundtrip<uint64_t>({0, 1, 1LLU << 40, 1LLU << 63});
}
{
SCOPED_TRACE("float");
TestByteBufferRoundtrip<float>({0.0f,
1.0f,
-1.0f,
10.0f,
-10.0f,
1e10f,
1e20f,
1e-10f,
1e-20f,
2.71828f,
3.14159f});
}
{
SCOPED_TRACE("double");
TestByteBufferRoundtrip<double>(
{0.0,
1.0,
-1.0,
10.0,
-10.0,
1e10,
1e100,
1e-10,
1e-100,
2.718281828459045,
3.141592653589793238462643383279502884197169399375105820974944});
}
}
TEST(ByteBufferTest, ByteBufferToStripsCvQualifiers) {
auto to_value = StaticByteBuffer(2).To<const volatile char>();
static_assert(std::is_same_v<char, decltype(to_value)>);
}
TEST(ByteBufferTest, ByteBufferWithAdditionalBytesToBasicType) {
EXPECT_EQ(191u, StaticByteBuffer(191, 25).To<uint8_t>());
}
TEST(ByteBufferTest, ByteBufferToStruct) {
const StaticByteBuffer data(10, 12);
struct [[gnu::packed]] point {
uint8_t x;
uint8_t y;
};
EXPECT_EQ(10, data.To<point>().x);
EXPECT_EQ(12, data.To<point>().y);
}
TEST(ByteBufferTest, ByteBufferToArray) {
const StaticByteBuffer buf(191, 25);
const auto array = buf.To<uint8_t[2]>();
EXPECT_EQ(buf.size(), array.size());
EXPECT_EQ(191, array[0]);
EXPECT_EQ(25, array[1]);
}
TEST(ByteBufferTest, ByteBufferToDoesNotReadUnaligned) {
const DynamicByteBuffer buf(2 * sizeof(float) - 1);
// Advance past the float alignment boundary
const size_t offset = std::distance(
buf.begin(), std::find_if(buf.begin(), buf.end(), [](auto& b) {
return reinterpret_cast<uintptr_t>(&b) % alignof(float) != 0;
}));
BufferView view = buf.view(offset, sizeof(float));
// Prove that the data in the view isn't aligned for a float
ASSERT_NE(0U, reinterpret_cast<uintptr_t>(view.data()) % alignof(float));
// This cref binds to a new object that ByteBuffer::To creates, so the
// alignment is correct
const float& to_object = view.To<float>();
EXPECT_EQ(0U, reinterpret_cast<uintptr_t>(&to_object) % alignof(float));
}
TEST(ByteBufferTest, ByteBufferReadMember) {
struct [[gnu::packed]] Point {
uint8_t x;
const uint8_t y;
const uint8_t array[2];
char multi[2][1];
uint8_t flex[];
};
StaticByteBuffer data(0x01, 0x02, 0x03, 0x37, 0x7f, 0x02, 0x45);
auto x = data.ReadMember<&Point::x>();
static_assert(std::is_same_v<uint8_t, decltype(x)>);
EXPECT_EQ(data[offsetof(Point, x)], x);
// Top-level const qualifier is removed
auto y = data.ReadMember<&Point::y>();
static_assert(std::is_same_v<uint8_t, decltype(y)>);
EXPECT_EQ(data[offsetof(Point, y)], y);
// Returned array elements are const just like in the original struct
auto array = data.ReadMember<&Point::array>();
static_assert(std::is_same_v<std::array<const uint8_t, 2>, decltype(array)>);
EXPECT_THAT(array, ::testing::ElementsAre(uint8_t{0x03}, uint8_t{0x37}));
auto multi = data.ReadMember<&Point::multi>();
static_assert(
std::is_same_v<std::array<std::array<char, 1>, 2>, decltype(multi)>);
EXPECT_THAT(
multi,
::testing::ElementsAre(std::array{char{0x7f}}, std::array{char{0x02}}));
}
TEST(ByteBufferDeathTest, ByteBufferReadMemberOfFixedArrayType) {
struct [[gnu::packed]] Point {
float f;
int8_t coordinates[3];
char multi[2][1];
};
StaticByteBuffer data(
// f
0,
0,
0,
0,
// coordinates[3]
0x01,
0x02,
0x03,
// multi[2][1]
0x37,
0x45);
ASSERT_LE(sizeof(Point), data.size());
EXPECT_DEATH_IF_SUPPORTED(data.ReadMember<&Point::coordinates>(3),
"index past array bounds");
auto view = data.view(0, 6);
ASSERT_GT(sizeof(Point), view.size());
EXPECT_DEATH_IF_SUPPORTED(view.ReadMember<&Point::coordinates>(0),
"insufficient buffer");
EXPECT_EQ(data[offsetof(Point, coordinates)],
data.ReadMember<&Point::coordinates>(0));
EXPECT_EQ(data[offsetof(Point, coordinates) + 1],
data.ReadMember<&Point::coordinates>(1));
// Elements of a multi-dimensional C array are returned as std::arrays
auto inner = data.ReadMember<&Point::multi>(1);
EXPECT_EQ(data[offsetof(Point, multi) + 1], inner.at(0));
}
TEST(ByteBufferDeathTest, ByteBufferReadMemberOfStdArrayType) {
struct [[gnu::packed]] Point {
float f;
std::array<int8_t, 3> coordinates;
};
StaticByteBuffer data(0, 0, 0, 0, 0x01, 0x02, 0x03, 0x37);
ASSERT_LE(sizeof(Point), data.size());
EXPECT_DEATH_IF_SUPPORTED(data.ReadMember<&Point::coordinates>(3),
"index past array bounds");
EXPECT_EQ(data[offsetof(Point, coordinates)],
data.ReadMember<&Point::coordinates>(0));
EXPECT_EQ(data[offsetof(Point, coordinates) + 1],
data.ReadMember<&Point::coordinates>(1));
}
TEST(ByteBufferDeathTest, ByteBufferReadMemberOfFlexibleArrayType) {
struct [[gnu::packed]] Point {
uint16_t dimensions;
int8_t coordinates[];
};
StaticByteBuffer data(0, 0, 0x01, 0x02);
ASSERT_LE(sizeof(Point), data.size());
EXPECT_DEATH_IF_SUPPORTED(data.ReadMember<&Point::coordinates>(2),
"end exceeds source range");
EXPECT_EQ(data[offsetof(Point, coordinates)],
data.ReadMember<&Point::coordinates>(0));
EXPECT_EQ(data[offsetof(Point, coordinates) + 1],
data.ReadMember<&Point::coordinates>(1));
}
TEST(ByteBufferTest, ByteBufferReadMemberOfUnalignedArrayType) {
struct [[gnu::packed]] Point {
int8_t byte;
float f[1];
} point;
BufferView view(&point, sizeof(point));
static_assert(alignof(decltype(view.ReadMember<&Point::f>(0))) ==
alignof(float));
// This branch (that the second field of |point| is unaligned) does get taken
// in manual testing but there's no way to guarantee it, so make it run-time
// conditional.
if (reinterpret_cast<uintptr_t>(&point.f) % alignof(float) != 0) {
// Casting (which is UB here) a pointer into the buffer contents yields a
// pointer that is not aligned to type requirements
const float* unaligned_pointer =
reinterpret_cast<const Point*>(view.data())->f;
ASSERT_NE(0U,
reinterpret_cast<uintptr_t>(unaligned_pointer) % alignof(float));
}
// The same cref binds to a temporary new object that ByteBuffer::ReadMember
// creates, so the alignment is correct.
const float& through_read_member = view.ReadMember<&Point::f>(0);
EXPECT_EQ(0U,
reinterpret_cast<uintptr_t>(&through_read_member) % alignof(float));
}
TEST(ByteBufferTest, MutableByteBufferAsMutableFundamental) {
StaticByteBuffer data(10, 12);
++*data.AsMutable<uint8_t>();
EXPECT_EQ(11, data[0]);
}
TEST(ByteBufferTest, MutableByteBufferAsMutableStruct) {
StaticByteBuffer data(10, 12);
struct point {
uint8_t x;
uint8_t y;
};
++data.AsMutable<point>()->x;
++data.AsMutable<point>()->y;
const StaticByteBuffer expected_data(11, 13);
EXPECT_EQ(expected_data, data);
}
TEST(ByteBufferTest, MutableByteBufferAsMutableArray) {
StaticByteBuffer buf(10, 12);
uint8_t(&array)[2] = *buf.AsMutable<uint8_t[2]>();
++array[0];
++array[1];
EXPECT_EQ(11, buf[0]);
EXPECT_EQ(13, buf[1]);
}
TEST(ByteBufferDeathTest, MutableByteBufferWrite) {
const StaticByteBuffer kData0('T', 'e', 's', 't');
const StaticByteBuffer kData1('F', 'o', 'o');
StaticByteBuffer buffer('X', 'X', 'X', 'X', 'X', 'X', 'X', 'X');
EXPECT_EQ("XXXXXXXX", buffer.AsString());
buffer.Write(kData0);
EXPECT_EQ("TestXXXX", buffer.AsString());
// Write from raw pointer.
buffer = StaticByteBuffer('X', 'X', 'X', 'X', 'X', 'X', 'X', 'X');
buffer.Write(kData0.data(), kData0.size());
EXPECT_EQ("TestXXXX", buffer.AsString());
// Write at offset.
buffer.Write(kData1, 1);
EXPECT_EQ("TFooXXXX", buffer.AsString());
// Write at offset from raw pointer
buffer.Write(kData1.data(), kData1.size(), 3);
EXPECT_EQ("TFoFooXX", buffer.AsString());
// Writing zero bytes should have no effect.
buffer = StaticByteBuffer('X', 'X', 'X', 'X', 'X', 'X', 'X', 'X');
buffer.Write(kData1.data(), 0u);
buffer.Write(/*data=*/nullptr, 0u); // Passing nullptr is OK when size is 0
EXPECT_EQ("XXXXXXXX", buffer.AsString());
// Writing zero bytes just past the buffer should be accepted (i.e. no
// assertion) and have no effect.
buffer.Write(kData1.data(), 0u, buffer.size());
EXPECT_EQ("XXXXXXXX", buffer.AsString());
// Buffer limits are strictly enforced
EXPECT_DEATH_IF_SUPPORTED(buffer.Write(kData0.data(), buffer.size() + 1, 0),
"destination not large enough");
EXPECT_DEATH_IF_SUPPORTED(buffer.Write(kData0.data(), 0, buffer.size() + 1),
"offset past buffer");
}
TEST(ByteBufferTest, AsString) {
StaticByteBuffer buffer('T', 'e', 's', 't');
EXPECT_EQ("Test", buffer.AsString());
}
TEST(ByteBufferTest, Fill) {
StaticByteBuffer<5> buffer;
buffer.Fill('A');
EXPECT_EQ("AAAAA", buffer.AsString());
}
TEST(ByteBufferTest, ToVectorEmpty) {
BufferView buffer;
std::vector<uint8_t> vec = buffer.ToVector();
EXPECT_TRUE(vec.empty());
}
TEST(ByteBufferTest, ToVector) {
StaticByteBuffer buffer('h', 'e', 'l', 'l', 'o');
std::vector<uint8_t> vec = buffer.ToVector();
EXPECT_TRUE(ContainersEqual(vec, buffer));
}
TEST(ByteBufferTest, PrintableAscii) {
StaticByteBuffer buffer('f', 'o', 'o', 'b', 'a', 'r', 'b', 'a', 'z');
std::string result = buffer.Printable(3, 3);
EXPECT_EQ("bar", result);
}
TEST(ByteBufferTest, PrintableUtf8) {
StaticByteBuffer buffer(
0xce, 0xba, 0xe1, 0xbd, 0xb9, 0xcf, 0x83, 0xce, 0xbc, 0xce, 0xB5);
std::string result = buffer.Printable(0, buffer.size());
EXPECT_EQ("κόσμε", result);
}
TEST(ByteBufferTest, PrintableInvalidUtf8) {
StaticByteBuffer buffer(0xce, 0xba, 0x80);
std::string result = buffer.Printable(0, buffer.size());
EXPECT_EQ("...", result);
}
TEST(ByteBufferDeathTest, PrintableSizeTooLarge) {
StaticByteBuffer buffer(0xce, 0xba, 0x80);
ASSERT_DEATH({ buffer.Printable(0, buffer.size() + 1); }, "");
ASSERT_DEATH({ buffer.Printable(1, buffer.size()); }, "");
ASSERT_DEATH({ buffer.Printable(1, buffer.size() + 1); }, "");
}
} // namespace
} // namespace bt