blob: a31f5b811581a871701b03b616e9125a11db41ea [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 "database.h"
#include <zircon/assert.h>
#include "gtest/gtest.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
namespace bt {
namespace att {
namespace {
constexpr Handle kTestRangeStart = 1;
constexpr Handle kTestRangeEnd = 10;
constexpr UUID kTestType1((uint16_t)1);
constexpr UUID kTestType2((uint16_t)2);
constexpr UUID kTestType3((uint16_t)3);
const AccessRequirements kAllowed(false, false, false);
const sm::SecurityProperties kNoSecurity(sm::SecurityLevel::kNoSecurity, 16,
false);
// Values with different lengths
const auto kTestValue1 = CreateStaticByteBuffer('x', 'x');
const auto kTestValue2 = CreateStaticByteBuffer('x', 'x', 'x');
// Returns the handles of each attribute visited by advancing |iter| until the
// end.
std::vector<Handle> IterHandles(Database::Iterator* iter) {
ZX_DEBUG_ASSERT(iter);
std::vector<Handle> handles;
for (; !iter->AtEnd(); iter->Advance()) {
handles.push_back(iter->get()->handle());
}
return handles;
}
TEST(ATT_DatabaseTest, NewGroupingWhileEmptyError) {
constexpr size_t kTooLarge = kTestRangeEnd - kTestRangeStart + 1;
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
EXPECT_FALSE(db->NewGrouping(kTestType1, kTooLarge, kTestValue1));
}
TEST(ATT_DatabaseTest, NewGroupingWhileEmptyFill) {
constexpr size_t kExact = kTestRangeEnd - kTestRangeStart;
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto* grp = db->NewGrouping(kTestType1, kExact, kTestValue1);
ASSERT_TRUE(grp);
EXPECT_EQ(kTestType1, grp->group_type());
EXPECT_EQ(kTestRangeStart, grp->start_handle());
EXPECT_EQ(kTestRangeEnd, grp->end_handle());
// Ran out of space.
EXPECT_FALSE(db->NewGrouping(kTestType1, 0, kTestValue1));
}
// This test case performs multiple insertions and removals on the same
// database.
TEST(ATT_DatabaseTest, NewGroupingMultipleInsertions) {
// [__________]
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
// Insert to empty db
// [XXX_______] (insert X)
auto* grp = db->NewGrouping(kTestType1, 2, kTestValue1);
ASSERT_TRUE(grp);
EXPECT_EQ(1, grp->start_handle());
EXPECT_EQ(3, grp->end_handle());
// Not enough space
grp = db->NewGrouping(kTestType1, 7, kTestValue1);
EXPECT_FALSE(grp);
// Insert back
// [XXXYYYYY__] (insert Y)
grp = db->NewGrouping(kTestType1, 4, kTestValue1);
ASSERT_TRUE(grp);
EXPECT_EQ(4, grp->start_handle());
EXPECT_EQ(8, grp->end_handle());
// Not enough space
grp = db->NewGrouping(kTestType1, 2, kTestValue1);
EXPECT_FALSE(grp);
// Insert back
// [XXXYYYYYZZ] (insert Z)
grp = db->NewGrouping(kTestType1, 1, kTestValue1);
ASSERT_TRUE(grp);
EXPECT_EQ(9, grp->start_handle());
EXPECT_EQ(10, grp->end_handle());
// Out of space
EXPECT_FALSE(db->NewGrouping(kTestType1, 0, kTestValue1));
// Remove first grouping. It should be possible to reinsert a smaller group.
// [___YYYYYZZ]
EXPECT_TRUE(db->RemoveGrouping(1));
// Not enough space
grp = db->NewGrouping(kTestType1, 3, kTestValue1);
EXPECT_FALSE(grp);
// Insert front
// [XX_YYYYYZZ] (insert X)
grp = db->NewGrouping(kTestType1, 1, kTestValue1);
ASSERT_TRUE(grp);
EXPECT_EQ(1, grp->start_handle());
EXPECT_EQ(2, grp->end_handle());
// Handle doesn't exist.
EXPECT_FALSE(db->RemoveGrouping(3));
// Insert in the middle
// [XXWYYYYYZZ] (insert W)
grp = db->NewGrouping(kTestType1, 0, kTestValue1);
ASSERT_TRUE(grp);
EXPECT_EQ(3, grp->start_handle());
EXPECT_EQ(3, grp->end_handle());
// [XXW_____ZZ] (remove Y)
EXPECT_TRUE(db->RemoveGrouping(4));
// Insert in the middle
// [XXWAAA__ZZ] (insert A)
grp = db->NewGrouping(kTestType1, 2, kTestValue1);
ASSERT_TRUE(grp);
EXPECT_EQ(4, grp->start_handle());
EXPECT_EQ(6, grp->end_handle());
// Insert in the middle
// [XXWAAABBZZ] (insert B)
grp = db->NewGrouping(kTestType1, 1, kTestValue1);
ASSERT_TRUE(grp);
EXPECT_EQ(7, grp->start_handle());
EXPECT_EQ(8, grp->end_handle());
// Out of space
EXPECT_FALSE(db->NewGrouping(kTestType1, 0, kTestValue1));
}
TEST(ATT_DatabaseTest, RemoveWhileEmpty) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
EXPECT_FALSE(db->RemoveGrouping(kTestRangeStart));
}
TEST(ATT_DatabaseTest, FindAttributeInvalidHandle) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
EXPECT_EQ(nullptr, db->FindAttribute(kInvalidHandle));
}
TEST(ATT_DatabaseTest, FindAttributeGroupingNotFound) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
// Create the following layout:
//
// handle 0x0001: occupied
// handle 0x0002: empty
// handle 0x0003: occupied
db->NewGrouping(kTestType1, 0, kTestValue1)->set_active(true);
auto* grp = db->NewGrouping(kTestType1, 0, kTestValue1);
grp->set_active(true);
db->NewGrouping(kTestType1, 0, kTestValue1)->set_active(true);
db->RemoveGrouping(grp->start_handle());
EXPECT_EQ(nullptr, db->FindAttribute(0xFFFF));
EXPECT_EQ(nullptr, db->FindAttribute(0x0002));
EXPECT_NE(nullptr, db->FindAttribute(0x0001));
EXPECT_NE(nullptr, db->FindAttribute(0x0003));
}
TEST(ATT_DatabaseTest, FindAttributeIncompleteGrouping) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto* grp = db->NewGrouping(kTestType1, 1, kTestValue1);
EXPECT_EQ(nullptr, db->FindAttribute(grp->start_handle()));
}
TEST(ATT_DatabaseTest, FindAttributeInactiveGrouping) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto* grp = db->NewGrouping(kTestType1, 0, kTestValue1);
EXPECT_EQ(nullptr, db->FindAttribute(grp->start_handle()));
}
TEST(ATT_DatabaseTest, FindAttributeOnePerGrouping) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto* grp1 = db->NewGrouping(kTestType1, 0, kTestValue1);
grp1->set_active(true);
auto* grp2 = db->NewGrouping(kTestType1, 0, kTestValue1);
grp2->set_active(true);
EXPECT_EQ(&grp1->attributes()[0], db->FindAttribute(grp1->start_handle()));
EXPECT_EQ(&grp2->attributes()[0], db->FindAttribute(grp2->start_handle()));
}
TEST(ATT_DatabaseTest, FindAttributeIndexIntoGrouping) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto* grp = db->NewGrouping(kTestType1, 1, kTestValue1);
auto* attr =
grp->AddAttribute(kTestType2, AccessRequirements(), AccessRequirements());
grp->set_active(true);
EXPECT_EQ(attr, db->FindAttribute(grp->end_handle()));
}
TEST(ATT_DatabaseTest, IteratorEmpty) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto iter = db->GetIterator(kTestRangeStart, kTestRangeEnd);
EXPECT_TRUE(iter.AtEnd());
EXPECT_FALSE(iter.get());
// Advance should have no effect.
iter.Advance();
EXPECT_TRUE(iter.AtEnd());
}
TEST(ATT_DatabaseTest, IteratorGroupOnlySingleInactive) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
db->NewGrouping(kTestType1, 0, kTestValue1);
// |grp| is not active
auto iter = db->GetIterator(kTestRangeStart, kTestRangeEnd, nullptr,
true /* groups_only */);
EXPECT_TRUE(iter.AtEnd());
EXPECT_FALSE(iter.get());
}
TEST(ATT_DatabaseTest, IteratorGroupOnlySingle) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto grp = db->NewGrouping(kTestType1, 0, kTestValue1);
grp->set_active(true);
// Not within range.
auto iter = db->GetIterator(grp->start_handle() + 1, kTestRangeEnd, nullptr,
true /* groups_only */);
EXPECT_TRUE(iter.AtEnd());
EXPECT_FALSE(iter.get());
iter = db->GetIterator(kTestRangeStart, kTestRangeEnd, nullptr,
true /* groups_only */);
EXPECT_FALSE(iter.AtEnd());
auto handles = IterHandles(&iter);
ASSERT_EQ(1u, handles.size());
EXPECT_EQ(grp->start_handle(), handles[0]);
}
TEST(ATT_DatabaseTest, IteratorGroupOnlyMultiple) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto grp1 = db->NewGrouping(kTestType1, 0, kTestValue1);
auto grp2 = db->NewGrouping(kTestType1, 0, kTestValue1);
auto grp3 = db->NewGrouping(kTestType1, 0, kTestValue1);
auto grp4 = db->NewGrouping(kTestType1, 0, kTestValue1);
// Leave |grp2| as inactive.
grp1->set_active(true);
grp3->set_active(true);
grp4->set_active(true);
auto iter = db->GetIterator(kTestRangeStart, kTestRangeEnd, nullptr,
true /* groups_only */);
EXPECT_FALSE(iter.AtEnd());
// |grp2| should be omitted.
auto handles = IterHandles(&iter);
ASSERT_EQ(3u, handles.size());
EXPECT_EQ(grp1->start_handle(), handles[0]);
EXPECT_EQ(grp3->start_handle(), handles[1]);
EXPECT_EQ(grp4->start_handle(), handles[2]);
grp2->set_active(true);
// Pick a narrow range that excludes |grp1| and |grp4|.
iter = db->GetIterator(grp2->start_handle(), grp3->end_handle(), nullptr,
true /* groups_only */);
handles = IterHandles(&iter);
ASSERT_EQ(2u, handles.size());
EXPECT_EQ(grp2->start_handle(), handles[0]);
EXPECT_EQ(grp3->start_handle(), handles[1]);
}
TEST(ATT_DatabaseTest, IteratorGroupOnlySingleWithFilter) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto grp = db->NewGrouping(kTestType1, 0, kTestValue1);
grp->set_active(true);
// No match.
auto iter = db->GetIterator(kTestRangeStart, kTestRangeEnd, &kTestType2,
true /* groups_only */);
EXPECT_TRUE(iter.AtEnd());
iter = db->GetIterator(kTestRangeStart, kTestRangeEnd, &kTestType1,
true /* groups_only */);
EXPECT_FALSE(iter.AtEnd());
auto handles = IterHandles(&iter);
ASSERT_EQ(1u, handles.size());
EXPECT_EQ(grp->start_handle(), handles[0]);
}
TEST(ATT_DatabaseTest, IteratorGroupOnlyManyWithFilter) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto grp1 = db->NewGrouping(kTestType1, 1, kTestValue1); // match
grp1->AddAttribute(kTestType1); // match but skipped - not group decl.
grp1->set_active(true);
// Matching but inactive.
db->NewGrouping(kTestType1, 0, kTestValue1);
auto grp2 = db->NewGrouping(kTestType2, 0, kTestValue1);
grp2->set_active(true);
auto grp3 = db->NewGrouping(kTestType1, 0, kTestValue1);
grp3->set_active(true);
auto grp4 = db->NewGrouping(kTestType2, 0, kTestValue1);
grp4->set_active(true);
auto grp5 = db->NewGrouping(kTestType2, 0, kTestValue1);
grp5->set_active(true);
auto grp6 = db->NewGrouping(kTestType1, 0, kTestValue1);
grp6->set_active(true);
// Filter by |kTestType1|
auto iter = db->GetIterator(kTestRangeStart, kTestRangeEnd, &kTestType1,
true /* groups_only */);
EXPECT_FALSE(iter.AtEnd());
auto handles = IterHandles(&iter);
ASSERT_EQ(3u, handles.size());
EXPECT_EQ(grp1->start_handle(), handles[0]);
EXPECT_EQ(grp3->start_handle(), handles[1]);
EXPECT_EQ(grp6->start_handle(), handles[2]);
// Filter by |kTestType2|
iter = db->GetIterator(kTestRangeStart, kTestRangeEnd, &kTestType2,
true /* groups_only */);
EXPECT_FALSE(iter.AtEnd());
handles = IterHandles(&iter);
ASSERT_EQ(3u, handles.size());
EXPECT_EQ(grp2->start_handle(), handles[0]);
EXPECT_EQ(grp4->start_handle(), handles[1]);
EXPECT_EQ(grp5->start_handle(), handles[2]);
// Search narrower range.
iter = db->GetIterator(grp1->end_handle(), grp5->end_handle(), &kTestType1,
true /* groups_only */);
EXPECT_FALSE(iter.AtEnd());
handles = IterHandles(&iter);
ASSERT_EQ(1u, handles.size());
EXPECT_EQ(grp3->start_handle(), handles[0]);
}
TEST(ATT_DatabaseTest, IteratorSingleInactive) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto grp = db->NewGrouping(kTestType1, 1, kTestValue1);
auto iter = db->GetIterator(kTestRangeStart, kTestRangeEnd);
EXPECT_TRUE(iter.AtEnd());
EXPECT_FALSE(iter.get());
// Complete but still inactive.
grp->AddAttribute(kTestType1);
iter = db->GetIterator(kTestRangeStart, kTestRangeEnd);
EXPECT_TRUE(iter.AtEnd());
EXPECT_FALSE(iter.get());
}
TEST(ATT_DatabaseTest, IteratorSingle) {
auto db = Database::Create(kTestRangeStart, kTestRangeEnd);
auto grp = db->NewGrouping(kTestType1, 0, kTestValue1);
grp->set_active(true);
// Not within range.
auto iter = db->GetIterator(grp->start_handle() + 1, kTestRangeEnd);
EXPECT_TRUE(iter.AtEnd());
EXPECT_FALSE(iter.get());
iter = db->GetIterator(kTestRangeStart, kTestRangeEnd);
EXPECT_FALSE(iter.AtEnd());
auto handles = IterHandles(&iter);
ASSERT_EQ(1u, handles.size());
EXPECT_EQ(grp->start_handle(), handles[0]);
}
class ATT_DatabaseIteratorManyTest : public ::testing::Test {
public:
ATT_DatabaseIteratorManyTest() = default;
~ATT_DatabaseIteratorManyTest() override = default;
protected:
static constexpr size_t kActiveAttrCount = 8;
void SetUp() override {
db_ = Database::Create(kTestRangeStart, kTestRangeEnd);
auto grp1 = db()->NewGrouping(kTestType1, 3, kTestValue1); // 1
grp1->AddAttribute(kTestType2); // 2
grp1->AddAttribute(kTestType2); // 3
grp1->AddAttribute(kTestType1); // 4
grp1->set_active(true);
auto grp2 = db()->NewGrouping(kTestType2, 2, kTestValue1); // 5
grp2->AddAttribute(kTestType1); // 6
grp2->AddAttribute(kTestType2); // 7
grp2->set_active(true);
auto grp3 = db()->NewGrouping(kTestType1, 1, kTestValue1); // 8 (inactive)
grp3->AddAttribute(kTestType2); // 9 (inactive)
auto grp4 = db()->NewGrouping(kTestType1, 0, kTestValue1); // 10
grp4->set_active(true);
}
Database* db() const { return db_.get(); }
private:
fxl::RefPtr<Database> db_;
DISALLOW_COPY_ASSIGN_AND_MOVE(ATT_DatabaseIteratorManyTest);
};
// static
const size_t ATT_DatabaseIteratorManyTest::kActiveAttrCount;
TEST_F(ATT_DatabaseIteratorManyTest, NoFilter) {
auto iter = db()->GetIterator(kTestRangeStart, kTestRangeEnd);
EXPECT_FALSE(iter.AtEnd());
// Should cover all but the inactive attribute.
auto handles = IterHandles(&iter);
// All active attribute handles.
const std::array<Handle, kActiveAttrCount> kExpected = {1, 2, 3, 4,
5, 6, 7, 10};
ASSERT_EQ(kExpected.size(), handles.size());
for (size_t i = 0; i < handles.size(); i++) {
EXPECT_EQ(kExpected[i], handles[i]);
}
}
TEST_F(ATT_DatabaseIteratorManyTest, FilterTestType1) {
// Filter by |kTestType1|.
auto iter = db()->GetIterator(kTestRangeStart, kTestRangeEnd, &kTestType1);
EXPECT_FALSE(iter.AtEnd());
auto handles = IterHandles(&iter);
// Handles of attributes with type |kTestType1|.
const std::array<Handle, 4u> kExpected = {1, 4, 6, 10};
ASSERT_EQ(kExpected.size(), handles.size());
for (size_t i = 0; i < handles.size(); i++) {
EXPECT_EQ(kExpected[i], handles[i]);
}
}
TEST_F(ATT_DatabaseIteratorManyTest, FilterTestType2) {
// Filter by |kTestType2|.
auto iter = db()->GetIterator(kTestRangeStart, kTestRangeEnd, &kTestType2);
EXPECT_FALSE(iter.AtEnd());
auto handles = IterHandles(&iter);
// Handles of attributes with type |kTestType2|.
const std::array<Handle, 4u> kExpected = {2, 3, 5, 7};
ASSERT_EQ(kExpected.size(), handles.size());
for (size_t i = 0; i < handles.size(); i++) {
EXPECT_EQ(kExpected[i], handles[i]);
}
}
TEST_F(ATT_DatabaseIteratorManyTest, FilterTestType3) {
// Filter by |kTestType3|.
auto iter = db()->GetIterator(kTestRangeStart, kTestRangeEnd, &kTestType3);
EXPECT_TRUE(iter.AtEnd());
}
TEST_F(ATT_DatabaseIteratorManyTest, UnaryRange) {
// Test ranges with a single attribute. Test group begin, middle, and end
// cases.
constexpr Handle kBegin = 5;
constexpr Handle kMiddle = 6;
constexpr Handle kEnd = 7;
auto iter = db()->GetIterator(kBegin, kBegin);
EXPECT_FALSE(iter.AtEnd());
auto handles = IterHandles(&iter);
ASSERT_EQ(1u, handles.size());
EXPECT_EQ(kBegin, handles[0]);
iter = db()->GetIterator(kMiddle, kMiddle);
EXPECT_FALSE(iter.AtEnd());
handles = IterHandles(&iter);
ASSERT_EQ(1u, handles.size());
EXPECT_EQ(kMiddle, handles[0]);
iter = db()->GetIterator(kEnd, kEnd);
EXPECT_FALSE(iter.AtEnd());
handles = IterHandles(&iter);
ASSERT_EQ(1u, handles.size());
EXPECT_EQ(kEnd, handles[0]);
}
TEST_F(ATT_DatabaseIteratorManyTest, Range) {
auto iter = db()->GetIterator(4, 6);
EXPECT_FALSE(iter.AtEnd());
auto handles = IterHandles(&iter);
// All active attribute handles.
const std::array<Handle, 3> kExpected = {4, 5, 6};
ASSERT_EQ(kExpected.size(), handles.size());
for (size_t i = 0; i < handles.size(); i++) {
EXPECT_EQ(kExpected[i], handles[i]);
}
}
class ATT_DatabaseExecuteWriteQueueTest : public ::testing::Test {
public:
ATT_DatabaseExecuteWriteQueueTest() = default;
~ATT_DatabaseExecuteWriteQueueTest() = default;
protected:
struct PendingWrite {
PeerId peer_id;
Handle handle;
uint16_t offset;
DynamicByteBuffer value;
Attribute::WriteResultCallback result_callback;
};
void SetUp() override {
db_ = Database::Create(kTestRangeStart, kTestRangeEnd);
}
void ExecuteWriteQueue(PeerId peer_id, PrepareWriteQueue wq,
const sm::SecurityProperties& security = kNoSecurity) {
db_->ExecuteWriteQueue(peer_id, std::move(wq), security,
[this](Handle h, ErrorCode e) {
callback_count_++;
ecode_ = e;
handle_in_error_ = h;
});
}
// Sets up an attribute grouping with 4 attributes.
void SetUpAttributes() {
auto* grp = db()->NewGrouping(kTestType1, 3, kTestValue1); // handle: 1
group_decl_handle_ = grp->start_handle();
auto* attr =
grp->AddAttribute(kTestType2, kAllowed, kAllowed); // handle: 2
attr->set_write_handler(fit::bind_member(
this, &ATT_DatabaseExecuteWriteQueueTest::WriteHandler));
test_handle1_ = attr->handle();
attr = grp->AddAttribute(kTestType2, kAllowed, kAllowed); // handle: 3
attr->set_write_handler(fit::bind_member(
this, &ATT_DatabaseExecuteWriteQueueTest::WriteHandler));
test_handle2_ = attr->handle();
attr = grp->AddAttribute(kTestType2, kAllowed, kAllowed); // handle: 4
attr->set_write_handler(fit::bind_member(
this, &ATT_DatabaseExecuteWriteQueueTest::WriteHandler));
test_handle3_ = attr->handle();
grp->set_active(true);
}
void ResolveNextPendingWrite(ErrorCode ecode) {
ASSERT_FALSE(pending_writes_.empty());
auto pw = std::move(pending_writes_.front());
pending_writes_.pop();
pw.result_callback(ecode);
}
ErrorCode ecode() const { return ecode_; }
Handle handle_in_error() const { return handle_in_error_; }
int callback_count() const { return callback_count_; }
Handle group_decl_handle() const { return group_decl_handle_; }
Handle test_handle1() const { return test_handle1_; }
Handle test_handle2() const { return test_handle2_; }
Handle test_handle3() const { return test_handle3_; }
const std::queue<PendingWrite>& pending_writes() const {
return pending_writes_;
}
Database* db() { return db_.get(); }
private:
void WriteHandler(PeerId peer_id, Handle handle, uint16_t offset,
const ByteBuffer& value,
Attribute::WriteResultCallback result_callback) {
PendingWrite pw;
pw.peer_id = peer_id;
pw.handle = handle;
pw.offset = offset;
pw.value = DynamicByteBuffer(value);
pw.result_callback = std::move(result_callback);
pending_writes_.push(std::move(pw));
}
ErrorCode ecode_ = ErrorCode::kNoError;
Handle handle_in_error_ = kInvalidHandle;
int callback_count_ = 0;
fxl::RefPtr<Database> db_;
std::queue<PendingWrite> pending_writes_;
// Handles of the test attributes.
Handle group_decl_handle_ = kInvalidHandle;
Handle test_handle1_ = kInvalidHandle;
Handle test_handle2_ = kInvalidHandle;
Handle test_handle3_ = kInvalidHandle;
DISALLOW_COPY_ASSIGN_AND_MOVE(ATT_DatabaseExecuteWriteQueueTest);
};
constexpr PeerId kPeerId(1);
TEST_F(ATT_DatabaseExecuteWriteQueueTest, EmptyQueueSucceedsImmediately) {
ExecuteWriteQueue(kPeerId, {});
EXPECT_EQ(1, callback_count());
EXPECT_EQ(ErrorCode::kNoError, ecode());
EXPECT_EQ(kInvalidHandle, handle_in_error());
}
TEST_F(ATT_DatabaseExecuteWriteQueueTest, InvalidHandle) {
constexpr Handle kHandle = 1;
PrepareWriteQueue wq;
wq.push(QueuedWrite(kHandle, 0, BufferView()));
ExecuteWriteQueue(kPeerId, std::move(wq));
EXPECT_EQ(1, callback_count());
EXPECT_EQ(ErrorCode::kInvalidHandle, ecode());
EXPECT_EQ(kHandle, handle_in_error());
}
TEST_F(ATT_DatabaseExecuteWriteQueueTest, ValueLength) {
auto* grp = db()->NewGrouping(kTestType1, 1, kTestValue1);
auto* attr = grp->AddAttribute(kTestType2);
grp->set_active(true);
PrepareWriteQueue wq;
wq.push(QueuedWrite(attr->handle(), 0,
DynamicByteBuffer(kMaxAttributeValueLength + 1)));
ExecuteWriteQueue(kPeerId, std::move(wq));
EXPECT_EQ(1, callback_count());
EXPECT_EQ(ErrorCode::kInvalidAttributeValueLength, ecode());
EXPECT_EQ(attr->handle(), handle_in_error());
}
TEST_F(ATT_DatabaseExecuteWriteQueueTest, WritingStaticValueNotPermitted) {
auto* grp = db()->NewGrouping(kTestType1, 1, kTestValue1);
auto* attr = grp->AddAttribute(kTestType2); // read/write not permitted
grp->set_active(true);
PrepareWriteQueue wq;
wq.push(QueuedWrite(attr->handle(), 0, kTestValue1));
ExecuteWriteQueue(kPeerId, std::move(wq));
EXPECT_EQ(1, callback_count());
EXPECT_EQ(ErrorCode::kWriteNotPermitted, ecode());
EXPECT_EQ(attr->handle(), handle_in_error());
}
TEST_F(ATT_DatabaseExecuteWriteQueueTest, SecurityChecks) {
auto* grp = db()->NewGrouping(kTestType1, 1, kTestValue1);
auto* attr =
grp->AddAttribute(kTestType2, att::AccessRequirements(),
att::AccessRequirements(
true, false, false)); // write requires encryption
grp->set_active(true);
PrepareWriteQueue wq;
wq.push(QueuedWrite(attr->handle(), 0, kTestValue1));
ExecuteWriteQueue(kPeerId, std::move(wq), kNoSecurity);
EXPECT_EQ(1, callback_count());
EXPECT_EQ(ErrorCode::kInsufficientAuthentication, ecode());
EXPECT_EQ(attr->handle(), handle_in_error());
// Request should succeed with an encrypted link.
ExecuteWriteQueue(
kPeerId, std::move(wq),
sm::SecurityProperties(sm::SecurityLevel::kEncrypted, 16, false));
EXPECT_EQ(2, callback_count());
EXPECT_EQ(ErrorCode::kNoError, ecode());
}
// If an error is caught before delivering the request to the delegate then we
// expect subsequent entries to not be delivered.
TEST_F(ATT_DatabaseExecuteWriteQueueTest, UndelegatedWriteErrorAborts) {
SetUpAttributes();
PrepareWriteQueue wq;
// Queue a request to one of the delegated handles. This won't generate a
// result until we explicitly send a reply via ResolveNextPendingWrite().
wq.push(QueuedWrite(test_handle1(), 0, kTestValue1));
// Queue a write to the group declaration handle. This should get rejected
// right away since it's not writable. Since the database catches this error
// internally, we expect the following queued writes to get aborted.
wq.push(QueuedWrite(group_decl_handle(), 0, kTestValue1));
// Queue more writes.
wq.push(QueuedWrite(test_handle2(), 1, kTestValue2));
wq.push(QueuedWrite(test_handle3(), 2, kTestValue1));
wq.push(QueuedWrite(test_handle1(), 3, kTestValue2));
ExecuteWriteQueue(kPeerId, std::move(wq));
// The database should have generated an error response.
EXPECT_EQ(1, callback_count());
EXPECT_EQ(ErrorCode::kWriteNotPermitted, ecode());
EXPECT_EQ(group_decl_handle(), handle_in_error());
// Only the first write should have been delivered.
ASSERT_EQ(1u, pending_writes().size());
EXPECT_EQ(test_handle1(), pending_writes().front().handle);
EXPECT_EQ(0, pending_writes().front().offset);
EXPECT_TRUE(ContainersEqual(kTestValue1, pending_writes().front().value));
// Resolving the request should have no effect.
ResolveNextPendingWrite(ErrorCode::kUnlikelyError);
EXPECT_EQ(1, callback_count()); // still 1
}
TEST_F(ATT_DatabaseExecuteWriteQueueTest, ErrorInMultipleQueuedWrites) {
SetUpAttributes();
PrepareWriteQueue wq;
// Queue writes to the writable handles in arbitrary order. We expect all of
// them to be delivered to the delegate.
wq.push(QueuedWrite(test_handle1(), 0, kTestValue1));
wq.push(QueuedWrite(test_handle2(), 1, kTestValue2));
wq.push(QueuedWrite(test_handle3(), 2, kTestValue1));
wq.push(QueuedWrite(test_handle1(), 3, kTestValue2));
ExecuteWriteQueue(kPeerId, std::move(wq));
// The execute write request should be pending.
EXPECT_EQ(0, callback_count());
// All 4 requests should have been delivered.
ASSERT_EQ(4u, pending_writes().size());
// Resolve the first request with success. The execute write request should
// remain pending.
{
const auto& next = pending_writes().front();
EXPECT_EQ(kPeerId, next.peer_id);
EXPECT_EQ(test_handle1(), next.handle);
EXPECT_EQ(0, next.offset);
EXPECT_TRUE(ContainersEqual(kTestValue1, next.value));
ResolveNextPendingWrite(ErrorCode::kNoError);
EXPECT_EQ(0, callback_count());
}
// Resolve the second request with an error.
{
const auto& next = pending_writes().front();
EXPECT_EQ(kPeerId, next.peer_id);
EXPECT_EQ(test_handle2(), next.handle);
EXPECT_EQ(1, next.offset);
EXPECT_TRUE(ContainersEqual(kTestValue2, next.value));
ResolveNextPendingWrite(ErrorCode::kUnlikelyError);
EXPECT_EQ(1, callback_count());
EXPECT_EQ(ErrorCode::kUnlikelyError, ecode());
EXPECT_EQ(test_handle2(), handle_in_error());
}
// Resolving the remaining writes should have no effect.
ResolveNextPendingWrite(ErrorCode::kNoError);
ResolveNextPendingWrite(ErrorCode::kUnlikelyError);
EXPECT_EQ(1, callback_count());
}
TEST_F(ATT_DatabaseExecuteWriteQueueTest, MultipleQueuedWritesSucceed) {
SetUpAttributes();
PrepareWriteQueue wq;
// Queue writes to the writable handles in arbitrary order. We expect all of
// them to be delivered to the delegate.
wq.push(QueuedWrite(test_handle1(), 0, kTestValue1));
wq.push(QueuedWrite(test_handle2(), 1, kTestValue2));
wq.push(QueuedWrite(test_handle3(), 2, kTestValue1));
wq.push(QueuedWrite(test_handle1(), 3, kTestValue2));
ExecuteWriteQueue(kPeerId, std::move(wq));
// The execute write request should be pending.
EXPECT_EQ(0, callback_count());
// All 4 requests should have been delivered.
ASSERT_EQ(4u, pending_writes().size());
// Resolve all requests with success.
{
const auto& next = pending_writes().front();
EXPECT_EQ(kPeerId, next.peer_id);
EXPECT_EQ(test_handle1(), next.handle);
EXPECT_EQ(0, next.offset);
EXPECT_TRUE(ContainersEqual(kTestValue1, next.value));
ResolveNextPendingWrite(ErrorCode::kNoError);
EXPECT_EQ(0, callback_count());
}
{
const auto& next = pending_writes().front();
EXPECT_EQ(kPeerId, next.peer_id);
EXPECT_EQ(test_handle2(), next.handle);
EXPECT_EQ(1, next.offset);
EXPECT_TRUE(ContainersEqual(kTestValue2, next.value));
ResolveNextPendingWrite(ErrorCode::kNoError);
EXPECT_EQ(0, callback_count());
}
{
const auto& next = pending_writes().front();
EXPECT_EQ(kPeerId, next.peer_id);
EXPECT_EQ(test_handle3(), next.handle);
EXPECT_EQ(2, next.offset);
EXPECT_TRUE(ContainersEqual(kTestValue1, next.value));
ResolveNextPendingWrite(ErrorCode::kNoError);
EXPECT_EQ(0, callback_count());
}
// Resolving the last request should complete the execute write request.
{
const auto& next = pending_writes().front();
EXPECT_EQ(kPeerId, next.peer_id);
EXPECT_EQ(test_handle1(), next.handle);
EXPECT_EQ(3, next.offset);
EXPECT_TRUE(ContainersEqual(kTestValue2, next.value));
ResolveNextPendingWrite(ErrorCode::kNoError);
EXPECT_EQ(1, callback_count());
EXPECT_EQ(ErrorCode::kNoError, ecode());
}
}
} // namespace
} // namespace att
} // namespace bt