Introduce a Status + Cluster Code abstraction (#31121)
* Introduce a Status + Cluster Code abstraction
- Existing code always has to split IM status and
cluster-specific status codes, which causes clumsy
implementation of clusters (see issue #31120).
- This PR introduces a value that encapsulates both
the IM status and cluster-specific status, in an
easy-to-pass-around abstraction, which can be directly
used in place of Status.
- Subsequent PR will implement usage in IM with overloads
for common cases, such as for AddStatus.
Issue #31120
Testing done:
- Added unit tests
- Other tests still pass
* Restyled by clang-format
* Restyled by gn
* Address review comments
* Rename ClusterStatus to ClusterStatusCode
* Fix build deps
* Restyled by gn
---------
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/examples/darwin-framework-tool/BUILD.gn b/examples/darwin-framework-tool/BUILD.gn
index e6b1a3e..7ee396d 100644
--- a/examples/darwin-framework-tool/BUILD.gn
+++ b/examples/darwin-framework-tool/BUILD.gn
@@ -248,7 +248,7 @@
# pics is needed by tests
"${chip_root}/src/app/tests/suites/pics",
- "${chip_root}/src/protocols:im_status",
+ "${chip_root}/src/protocols/interaction_model",
]
defines = []
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 532da2a..2d0204d 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -60,6 +60,7 @@
"${chip_root}/src/lib/core/tests",
"${chip_root}/src/messaging/tests",
"${chip_root}/src/protocols/bdx/tests",
+ "${chip_root}/src/protocols/interaction_model/tests",
"${chip_root}/src/protocols/user_directed_commissioning/tests",
"${chip_root}/src/transport/retransmit/tests",
]
diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn
index e053756..a96dc22 100644
--- a/src/app/BUILD.gn
+++ b/src/app/BUILD.gn
@@ -250,6 +250,7 @@
"${chip_root}/src/lib/address_resolve",
"${chip_root}/src/lib/support",
"${chip_root}/src/messaging",
+ "${chip_root}/src/protocols/interaction_model",
"${chip_root}/src/protocols/secure_channel",
"${chip_root}/src/system",
"${nlio_root}:nlio",
diff --git a/src/app/common/BUILD.gn b/src/app/common/BUILD.gn
index fee9795..c6c350e 100644
--- a/src/app/common/BUILD.gn
+++ b/src/app/common/BUILD.gn
@@ -27,6 +27,7 @@
public_deps = [
"${chip_root}/src/lib/core",
"${chip_root}/src/lib/support",
+ "${chip_root}/src/protocols/interaction_model",
]
defines = []
diff --git a/src/app/tests/TestStatusIB.cpp b/src/app/tests/TestStatusIB.cpp
index 4ff97e4..3eb47f4 100644
--- a/src/app/tests/TestStatusIB.cpp
+++ b/src/app/tests/TestStatusIB.cpp
@@ -22,6 +22,7 @@
#include <lib/core/ErrorStr.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/UnitTestRegistration.h>
+#include <protocols/interaction_model/StatusCode.h>
#include <nlunit-test.h>
diff --git a/src/app/tests/suites/commands/interaction_model/BUILD.gn b/src/app/tests/suites/commands/interaction_model/BUILD.gn
index e132971..ecefa4a 100644
--- a/src/app/tests/suites/commands/interaction_model/BUILD.gn
+++ b/src/app/tests/suites/commands/interaction_model/BUILD.gn
@@ -16,7 +16,7 @@
import("//build_overrides/chip.gni")
static_library("interaction_model") {
- output_name = "libInteractionModel"
+ output_name = "libInteractionModelCommands"
sources = [
"InteractionModel.cpp",
diff --git a/src/protocols/BUILD.gn b/src/protocols/BUILD.gn
index 10b48fb..2895334 100644
--- a/src/protocols/BUILD.gn
+++ b/src/protocols/BUILD.gn
@@ -38,27 +38,12 @@
cflags = [ "-Wconversion" ]
public_deps = [
- ":im_status",
":type_definitions",
"${chip_root}/src/lib/core",
"${chip_root}/src/lib/support",
"${chip_root}/src/messaging",
"${chip_root}/src/protocols/bdx",
+ "${chip_root}/src/protocols/interaction_model",
"${chip_root}/src/protocols/secure_channel",
]
}
-
-static_library("im_status") {
- sources = [
- "interaction_model/StatusCode.cpp",
- "interaction_model/StatusCode.h",
- ]
-
- cflags = [ "-Wconversion" ]
-
- public_deps = [
- ":type_definitions",
- "${chip_root}/src/lib/core",
- "${chip_root}/src/lib/support",
- ]
-}
diff --git a/src/protocols/interaction_model/BUILD.gn b/src/protocols/interaction_model/BUILD.gn
new file mode 100644
index 0000000..b8c3bd7
--- /dev/null
+++ b/src/protocols/interaction_model/BUILD.gn
@@ -0,0 +1,34 @@
+# Copyright (c) 2020 Project CHIP 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
+#
+# 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.
+
+import("//build_overrides/chip.gni")
+
+static_library("interaction_model") {
+ output_name = "libInteractionModel"
+
+ sources = [
+ "Constants.h",
+ "StatusCode.cpp",
+ "StatusCode.h",
+ "StatusCodeList.h",
+ ]
+
+ cflags = [ "-Wconversion" ]
+
+ public_deps = [
+ "${chip_root}/src/lib/core",
+ "${chip_root}/src/lib/support",
+ "${chip_root}/src/protocols:type_definitions",
+ ]
+}
diff --git a/src/protocols/interaction_model/StatusCode.h b/src/protocols/interaction_model/StatusCode.h
index 5d0453e..3161567 100644
--- a/src/protocols/interaction_model/StatusCode.h
+++ b/src/protocols/interaction_model/StatusCode.h
@@ -17,9 +17,12 @@
#pragma once
+#include <limits>
#include <stdint.h>
#include <lib/core/CHIPConfig.h>
+#include <lib/core/DataModelTypes.h>
+#include <lib/core/Optional.h>
#include <lib/support/TypeTraits.h>
#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
@@ -48,6 +51,112 @@
const char * StatusName(Status status);
#endif // CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
+/**
+ * @brief Class to encapsulate a Status code, including possibly a
+ * cluster-specific code for generic SUCCESS/FAILURE.
+ *
+ * This abstractions joins together Status and ClusterStatus, which
+ * are the components of a StatusIB, used in many IM actions, in a
+ * way which allows both of them to carry together.
+ *
+ * This can be used everywhere a `Status` is used, but it is lossy
+ * to the cluster-specific code if used in place of `Status` when
+ * the cluster-specific code is set.
+ *
+ * This class can only be directly constructed from a `Status`. To
+ * attach a cluster-specific-code, please use the `ClusterSpecificFailure()`
+ * and `ClusterSpecificSuccess()` factory methods.
+ */
+class ClusterStatusCode
+{
+public:
+ explicit ClusterStatusCode(Status status) : mStatus(status) {}
+
+ // We only have simple copyable members, so we should be trivially copyable.
+ ClusterStatusCode(const ClusterStatusCode & other) = default;
+ ClusterStatusCode & operator=(const ClusterStatusCode & other) = default;
+
+ bool operator==(const ClusterStatusCode & other)
+ {
+ return (this->mStatus == other.mStatus) && (this->HasClusterSpecificCode() == other.HasClusterSpecificCode()) &&
+ (this->GetClusterSpecificCode() == other.GetClusterSpecificCode());
+ }
+
+ bool operator!=(const ClusterStatusCode & other) { return !(*this == other); }
+
+ ClusterStatusCode & operator=(const Status & status)
+ {
+ this->mStatus = status;
+ this->mClusterSpecificCode = chip::NullOptional;
+ return *this;
+ }
+
+ /**
+ * @brief Builder for a cluster-specific failure status code.
+ *
+ * @tparam T - enum type for the cluster-specific status code
+ * (e.g. chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum)
+ * @param cluster_specific_code - cluster-specific code to record with the failure
+ * (e.g. chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen)
+ * @return a ClusterStatusCode instance properly configured.
+ */
+ template <typename T>
+ static ClusterStatusCode ClusterSpecificFailure(T cluster_specific_code)
+ {
+ static_assert(std::numeric_limits<T>::max() <= std::numeric_limits<ClusterStatus>::max(), "Type used must fit in uint8_t");
+ return ClusterStatusCode(Status::Failure, chip::to_underlying(cluster_specific_code));
+ }
+
+ /**
+ * @brief Builder for a cluster-specific success status code.
+ *
+ * @tparam T - enum type for the cluster-specific status code
+ * (e.g. chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum)
+ * @param cluster_specific_code - cluster-specific code to record with the success
+ * (e.g. chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kBasicWindowOpen)
+ * @return a ClusterStatusCode instance properly configured.
+ */
+ template <typename T>
+ static ClusterStatusCode ClusterSpecificSuccess(T cluster_specific_code)
+ {
+ static_assert(std::numeric_limits<T>::max() <= std::numeric_limits<ClusterStatus>::max(), "Type used must fit in uint8_t");
+ return ClusterStatusCode(Status::Success, chip::to_underlying(cluster_specific_code));
+ }
+
+ /// @return true if the core Status associated with this ClusterStatusCode is the one for success.
+ bool IsSuccess() const { return mStatus == Status::Success; }
+
+ /// @return the core Status code associated withi this ClusterStatusCode.
+ Status GetStatus() const { return mStatus; }
+
+ /// @return true if a cluster-specific code is associated with the ClusterStatusCode.
+ bool HasClusterSpecificCode() const { return mClusterSpecificCode.HasValue(); }
+
+ /// @return the cluster-specific code associated with this ClusterStatusCode or chip::NullOptional if none is associated.
+ chip::Optional<ClusterStatus> GetClusterSpecificCode() const
+ {
+ if ((mStatus != Status::Failure) && (mStatus != Status::Success))
+ {
+ return chip::NullOptional;
+ }
+ return mClusterSpecificCode;
+ }
+
+ // Automatic conversions to common types, using the status code alone.
+ operator Status() const { return mStatus; }
+
+private:
+ ClusterStatusCode() = delete;
+ ClusterStatusCode(Status status, ClusterStatus cluster_specific_code) :
+ mStatus(status), mClusterSpecificCode(chip::MakeOptional(cluster_specific_code))
+ {}
+
+ Status mStatus;
+ chip::Optional<ClusterStatus> mClusterSpecificCode;
+};
+
+static_assert(sizeof(ClusterStatusCode) <= sizeof(uint32_t), "ClusterStatusCode must not grow to be larger than a uint32_t");
+
} // namespace InteractionModel
} // namespace Protocols
} // namespace chip
diff --git a/src/protocols/interaction_model/tests/BUILD.gn b/src/protocols/interaction_model/tests/BUILD.gn
new file mode 100644
index 0000000..1679200
--- /dev/null
+++ b/src/protocols/interaction_model/tests/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright (c) 2023 Project CHIP 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
+#
+# 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.
+
+import("//build_overrides/build.gni")
+import("//build_overrides/chip.gni")
+import("//build_overrides/nlunit_test.gni")
+
+import("${chip_root}/build/chip/chip_test_suite.gni")
+
+chip_test_suite_using_nltest("tests") {
+ output_name = "libInteractionModelTests"
+
+ test_sources = [ "TestStatusCode.cpp" ]
+
+ public_deps = [
+ "${chip_root}/src/lib/core",
+ "${chip_root}/src/lib/support",
+ "${chip_root}/src/lib/support:testing",
+ "${chip_root}/src/protocols/interaction_model",
+ "${nlunit_test_root}:nlunit-test",
+ ]
+
+ cflags = [ "-Wconversion" ]
+}
diff --git a/src/protocols/interaction_model/tests/TestStatusCode.cpp b/src/protocols/interaction_model/tests/TestStatusCode.cpp
new file mode 100644
index 0000000..4b89e0f
--- /dev/null
+++ b/src/protocols/interaction_model/tests/TestStatusCode.cpp
@@ -0,0 +1,135 @@
+/*
+ *
+ * Copyright (c) 2023 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 <stdint.h>
+
+#include <lib/core/Optional.h>
+#include <lib/support/UnitTestExtendedAssertions.h>
+#include <lib/support/UnitTestRegistration.h>
+#include <protocols/interaction_model/StatusCode.h>
+
+#include <nlunit-test.h>
+
+using namespace ::chip;
+using namespace ::chip::Protocols::InteractionModel;
+
+namespace {
+
+void TestStatusBasicValues(nlTestSuite * inSuite, void * inContext)
+{
+ NL_TEST_ASSERT_EQUALS(inSuite, static_cast<int>(Status::Success), 0);
+ NL_TEST_ASSERT_EQUALS(inSuite, static_cast<int>(Status::Failure), 1);
+ NL_TEST_ASSERT_EQUALS(inSuite, static_cast<int>(Status::UnsupportedEndpoint), 0x7f);
+ NL_TEST_ASSERT_EQUALS(inSuite, static_cast<int>(Status::InvalidInState), 0xcb);
+}
+
+void TestClusterStatusCode(nlTestSuite * inSuite, void * inContext)
+{
+ // Basic usage as a Status.
+ {
+ ClusterStatusCode status_code_success{ Status::Success };
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_success, Status::Success);
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_success.GetStatus(), Status::Success);
+ NL_TEST_ASSERT(inSuite, !status_code_success.HasClusterSpecificCode());
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_success.GetClusterSpecificCode(), chip::NullOptional);
+ NL_TEST_ASSERT(inSuite, status_code_success.IsSuccess());
+
+ ClusterStatusCode status_code_failure{ Status::Failure };
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_failure, Status::Failure);
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_failure.GetStatus(), Status::Failure);
+ NL_TEST_ASSERT(inSuite, !status_code_failure.HasClusterSpecificCode());
+ NL_TEST_ASSERT(inSuite, !status_code_failure.IsSuccess());
+
+ ClusterStatusCode status_code_unsupported_ep{ Status::UnsupportedEndpoint };
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_unsupported_ep, Status::UnsupportedEndpoint);
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_unsupported_ep.GetStatus(), Status::UnsupportedEndpoint);
+ NL_TEST_ASSERT(inSuite, !status_code_unsupported_ep.HasClusterSpecificCode());
+ NL_TEST_ASSERT(inSuite, !status_code_unsupported_ep.IsSuccess());
+
+ ClusterStatusCode status_code_invalid_in_state{ Status::InvalidInState };
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_invalid_in_state, Status::InvalidInState);
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_invalid_in_state.GetStatus(), Status::InvalidInState);
+ NL_TEST_ASSERT(inSuite, !status_code_invalid_in_state.HasClusterSpecificCode());
+ NL_TEST_ASSERT(inSuite, !status_code_invalid_in_state.IsSuccess());
+ }
+
+ enum RobotoClusterStatus : uint8_t
+ {
+ kSandwichError = 7,
+ kSauceSuccess = 81,
+ };
+
+ // Cluster-specific usage.
+ {
+ ClusterStatusCode status_code_success = ClusterStatusCode::ClusterSpecificSuccess(RobotoClusterStatus::kSauceSuccess);
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_success, Status::Success);
+ NL_TEST_ASSERT(inSuite, status_code_success.HasClusterSpecificCode());
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_success.GetClusterSpecificCode(),
+ static_cast<uint8_t>(RobotoClusterStatus::kSauceSuccess));
+ NL_TEST_ASSERT(inSuite, status_code_success.IsSuccess());
+
+ ClusterStatusCode status_code_failure = ClusterStatusCode::ClusterSpecificFailure(RobotoClusterStatus::kSandwichError);
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_failure, Status::Failure);
+ NL_TEST_ASSERT(inSuite, status_code_failure.HasClusterSpecificCode());
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_failure.GetClusterSpecificCode(),
+ static_cast<uint8_t>(RobotoClusterStatus::kSandwichError));
+ NL_TEST_ASSERT(inSuite, !status_code_failure.IsSuccess());
+ }
+
+ // Copy/Assignment
+ {
+ ClusterStatusCode status_code_failure1 = ClusterStatusCode::ClusterSpecificFailure(RobotoClusterStatus::kSandwichError);
+ ClusterStatusCode status_code_failure2(status_code_failure1);
+
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_failure1, status_code_failure2);
+ NL_TEST_ASSERT(inSuite, status_code_failure1.HasClusterSpecificCode());
+ NL_TEST_ASSERT(inSuite, status_code_failure2.HasClusterSpecificCode());
+
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_failure1.GetClusterSpecificCode(),
+ static_cast<uint8_t>(RobotoClusterStatus::kSandwichError));
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_failure2.GetClusterSpecificCode(),
+ static_cast<uint8_t>(RobotoClusterStatus::kSandwichError));
+
+ ClusterStatusCode status_code_failure3{ Status::InvalidCommand };
+ NL_TEST_ASSERT(inSuite, status_code_failure2 != status_code_failure3);
+
+ status_code_failure3 = status_code_failure2;
+ NL_TEST_ASSERT_EQUALS(inSuite, status_code_failure2, status_code_failure3);
+ }
+}
+
+// clang-format off
+const nlTest sTests[] =
+{
+ NL_TEST_DEF("TestStatusBasicValues", TestStatusBasicValues),
+ NL_TEST_DEF("TestClusterStatusCode", TestClusterStatusCode),
+ NL_TEST_SENTINEL()
+};
+// clang-format on
+
+nlTestSuite sSuite = { "Test IM Status Code abstractions", &sTests[0], nullptr, nullptr };
+} // namespace
+
+int TestClusterStatusCode()
+{
+ nlTestRunner(&sSuite, nullptr);
+
+ return (nlTestRunnerStats(&sSuite));
+}
+
+CHIP_REGISTER_TEST_SUITE(TestClusterStatusCode)