From 1087936ed1f4b97cade6a2177ad4fc50fc53d30e Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 13 Jan 2022 08:44:14 -0500 Subject: [PATCH] Add a way to encapsulate a StatusIB in a CHIP_ERROR. (#13475) --- src/app/MessageDef/StatusIB.cpp | 41 ++++++++++ src/app/MessageDef/StatusIB.h | 15 ++++ src/app/tests/BUILD.gn | 1 + src/app/tests/TestStatusIB.cpp | 137 ++++++++++++++++++++++++++++++++ src/lib/core/CHIPError.h | 33 ++++++-- 5 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 src/app/tests/TestStatusIB.cpp diff --git a/src/app/MessageDef/StatusIB.cpp b/src/app/MessageDef/StatusIB.cpp index e767157078a009..cedf3946bf2d53 100644 --- a/src/app/MessageDef/StatusIB.cpp +++ b/src/app/MessageDef/StatusIB.cpp @@ -33,6 +33,7 @@ using namespace chip; using namespace chip::TLV; +using namespace chip::Protocols::InteractionModel; namespace chip { namespace app { @@ -142,5 +143,45 @@ StatusIB::Builder & StatusIB::Builder::EncodeStatusIB(const StatusIB & aStatusIB return *this; } +CHIP_ERROR StatusIB::ToChipError() const +{ + if (mStatus == Status::Success) + { + return CHIP_NO_ERROR; + } + + if (mClusterStatus.HasValue()) + { + return ChipError(ChipError::SdkPart::kIMClusterStatus, mClusterStatus.Value()); + } + + return ChipError(ChipError::SdkPart::kIMGlobalStatus, to_underlying(mStatus)); +} + +void StatusIB::InitFromChipError(CHIP_ERROR aError) +{ + if (aError.IsPart(ChipError::SdkPart::kIMClusterStatus)) + { + mStatus = Status::Failure; + mClusterStatus = MakeOptional(aError.GetSdkCode()); + return; + } + + mClusterStatus = NullOptional; + if (aError == CHIP_NO_ERROR) + { + mStatus = Status::Success; + return; + } + + if (aError.IsPart(ChipError::SdkPart::kIMGlobalStatus)) + { + mStatus = static_cast(aError.GetSdkCode()); + return; + } + + mStatus = Status::Failure; +} + }; // namespace app }; // namespace chip diff --git a/src/app/MessageDef/StatusIB.h b/src/app/MessageDef/StatusIB.h index 3c634a64063fc5..7df3b0ce5a46f8 100644 --- a/src/app/MessageDef/StatusIB.h +++ b/src/app/MessageDef/StatusIB.h @@ -95,6 +95,21 @@ struct StatusIB StatusIB::Builder & EncodeStatusIB(const StatusIB & aStatusIB); }; + /** + * Encapsulate a StatusIB in a CHIP_ERROR. This can be done for any + * StatusIB, but will treat all success codes (including cluster-specific + * ones) as CHIP_NO_ERROR. The resulting CHIP_ERROR will either be + * CHIP_NO_ERROR or test true for IsIMStatus(). + */ + CHIP_ERROR ToChipError() const; + + /** + * Extract a CHIP_ERROR into this StatusIB. If IsIMStatus is false for the + * error, this might do a best-effort attempt to come up with a + * corresponding StatusIB, defaulting to a generic Status::Failure. + */ + void InitFromChipError(CHIP_ERROR aError); + Protocols::InteractionModel::Status mStatus = Protocols::InteractionModel::Status::Success; Optional mClusterStatus = Optional::Missing(); diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 2bfb555d602a69..0fab92b3a179e5 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -56,6 +56,7 @@ chip_test_suite("tests") { "TestNumericAttributeTraits.cpp", "TestReadInteraction.cpp", "TestReportingEngine.cpp", + "TestStatusIB.cpp", "TestStatusResponseMessage.cpp", "TestTimedHandler.cpp", "TestWriteInteraction.cpp", diff --git a/src/app/tests/TestStatusIB.cpp b/src/app/tests/TestStatusIB.cpp new file mode 100644 index 00000000000000..3438a34df5e74e --- /dev/null +++ b/src/app/tests/TestStatusIB.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 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 +#include +#include + +#include + +namespace { + +using namespace chip; +using namespace chip::app; +using namespace chip::Protocols::InteractionModel; + +// Macro so failures will blame the right line. +#define VERIFY_ROUNDTRIP(err, status) \ + do \ + { \ + StatusIB newStatus; \ + newStatus.InitFromChipError(err); \ + NL_TEST_ASSERT(aSuite, newStatus.mStatus == status.mStatus); \ + NL_TEST_ASSERT(aSuite, newStatus.mClusterStatus == status.mClusterStatus); \ + } while (0); + +void TestStatusIBToFromChipError(nlTestSuite * aSuite, void * aContext) +{ + StatusIB status; + + status.mStatus = Status::Success; + CHIP_ERROR err = status.ToChipError(); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); + VERIFY_ROUNDTRIP(err, status); + + status.mStatus = Status::Failure; + err = status.ToChipError(); + NL_TEST_ASSERT(aSuite, err != CHIP_NO_ERROR); + VERIFY_ROUNDTRIP(err, status); + + status.mStatus = Status::InvalidAction; + err = status.ToChipError(); + NL_TEST_ASSERT(aSuite, err != CHIP_NO_ERROR); + VERIFY_ROUNDTRIP(err, status); + + status.mClusterStatus = MakeOptional(static_cast(5)); + + status.mStatus = Status::Success; + err = status.ToChipError(); + NL_TEST_ASSERT(aSuite, err == CHIP_NO_ERROR); + + status.mStatus = Status::Failure; + err = status.ToChipError(); + NL_TEST_ASSERT(aSuite, err != CHIP_NO_ERROR); + VERIFY_ROUNDTRIP(err, status); + + status.mStatus = Status::InvalidAction; + err = status.ToChipError(); + NL_TEST_ASSERT(aSuite, err != CHIP_NO_ERROR); + { + StatusIB newStatus; + newStatus.InitFromChipError(err); + NL_TEST_ASSERT(aSuite, newStatus.mStatus == Status::Failure); + NL_TEST_ASSERT(aSuite, newStatus.mClusterStatus == status.mClusterStatus); + } + + err = CHIP_ERROR_NO_MEMORY; + { + StatusIB newStatus; + newStatus.InitFromChipError(err); + NL_TEST_ASSERT(aSuite, newStatus.mStatus == Status::Failure); + NL_TEST_ASSERT(aSuite, !newStatus.mClusterStatus.HasValue()); + } +} + +// clang-format off +const nlTest sTests[] = +{ + NL_TEST_DEF("StatusIBToFromChipError", TestStatusIBToFromChipError), + NL_TEST_SENTINEL() +}; +// clang-format on +} // namespace + +/** + * Set up the test suite. + */ +static int TestSetup(void * inContext) +{ + CHIP_ERROR error = chip::Platform::MemoryInit(); + if (error != CHIP_NO_ERROR) + return FAILURE; + return SUCCESS; +} + +/** + * Tear down the test suite. + */ +static int TestTeardown(void * inContext) +{ + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +int TestStatusIB() +{ + // clang-format off + nlTestSuite theSuite = + { + "StatusIB", + &sTests[0], + TestSetup, + TestTeardown, + }; + // clang-format on + + nlTestRunner(&theSuite, nullptr); + + return (nlTestRunnerStats(&theSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestStatusIB) diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h index f7961f7c55768d..895af548e0ffe8 100644 --- a/src/lib/core/CHIPError.h +++ b/src/lib/core/CHIPError.h @@ -91,7 +91,7 @@ class ChipError kLwIP = 0x3, ///< Encapsulated LwIP errors. kOpenThread = 0x4, ///< Encapsulated OpenThread errors. kPlatform = 0x5, ///< Platform-defined encapsulation. - kLastRange = kPlatform + kLastRange = kPlatform, }; /** @@ -99,12 +99,14 @@ class ChipError */ enum class SdkPart : uint8_t { - kCore = 0, ///< SDK core errors. - kInet = 1, ///< Inet layer errors; see . - kDevice = 2, ///< Device layer errors; see . - kASN1 = 3, ///< ASN1 errors; see . - kBLE = 4, ///< BLE layer errors; see . - kApplication = 7, ///< Application-defined errors; see CHIP_APPLICATION_ERROR + kCore = 0, ///< SDK core errors. + kInet = 1, ///< Inet layer errors; see . + kDevice = 2, ///< Device layer errors; see . + kASN1 = 3, ///< ASN1 errors; see . + kBLE = 4, ///< BLE layer errors; see . + kIMGlobalStatus = 5, ///< Interaction Model global status code. + kIMClusterStatus = 6, ///< Interaction Model cluster-specific status code. + kApplication = 7, ///< Application-defined errors; see CHIP_APPLICATION_ERROR }; ChipError() = default; @@ -263,6 +265,23 @@ class ChipError MakeField(kSdkPartStart, static_cast(part))); } + /** + * Get the SDK code for an SDK error. + */ + constexpr uint8_t GetSdkCode() const { return static_cast(GetField(kSdkCodeStart, kSdkCodeLength, mError)); } + + /** + * Test whether @a error is an SDK error representing an Interaction Model + * status. If it is, it can be converted to/from an interaction model + * StatusIB struct. + */ + constexpr bool IsIMStatus() const + { + // Open question: should CHIP_NO_ERROR be treated as an IM status for + // purposes of this test? + return IsPart(SdkPart::kIMGlobalStatus) || IsPart(SdkPart::kIMClusterStatus); + } + #if CHIP_CONFIG_ERROR_SOURCE /**