/* * * Copyright (c) 2021 Project CHIP Authors * All rights reserved. * * 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 * * http://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 #include #include "DataModelFixtures.h" #include "app-common/zap-generated/ids/Clusters.h" #include #include #include #include #include #include #include #include #include #include using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::UnitTesting; using namespace chip::app::DataModelTests; using namespace chip::Protocols; namespace { class SingleWriteCallback : public WriteClient::Callback { public: explicit SingleWriteCallback(ConcreteAttributePath path) : mPath(path) {} void OnResponse(const WriteClient * apWriteClient, const ConcreteDataAttributePath & aPath, StatusIB attributeStatus) override { if (aPath.MatchesConcreteAttributePath(mPath)) { mPathWasReponded = true; mPathStatus = attributeStatus; } } void OnError(const WriteClient * apWriteClient, CHIP_ERROR aError) override { (void) apWriteClient; mLastChipError = aError; } void OnDone(WriteClient * apWriteClient) override { (void) apWriteClient; mOnDoneCalled = true; } bool WasDone() const { return mOnDoneCalled; } bool PathWasResponded() const { return mOnDoneCalled; } CHIP_ERROR GetLastChipError() const { return mLastChipError; } StatusIB GetPathStatus() const { return mPathStatus; } private: ConcreteAttributePath mPath; bool mOnDoneCalled = false; CHIP_ERROR mLastChipError = CHIP_NO_ERROR; bool mPathWasReponded = false; StatusIB mPathStatus; }; class TestWrite : public chip::Test::AppContext { public: void SetUp() override { chip::Test::AppContext::SetUp(); mOldProvider = InteractionModelEngine::GetInstance()->SetDataModelProvider(&CustomDataModel::Instance()); } // Performs teardown for each individual test in the test suite void TearDown() override { InteractionModelEngine::GetInstance()->SetDataModelProvider(mOldProvider); chip::Test::AppContext::TearDown(); } void ResetCallback() { mSingleWriteCallback.reset(); } void PrepareWriteCallback(ConcreteAttributePath path) { mSingleWriteCallback = std::make_unique(path); } SingleWriteCallback * GetWriteCallback() { return mSingleWriteCallback.get(); } protected: std::unique_ptr mSingleWriteCallback; chip::app::DataModel::Provider * mOldProvider = nullptr; }; TEST_F(TestWrite, TestDataResponse) { auto sessionHandle = GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4]; Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value; value = valueBuf; uint8_t i = 0; for (auto & item : valueBuf) { item.member1 = i; i++; } ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendAttributeSuccess); // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onSuccessCb = [&onSuccessCbInvoked](const ConcreteAttributePath & attributePath) { onSuccessCbInvoked = true; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onFailureCb = [&onFailureCbInvoked](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { onFailureCbInvoked = true; }; chip::Controller::WriteAttribute( sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb); DrainAndServiceIO(); EXPECT_TRUE(onSuccessCbInvoked); EXPECT_FALSE(onFailureCbInvoked); EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestWrite, TestDataResponseWithAcceptedDataVersion) { auto sessionHandle = GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4]; Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value; value = valueBuf; uint8_t i = 0; for (auto & item : valueBuf) { item.member1 = i; i++; } ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendAttributeSuccess); // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath) { onSuccessCbInvoked = true; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { onFailureCbInvoked = true; }; chip::Optional dataVersion; dataVersion.SetValue(kAcceptedDataVersion); chip::Controller::WriteAttribute( sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb, nullptr, dataVersion); DrainAndServiceIO(); EXPECT_TRUE(onSuccessCbInvoked && !onFailureCbInvoked); EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestWrite, TestDataResponseWithRejectedDataVersion) { auto sessionHandle = GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4]; Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value; value = valueBuf; uint8_t i = 0; for (auto & item : valueBuf) { item.member1 = i; i++; } ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendAttributeSuccess); // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath) { onSuccessCbInvoked = true; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) { onFailureCbInvoked = true; }; chip::Optional dataVersion(kRejectedDataVersion); chip::Controller::WriteAttribute( sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb, nullptr, dataVersion); DrainAndServiceIO(); EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked); EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestWrite, TestAttributeError) { auto sessionHandle = GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Attributes::ListStructOctetString::TypeInfo::Type value; Structs::TestListStructOctet::Type valueBuf[4]; value = valueBuf; uint8_t i = 0; for (auto & item : valueBuf) { item.member1 = i; i++; } ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendAttributeError); // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onSuccessCb = [&onSuccessCbInvoked](const ConcreteAttributePath & attributePath) { onSuccessCbInvoked = true; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onFailureCb = [&onFailureCbInvoked](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { EXPECT_TRUE(attributePath != nullptr); onFailureCbInvoked = true; }; Controller::WriteAttribute(sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb); DrainAndServiceIO(); EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked); EXPECT_EQ(InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestWrite, TestFabricScopedAttributeWithoutFabricIndex) { auto sessionHandle = GetSessionBobToAlice(); bool onSuccessCbInvoked = false, onFailureCbInvoked = false; Clusters::UnitTesting::Structs::TestFabricScoped::Type valueBuf[4]; Clusters::UnitTesting::Attributes::ListFabricScoped::TypeInfo::Type value; value = valueBuf; uint8_t i = 0; for (auto & item : valueBuf) { item.fabricIndex = i; i++; } // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onSuccessCb = [&onSuccessCbInvoked](const ConcreteAttributePath & attributePath) { onSuccessCbInvoked = true; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onFailureCb = [&onFailureCbInvoked](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { EXPECT_EQ(aError, CHIP_IM_GLOBAL_STATUS(UnsupportedAccess)); onFailureCbInvoked = true; }; chip::Controller::WriteAttribute( sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb); DrainAndServiceIO(); EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked); EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestWrite, TestMultipleSuccessResponses) { auto sessionHandle = GetSessionBobToAlice(); size_t successCalls = 0; size_t failureCalls = 0; ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendMultipleSuccess); // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onSuccessCb = [&successCalls](const ConcreteAttributePath & attributePath) { ++successCalls; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onFailureCb = [&failureCalls](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { ++failureCalls; }; chip::Controller::WriteAttribute(sessionHandle, kTestEndpointId, true, onSuccessCb, onFailureCb); DrainAndServiceIO(); EXPECT_EQ(successCalls, 1u); EXPECT_EQ(failureCalls, 0u); EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestWrite, TestMultipleFailureResponses) { auto sessionHandle = GetSessionBobToAlice(); size_t successCalls = 0; size_t failureCalls = 0; ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendMultipleErrors); // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onSuccessCb = [&successCalls](const ConcreteAttributePath & attributePath) { ++successCalls; }; // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's // not safe to do so. auto onFailureCb = [&failureCalls](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { ++failureCalls; }; chip::Controller::WriteAttribute(sessionHandle, kTestEndpointId, true, onSuccessCb, onFailureCb); DrainAndServiceIO(); EXPECT_EQ(successCalls, 0u); EXPECT_EQ(failureCalls, 1u); EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } TEST_F(TestWrite, TestWriteClusterSpecificStatuses) { auto sessionHandle = GetSessionBobToAlice(); // Cluster-specific success code case { ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendClusterSpecificSuccess); this->ResetCallback(); this->PrepareWriteCallback( ConcreteAttributePath{ kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int8u::Id }); SingleWriteCallback * writeCb = this->GetWriteCallback(); WriteClient writeClient(&GetExchangeManager(), this->GetWriteCallback(), Optional::Missing()); AttributePathParams attributePath{ kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int8u::Id }; constexpr uint8_t attributeValue = 1u; ASSERT_EQ(writeClient.EncodeAttribute(attributePath, attributeValue), CHIP_NO_ERROR); ASSERT_EQ(writeClient.SendWriteRequest(sessionHandle), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(writeCb->WasDone()); EXPECT_TRUE(writeCb->PathWasResponded()); EXPECT_EQ(writeCb->GetLastChipError(), CHIP_NO_ERROR); StatusIB pathStatus = writeCb->GetPathStatus(); EXPECT_EQ(pathStatus.mStatus, Protocols::InteractionModel::Status::Success); ASSERT_TRUE(pathStatus.mClusterStatus.HasValue()); EXPECT_EQ(pathStatus.mClusterStatus.Value(), kExampleClusterSpecificSuccess); EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } // Cluster-specific failure code case { ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendClusterSpecificFailure); this->ResetCallback(); this->PrepareWriteCallback( ConcreteAttributePath{ kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int8u::Id }); SingleWriteCallback * writeCb = this->GetWriteCallback(); WriteClient writeClient(&GetExchangeManager(), this->GetWriteCallback(), Optional::Missing()); AttributePathParams attributePath{ kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int8u::Id }; constexpr uint8_t attributeValue = 2u; ASSERT_EQ(writeClient.EncodeAttribute(attributePath, attributeValue), CHIP_NO_ERROR); ASSERT_EQ(writeClient.SendWriteRequest(sessionHandle), CHIP_NO_ERROR); DrainAndServiceIO(); EXPECT_TRUE(writeCb->WasDone()); EXPECT_TRUE(writeCb->PathWasResponded()); EXPECT_EQ(writeCb->GetLastChipError(), CHIP_NO_ERROR); StatusIB pathStatus = writeCb->GetPathStatus(); EXPECT_EQ(pathStatus.mStatus, Protocols::InteractionModel::Status::Failure); ASSERT_TRUE(pathStatus.mClusterStatus.HasValue()); EXPECT_EQ(pathStatus.mClusterStatus.Value(), kExampleClusterSpecificFailure); EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u); EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); } } } // namespace