// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_sapphire/internal/host/att/attribute.h" #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" #include "pw_unit_test/framework.h" namespace bt::att { namespace { constexpr PeerId kTestPeerId(1); constexpr Handle kTestHandle = 0x0001; constexpr UUID kTestType1(uint16_t{0x0001}); constexpr UUID kTestType2(uint16_t{0x0002}); constexpr UUID kTestType3(uint16_t{0x0003}); constexpr UUID kTestType4(uint16_t{0x0004}); const StaticByteBuffer kTestValue('t', 'e', 's', 't'); TEST(AttributeDeathTest, OverflowingMaxHandleOnAttributeGroupDies) { ASSERT_DEATH_IF_SUPPORTED( { AttributeGrouping A(kTestType1, kHandleMax - 1, size_t{2}, kTestValue); }, ".*attr_count.*"); } TEST(AttributeTest, AccessRequirementsDefault) { AccessRequirements reqs; EXPECT_FALSE(reqs.allowed()); EXPECT_FALSE(reqs.encryption_required()); EXPECT_FALSE(reqs.authentication_required()); EXPECT_FALSE(reqs.authorization_required()); } TEST(AttributeTest, AccessRequirements) { AccessRequirements reqs0( /*encryption=*/true, /*authentication=*/false, /*authorization=*/false); EXPECT_TRUE(reqs0.allowed()); EXPECT_TRUE(reqs0.encryption_required()); EXPECT_FALSE(reqs0.authentication_required()); EXPECT_FALSE(reqs0.authorization_required()); EXPECT_EQ(reqs0.min_enc_key_size(), sm::kMaxEncryptionKeySize); AccessRequirements reqs1(/*encryption=*/true, /*authentication=*/false, /*authorization=*/false, 8); EXPECT_TRUE(reqs1.allowed()); EXPECT_TRUE(reqs1.encryption_required()); EXPECT_FALSE(reqs1.authentication_required()); EXPECT_FALSE(reqs1.authorization_required()); EXPECT_EQ(reqs1.min_enc_key_size(), 8u); AccessRequirements reqs2( /*encryption=*/false, /*authentication=*/true, /*authorization=*/false); EXPECT_TRUE(reqs2.allowed()); EXPECT_FALSE(reqs2.encryption_required()); EXPECT_TRUE(reqs2.authentication_required()); EXPECT_FALSE(reqs2.authorization_required()); AccessRequirements reqs3( /*encryption=*/false, /*authentication=*/false, /*authorization=*/true); EXPECT_TRUE(reqs3.allowed()); EXPECT_FALSE(reqs3.encryption_required()); EXPECT_FALSE(reqs3.authentication_required()); EXPECT_TRUE(reqs3.authorization_required()); AccessRequirements reqs4( /*encryption=*/false, /*authentication=*/false, /*authorization=*/false); EXPECT_TRUE(reqs4.allowed()); EXPECT_FALSE(reqs4.encryption_required()); EXPECT_FALSE(reqs4.authentication_required()); EXPECT_FALSE(reqs4.authorization_required()); } TEST(AttributeTest, GroupingDeclAttr) { constexpr size_t kAttrCount = 0u; AttributeGrouping group(kTestType1, kTestHandle, kAttrCount, kTestValue); // The grouping consists of just the group declaration and is thus complete. EXPECT_TRUE(group.complete()); EXPECT_EQ(kTestType1, group.group_type()); EXPECT_EQ(kTestHandle, group.start_handle()); EXPECT_EQ(kTestHandle, group.end_handle()); EXPECT_EQ(1u, group.attributes().size()); // The grouping is already complete. EXPECT_FALSE(group.AddAttribute( kTestType2, AccessRequirements(), AccessRequirements())); const Attribute& decl_attr = group.attributes()[0]; EXPECT_EQ(kTestHandle, decl_attr.handle()); EXPECT_EQ(kTestType1, decl_attr.type()); ASSERT_TRUE(decl_attr.value()); EXPECT_TRUE(ContainersEqual(kTestValue, *decl_attr.value())); EXPECT_TRUE(decl_attr.read_reqs().allowed()); EXPECT_FALSE(decl_attr.read_reqs().encryption_required()); EXPECT_FALSE(decl_attr.read_reqs().authentication_required()); EXPECT_FALSE(decl_attr.read_reqs().authorization_required()); EXPECT_FALSE(decl_attr.write_reqs().allowed()); EXPECT_EQ(&group, &decl_attr.group()); } TEST(AttributeTest, GroupingAddAttribute) { constexpr size_t kAttrCount = 2; AttributeGrouping group(kTestType1, kTestHandle, kAttrCount, kTestValue); EXPECT_FALSE(group.complete()); EXPECT_EQ(kTestHandle, group.start_handle()); EXPECT_EQ(kTestHandle + kAttrCount, group.end_handle()); Attribute* attr = group.AddAttribute( kTestType2, AccessRequirements(), AccessRequirements()); ASSERT_TRUE(attr); EXPECT_EQ(kTestType2, attr->type()); EXPECT_EQ(kTestHandle + 1, attr->handle()); EXPECT_EQ(&group, &attr->group()); // The attribute should have no value until assigned. EXPECT_FALSE(attr->value()); attr->SetValue(kTestValue); ASSERT_TRUE(attr->value()); EXPECT_TRUE(ContainersEqual(kTestValue, *attr->value())); // The group is not complete until |kAttrCount| attributes have been added. EXPECT_FALSE(group.complete()); EXPECT_EQ(2u, group.attributes().size()); attr = group.AddAttribute( kTestType3, AccessRequirements(), AccessRequirements()); ASSERT_TRUE(attr); EXPECT_EQ(kTestType3, attr->type()); EXPECT_TRUE(group.complete()); EXPECT_EQ(group.end_handle(), attr->handle()); EXPECT_EQ(3u, group.attributes().size()); EXPECT_FALSE(group.AddAttribute( kTestType4, AccessRequirements(), AccessRequirements())); } TEST(AttributeTest, ReadAsyncReadNotAllowed) { AttributeGrouping group(kTestType1, kTestHandle, 1, kTestValue); Attribute* attr = group.AddAttribute( kTestType2, AccessRequirements(), AccessRequirements()); EXPECT_FALSE(attr->ReadAsync(kTestPeerId, 0, [](auto, const auto&) {})); } TEST(AttributeTest, ReadAsyncReadNoHandler) { AttributeGrouping group(kTestType1, kTestHandle, 1, kTestValue); Attribute* attr = group.AddAttribute( kTestType2, AccessRequirements(/*encryption=*/false, /*authentication=*/false, /*authorization=*/false), // read (no security) AccessRequirements()); // write not allowed EXPECT_FALSE(attr->ReadAsync(kTestPeerId, 0, [](auto, const auto&) {})); } TEST(AttributeTest, ReadAsync) { constexpr uint16_t kTestOffset = 5; AttributeGrouping group(kTestType1, kTestHandle, 1, kTestValue); Attribute* attr = group.AddAttribute( kTestType2, AccessRequirements(/*encryption=*/false, /*authentication=*/false, /*authorization=*/false), // read (no security) AccessRequirements()); // write not allowed bool callback_called = false; auto callback = [&](fit::result status, const auto& value) { EXPECT_EQ(fit::ok(), status); EXPECT_TRUE(ContainersEqual(StaticByteBuffer('h', 'i'), value)); callback_called = true; }; auto handler = [&](PeerId peer_id, Handle handle, uint16_t offset, auto result_cb) { EXPECT_EQ(kTestPeerId, peer_id); EXPECT_EQ(attr->handle(), handle); EXPECT_EQ(kTestOffset, offset); EXPECT_TRUE(result_cb); result_cb(fit::ok(), StaticByteBuffer('h', 'i')); }; attr->set_read_handler(handler); EXPECT_TRUE(attr->ReadAsync(kTestPeerId, kTestOffset, callback)); EXPECT_TRUE(callback_called); } TEST(AttributeTest, WriteAsyncWriteNotAllowed) { AttributeGrouping group(kTestType1, kTestHandle, 1, kTestValue); Attribute* attr = group.AddAttribute( kTestType2, AccessRequirements(), AccessRequirements()); EXPECT_FALSE(attr->WriteAsync(kTestPeerId, 0, BufferView(), [](auto) {})); } TEST(AttributeTest, WriteAsyncWriteNoHandler) { AttributeGrouping group(kTestType1, kTestHandle, 1, kTestValue); Attribute* attr = group.AddAttribute( kTestType2, AccessRequirements(), // read not allowed AccessRequirements(/*encryption=*/false, /*authentication=*/false, /*authorization=*/false)); // write no security EXPECT_FALSE(attr->WriteAsync(kTestPeerId, 0, BufferView(), [](auto) {})); } TEST(AttributeTest, WriteAsync) { constexpr uint16_t kTestOffset = 5; AttributeGrouping group(kTestType1, kTestHandle, 1, kTestValue); Attribute* attr = group.AddAttribute( kTestType2, AccessRequirements(), // read not allowed AccessRequirements(/*encryption=*/false, /*authentication=*/false, /*authorization=*/false)); // write no security bool callback_called = false; auto callback = [&](fit::result status) { EXPECT_EQ(fit::ok(), status); callback_called = true; }; auto handler = [&](PeerId peer_id, Handle handle, uint16_t offset, const auto& value, auto result_cb) { EXPECT_EQ(kTestPeerId, peer_id); EXPECT_EQ(attr->handle(), handle); EXPECT_EQ(kTestOffset, offset); EXPECT_TRUE(ContainersEqual(StaticByteBuffer('h', 'i'), value)); EXPECT_TRUE(result_cb); result_cb(fit::ok()); }; attr->set_write_handler(handler); EXPECT_TRUE(attr->WriteAsync( kTestPeerId, kTestOffset, StaticByteBuffer('h', 'i'), callback)); EXPECT_TRUE(callback_called); } } // namespace } // namespace bt::att