diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index dd8742b5ee19f0..4172fd28e15959 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -494,6 +494,7 @@ jobs: --target linux-x64-fabric-admin-rpc-ipv6only-clang \ --target linux-x64-fabric-bridge-rpc-ipv6only-no-ble-no-wifi-clang \ --target linux-x64-light-data-model-no-unique-id-ipv6only-no-ble-no-wifi-clang \ + --target linux-x64-terms-and-conditions \ --target linux-x64-python-bindings \ build \ --copy-artifacts-to objdir-clone \ @@ -511,6 +512,7 @@ jobs: echo "FABRIC_ADMIN_APP: out/linux-x64-fabric-admin-rpc-ipv6only-clang/fabric-admin" >> /tmp/test_env.yaml echo "FABRIC_BRIDGE_APP: out/linux-x64-fabric-bridge-rpc-ipv6only-no-ble-no-wifi-clang/fabric-bridge-app" >> /tmp/test_env.yaml echo "LIGHTING_APP_NO_UNIQUE_ID: out/linux-x64-light-data-model-no-unique-id-ipv6only-no-ble-no-wifi-clang/chip-lighting-app" >> /tmp/test_env.yaml + echo "TERMS_AND_CONDITIONS_APP: out/linux-x64-terms-and-conditions/chip-terms-and-conditions-app" >> /tmp/test_env.yaml echo "TRACE_APP: out/trace_data/app-{SCRIPT_BASE_NAME}" >> /tmp/test_env.yaml echo "TRACE_TEST_JSON: out/trace_data/test-{SCRIPT_BASE_NAME}" >> /tmp/test_env.yaml echo "TRACE_TEST_PERFETTO: out/trace_data/test-{SCRIPT_BASE_NAME}" >> /tmp/test_env.yaml diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn index acacbc70e8bfad..992fbd53e4ecf5 100644 --- a/examples/chip-tool/BUILD.gn +++ b/examples/chip-tool/BUILD.gn @@ -23,6 +23,9 @@ if (config_use_interactive_mode) { import("//build_overrides/editline.gni") } +import("${chip_root}/build_overrides/boringssl.gni") +import("${chip_root}/src/crypto/crypto.gni") + assert(chip_build_tools) config("config") { @@ -67,6 +70,14 @@ static_library("chip-tool-utils") { "commands/common/HexConversion.h", "commands/common/RemoteDataModelLogger.cpp", "commands/common/RemoteDataModelLogger.h", + "commands/dcl/DCLClient.cpp", + "commands/dcl/DCLClient.h", + "commands/dcl/DisplayTermsAndConditions.cpp", + "commands/dcl/DisplayTermsAndConditions.h", + "commands/dcl/HTTPSRequest.cpp", + "commands/dcl/HTTPSRequest.h", + "commands/dcl/JsonSchemaMacros.cpp", + "commands/dcl/JsonSchemaMacros.h", "commands/delay/SleepCommand.cpp", "commands/delay/WaitForCommissioneeCommand.cpp", "commands/discover/DiscoverCommand.cpp", @@ -102,6 +113,10 @@ static_library("chip-tool-utils") { sources += [ "commands/common/DeviceScanner.cpp" ] } + if (chip_device_platform == "darwin" || chip_crypto == "boringssl") { + deps += [ "${boringssl_root}:boringssl_with_ssl_sources" ] + } + public_deps = [ "${chip_root}/examples/common/tracing:commandline", "${chip_root}/src/app/icd/client:handler", diff --git a/examples/chip-tool/commands/dcl/Commands.h b/examples/chip-tool/commands/dcl/Commands.h new file mode 100644 index 00000000000000..05220771fcda09 --- /dev/null +++ b/examples/chip-tool/commands/dcl/Commands.h @@ -0,0 +1,37 @@ +/* + * 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. + * + */ + +#pragma once + +#include "commands/common/Commands.h" +#include "commands/dcl/DCLCommands.h" + +void registerCommandsDCL(Commands & commands) +{ + const char * clusterName = "DCL"; + commands_list clusterCommands = { + make_unique(), // + make_unique(), // + make_unique(), // + make_unique(), // + make_unique(), // + make_unique(), // + }; + + commands.RegisterCommandSet(clusterName, clusterCommands, "Commands to interact with the DCL."); +} diff --git a/examples/chip-tool/commands/dcl/DCLClient.cpp b/examples/chip-tool/commands/dcl/DCLClient.cpp new file mode 100644 index 00000000000000..ada5d8113d7d05 --- /dev/null +++ b/examples/chip-tool/commands/dcl/DCLClient.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2024 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 "DCLClient.h" + +#include +#include +#include +#include + +#include "HTTPSRequest.h" +#include "JsonSchemaMacros.h" + +namespace { +constexpr const char * kDefaultDCLHostName = "on.dcl.csa-iot.org"; +constexpr const char * kErrorSchemaValidation = "Model schema validation failed for response content: "; +constexpr const char * kErrorVendorIdIsZero = "Invalid argument: Vendor ID should not be 0"; +constexpr const char * kErrorProductIdIsZero = "Invalid argument: Product ID should not be 0"; +constexpr const char * kErrorOrdinalValueTooLarge = "Ordinal value exceeds the maximum allowable bits: "; +constexpr const char * kRequestModelVendorProductPath = "/dcl/model/models/%u/%u"; +constexpr uint8_t kRequestPathBufferSize = 64; +constexpr uint16_t kTermsAndConditionSchemaVersion = 1; +} // namespace + +namespace chip { +namespace tool { +namespace dcl { + +namespace { +CHIP_ERROR ValidateModelSchema(const Json::Value & json) +{ + CHECK_REQUIRED_TYPE(json, model, Object) + auto model = json["model"]; + + CHECK_REQUIRED_TYPE(model, commissioningCustomFlow, UInt); + + // The "enhancedSetupFlowOptions" field is theoretically required by the schema. + // However, the current DCL implementation does not include it. + // To handle this gracefully, we inject the field and set its value to 0 if it is missing. + if (!model.isMember("enhancedSetupFlowOptions")) + { + model["enhancedSetupFlowOptions"] = 0; + } + + CHECK_REQUIRED_TYPE(model, enhancedSetupFlowOptions, UInt) + + // Check if enhancedSetupFlowOptions has bit 0 set. + // Bit 0 indicates that enhanced setup flow is enabled. + auto enhancedSetupFlowOptions = model["enhancedSetupFlowOptions"]; + VerifyOrReturnError((enhancedSetupFlowOptions.asUInt() & 0x01) != 0, CHIP_NO_ERROR); + + // List of required keys in the "model" object if enhancedSetupFlowOptions has bit 0 set. + CHECK_REQUIRED_TYPE(model, enhancedSetupFlowTCUrl, String) + CHECK_REQUIRED_TYPE(model, enhancedSetupFlowTCDigest, String) + CHECK_REQUIRED_TYPE(model, enhancedSetupFlowTCFileSize, UInt) + CHECK_REQUIRED_TYPE(model, enhancedSetupFlowTCRevision, UInt) + CHECK_REQUIRED_TYPE(model, enhancedSetupFlowMaintenanceUrl, String) + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ValidateModelCustomFlow(const Json::Value & json, CommissioningFlow payloadCommissioningFlow) +{ + auto model = json["model"]; + CHECK_REQUIRED_VALUE(model, commissioningCustomFlow, to_underlying(payloadCommissioningFlow)) + return CHIP_NO_ERROR; +} + +CHIP_ERROR ValidateTCLanguageEntries(const Json::Value & languageEntries) +{ + for (Json::Value::const_iterator it = languageEntries.begin(); it != languageEntries.end(); it++) + { + const Json::Value & languageArray = *it; + + CHECK_TYPE(languageArray, languageArray, Array); + + for (Json::ArrayIndex i = 0; i < languageArray.size(); i++) + { + const Json::Value & term = languageArray[i]; + CHECK_REQUIRED_TYPE(term, title, String); + CHECK_REQUIRED_TYPE(term, text, String); + CHECK_REQUIRED_TYPE(term, required, Bool); + CHECK_REQUIRED_TYPE(term, ordinal, UInt); + + auto ordinal = term["ordinal"].asUInt(); + VerifyOrReturnError(ordinal < 16, CHIP_ERROR_INVALID_ARGUMENT, + ChipLogError(chipTool, "%s%u", kErrorOrdinalValueTooLarge, ordinal)); + } + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ValidateTCCountryEntries(const Json::Value & countryEntries) +{ + for (Json::Value::const_iterator it = countryEntries.begin(); it != countryEntries.end(); it++) + { + const Json::Value & countryEntry = *it; + + CHECK_REQUIRED_TYPE(countryEntry, defaultLanguage, String); + CHECK_REQUIRED_TYPE(countryEntry, languageEntries, Object); + + ReturnErrorOnFailure(ValidateTCLanguageEntries(countryEntry["languageEntries"])); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ValidateTermsAndConditionsSchema(const Json::Value & tc, unsigned int expectedEnhancedSetupFlowTCRevision) +{ + CHECK_REQUIRED_VALUE(tc, schemaVersion, kTermsAndConditionSchemaVersion) + CHECK_REQUIRED_TYPE(tc, esfRevision, UInt) + CHECK_REQUIRED_TYPE(tc, defaultCountry, String) + CHECK_REQUIRED_TYPE(tc, countryEntries, Object) + CHECK_REQUIRED_VALUE(tc, esfRevision, expectedEnhancedSetupFlowTCRevision) + return ValidateTCCountryEntries(tc["countryEntries"]); +} + +CHIP_ERROR RequestTermsAndConditions(const Json::Value & json, Json::Value & tc) +{ + auto & model = json["model"]; + if ((model["enhancedSetupFlowOptions"].asUInt() & 0x01) == 0) + { + ChipLogProgress(chipTool, + "Enhanced setup flow is not enabled for this model (bit 0 of enhancedSetupFlowOptions is not set). No " + "Terms and Conditions are required for this configuration."); + tc = Json::nullValue; + return CHIP_NO_ERROR; + } + + auto & enhancedSetupFlowTCUrl = model["enhancedSetupFlowTCUrl"]; + auto & enhancedSetupFlowTCFileSize = model["enhancedSetupFlowTCFileSize"]; + auto & enhancedSetupFlowTCDigest = model["enhancedSetupFlowTCDigest"]; + auto & enhancedSetupFlowTCRevision = model["enhancedSetupFlowTCRevision"]; + + auto * tcUrl = enhancedSetupFlowTCUrl.asCString(); + const auto optionalFileSize = MakeOptional(static_cast(enhancedSetupFlowTCFileSize.asUInt())); + const auto optionalDigest = MakeOptional(enhancedSetupFlowTCDigest.asCString()); + ReturnErrorOnFailure(https::Request(tcUrl, tc, optionalFileSize, optionalDigest)); + ReturnErrorOnFailure(ValidateTermsAndConditionsSchema(tc, enhancedSetupFlowTCRevision.asUInt())); + + return CHIP_NO_ERROR; +} + +} // namespace + +DCLClient::DCLClient(Optional hostname, Optional port) +{ + mHostName = hostname.ValueOr(kDefaultDCLHostName); + mPort = port.ValueOr(0); +} + +CHIP_ERROR DCLClient::Model(const char * onboardingPayload, Json::Value & outModel) +{ + SetupPayload payload; + bool isQRCode = strncmp(onboardingPayload, kQRCodePrefix, strlen(kQRCodePrefix)) == 0; + if (isQRCode) + { + ReturnErrorOnFailure(QRCodeSetupPayloadParser(onboardingPayload).populatePayload(payload)); + VerifyOrReturnError(payload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT); + } + else + { + ReturnErrorOnFailure(ManualSetupPayloadParser(onboardingPayload).populatePayload(payload)); + VerifyOrReturnError(payload.isValidManualCode(), CHIP_ERROR_INVALID_ARGUMENT); + } + + auto vendorId = static_cast(payload.vendorID); + auto productId = payload.productID; + + // If both vendorId and productId are zero, return a null model without error. + if (vendorId == 0 && productId == 0) + { + ChipLogProgress(chipTool, "Vendor ID and Product ID not found in the provided payload. DCL lookup will not be used."); + outModel = Json::nullValue; + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(Model(vendorId, productId, outModel)); + + auto commissioningFlow = payload.commissioningFlow; + CHIP_ERROR error = ValidateModelCustomFlow(outModel, commissioningFlow); + VerifyOrReturnError(CHIP_NO_ERROR == error, error, + ChipLogError(chipTool, "%s%s", kErrorSchemaValidation, outModel.toStyledString().c_str())); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DCLClient::Model(const chip::VendorId vendorId, const uint16_t productId, Json::Value & outModel) +{ + VerifyOrReturnError(0 != vendorId, CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(chipTool, "%s", kErrorVendorIdIsZero)); + VerifyOrReturnError(0 != productId, CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(chipTool, "%s", kErrorProductIdIsZero)); + + char path[kRequestPathBufferSize]; + VerifyOrReturnError(snprintf(path, sizeof(path), kRequestModelVendorProductPath, to_underlying(vendorId), productId) >= 0, + CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(https::Request(mHostName, mPort, path, outModel)); + + CHIP_ERROR error = ValidateModelSchema(outModel); + VerifyOrReturnError(CHIP_NO_ERROR == error, error, + ChipLogError(chipTool, "%s%s", kErrorSchemaValidation, outModel.toStyledString().c_str())); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DCLClient::TermsAndConditions(const char * onboardingPayload, Json::Value & outTc) +{ + Json::Value json; + ReturnErrorOnFailure(Model(onboardingPayload, json)); + VerifyOrReturnError(Json::nullValue != json.type(), CHIP_NO_ERROR, outTc = Json::nullValue); + ReturnErrorOnFailure(RequestTermsAndConditions(json, outTc)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR DCLClient::TermsAndConditions(const chip::VendorId vendorId, const uint16_t productId, Json::Value & outTc) +{ + Json::Value json; + ReturnErrorOnFailure(Model(vendorId, productId, json)); + VerifyOrReturnError(Json::nullValue != json.type(), CHIP_NO_ERROR, outTc = Json::nullValue); + ReturnErrorOnFailure(RequestTermsAndConditions(json, outTc)); + return CHIP_NO_ERROR; +} + +} // namespace dcl +} // namespace tool +} // namespace chip diff --git a/examples/chip-tool/commands/dcl/DCLClient.h b/examples/chip-tool/commands/dcl/DCLClient.h new file mode 100644 index 00000000000000..d4ce72c38f76f8 --- /dev/null +++ b/examples/chip-tool/commands/dcl/DCLClient.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 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 + +namespace chip { +namespace tool { +namespace dcl { +class DCLClient +{ +public: + DCLClient(Optional hostname, Optional port); + + /** + * @brief Retrieves the model information from the DCL based on the onboarding payload. + * + * This function uses the onboarding payload (a QR Code or Manual Code) to fetch the model information. + * It constructs an HTTPS request to retrieve the model data associated with the specified vendor ID and product ID from the + * payload. + * + * @param[in] onboardingPayload A null-terminated string containing the onboarding payload. + * This can either start with a QR Code prefix or be a Manual Code. + * @param[out] outModel A Json::Value object to store the retrieved model information. + * If the vendor and product IDs are missing, this will be set to null. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, error code otherwise. + */ + CHIP_ERROR Model(const char * onboardingPayload, Json::Value & outModel); + + /** + * @brief Retrieves the model information from the DCL using vendor ID and product ID. + * + * This function constructs an HTTPS request to retrieve the model data associated with the specified vendor ID and product ID. + * + * @param[in] vendorId The vendor ID of the model (must not be 0). + * @param[in] productId The product ID of the model (must not be 0). + * @param[out] outModel A Json::Value object to store the retrieved model information. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, error code otherwise. + */ + CHIP_ERROR Model(const VendorId vendorId, const uint16_t productId, Json::Value & outModel); + + /** + * @brief Retrieves the Terms and Conditions from the DCL based on the onboarding payload. + * + * This function uses the onboarding payload (a QR Code or Manual Code) to fetch the model information. + * If the model includes enhanced setup flow options, it requests and validates the associated Terms + * and Conditions data. If enhanced setup flow is not enabled, the output `tc` is set to null. + * + * @param[in] onboardingPayload A null-terminated string containing the onboarding payload. + * This can either start with a QR Code prefix or be a Manual Code. + * @param[out] outTc A Json::Value object to store the retrieved Terms and Conditions data. + * If enhanced setup flow options are not enabled, this will be set to null. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, error code otherwise. + */ + CHIP_ERROR TermsAndConditions(const char * onboardingPayload, Json::Value & outTc); + + /** + * @brief Retrieves the Terms and Conditions from the DCL using vendor ID and product ID. + * + * This function first retrieves the model information using the specified vendor ID and product ID. + * If the model includes enhanced setup flow options, it fetches the Terms and Conditions, validates the data, and returns it. + * + * @param[in] vendorId The vendor ID of the model (must not be 0). + * @param[in] productId The product ID of the model (must not be 0). + * @param[out] outTc A Json::Value object to store the retrieved Terms and Conditions data. + * If enhanced setup flow options are not enabled, this will be set to null. + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, error code otherwise. + */ + CHIP_ERROR TermsAndConditions(const chip::VendorId vendorId, const uint16_t productId, Json::Value & outTc); + +private: + std::string mHostName; + uint16_t mPort; +}; +} // namespace dcl +} // namespace tool +} // namespace chip diff --git a/examples/chip-tool/commands/dcl/DCLCommands.h b/examples/chip-tool/commands/dcl/DCLCommands.h new file mode 100644 index 00000000000000..7fc910aa727952 --- /dev/null +++ b/examples/chip-tool/commands/dcl/DCLCommands.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2024 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 "../common/Command.h" + +#include "DCLClient.h" +#include "DisplayTermsAndConditions.h" + +class DCLCommandBase : public Command +{ +public: + DCLCommandBase(const char * name) : Command(name) {} + + void AddArguments() + { + AddArgument("hostname", &mHostName, + "Hostname of the DCL server to fetch information from. Defaults to 'on.dcl.csa-iot.org'."); + AddArgument("port", 0, UINT16_MAX, &mPort, "Port number for connecting to the DCL server. Defaults to '443'."); + } + + CHIP_ERROR Run() + { + auto client = chip::tool::dcl::DCLClient(mHostName, mPort); + return RunCommand(client); + + return CHIP_NO_ERROR; + } + + virtual CHIP_ERROR RunCommand(chip::tool::dcl::DCLClient & client) = 0; + +private: + chip::Optional mHostName; + chip::Optional mPort; +}; + +class DCLPayloadCommandBase : public DCLCommandBase +{ +public: + DCLPayloadCommandBase(const char * name) : DCLCommandBase(name) + { + AddArgument("payload", &mPayload); + DCLCommandBase::AddArguments(); + } + +protected: + char * mPayload; +}; + +class DCLIdsCommandBase : public DCLCommandBase +{ +public: + DCLIdsCommandBase(const char * name) : DCLCommandBase(name) + { + AddArgument("vendor-id", 0, UINT16_MAX, &mVendorId); + AddArgument("product-id", 0, UINT16_MAX, &mProductId); + DCLCommandBase::AddArguments(); + } + +protected: + uint16_t mVendorId; + uint16_t mProductId; +}; + +class DCLModelByPayloadCommand : public DCLPayloadCommandBase +{ +public: + DCLModelByPayloadCommand() : DCLPayloadCommandBase("model-by-payload") {} + + CHIP_ERROR RunCommand(chip::tool::dcl::DCLClient & client) + { + Json::Value model; + ReturnErrorOnFailure(client.Model(mPayload, model)); + VerifyOrReturnError(model != Json::nullValue, CHIP_NO_ERROR); + + ChipLogProgress(chipTool, "%s", model.toStyledString().c_str()); + return CHIP_NO_ERROR; + } +}; + +class DCLModelCommand : public DCLIdsCommandBase +{ +public: + DCLModelCommand() : DCLIdsCommandBase("model") {} + + CHIP_ERROR RunCommand(chip::tool::dcl::DCLClient & client) + { + Json::Value model; + ReturnErrorOnFailure(client.Model(static_cast(mVendorId), mProductId, model)); + VerifyOrReturnError(model != Json::nullValue, CHIP_NO_ERROR); + + ChipLogProgress(chipTool, "%s", model.toStyledString().c_str()); + return CHIP_NO_ERROR; + } +}; + +class DCLTCByPayloadCommand : public DCLPayloadCommandBase +{ +public: + DCLTCByPayloadCommand() : DCLPayloadCommandBase("tc-by-payload") {} + + CHIP_ERROR RunCommand(chip::tool::dcl::DCLClient & client) + { + Json::Value tc; + ReturnErrorOnFailure(client.TermsAndConditions(mPayload, tc)); + VerifyOrReturnError(tc != Json::nullValue, CHIP_NO_ERROR); + + ChipLogProgress(chipTool, "%s", tc.toStyledString().c_str()); + return CHIP_NO_ERROR; + } +}; + +class DCLTCCommand : public DCLIdsCommandBase +{ +public: + DCLTCCommand() : DCLIdsCommandBase("tc") {} + + CHIP_ERROR RunCommand(chip::tool::dcl::DCLClient & client) + { + Json::Value tc; + ReturnErrorOnFailure(client.TermsAndConditions(static_cast(mVendorId), mProductId, tc)); + VerifyOrReturnError(tc != Json::nullValue, CHIP_NO_ERROR); + + ChipLogProgress(chipTool, "%s", tc.toStyledString().c_str()); + return CHIP_NO_ERROR; + } +}; + +class DCLTCDisplayByPayloadCommand : public DCLPayloadCommandBase +{ +public: + DCLTCDisplayByPayloadCommand() : DCLPayloadCommandBase("tc-display-by-payload") + { + AddArgument("country-code", &mCountryCode, + "The country code to retrieve terms and conditions for. Defaults to the country configured in the DCL."); + AddArgument("language-code", &mLanguageCode, + "The language code to retrieve terms and conditions for. Defaults to the language configured for the chosen " + "country in the DCL."); + } + + CHIP_ERROR RunCommand(chip::tool::dcl::DCLClient & client) + { + Json::Value tc; + ReturnErrorOnFailure(client.TermsAndConditions(mPayload, tc)); + VerifyOrReturnError(tc != Json::nullValue, CHIP_NO_ERROR); + + uint16_t version = 0; + uint16_t userResponse = 0; + ReturnErrorOnFailure(chip::tool::dcl::DisplayTermsAndConditions(tc, version, userResponse, mCountryCode, mLanguageCode)); + + ChipLogProgress(chipTool, "\nTerms and conditions\n\tRevision : %u\n\tUserResponse: %u", version, userResponse); + return CHIP_NO_ERROR; + } + +private: + chip::Optional mCountryCode; + chip::Optional mLanguageCode; +}; + +class DCLTCDisplayCommand : public DCLIdsCommandBase +{ +public: + DCLTCDisplayCommand() : DCLIdsCommandBase("tc-display") + { + AddArgument("country-code", &mCountryCode, + "The country code to retrieve terms and conditions for. Defaults to the country configured in the DCL."); + AddArgument("language-code", &mLanguageCode, + "The language code to retrieve terms and conditions for. Defaults to the language configured for the chosen " + "country in the DCL."); + } + CHIP_ERROR RunCommand(chip::tool::dcl::DCLClient & client) + { + Json::Value tc; + ReturnErrorOnFailure(client.TermsAndConditions(static_cast(mVendorId), mProductId, tc)); + VerifyOrReturnError(tc != Json::nullValue, CHIP_NO_ERROR); + + uint16_t version = 0; + uint16_t userResponse = 0; + ReturnErrorOnFailure(chip::tool::dcl::DisplayTermsAndConditions(tc, version, userResponse, mCountryCode, mLanguageCode)); + + ChipLogProgress(chipTool, "\nTerms and conditions\n\tRevision : %u\n\tUserResponse: %u", version, userResponse); + return CHIP_NO_ERROR; + } + +private: + chip::Optional mCountryCode; + chip::Optional mLanguageCode; +}; diff --git a/examples/chip-tool/commands/dcl/DisplayTermsAndConditions.cpp b/examples/chip-tool/commands/dcl/DisplayTermsAndConditions.cpp new file mode 100644 index 00000000000000..8745db90c6dff4 --- /dev/null +++ b/examples/chip-tool/commands/dcl/DisplayTermsAndConditions.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2024 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 "DisplayTermsAndConditions.h" + +#include +#include + +#include +#include +#include + +namespace chip { +namespace tool { +namespace dcl { +namespace { +constexpr const char * kAcceptTerms = "Do you accept these terms? [Y/n]: "; +constexpr const char * kRequiredTerms = "Required"; +constexpr const char * kOptionalTerms = "Optional"; +constexpr const char * kTitleAllowedTags = R"(<(/?)(b|em|i|small|strong|u)>)"; +constexpr const char * kTextAllowedTags = R"(<(/?)(b|br|em|h1|h2|h3|h4|h5|h6|hr|i|li|ol|p|small|strong|u|ul)>)"; +constexpr const char * kAnsiCodeReset = "\033[0m"; +constexpr const char * kAnsiCodeBold = "\033[1m"; +constexpr const char * kAnsiCodeFaint = "\033[2m"; +constexpr const char * kAnsiCodeItalics = "\033[3m"; +constexpr const char * kAnsiCodeUnderline = "\033[4m"; +constexpr const char * kLineBreak = "\n"; +constexpr const char * kListItem = " - "; +constexpr const char * kHorizontalLine = "\n==========================================\n"; +constexpr const char * kErrorInvalidInput = "Invalid input. Please enter 'Y' (yes) or 'N' (no). Default is 'Y'."; + +// Fields names for the ESF JSON schema +constexpr const char * kFieldCountryEntries = "countryEntries"; +constexpr const char * kFieldDefaultCountry = "defaultCountry"; +constexpr const char * kFieldLanguageEntries = "languageEntries"; +constexpr const char * kFieldDefaultLanguage = "defaultLanguage"; +constexpr const char * kFieldOrdinal = "ordinal"; +constexpr const char * kFieldRequired = "required"; +constexpr const char * kFieldSchemaVersion = "schemaVersion"; +constexpr const char * kFieldText = "text"; +constexpr const char * kFieldTitle = "title"; + +const std::unordered_map kHtmlToAnsiCodes = { + { "b", kAnsiCodeBold }, // + { "br", kLineBreak }, // + { "em", kAnsiCodeItalics }, // + { "h1", kAnsiCodeBold }, // + { "h2", kAnsiCodeBold }, // + { "h3", kAnsiCodeBold }, // + { "h4", kAnsiCodeBold }, // + { "h5", kAnsiCodeBold }, // + { "h6", kAnsiCodeBold }, // + { "hr", kHorizontalLine }, // + { "i", kAnsiCodeItalics }, // + { "li", kListItem }, // + { "ol", kLineBreak }, // + { "p", kLineBreak }, // + { "small", kAnsiCodeFaint }, // + { "strong", kAnsiCodeBold }, // + { "u", kAnsiCodeUnderline }, // + { "ul", kLineBreak }, // +}; + +std::string ToUpperCase(const std::string & input) +{ + std::string output = input; + std::transform(output.begin(), output.end(), output.begin(), [](unsigned char c) { return std::toupper(c); }); + return output; +} + +std::string ToLowerCase(const std::string & input) +{ + std::string output = input; + std::transform(output.begin(), output.end(), output.begin(), [](unsigned char c) { return std::tolower(c); }); + return output; +} + +std::string Center(const std::string & text) +{ + size_t lineWidth = strlen(kHorizontalLine) - 1; + if (text.length() >= lineWidth) + { + return text; // No padding if the text is longer than the width + } + + size_t totalPadding = lineWidth - text.length(); + size_t paddingLeft = totalPadding / 2; + size_t paddingRight = totalPadding - paddingLeft; + + return std::string(paddingLeft, ' ') + text + std::string(paddingRight, ' '); +} + +std::string HTMLTagToAnsiCode(const std::smatch & match) +{ + if (match[1] == "/") + { + return kAnsiCodeReset; + } + + std::string tag = match[2]; + auto ansiCode = kHtmlToAnsiCodes.find(ToLowerCase(tag)); + if (ansiCode == kHtmlToAnsiCodes.end()) + { + return "<" + tag + ">"; + } + + return ansiCode->second; +} + +std::string renderHTMLInTerminal(const std::string & html, const std::string & allowedTags = kTextAllowedTags) +{ + std::string formattedText; + std::string::const_iterator current = html.cbegin(); + + std::regex regex(allowedTags, std::regex_constants::icase); + for (std::sregex_iterator it(html.cbegin(), html.cend(), regex), end; it != end; ++it) + { + const auto & match = *it; + + formattedText += std::string(current, html.cbegin() + match.position()); + formattedText += HTMLTagToAnsiCode(match); + + current = html.cbegin() + match.position() + match.length(); + } + + formattedText += std::string(current, html.cend()); + formattedText += kAnsiCodeReset; + return formattedText; +} + +const char * ResolveValueOrDefault(const Json::Value & entries, const Optional & userValue, const char * defaultValue, + const char * valueType) +{ + const char * resolvedValue = userValue.ValueOr(defaultValue); + + if (userValue.HasValue() && !entries.isMember(resolvedValue)) + { + ChipLogProgress(chipTool, "User-chosen %s ('%s') not found. Defaulting to '%s'", valueType, userValue.Value(), + defaultValue); + resolvedValue = defaultValue; + } + + return resolvedValue; +} + +const Json::Value & GetTexts(const Json::Value & tc, Optional optionalCountryCode, + Optional optionalLanguageCode) +{ + const char * defaultCountry = tc[kFieldDefaultCountry].asCString(); + const char * chosenCountry = ResolveValueOrDefault(tc[kFieldCountryEntries], optionalCountryCode, defaultCountry, "country"); + auto & countryEntry = tc[kFieldCountryEntries][chosenCountry]; + + const char * defaultLanguage = countryEntry[kFieldDefaultLanguage].asCString(); + const char * chosenLanguage = + ResolveValueOrDefault(countryEntry[kFieldLanguageEntries], optionalLanguageCode, defaultLanguage, "language"); + auto & languageEntry = countryEntry[kFieldLanguageEntries][chosenLanguage]; + + return languageEntry; +} + +void PrintText(const Json::Value & json) +{ + auto title = renderHTMLInTerminal(Center(ToUpperCase(json[kFieldTitle].asCString())), kTitleAllowedTags); + auto text = renderHTMLInTerminal(json[kFieldText].asCString()); + auto userQuestion = renderHTMLInTerminal(kAcceptTerms); + auto required = json[kFieldRequired].asBool() ? kRequiredTerms : kOptionalTerms; + + printf("%s", kHorizontalLine); + printf("%s", title.c_str()); + printf("%s", kHorizontalLine); + printf("%s", text.c_str()); + printf("%s", kHorizontalLine); + printf("[%s] %s", required, userQuestion.c_str()); +} + +bool AcknowledgeText() +{ + while (true) + { + std::string userInput; + std::getline(std::cin, userInput); + + VerifyOrReturnValue(!userInput.empty() && userInput != "Y" && userInput != "y", true); + VerifyOrReturnValue(userInput != "N" && userInput != "n", false); + + ChipLogError(chipTool, "%s", kErrorInvalidInput); + } +} + +} // namespace + +CHIP_ERROR DisplayTermsAndConditions(const Json::Value & tc, uint16_t & outVersion, uint16_t & outUserResponse, + Optional countryCode, Optional languageCode) +{ + VerifyOrReturnError(CanCastTo(tc[kFieldSchemaVersion].asUInt()), CHIP_ERROR_INVALID_ARGUMENT); + outVersion = static_cast(tc[kFieldSchemaVersion].asUInt()); + + auto texts = GetTexts(tc, countryCode, languageCode); + for (const auto & text : texts) + { + PrintText(text); + + if (AcknowledgeText()) + { + auto ordinal = text[kFieldOrdinal].asUInt(); + VerifyOrReturnError(ordinal < 16, CHIP_ERROR_INVALID_ARGUMENT); // Only 16 bits are available for user response + uint16_t shiftedValue = static_cast((1U << (ordinal & 0x0F)) & 0xFFFF); + outUserResponse |= shiftedValue; + } + } + return CHIP_NO_ERROR; +} +} // namespace dcl +} // namespace tool +} // namespace chip diff --git a/examples/chip-tool/commands/dcl/DisplayTermsAndConditions.h b/examples/chip-tool/commands/dcl/DisplayTermsAndConditions.h new file mode 100644 index 00000000000000..49a68e3a6e1781 --- /dev/null +++ b/examples/chip-tool/commands/dcl/DisplayTermsAndConditions.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 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 + +namespace chip { +namespace tool { +namespace dcl { +/** + * Display the terms and conditions to the user and prompt for acceptance. + * + * @param[in] tc The terms and conditions JSON object. + * @param[out] outVersion The schema version of the terms and conditions. + * @param[out] outUserResponse The user response as a bitfield where each bit corresponds to the ordinal of the text. + * @param[in] countryCode The country code to use for the terms and conditions. If not provided, the default country will be used. + * @param[in] languageCode The language code to use for the terms and conditions. If not provided, the default language will be + * used. + * + * @return CHIP_NO_ERROR on success, error code otherwise. + */ +CHIP_ERROR DisplayTermsAndConditions(const Json::Value & tc, uint16_t & outVersion, uint16_t & outUserResponse, + Optional countryCode = NullOptional, + Optional languageCode = NullOptional); +} // namespace dcl +} // namespace tool +} // namespace chip diff --git a/examples/chip-tool/commands/dcl/HTTPSRequest.cpp b/examples/chip-tool/commands/dcl/HTTPSRequest.cpp new file mode 100644 index 00000000000000..29b0ed8f2726db --- /dev/null +++ b/examples/chip-tool/commands/dcl/HTTPSRequest.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2024 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 "HTTPSRequest.h" + +#include +#include +#include +#include +#include +#include + +#if (CHIP_CRYPTO_OPENSSL || CHIP_CRYPTO_BORINGSSL) +#include +#include +#include +#ifdef SHA256_DIGEST_LENGTH +#define USE_CHIP_CRYPTO 1 +#endif +#endif //(CHIP_CRYPTO_OPENSSL || CHIP_CRYPTO_BORINGSSL) + +namespace { +constexpr const char * kHttpsPrefix = "https://"; +constexpr uint16_t kHttpsPort = 443; +constexpr const char * kErrorJsonParse = "Failed to parse JSON: "; +constexpr const char * kErrorHTTPSPrefix = "URL must start with 'https://': "; +constexpr const char * kErrorHTTPSPort = "Invalid port: 0"; +constexpr const char * kErrorHTTPSHostName = "Invalid hostname: empty"; +constexpr const char * kErrorBase64Decode = "Error while decoding base64 data"; +constexpr const char * kErrorSizeMismatch = "The response size does not match the expected size: "; +} // namespace + +namespace chip { +namespace tool { +namespace https { +namespace { +#ifndef USE_CHIP_CRYPTO +/** + * @brief Stub implementation of HTTPSSessionHolder when neither OpenSSL nor BoringSSL is enabled. + * + * This class provides placeholder methods that log errors indicating the lack of SSL library support + * and encourages contributions for new implementations. + */ +class HTTPSSessionHolder +{ +public: + CHIP_ERROR Init(std::string & hostname, uint16_t port) { return LogNotImplementedError(); } + + CHIP_ERROR SendRequest(std::string & request) { return LogNotImplementedError(); } + + CHIP_ERROR ReceiveResponse(std::string & response) { return LogNotImplementedError(); } + +private: + CHIP_ERROR LogNotImplementedError() const + { + ChipLogError(chipTool, + "HTTPS requests are not available because neither OpenSSL nor BoringSSL is enabled. Contributions for " + "alternative implementations are welcome!"); + return CHIP_ERROR_NOT_IMPLEMENTED; + } +}; +#else // USE_CHIP_CRYPTO +constexpr uint16_t kResponseBufferSize = 4096; +constexpr const char * kErrorSendHTTPRequest = "Failed to send HTTP request"; +constexpr const char * kErrorReceiveHTTPResponse = "Failed to read HTTP response"; +constexpr const char * kErrorConnection = "Failed to connect to: "; +constexpr const char * kErrorSSLContextCreate = "Failed to create SSL context"; +constexpr const char * kErrorSSLObjectCreate = "Failed to create SSL object"; +constexpr const char * kErrorSSLHandshake = "SSL handshake failed"; +constexpr const char * kErrorDigestMismatch = "The response digest does not match the expected digest"; +class HTTPSSessionHolder +{ +public: + HTTPSSessionHolder(){}; + + ~HTTPSSessionHolder() + { + VerifyOrReturn(nullptr != mContext); + SSL_free(mSSL); + SSL_CTX_free(mContext); + close(mSock); + +#if !defined(OPENSSL_IS_BORINGSSL) + EVP_cleanup(); +#endif + } + + CHIP_ERROR Init(std::string & hostname, uint16_t port) + { + int sock; + ReturnErrorOnFailure(InitSocket(hostname, port, sock)); + ReturnErrorOnFailure(InitSSL(sock)); + return CHIP_NO_ERROR; + } + + CHIP_ERROR SendRequest(std::string & request) + { + int written = SSL_write(mSSL, request.c_str(), (int) request.size()); + VerifyOrReturnError(written > 0, CHIP_ERROR_BAD_REQUEST, ChipLogError(chipTool, "%s", kErrorSendHTTPRequest)); + return CHIP_NO_ERROR; + } + + CHIP_ERROR ReceiveResponse(std::string & response) + { + char buffer[kResponseBufferSize]; + + ssize_t n = -1; + while ((n = SSL_read(mSSL, buffer, sizeof(buffer))) > 0) + { + VerifyOrReturnError(CanCastTo(n), CHIP_ERROR_INVALID_ARGUMENT); + response.append(buffer, static_cast(n)); + } + + VerifyOrReturnError(n >= 0, CHIP_ERROR_INTERNAL, ChipLogError(chipTool, "%s", kErrorReceiveHTTPResponse)); + + return CHIP_NO_ERROR; + } + +private: + CHIP_ERROR InitSocket(std::string & hostname, uint16_t port, int & sock) + { + auto * server = gethostbyname(hostname.c_str()); + VerifyOrReturnError(nullptr != server, CHIP_ERROR_NOT_CONNECTED); + + sock = socket(AF_INET, SOCK_STREAM, 0); + VerifyOrReturnError(sock >= 0, CHIP_ERROR_NOT_CONNECTED); + + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + memcpy(&server_addr.sin_addr.s_addr, server->h_addr, (size_t) server->h_length); + + int rv = connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)); + VerifyOrReturnError(rv >= 0, CHIP_ERROR_POSIX(errno), + ChipLogError(chipTool, "%s%s:%u", kErrorConnection, hostname.c_str(), port)); + + return CHIP_NO_ERROR; + } + + CHIP_ERROR InitSSL(int sock) + { + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + + auto * context = SSL_CTX_new(TLS_client_method()); + VerifyOrReturnError(nullptr != context, CHIP_ERROR_NOT_CONNECTED, ChipLogError(chipTool, "%s", kErrorSSLContextCreate)); + + auto * ssl = SSL_new(context); + VerifyOrReturnError(nullptr != ssl, CHIP_ERROR_NOT_CONNECTED, ChipLogError(chipTool, "%s", kErrorSSLObjectCreate)); + + SSL_set_fd(ssl, sock); + VerifyOrReturnError(SSL_connect(ssl) > 0, CHIP_ERROR_NOT_CONNECTED, ChipLogError(chipTool, "%s", kErrorSSLHandshake)); + + mContext = context; + mSSL = ssl; + mSock = sock; + return CHIP_NO_ERROR; + } + + SSL_CTX * mContext = nullptr; + SSL * mSSL = nullptr; + int mSock = -1; +}; +#endif // USE_CHIP_CRYPTO + +std::string BuildRequest(std::string & hostname, std::string & path) +{ + return "GET " + path + " HTTP/1.1\r\n" + // + "Host: " + hostname + "\r\n" + // + "Accept: application/json\r\n" + // + "Connection: close\r\n\r\n"; // +} + +CHIP_ERROR RemoveHeader(std::string & response) +{ + size_t headerEnd = response.find("\r\n\r\n"); + VerifyOrReturnError(std::string::npos != headerEnd, CHIP_ERROR_INVALID_ARGUMENT); + + auto body = response.substr(headerEnd + 4); + response = body; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR MaybeCheckResponseSize(const std::string & response, const chip::Optional & optionalExpectedSize) +{ + VerifyOrReturnError(optionalExpectedSize.HasValue(), CHIP_NO_ERROR); + VerifyOrReturnError(chip::CanCastTo(response.size()), CHIP_ERROR_INVALID_ARGUMENT); + + uint32_t responseSize = static_cast(response.size()); + uint32_t expectedSize = optionalExpectedSize.Value(); + VerifyOrReturnError(expectedSize == responseSize, CHIP_ERROR_INVALID_ARGUMENT, + ChipLogError(chipTool, "%s%u != %u", kErrorSizeMismatch, responseSize, expectedSize)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR MaybeCheckResponseDigest(const std::string & response, const chip::Optional & optionalExpectedDigest) +{ + VerifyOrReturnError(optionalExpectedDigest.HasValue(), CHIP_NO_ERROR); + VerifyOrReturnError(CanCastTo(strlen(optionalExpectedDigest.Value())), CHIP_ERROR_INVALID_ARGUMENT); + + const char * encodedData = optionalExpectedDigest.Value(); + uint16_t encodedDataSize = static_cast(strlen(encodedData)); + + size_t expectedMaxDecodedSize = BASE64_MAX_DECODED_LEN(encodedDataSize); + chip::Platform::ScopedMemoryBuffer decodedData; + VerifyOrReturnError(decodedData.Calloc(expectedMaxDecodedSize + 1 /* for null */), CHIP_ERROR_INVALID_ARGUMENT); + + size_t decodedDataSize = chip::Base64Decode(encodedData, encodedDataSize, decodedData.Get()); + VerifyOrReturnError(0 != decodedDataSize, CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(chipTool, "%s", kErrorBase64Decode)); + +#ifdef USE_CHIP_CRYPTO + // Compute the SHA-256 hash of the response + unsigned char responseDigest[SHA256_DIGEST_LENGTH]; + SHA256(reinterpret_cast(response.c_str()), response.size(), responseDigest); + + VerifyOrReturnError(memcmp(responseDigest, decodedData.Get(), SHA256_DIGEST_LENGTH) == 0, CHIP_ERROR_INVALID_ARGUMENT, + ChipLogError(chipTool, "%s", kErrorDigestMismatch)); +#else + return CHIP_ERROR_NOT_IMPLEMENTED; +#endif // USE_CHIP_CRYPTO + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConvertResponseToJSON(std::string & body, Json::Value & jsonResponse) +{ + + Json::CharReaderBuilder readerBuilder; + std::string errors; + std::istringstream jsonStream(body); + bool success = Json::parseFromStream(readerBuilder, jsonStream, &jsonResponse, &errors); + VerifyOrReturnError(success, CHIP_ERROR_INTERNAL, ChipLogError(chipTool, "%s%s", kErrorJsonParse, errors.c_str())); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ExtractHostAndPath(const std::string & url, std::string & hostAndPort, std::string & outPath) +{ + VerifyOrReturnError(url.compare(0, strlen(kHttpsPrefix), kHttpsPrefix) == 0, CHIP_ERROR_INVALID_ARGUMENT, + ChipLogError(chipTool, "%s%s", kErrorHTTPSPrefix, url.c_str())); + + auto strippedUrl = url.substr(strlen(kHttpsPrefix)); + VerifyOrReturnError("" != strippedUrl, CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(chipTool, "%s", kErrorHTTPSHostName)); + + size_t position = strippedUrl.find('/'); + if (position == std::string::npos) + { + hostAndPort = strippedUrl; + outPath = "/"; + } + else + { + hostAndPort = strippedUrl.substr(0, position); + outPath = strippedUrl.substr(position); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ExtractHostAndPort(const std::string & hostAndPort, std::string & outHostName, uint16_t & outPort) +{ + size_t position = hostAndPort.find(':'); + if (position == std::string::npos) + { + outHostName = hostAndPort; + outPort = kHttpsPort; + } + else + { + outHostName = hostAndPort.substr(0, position); + auto portString = hostAndPort.substr(position + 1); + outPort = static_cast(std::atoi(portString.c_str())); + VerifyOrReturnError(0 != outPort, CHIP_ERROR_INVALID_ARGUMENT, ChipLogError(chipTool, "%s", kErrorHTTPSPort)); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ExtractHostNamePortPath(std::string url, std::string & outHostName, uint16_t & outPort, std::string & outPath) +{ + std::string hostAndPort; + ReturnErrorOnFailure(ExtractHostAndPath(url, hostAndPort, outPath)); + ReturnErrorOnFailure(ExtractHostAndPort(hostAndPort, outHostName, outPort)); + + return CHIP_NO_ERROR; +} +} // namespace + +CHIP_ERROR Request(std::string url, Json::Value & jsonResponse, const Optional & optionalExpectedSize, + const Optional & optionalExpectedDigest) +{ + std::string hostname; + uint16_t port; + std::string path; + ReturnErrorOnFailure(ExtractHostNamePortPath(url, hostname, port, path)); + return Request(hostname, port, path, jsonResponse, optionalExpectedSize, optionalExpectedDigest); +} + +CHIP_ERROR Request(std::string hostname, uint16_t port, std::string path, Json::Value & jsonResponse, + const Optional & optionalExpectedSize, const Optional & optionalExpectedDigest) +{ + VerifyOrDo(port != 0, port = kHttpsPort); + + ChipLogDetail(chipTool, "HTTPS request to %s:%u%s", hostname.c_str(), port, path.c_str()); + + std::string request = BuildRequest(hostname, path); + std::string response; + + HTTPSSessionHolder session; + ReturnErrorOnFailure(session.Init(hostname, port)); + ReturnErrorOnFailure(session.SendRequest(request)); + ReturnErrorOnFailure(session.ReceiveResponse(response)); + ReturnErrorOnFailure(RemoveHeader(response)); + ReturnErrorOnFailure(MaybeCheckResponseSize(response, optionalExpectedSize)); + ReturnErrorOnFailure(MaybeCheckResponseDigest(response, optionalExpectedDigest)); + ReturnErrorOnFailure(ConvertResponseToJSON(response, jsonResponse)); + return CHIP_NO_ERROR; +} + +} // namespace https +} // namespace tool +} // namespace chip diff --git a/examples/chip-tool/commands/dcl/HTTPSRequest.h b/examples/chip-tool/commands/dcl/HTTPSRequest.h new file mode 100644 index 00000000000000..f11e3b33d354bb --- /dev/null +++ b/examples/chip-tool/commands/dcl/HTTPSRequest.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 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 + +namespace chip { +namespace tool { +namespace https { + +CHIP_ERROR Request(std::string url, Json::Value & jsonResponse, + const chip::Optional & optionalExpectedSize = chip::NullOptional, + const chip::Optional & optionalExpectedDigest = chip::NullOptional); + +CHIP_ERROR Request(std::string hostname, uint16_t port, std::string path, Json::Value & jsonResponse, + const chip::Optional & optionalExpectedSize = chip::NullOptional, + const chip::Optional & optionalExpectedDigest = chip::NullOptional); + +} // namespace https +} // namespace tool +} // namespace chip diff --git a/examples/chip-tool/commands/dcl/JsonSchemaMacros.cpp b/examples/chip-tool/commands/dcl/JsonSchemaMacros.cpp new file mode 100644 index 00000000000000..f73fb4dac9d116 --- /dev/null +++ b/examples/chip-tool/commands/dcl/JsonSchemaMacros.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 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 "JsonSchemaMacros.h" + +namespace { +constexpr const char * kJsonTypeNull = "Null"; +constexpr const char * kJsonTypeInt = "Int"; +constexpr const char * kJsonTypeUInt = "UInt"; +constexpr const char * kJsonTypeReal = "Real"; +constexpr const char * kJsonTypeString = "String"; +constexpr const char * kJsonTypeBool = "Bool"; +constexpr const char * kJsonTypeArray = "Array"; +constexpr const char * kJsonTypeObject = "Object"; +constexpr const char * kJsonTypeUnknown = "Unknown"; +} // namespace + +namespace chip { +namespace json { +const char * GetTypeName(const Json::Value & value) +{ + const char * type = kJsonTypeUnknown; + + switch (value.type()) + { + case Json::nullValue: + return kJsonTypeNull; + case Json::intValue: + return kJsonTypeInt; + case Json::uintValue: + return kJsonTypeUInt; + case Json::realValue: + return kJsonTypeReal; + case Json::stringValue: + return kJsonTypeString; + case Json::booleanValue: + return kJsonTypeBool; + case Json::arrayValue: + return kJsonTypeArray; + case Json::objectValue: + return kJsonTypeObject; + default: + return kJsonTypeUnknown; + } + + return type; +} +} // namespace json +} // namespace chip diff --git a/examples/chip-tool/commands/dcl/JsonSchemaMacros.h b/examples/chip-tool/commands/dcl/JsonSchemaMacros.h new file mode 100644 index 00000000000000..812cf516787c98 --- /dev/null +++ b/examples/chip-tool/commands/dcl/JsonSchemaMacros.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 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 + +#define CHECK_TYPE(source, fieldName, fieldType) \ + VerifyOrReturnError(source.is##fieldType(), CHIP_ERROR_SCHEMA_MISMATCH, \ + ChipLogError(chipTool, "Type mismatch for field '%s': expected '%s', got '%s'", #fieldName, #fieldType, \ + chip::json::GetTypeName(source))); + +#define CHECK_REQUIRED_TYPE(source, fieldName, fieldType) \ + VerifyOrReturnError(source.isMember(#fieldName), CHIP_ERROR_SCHEMA_MISMATCH, \ + ChipLogError(chipTool, "Missing required field: '%s'", #fieldName)); \ + CHECK_TYPE(source[#fieldName], fieldName, fieldType) + +#define CHECK_REQUIRED_VALUE(source, fieldName, expectedValue) \ + CHECK_REQUIRED_TYPE(source, fieldName, UInt); \ + VerifyOrReturnError(source[#fieldName].asUInt() == expectedValue, CHIP_ERROR_INCORRECT_STATE, \ + ChipLogError(chipTool, "Value mismatch for '%s': expected '%u', got '%u'", #fieldName, expectedValue, \ + source[#fieldName].asUInt())); + +namespace chip { +namespace json { +const char * GetTypeName(const Json::Value & value); +} // namespace json +} // namespace chip diff --git a/examples/chip-tool/commands/dcl/test_dcl_server.py b/examples/chip-tool/commands/dcl/test_dcl_server.py new file mode 100755 index 00000000000000..f22f2d2a8e9390 --- /dev/null +++ b/examples/chip-tool/commands/dcl/test_dcl_server.py @@ -0,0 +1,245 @@ +#!/usr/bin/env -S python3 -B + +# Copyright (c) 2025 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 base64 +import hashlib +import http.server +import json +import os +import re +import ssl + +DEFAULT_HOSTNAME = "localhost" +DEFAULT_PORT = 4443 + + +TC = { + 0XFFF1: { + 0x8001: { + "schemaVersion": 1, + "esfRevision": 1, + "defaultCountry": "US", + "countryEntries": { + "US": { + "defaultLanguage": "en", + "languageEntries": { + "en": [ + { + "ordinal": 0, + "required": True, + "title": "Terms and Conditions", + "text": "

Feature 1 Text

Please accept these.

" + }, + { + "ordinal": 1, + "required": False, + "title": "Privacy Policy", + "text": "

Feature 2 Text

" + } + ], + "es": [ + { + "ordinal": 0, + "required": True, + "title": "Términos y condiciones", + "text": "

Característica 1 Texto

Por favor acéptelos.

" + }, + { + "ordinal": 1, + "required": False, + "title": "Política de privacidad", + "text": "

Característica 2 Texto

" + } + ] + } + }, + "MX": { + "defaultLanguage": "es", + "languageEntries": { + "es": [ + { + "ordinal": 0, + "required": True, + "title": "Términos y condiciones", + "text": "

Característica 1 Texto

Por favor acéptelos.

" + } + ] + } + }, + "CN": { + "defaultLanguage": "zh", + "languageEntries": { + "zh": [ + { + "ordinal": 0, + "required": True, + "title": "条款和条件", + "text": "

产品1文字

" + }, + { + "ordinal": 1, + "required": False, + "title": "隐私条款", + "text": "

产品2文字

" + } + ] + } + }, + "RU": { + "defaultLanguage": "ru", + "languageEntries": { + "ru": [ + { + "ordinal": 0, + "required": True, + "title": "Условия и положения", + "text": "

Текст функции 1

Пожалуйста, примите эти условия пользования.

" + }, + { + "ordinal": 1, + "required": False, + "title": "Положение о конфиденциальности", + "text": "

Текст функции 2

" + } + ] + } + } + } + } + } +} + +MODELS = { + 0XFFF1: { + 0x8001: { + "model": + { + "vid": 65521, + "pid": 32769, + "deviceTypeId": 65535, + "productName": "TEST_PRODUCT", + "productLabel": "All Clusters App", + "partNumber": "", + "commissioningCustomFlow": 2, + "commissioningCustomFlowUrl": "", + "commissioningModeInitialStepsHint": 0, + "commissioningModeInitialStepsInstruction": "", + "commissioningModeSecondaryStepsHint": 0, + "commissioningModeSecondaryStepsInstruction": "", + "creator": "chip project", + "lsfRevision": 0, + "lsfUrl": "", + "productUrl": "https://github.com/project-chip/connectedhomeip/tree/master/examples/all-clusters-app", + "supportUrl": "https://github.com/project-chip/connectedhomeip/", + "userManualUrl": "", + "enhancedSetupFlowOptions": 1, + "enhancedSetupFlowTCUrl": f"https://{DEFAULT_HOSTNAME}:{DEFAULT_PORT}/tc/65521/32769", + "enhancedSetupFlowTCRevision": 1, + "enhancedSetupFlowTCDigest": "", + "enhancedSetupFlowTCFileSize": 0, + "enhancedSetupFlowMaintenanceUrl": "" + } + } + } +} + + +class RESTRequestHandler(http.server.BaseHTTPRequestHandler): + def __init__(self, *args, **kwargs): + self.routes = { + r"/dcl/model/models/(\d+)/(\d+)": self.handle_model_request, + r"/tc/(\d+)/(\d+)": self.handle_tc_request, + } + super().__init__(*args, **kwargs) + + def do_GET(self): + for pattern, handler in self.routes.items(): + match = re.match(pattern, self.path) + if match: + response = handler(*match.groups()) + if response: + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(json.dumps(response).encode("utf-8")) + return + + # Handle 404 for unmatched paths + self.send_response(404) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(json.dumps({"error": "Not found"}).encode("utf-8")) + + def handle_model_request(self, vendor_id, product_id): + vendor_id = int(vendor_id) + product_id = int(product_id) + if vendor_id in MODELS and product_id in MODELS[vendor_id]: + model = MODELS[int(vendor_id)][int(product_id)] + # We will return a model that contains the file size and the digest of the TC. + # Instead of manually setting them, it is calculated on the fly. + tc = TC[int(vendor_id)][int(product_id)] + tc_encoded = json.dumps(tc).encode("utf-8") + sha256_hash = hashlib.sha256(tc_encoded).digest() + model['model']['enhancedSetupFlowTCFileSize'] = len(tc_encoded) + model['model']['enhancedSetupFlowTCDigest'] = base64.b64encode( + sha256_hash).decode("utf-8") + + return model + + return None + + def handle_tc_request(self, vendor_id, product_id): + vendor_id = int(vendor_id) + product_id = int(product_id) + if vendor_id in TC and product_id in TC[vendor_id]: + return TC[int(vendor_id)][int(product_id)] + + return None + + +def run_https_server(cert_file="cert.pem", key_file="key.pem"): + httpd = http.server.HTTPServer( + (DEFAULT_HOSTNAME, DEFAULT_PORT), RESTRequestHandler) + + httpd.socket = ssl.wrap_socket( + httpd.socket, + server_side=True, + certfile=cert_file, + keyfile=key_file, + ssl_version=ssl.PROTOCOL_TLS, + ) + + print(f"Serving on https://{DEFAULT_HOSTNAME}:{DEFAULT_PORT}") + httpd.serve_forever() + + +# Generate self-signed certificates if needed +def generate_self_signed_cert(cert_file="cert.pem", key_file="key.pem"): + from subprocess import run + run([ + "openssl", "req", "-x509", "-nodes", "-days", "365", "-newkey", "rsa:2048", + "-keyout", key_file, "-out", cert_file, + "-subj", f"/C=US/ST=Test/L=Test/O=Test/OU=Test/CN={DEFAULT_HOSTNAME}" + ]) + + +# Check if certificates exist; if not, generate them +if not os.path.exists("cert.pem") or not os.path.exists("key.pem"): + print("Generating self-signed certificates...") + generate_self_signed_cert() + +# Run the server +run_https_server() diff --git a/examples/chip-tool/commands/pairing/PairingCommand.cpp b/examples/chip-tool/commands/pairing/PairingCommand.cpp index 7e762bd690c292..7ebf74433ade7e 100644 --- a/examples/chip-tool/commands/pairing/PairingCommand.cpp +++ b/examples/chip-tool/commands/pairing/PairingCommand.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,9 @@ #include #include +#include "../dcl/DCLClient.h" +#include "../dcl/DisplayTermsAndConditions.h" + #include using namespace ::chip; @@ -129,6 +132,17 @@ CommissioningParameters PairingCommand::GetCommissioningParameters() params.SetCountryCode(CharSpan::fromCharString(mCountryCode.Value())); } + // mTCAcknowledgements and mTCAcknowledgementVersion are optional, but related. When one is missing, default the value to 0, to + // increase the test tools ability to test the applications. + if (mTCAcknowledgements.HasValue() || mTCAcknowledgementVersion.HasValue()) + { + TermsAndConditionsAcknowledgement termsAndConditionsAcknowledgement = { + .acceptedTermsAndConditions = mTCAcknowledgements.ValueOr(0), + .acceptedTermsAndConditionsVersion = mTCAcknowledgementVersion.ValueOr(0), + }; + params.SetTermsAndConditionsAcknowledgement(termsAndConditionsAcknowledgement); + } + // mTimeZoneList is an optional argument managed by TypedComplexArgument mComplex_TimeZones. // Since optional Complex arguments are not currently supported via the class, // we will use mTimeZoneList.data() value to determine if the argument was provided. @@ -221,6 +235,7 @@ CHIP_ERROR PairingCommand::PairWithCode(NodeId remoteId) discoveryType = DiscoveryType::kDiscoveryNetworkOnlyWithoutPASEAutoRetry; } + ReturnErrorOnFailure(MaybeDisplayTermsAndConditions(commissioningParams)); return CurrentCommissioner().PairDevice(remoteId, mOnboardingPayload, commissioningParams, discoveryType); } @@ -574,3 +589,26 @@ void PairingCommand::OnDeviceAttestationCompleted(Controller::DeviceCommissioner SetCommandExitStatus(err); } } + +CHIP_ERROR PairingCommand::MaybeDisplayTermsAndConditions(CommissioningParameters & params) +{ + VerifyOrReturnError(mUseDCL.ValueOr(false), CHIP_NO_ERROR); + + Json::Value tc; + auto client = tool::dcl::DCLClient(mDCLHostName, mDCLPort); + ReturnErrorOnFailure(client.TermsAndConditions(mOnboardingPayload, tc)); + if (tc != Json::nullValue) + { + uint16_t version = 0; + uint16_t userResponse = 0; + ReturnErrorOnFailure(tool::dcl::DisplayTermsAndConditions(tc, version, userResponse, mCountryCode)); + + TermsAndConditionsAcknowledgement termsAndConditionsAcknowledgement = { + .acceptedTermsAndConditions = userResponse, + .acceptedTermsAndConditionsVersion = version, + }; + params.SetTermsAndConditionsAcknowledgement(termsAndConditionsAcknowledgement); + } + + return CHIP_NO_ERROR; +} diff --git a/examples/chip-tool/commands/pairing/PairingCommand.h b/examples/chip-tool/commands/pairing/PairingCommand.h index 66c45d5dfe3143..3b0f58936cfac2 100644 --- a/examples/chip-tool/commands/pairing/PairingCommand.h +++ b/examples/chip-tool/commands/pairing/PairingCommand.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -106,6 +106,10 @@ class PairingCommand : public CHIPCommand, break; case PairingMode::Code: AddArgument("skip-commissioning-complete", 0, 1, &mSkipCommissioningComplete); + AddArgument("dcl-hostname", &mDCLHostName, + "Hostname of the DCL server to fetch information from. Defaults to 'on.dcl.csa-iot.org'."); + AddArgument("dcl-port", 0, UINT16_MAX, &mDCLPort, "Port number for connecting to the DCL server. Defaults to '443'."); + AddArgument("use-dcl", 0, 1, &mUseDCL, "Use DCL to fetch onboarding information"); FALLTHROUGH; case PairingMode::CodePaseOnly: AddArgument("payload", &mOnboardingPayload); @@ -202,6 +206,14 @@ class PairingCommand : public CHIPCommand, AddArgument("dst-offset", &mComplex_DSTOffsets, "DSTOffset list to use when setting Time Synchronization cluster's DSTOffset attribute", Argument::kOptional); + + AddArgument("tc-acknowledgements", 0, UINT16_MAX, &mTCAcknowledgements, + "Bit-field value indicating which Terms and Conditions have been accepted by the user. This value is sent " + "to the device during commissioning via the General Commissioning cluster"); + + AddArgument("tc-acknowledgements-version", 0, UINT16_MAX, &mTCAcknowledgementVersion, + "Version number of the Terms and Conditions that were accepted by the user. This value is sent to the " + "device during commissioning to indicate which T&C version was acknowledged"); } AddArgument("timeout", 0, UINT16_MAX, &mTimeout); @@ -239,6 +251,7 @@ class PairingCommand : public CHIPCommand, CHIP_ERROR PairWithMdnsOrBleByIndexWithCode(NodeId remoteId, uint16_t index); CHIP_ERROR Unpair(NodeId remoteId); chip::Controller::CommissioningParameters GetCommissioningParameters(); + CHIP_ERROR MaybeDisplayTermsAndConditions(chip::Controller::CommissioningParameters & params); const PairingMode mPairingMode; const PairingNetworkType mNetworkType; @@ -259,6 +272,11 @@ class PairingCommand : public CHIPCommand, chip::Optional mICDMonitoredSubject; chip::Optional mICDClientType; chip::Optional mICDStayActiveDurationMsec; + chip::Optional mTCAcknowledgements; + chip::Optional mTCAcknowledgementVersion; + chip::Optional mDCLHostName; + chip::Optional mDCLPort; + chip::Optional mUseDCL; chip::app::DataModel::List mTimeZoneList; TypedComplexArgument> mComplex_TimeZones; diff --git a/examples/chip-tool/main.cpp b/examples/chip-tool/main.cpp index 6a52941e8b8d9c..cb296f3f31bdcf 100644 --- a/examples/chip-tool/main.cpp +++ b/examples/chip-tool/main.cpp @@ -20,6 +20,7 @@ #include "commands/example/ExampleCredentialIssuerCommands.h" #include "commands/clusters/SubscriptionsCommands.h" +#include "commands/dcl/Commands.h" #include "commands/delay/Commands.h" #include "commands/discover/Commands.h" #include "commands/group/Commands.h" @@ -39,6 +40,7 @@ int main(int argc, char * argv[]) { ExampleCredentialIssuerCommands credIssuerCommands; Commands commands; + registerCommandsDCL(commands); registerCommandsDelay(commands, &credIssuerCommands); registerCommandsDiscover(commands, &credIssuerCommands); registerCommandsICD(commands, &credIssuerCommands); diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index a2e341efbe7a0f..8bc31984b3a18f 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -107,6 +107,10 @@ #include "ExampleAccessRestrictionProvider.h" #endif +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED +#include // nogncheck +#endif + #if CHIP_DEVICE_LAYER_TARGET_DARWIN #include #if CHIP_DEVICE_CONFIG_ENABLE_WIFI @@ -525,6 +529,16 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) static chip::CommonCaseDeviceServerInitParams initParams; VerifyOrDie(initParams.InitializeStaticResourcesBeforeServerInit() == CHIP_NO_ERROR); +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + if (LinuxDeviceOptions::GetInstance().tcVersion.HasValue() && LinuxDeviceOptions::GetInstance().tcRequired.HasValue()) + { + uint16_t version = LinuxDeviceOptions::GetInstance().tcVersion.Value(); + uint16_t required = LinuxDeviceOptions::GetInstance().tcRequired.Value(); + Optional requiredAcknowledgements(app::TermsAndConditions(required, version)); + app::TermsAndConditionsManager::GetInstance()->Init(initParams.persistentStorageDelegate, requiredAcknowledgements); + } +#endif // CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + #if defined(ENABLE_CHIP_SHELL) Engine::Root().Init(); std::thread shellThread([]() { Engine::Root().RunMainLoop(); }); diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 6f8afea5bb496b..8fc56e236c57f4 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -119,6 +119,10 @@ enum #if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF kDeviceOption_WiFi_PAF, #endif +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + kDeviceOption_TermsAndConditions_Version, + kDeviceOption_TermsAndConditions_Required, +#endif }; constexpr unsigned kAppUsageLength = 64; @@ -190,6 +194,10 @@ OptionDef sDeviceOptionDefs[] = { #endif #if CHIP_WITH_NLFAULTINJECTION { "faults", kArgumentRequired, kDeviceOption_FaultInjection }, +#endif +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + { "tc-version", kArgumentRequired, kDeviceOption_TermsAndConditions_Version }, + { "tc-required", kArgumentRequired, kDeviceOption_TermsAndConditions_Required }, #endif {} }; @@ -336,6 +344,15 @@ const char * sDeviceOptionHelp = " --subscription-resumption-retry-interval\n" " subscription timeout resumption retry interval in seconds\n" #endif +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + " --tc-version\n" + " Sets the minimum required version of the Terms and Conditions\n" + "\n" + " --tc-required\n" + " Sets the required acknowledgements for the Terms and Conditions as a 16-bit enumeration.\n" + " Each bit represents an ordinal corresponding to a specific acknowledgment requirement.\n" + "\n" +#endif #if CHIP_WITH_NLFAULTINJECTION " --faults \n" " Inject specified fault(s) at runtime.\n" @@ -684,6 +701,17 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, LinuxDeviceOptions::GetInstance().mWiFiPAFExtCmds = aValue; break; } +#endif +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + case kDeviceOption_TermsAndConditions_Version: { + LinuxDeviceOptions::GetInstance().tcVersion.SetValue(static_cast(atoi(aValue))); + break; + } + + case kDeviceOption_TermsAndConditions_Required: { + LinuxDeviceOptions::GetInstance().tcRequired.SetValue(static_cast(atoi(aValue))); + break; + } #endif default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", aProgram, aName); diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h index 11a9061efcade8..16560a0cb151aa 100644 --- a/examples/platform/linux/Options.h +++ b/examples/platform/linux/Options.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -90,6 +91,10 @@ struct LinuxDeviceOptions #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS chip::Optional> commissioningArlEntries; chip::Optional> arlEntries; +#endif +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + chip::Optional tcVersion; + chip::Optional tcRequired; #endif static LinuxDeviceOptions & GetInstance(); }; diff --git a/examples/terms-and-conditions-app/linux/.gn b/examples/terms-and-conditions-app/linux/.gn new file mode 100644 index 00000000000000..3b11e2ba2e62ee --- /dev/null +++ b/examples/terms-and-conditions-app/linux/.gn @@ -0,0 +1,25 @@ +# Copyright (c) 2024 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") + +# The location of the build configuration file. +buildconfig = "${build_root}/config/BUILDCONFIG.gn" + +# CHIP uses angle bracket includes. +check_system_includes = true + +default_args = { + import("//args.gni") +} diff --git a/examples/terms-and-conditions-app/linux/BUILD.gn b/examples/terms-and-conditions-app/linux/BUILD.gn new file mode 100644 index 00000000000000..71d4295f014a3a --- /dev/null +++ b/examples/terms-and-conditions-app/linux/BUILD.gn @@ -0,0 +1,32 @@ +# Copyright (c) 2024 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") +import("${chip_root}/build/chip/tools.gni") +import("${chip_root}/src/app/common_flags.gni") + +executable("chip-terms-and-conditions-app") { + sources = [ "main.cpp" ] + + deps = [ + "${chip_root}/examples/platform/linux:app-main", + "${chip_root}/examples/terms-and-conditions-app/terms-and-conditions-common", + ] + + output_dir = root_out_dir +} + +group("default") { + deps = [ ":chip-terms-and-conditions-app" ] +} diff --git a/examples/terms-and-conditions-app/linux/args.gni b/examples/terms-and-conditions-app/linux/args.gni new file mode 100644 index 00000000000000..1978783bae2c00 --- /dev/null +++ b/examples/terms-and-conditions-app/linux/args.gni @@ -0,0 +1,18 @@ +# Copyright (c) 2024 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") +import("${chip_root}/config/standalone/args.gni") + +chip_terms_and_conditions_required = true diff --git a/examples/terms-and-conditions-app/linux/build_overrides b/examples/terms-and-conditions-app/linux/build_overrides new file mode 120000 index 00000000000000..e578e73312ebd1 --- /dev/null +++ b/examples/terms-and-conditions-app/linux/build_overrides @@ -0,0 +1 @@ +../../build_overrides \ No newline at end of file diff --git a/examples/terms-and-conditions-app/linux/main.cpp b/examples/terms-and-conditions-app/linux/main.cpp new file mode 100644 index 00000000000000..c562000c0ac855 --- /dev/null +++ b/examples/terms-and-conditions-app/linux/main.cpp @@ -0,0 +1,82 @@ +/* + * + * Copyright (c) 2024 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 + +using namespace chip; +using namespace chip::ArgParser; + +static uint16_t sTcMinRequiredVersion = 0; +static uint16_t sTcRequiredAcknowledgements = 0; + +static OptionDef sTermsAndConditionsOptions[] = { + { "tc-min-required-version", kArgumentRequired, 'm' }, + { "tc-required-acknowledgements", kArgumentRequired, 'r' }, + { nullptr }, +}; + +static const char * const sTermsAndConditionsOptionHelp = + "-m, --tc-min-required-version \n Configure the minimum required TC version.\n\n" + "-r, --tc-required-acknowledgements \n Configure the required TC acknowledgements.\n\n"; + +static bool HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) +{ + switch (id) + { + case 'm': + sTcMinRequiredVersion = static_cast(atoi(arg)); + break; + case 'r': + sTcRequiredAcknowledgements = static_cast(atoi(arg)); + break; + default: + PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", progName, name); + return false; + } + + return true; +} + +static OptionSet sTermsAndConditionsCmdLineOptions = { + HandleOption, + sTermsAndConditionsOptions, + "TERMS AND CONDITIONS OPTIONS", + sTermsAndConditionsOptionHelp, +}; + +void ApplicationInit() +{ + const app::TermsAndConditions termsAndConditions = app::TermsAndConditions(sTcRequiredAcknowledgements, sTcMinRequiredVersion); + PersistentStorageDelegate & persistentStorageDelegate = Server::GetInstance().GetPersistentStorage(); + app::TermsAndConditionsManager::GetInstance()->Init(&persistentStorageDelegate, MakeOptional(termsAndConditions)); +} + +void ApplicationShutdown() {} + +int main(int argc, char * argv[]) +{ + if (ChipLinuxAppInit(argc, argv, &sTermsAndConditionsCmdLineOptions) != 0) + { + return -1; + } + + ChipLinuxAppMainLoop(); + return 0; +} diff --git a/examples/terms-and-conditions-app/linux/third_party/connectedhomeip b/examples/terms-and-conditions-app/linux/third_party/connectedhomeip new file mode 120000 index 00000000000000..11a54ed360106c --- /dev/null +++ b/examples/terms-and-conditions-app/linux/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../../ \ No newline at end of file diff --git a/examples/terms-and-conditions-app/terms-and-conditions-common/BUILD.gn b/examples/terms-and-conditions-app/terms-and-conditions-common/BUILD.gn new file mode 100644 index 00000000000000..b90f9b5f35b945 --- /dev/null +++ b/examples/terms-and-conditions-app/terms-and-conditions-common/BUILD.gn @@ -0,0 +1,22 @@ +# Copyright (c) 2024 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") + +import("${chip_root}/src/app/chip_data_model.gni") + +chip_data_model("terms-and-conditions-common") { + zap_file = "terms-and-conditions-app.zap" + is_server = true +} diff --git a/examples/terms-and-conditions-app/terms-and-conditions-common/terms-and-conditions-app.matter b/examples/terms-and-conditions-app/terms-and-conditions-common/terms-and-conditions-app.matter new file mode 100644 index 00000000000000..e62af3ba8c6893 --- /dev/null +++ b/examples/terms-and-conditions-app/terms-and-conditions-common/terms-and-conditions-app.matter @@ -0,0 +1,1592 @@ +// This IDL was generated automatically by ZAP. +// It is for view/code review purposes only. + +enum AreaTypeTag : enum8 { + kAisle = 0; + kAttic = 1; + kBackDoor = 2; + kBackYard = 3; + kBalcony = 4; + kBallroom = 5; + kBathroom = 6; + kBedroom = 7; + kBorder = 8; + kBoxroom = 9; + kBreakfastRoom = 10; + kCarport = 11; + kCellar = 12; + kCloakroom = 13; + kCloset = 14; + kConservatory = 15; + kCorridor = 16; + kCraftRoom = 17; + kCupboard = 18; + kDeck = 19; + kDen = 20; + kDining = 21; + kDrawingRoom = 22; + kDressingRoom = 23; + kDriveway = 24; + kElevator = 25; + kEnsuite = 26; + kEntrance = 27; + kEntryway = 28; + kFamilyRoom = 29; + kFoyer = 30; + kFrontDoor = 31; + kFrontYard = 32; + kGameRoom = 33; + kGarage = 34; + kGarageDoor = 35; + kGarden = 36; + kGardenDoor = 37; + kGuestBathroom = 38; + kGuestBedroom = 39; + kGuestRestroom = 40; + kGuestRoom = 41; + kGym = 42; + kHallway = 43; + kHearthRoom = 44; + kKidsRoom = 45; + kKidsBedroom = 46; + kKitchen = 47; + kLarder = 48; + kLaundryRoom = 49; + kLawn = 50; + kLibrary = 51; + kLivingRoom = 52; + kLounge = 53; + kMediaTVRoom = 54; + kMudRoom = 55; + kMusicRoom = 56; + kNursery = 57; + kOffice = 58; + kOutdoorKitchen = 59; + kOutside = 60; + kPantry = 61; + kParkingLot = 62; + kParlor = 63; + kPatio = 64; + kPlayRoom = 65; + kPoolRoom = 66; + kPorch = 67; + kPrimaryBathroom = 68; + kPrimaryBedroom = 69; + kRamp = 70; + kReceptionRoom = 71; + kRecreationRoom = 72; + kRestroom = 73; + kRoof = 74; + kSauna = 75; + kScullery = 76; + kSewingRoom = 77; + kShed = 78; + kSideDoor = 79; + kSideYard = 80; + kSittingRoom = 81; + kSnug = 82; + kSpa = 83; + kStaircase = 84; + kSteamRoom = 85; + kStorageRoom = 86; + kStudio = 87; + kStudy = 88; + kSunRoom = 89; + kSwimmingPool = 90; + kTerrace = 91; + kUtilityRoom = 92; + kWard = 93; + kWorkshop = 94; +} + +enum AtomicRequestTypeEnum : enum8 { + kBeginWrite = 0; + kCommitWrite = 1; + kRollbackWrite = 2; +} + +enum FloorSurfaceTag : enum8 { + kCarpet = 0; + kCeramic = 1; + kConcrete = 2; + kCork = 3; + kDeepCarpet = 4; + kDirt = 5; + kEngineeredWood = 6; + kGlass = 7; + kGrass = 8; + kHardwood = 9; + kLaminate = 10; + kLinoleum = 11; + kMat = 12; + kMetal = 13; + kPlastic = 14; + kPolishedConcrete = 15; + kRubber = 16; + kRug = 17; + kSand = 18; + kStone = 19; + kTatami = 20; + kTerrazzo = 21; + kTile = 22; + kVinyl = 23; +} + +enum LandmarkTag : enum8 { + kAirConditioner = 0; + kAirPurifier = 1; + kBackDoor = 2; + kBarStool = 3; + kBathMat = 4; + kBathtub = 5; + kBed = 6; + kBookshelf = 7; + kChair = 8; + kChristmasTree = 9; + kCoatRack = 10; + kCoffeeTable = 11; + kCookingRange = 12; + kCouch = 13; + kCountertop = 14; + kCradle = 15; + kCrib = 16; + kDesk = 17; + kDiningTable = 18; + kDishwasher = 19; + kDoor = 20; + kDresser = 21; + kLaundryDryer = 22; + kFan = 23; + kFireplace = 24; + kFreezer = 25; + kFrontDoor = 26; + kHighChair = 27; + kKitchenIsland = 28; + kLamp = 29; + kLitterBox = 30; + kMirror = 31; + kNightstand = 32; + kOven = 33; + kPetBed = 34; + kPetBowl = 35; + kPetCrate = 36; + kRefrigerator = 37; + kScratchingPost = 38; + kShoeRack = 39; + kShower = 40; + kSideDoor = 41; + kSink = 42; + kSofa = 43; + kStove = 44; + kTable = 45; + kToilet = 46; + kTrashCan = 47; + kLaundryWasher = 48; + kWindow = 49; + kWineCooler = 50; +} + +enum PositionTag : enum8 { + kLeft = 0; + kRight = 1; + kTop = 2; + kBottom = 3; + kMiddle = 4; + kRow = 5; + kColumn = 6; +} + +enum RelativePositionTag : enum8 { + kUnder = 0; + kNextTo = 1; + kAround = 2; + kOn = 3; + kAbove = 4; + kFrontOf = 5; + kBehind = 6; +} + +enum TestGlobalEnum : enum8 { + kSomeValue = 0; + kSomeOtherValue = 1; + kFinalValue = 2; +} + +enum ThreeLevelAutoEnum : enum8 { + kLow = 0; + kMedium = 1; + kHigh = 2; + kAutomatic = 3; +} + +bitmap TestGlobalBitmap : bitmap32 { + kFirstBit = 0x1; + kSecondBit = 0x2; +} + +struct TestGlobalStruct { + char_string<128> name = 0; + nullable TestGlobalBitmap myBitmap = 1; + optional nullable TestGlobalEnum myEnum = 2; +} + +struct LocationDescriptorStruct { + char_string<128> locationName = 0; + nullable int16s floorNumber = 1; + nullable AreaTypeTag areaType = 2; +} + +struct AtomicAttributeStatusStruct { + attrib_id attributeID = 0; + status statusCode = 1; +} + +/** Attributes and commands for putting a device into Identification mode (e.g. flashing a light). */ +cluster Identify = 3 { + revision 4; + + enum EffectIdentifierEnum : enum8 { + kBlink = 0; + kBreathe = 1; + kOkay = 2; + kChannelChange = 11; + kFinishEffect = 254; + kStopEffect = 255; + } + + enum EffectVariantEnum : enum8 { + kDefault = 0; + } + + enum IdentifyTypeEnum : enum8 { + kNone = 0; + kLightOutput = 1; + kVisibleIndicator = 2; + kAudibleBeep = 3; + kDisplay = 4; + kActuator = 5; + } + + attribute int16u identifyTime = 0; + readonly attribute IdentifyTypeEnum identifyType = 1; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct IdentifyRequest { + int16u identifyTime = 0; + } + + request struct TriggerEffectRequest { + EffectIdentifierEnum effectIdentifier = 0; + EffectVariantEnum effectVariant = 1; + } + + /** Command description for Identify */ + command access(invoke: manage) Identify(IdentifyRequest): DefaultSuccess = 0; + /** Command description for TriggerEffect */ + command access(invoke: manage) TriggerEffect(TriggerEffectRequest): DefaultSuccess = 64; +} + +/** Attributes and commands for group configuration and manipulation. */ +cluster Groups = 4 { + revision 4; + + bitmap Feature : bitmap32 { + kGroupNames = 0x1; + } + + bitmap NameSupportBitmap : bitmap8 { + kGroupNames = 0x80; + } + + readonly attribute NameSupportBitmap nameSupport = 0; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct AddGroupRequest { + group_id groupID = 0; + char_string<16> groupName = 1; + } + + response struct AddGroupResponse = 0 { + enum8 status = 0; + group_id groupID = 1; + } + + request struct ViewGroupRequest { + group_id groupID = 0; + } + + response struct ViewGroupResponse = 1 { + enum8 status = 0; + group_id groupID = 1; + char_string<16> groupName = 2; + } + + request struct GetGroupMembershipRequest { + group_id groupList[] = 0; + } + + response struct GetGroupMembershipResponse = 2 { + nullable int8u capacity = 0; + group_id groupList[] = 1; + } + + request struct RemoveGroupRequest { + group_id groupID = 0; + } + + response struct RemoveGroupResponse = 3 { + enum8 status = 0; + group_id groupID = 1; + } + + request struct AddGroupIfIdentifyingRequest { + group_id groupID = 0; + char_string<16> groupName = 1; + } + + /** Command description for AddGroup */ + fabric command access(invoke: manage) AddGroup(AddGroupRequest): AddGroupResponse = 0; + /** Command description for ViewGroup */ + fabric command ViewGroup(ViewGroupRequest): ViewGroupResponse = 1; + /** Command description for GetGroupMembership */ + fabric command GetGroupMembership(GetGroupMembershipRequest): GetGroupMembershipResponse = 2; + /** Command description for RemoveGroup */ + fabric command access(invoke: manage) RemoveGroup(RemoveGroupRequest): RemoveGroupResponse = 3; + /** Command description for RemoveAllGroups */ + fabric command access(invoke: manage) RemoveAllGroups(): DefaultSuccess = 4; + /** Command description for AddGroupIfIdentifying */ + fabric command access(invoke: manage) AddGroupIfIdentifying(AddGroupIfIdentifyingRequest): DefaultSuccess = 5; +} + +/** Attributes and commands for switching devices between 'On' and 'Off' states. */ +cluster OnOff = 6 { + revision 6; + + enum DelayedAllOffEffectVariantEnum : enum8 { + kDelayedOffFastFade = 0; + kNoFade = 1; + kDelayedOffSlowFade = 2; + } + + enum DyingLightEffectVariantEnum : enum8 { + kDyingLightFadeOff = 0; + } + + enum EffectIdentifierEnum : enum8 { + kDelayedAllOff = 0; + kDyingLight = 1; + } + + enum StartUpOnOffEnum : enum8 { + kOff = 0; + kOn = 1; + kToggle = 2; + } + + bitmap Feature : bitmap32 { + kLighting = 0x1; + kDeadFrontBehavior = 0x2; + kOffOnly = 0x4; + } + + bitmap OnOffControlBitmap : bitmap8 { + kAcceptOnlyWhenOn = 0x1; + } + + readonly attribute boolean onOff = 0; + readonly attribute optional boolean globalSceneControl = 16384; + attribute optional int16u onTime = 16385; + attribute optional int16u offWaitTime = 16386; + attribute access(write: manage) optional nullable StartUpOnOffEnum startUpOnOff = 16387; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct OffWithEffectRequest { + EffectIdentifierEnum effectIdentifier = 0; + enum8 effectVariant = 1; + } + + request struct OnWithTimedOffRequest { + OnOffControlBitmap onOffControl = 0; + int16u onTime = 1; + int16u offWaitTime = 2; + } + + /** On receipt of this command, a device SHALL enter its ‘Off’ state. This state is device dependent, but it is recommended that it is used for power off or similar functions. On receipt of the Off command, the OnTime attribute SHALL be set to 0. */ + command Off(): DefaultSuccess = 0; + /** On receipt of this command, a device SHALL enter its ‘On’ state. This state is device dependent, but it is recommended that it is used for power on or similar functions. On receipt of the On command, if the value of the OnTime attribute is equal to 0, the device SHALL set the OffWaitTime attribute to 0. */ + command On(): DefaultSuccess = 1; + /** On receipt of this command, if a device is in its ‘Off’ state it SHALL enter its ‘On’ state. Otherwise, if it is in its ‘On’ state it SHALL enter its ‘Off’ state. On receipt of the Toggle command, if the value of the OnOff attribute is equal to FALSE and if the value of the OnTime attribute is equal to 0, the device SHALL set the OffWaitTime attribute to 0. If the value of the OnOff attribute is equal to TRUE, the OnTime attribute SHALL be set to 0. */ + command Toggle(): DefaultSuccess = 2; + /** The OffWithEffect command allows devices to be turned off using enhanced ways of fading. */ + command OffWithEffect(OffWithEffectRequest): DefaultSuccess = 64; + /** The OnWithRecallGlobalScene command allows the recall of the settings when the device was turned off. */ + command OnWithRecallGlobalScene(): DefaultSuccess = 65; + /** The OnWithTimedOff command allows devices to be turned on for a specific duration with a guarded off duration so that SHOULD the device be subsequently switched off, further OnWithTimedOff commands, received during this time, are prevented from turning the devices back on. */ + command OnWithTimedOff(OnWithTimedOffRequest): DefaultSuccess = 66; +} + +/** The Descriptor Cluster is meant to replace the support from the Zigbee Device Object (ZDO) for describing a node, its endpoints and clusters. */ +cluster Descriptor = 29 { + revision 2; + + bitmap Feature : bitmap32 { + kTagList = 0x1; + } + + struct DeviceTypeStruct { + devtype_id deviceType = 0; + int16u revision = 1; + } + + struct SemanticTagStruct { + nullable vendor_id mfgCode = 0; + enum8 namespaceID = 1; + enum8 tag = 2; + optional nullable char_string label = 3; + } + + readonly attribute DeviceTypeStruct deviceTypeList[] = 0; + readonly attribute cluster_id serverList[] = 1; + readonly attribute cluster_id clientList[] = 2; + readonly attribute endpoint_no partsList[] = 3; + readonly attribute optional SemanticTagStruct tagList[] = 4; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** The Access Control Cluster exposes a data model view of a + Node's Access Control List (ACL), which codifies the rules used to manage + and enforce Access Control for the Node's endpoints and their associated + cluster instances. */ +cluster AccessControl = 31 { + revision 2; + + enum AccessControlEntryAuthModeEnum : enum8 { + kPASE = 1; + kCASE = 2; + kGroup = 3; + } + + enum AccessControlEntryPrivilegeEnum : enum8 { + kView = 1; + kProxyView = 2; + kOperate = 3; + kManage = 4; + kAdminister = 5; + } + + enum AccessRestrictionTypeEnum : enum8 { + kAttributeAccessForbidden = 0; + kAttributeWriteForbidden = 1; + kCommandForbidden = 2; + kEventForbidden = 3; + } + + enum ChangeTypeEnum : enum8 { + kChanged = 0; + kAdded = 1; + kRemoved = 2; + } + + bitmap Feature : bitmap32 { + kExtension = 0x1; + kManagedDevice = 0x2; + } + + struct AccessRestrictionStruct { + AccessRestrictionTypeEnum type = 0; + nullable int32u id = 1; + } + + struct CommissioningAccessRestrictionEntryStruct { + endpoint_no endpoint = 0; + cluster_id cluster = 1; + AccessRestrictionStruct restrictions[] = 2; + } + + fabric_scoped struct AccessRestrictionEntryStruct { + fabric_sensitive endpoint_no endpoint = 0; + fabric_sensitive cluster_id cluster = 1; + fabric_sensitive AccessRestrictionStruct restrictions[] = 2; + fabric_idx fabricIndex = 254; + } + + struct AccessControlTargetStruct { + nullable cluster_id cluster = 0; + nullable endpoint_no endpoint = 1; + nullable devtype_id deviceType = 2; + } + + fabric_scoped struct AccessControlEntryStruct { + fabric_sensitive AccessControlEntryPrivilegeEnum privilege = 1; + fabric_sensitive AccessControlEntryAuthModeEnum authMode = 2; + nullable fabric_sensitive int64u subjects[] = 3; + nullable fabric_sensitive AccessControlTargetStruct targets[] = 4; + fabric_idx fabricIndex = 254; + } + + fabric_scoped struct AccessControlExtensionStruct { + fabric_sensitive octet_string<128> data = 1; + fabric_idx fabricIndex = 254; + } + + fabric_sensitive info event access(read: administer) AccessControlEntryChanged = 0 { + nullable node_id adminNodeID = 1; + nullable int16u adminPasscodeID = 2; + ChangeTypeEnum changeType = 3; + nullable AccessControlEntryStruct latestValue = 4; + fabric_idx fabricIndex = 254; + } + + fabric_sensitive info event access(read: administer) AccessControlExtensionChanged = 1 { + nullable node_id adminNodeID = 1; + nullable int16u adminPasscodeID = 2; + ChangeTypeEnum changeType = 3; + nullable AccessControlExtensionStruct latestValue = 4; + fabric_idx fabricIndex = 254; + } + + fabric_sensitive info event access(read: administer) FabricRestrictionReviewUpdate = 2 { + int64u token = 0; + optional long_char_string instruction = 1; + optional long_char_string ARLRequestFlowUrl = 2; + fabric_idx fabricIndex = 254; + } + + attribute access(read: administer, write: administer) AccessControlEntryStruct acl[] = 0; + attribute access(read: administer, write: administer) optional AccessControlExtensionStruct extension[] = 1; + readonly attribute int16u subjectsPerAccessControlEntry = 2; + readonly attribute int16u targetsPerAccessControlEntry = 3; + readonly attribute int16u accessControlEntriesPerFabric = 4; + readonly attribute optional CommissioningAccessRestrictionEntryStruct commissioningARL[] = 5; + readonly attribute optional AccessRestrictionEntryStruct arl[] = 6; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct ReviewFabricRestrictionsRequest { + CommissioningAccessRestrictionEntryStruct arl[] = 0; + } + + response struct ReviewFabricRestrictionsResponse = 1 { + int64u token = 0; + } + + /** This command signals to the service associated with the device vendor that the fabric administrator would like a review of the current restrictions on the accessing fabric. */ + fabric command access(invoke: administer) ReviewFabricRestrictions(ReviewFabricRestrictionsRequest): ReviewFabricRestrictionsResponse = 0; +} + +/** This cluster provides attributes and events for determining basic information about Nodes, which supports both + Commissioning and operational determination of Node characteristics, such as Vendor ID, Product ID and serial number, + which apply to the whole Node. Also allows setting user device information such as location. */ +cluster BasicInformation = 40 { + revision 3; + + enum ColorEnum : enum8 { + kBlack = 0; + kNavy = 1; + kGreen = 2; + kTeal = 3; + kMaroon = 4; + kPurple = 5; + kOlive = 6; + kGray = 7; + kBlue = 8; + kLime = 9; + kAqua = 10; + kRed = 11; + kFuchsia = 12; + kYellow = 13; + kWhite = 14; + kNickel = 15; + kChrome = 16; + kBrass = 17; + kCopper = 18; + kSilver = 19; + kGold = 20; + } + + enum ProductFinishEnum : enum8 { + kOther = 0; + kMatte = 1; + kSatin = 2; + kPolished = 3; + kRugged = 4; + kFabric = 5; + } + + struct CapabilityMinimaStruct { + int16u caseSessionsPerFabric = 0; + int16u subscriptionsPerFabric = 1; + } + + struct ProductAppearanceStruct { + ProductFinishEnum finish = 0; + nullable ColorEnum primaryColor = 1; + } + + critical event StartUp = 0 { + int32u softwareVersion = 0; + } + + critical event ShutDown = 1 { + } + + info event Leave = 2 { + fabric_idx fabricIndex = 0; + } + + info event ReachableChanged = 3 { + boolean reachableNewValue = 0; + } + + readonly attribute int16u dataModelRevision = 0; + readonly attribute char_string<32> vendorName = 1; + readonly attribute vendor_id vendorID = 2; + readonly attribute char_string<32> productName = 3; + readonly attribute int16u productID = 4; + attribute access(write: manage) char_string<32> nodeLabel = 5; + attribute access(write: administer) char_string<2> location = 6; + readonly attribute int16u hardwareVersion = 7; + readonly attribute char_string<64> hardwareVersionString = 8; + readonly attribute int32u softwareVersion = 9; + readonly attribute char_string<64> softwareVersionString = 10; + readonly attribute optional char_string<16> manufacturingDate = 11; + readonly attribute optional char_string<32> partNumber = 12; + readonly attribute optional long_char_string<256> productURL = 13; + readonly attribute optional char_string<64> productLabel = 14; + readonly attribute optional char_string<32> serialNumber = 15; + attribute access(write: manage) optional boolean localConfigDisabled = 16; + readonly attribute optional boolean reachable = 17; + readonly attribute char_string<32> uniqueID = 18; + readonly attribute CapabilityMinimaStruct capabilityMinima = 19; + readonly attribute optional ProductAppearanceStruct productAppearance = 20; + readonly attribute int32u specificationVersion = 21; + readonly attribute int16u maxPathsPerInvoke = 22; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + command MfgSpecificPing(): DefaultSuccess = 0; +} + +/** This cluster is used to manage global aspects of the Commissioning flow. */ +cluster GeneralCommissioning = 48 { + revision 1; // NOTE: Default/not specifically set + + enum CommissioningErrorEnum : enum8 { + kOK = 0; + kValueOutsideRange = 1; + kInvalidAuthentication = 2; + kNoFailSafe = 3; + kBusyWithOtherAdmin = 4; + kRequiredTCNotAccepted = 5; + kTCAcknowledgementsNotReceived = 6; + kTCMinVersionNotMet = 7; + } + + enum RegulatoryLocationTypeEnum : enum8 { + kIndoor = 0; + kOutdoor = 1; + kIndoorOutdoor = 2; + } + + bitmap Feature : bitmap32 { + kTermsAndConditions = 0x1; + } + + struct BasicCommissioningInfo { + int16u failSafeExpiryLengthSeconds = 0; + int16u maxCumulativeFailsafeSeconds = 1; + } + + attribute access(write: administer) int64u breadcrumb = 0; + readonly attribute BasicCommissioningInfo basicCommissioningInfo = 1; + readonly attribute RegulatoryLocationTypeEnum regulatoryConfig = 2; + readonly attribute RegulatoryLocationTypeEnum locationCapability = 3; + readonly attribute boolean supportsConcurrentConnection = 4; + provisional readonly attribute access(read: administer) optional int16u TCAcceptedVersion = 5; + provisional readonly attribute access(read: administer) optional int16u TCMinRequiredVersion = 6; + provisional readonly attribute access(read: administer) optional bitmap16 TCAcknowledgements = 7; + provisional readonly attribute access(read: administer) optional boolean TCAcknowledgementsRequired = 8; + provisional readonly attribute access(read: administer) optional nullable int32u TCUpdateDeadline = 9; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct ArmFailSafeRequest { + int16u expiryLengthSeconds = 0; + int64u breadcrumb = 1; + } + + response struct ArmFailSafeResponse = 1 { + CommissioningErrorEnum errorCode = 0; + char_string<128> debugText = 1; + } + + request struct SetRegulatoryConfigRequest { + RegulatoryLocationTypeEnum newRegulatoryConfig = 0; + char_string<2> countryCode = 1; + int64u breadcrumb = 2; + } + + response struct SetRegulatoryConfigResponse = 3 { + CommissioningErrorEnum errorCode = 0; + char_string debugText = 1; + } + + response struct CommissioningCompleteResponse = 5 { + CommissioningErrorEnum errorCode = 0; + char_string debugText = 1; + } + + request struct SetTCAcknowledgementsRequest { + int16u TCVersion = 0; + bitmap16 TCUserResponse = 1; + } + + response struct SetTCAcknowledgementsResponse = 7 { + CommissioningErrorEnum errorCode = 0; + } + + /** Arm the persistent fail-safe timer with an expiry time of now + ExpiryLengthSeconds using device clock */ + command access(invoke: administer) ArmFailSafe(ArmFailSafeRequest): ArmFailSafeResponse = 0; + /** Set the regulatory configuration to be used during commissioning */ + command access(invoke: administer) SetRegulatoryConfig(SetRegulatoryConfigRequest): SetRegulatoryConfigResponse = 2; + /** Signals the Server that the Client has successfully completed all steps of Commissioning/Recofiguration needed during fail-safe period. */ + fabric command access(invoke: administer) CommissioningComplete(): CommissioningCompleteResponse = 4; + /** This command sets the user acknowledgements received in the Enhanced Setup Flow Terms and Conditions into the node. */ + command access(invoke: administer) SetTCAcknowledgements(SetTCAcknowledgementsRequest): SetTCAcknowledgementsResponse = 6; +} + +/** Functionality to configure, enable, disable network credentials and access on a Matter device. */ +cluster NetworkCommissioning = 49 { + revision 1; // NOTE: Default/not specifically set + + enum NetworkCommissioningStatusEnum : enum8 { + kSuccess = 0; + kOutOfRange = 1; + kBoundsExceeded = 2; + kNetworkIDNotFound = 3; + kDuplicateNetworkID = 4; + kNetworkNotFound = 5; + kRegulatoryError = 6; + kAuthFailure = 7; + kUnsupportedSecurity = 8; + kOtherConnectionFailure = 9; + kIPV6Failed = 10; + kIPBindFailed = 11; + kUnknownError = 12; + } + + enum WiFiBandEnum : enum8 { + k2G4 = 0; + k3G65 = 1; + k5G = 2; + k6G = 3; + k60G = 4; + k1G = 5; + } + + bitmap Feature : bitmap32 { + kWiFiNetworkInterface = 0x1; + kThreadNetworkInterface = 0x2; + kEthernetNetworkInterface = 0x4; + kPerDeviceCredentials = 0x8; + } + + bitmap ThreadCapabilitiesBitmap : bitmap16 { + kIsBorderRouterCapable = 0x1; + kIsRouterCapable = 0x2; + kIsSleepyEndDeviceCapable = 0x4; + kIsFullThreadDevice = 0x8; + kIsSynchronizedSleepyEndDeviceCapable = 0x10; + } + + bitmap WiFiSecurityBitmap : bitmap8 { + kUnencrypted = 0x1; + kWEP = 0x2; + kWPAPersonal = 0x4; + kWPA2Personal = 0x8; + kWPA3Personal = 0x10; + kWPA3MatterPDC = 0x20; + } + + struct NetworkInfoStruct { + octet_string<32> networkID = 0; + boolean connected = 1; + optional nullable octet_string<20> networkIdentifier = 2; + optional nullable octet_string<20> clientIdentifier = 3; + } + + struct ThreadInterfaceScanResultStruct { + int16u panId = 0; + int64u extendedPanId = 1; + char_string<16> networkName = 2; + int16u channel = 3; + int8u version = 4; + octet_string<8> extendedAddress = 5; + int8s rssi = 6; + int8u lqi = 7; + } + + struct WiFiInterfaceScanResultStruct { + WiFiSecurityBitmap security = 0; + octet_string<32> ssid = 1; + octet_string<6> bssid = 2; + int16u channel = 3; + WiFiBandEnum wiFiBand = 4; + int8s rssi = 5; + } + + readonly attribute access(read: administer) int8u maxNetworks = 0; + readonly attribute access(read: administer) NetworkInfoStruct networks[] = 1; + readonly attribute optional int8u scanMaxTimeSeconds = 2; + readonly attribute optional int8u connectMaxTimeSeconds = 3; + attribute access(write: administer) boolean interfaceEnabled = 4; + readonly attribute access(read: administer) nullable NetworkCommissioningStatusEnum lastNetworkingStatus = 5; + readonly attribute access(read: administer) nullable octet_string<32> lastNetworkID = 6; + readonly attribute access(read: administer) nullable int32s lastConnectErrorValue = 7; + provisional readonly attribute optional WiFiBandEnum supportedWiFiBands[] = 8; + provisional readonly attribute optional ThreadCapabilitiesBitmap supportedThreadFeatures = 9; + provisional readonly attribute optional int16u threadVersion = 10; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct ScanNetworksRequest { + optional nullable octet_string<32> ssid = 0; + optional int64u breadcrumb = 1; + } + + response struct ScanNetworksResponse = 1 { + NetworkCommissioningStatusEnum networkingStatus = 0; + optional char_string debugText = 1; + optional WiFiInterfaceScanResultStruct wiFiScanResults[] = 2; + optional ThreadInterfaceScanResultStruct threadScanResults[] = 3; + } + + request struct AddOrUpdateWiFiNetworkRequest { + octet_string<32> ssid = 0; + octet_string<64> credentials = 1; + optional int64u breadcrumb = 2; + optional octet_string<140> networkIdentity = 3; + optional octet_string<20> clientIdentifier = 4; + optional octet_string<32> possessionNonce = 5; + } + + request struct AddOrUpdateThreadNetworkRequest { + octet_string<254> operationalDataset = 0; + optional int64u breadcrumb = 1; + } + + request struct RemoveNetworkRequest { + octet_string<32> networkID = 0; + optional int64u breadcrumb = 1; + } + + response struct NetworkConfigResponse = 5 { + NetworkCommissioningStatusEnum networkingStatus = 0; + optional char_string<512> debugText = 1; + optional int8u networkIndex = 2; + optional octet_string<140> clientIdentity = 3; + optional octet_string<64> possessionSignature = 4; + } + + request struct ConnectNetworkRequest { + octet_string<32> networkID = 0; + optional int64u breadcrumb = 1; + } + + response struct ConnectNetworkResponse = 7 { + NetworkCommissioningStatusEnum networkingStatus = 0; + optional char_string debugText = 1; + nullable int32s errorValue = 2; + } + + request struct ReorderNetworkRequest { + octet_string<32> networkID = 0; + int8u networkIndex = 1; + optional int64u breadcrumb = 2; + } + + request struct QueryIdentityRequest { + octet_string<20> keyIdentifier = 0; + optional octet_string<32> possessionNonce = 1; + } + + response struct QueryIdentityResponse = 10 { + octet_string<140> identity = 0; + optional octet_string<64> possessionSignature = 1; + } + + /** Detemine the set of networks the device sees as available. */ + command access(invoke: administer) ScanNetworks(ScanNetworksRequest): ScanNetworksResponse = 0; + /** Add or update the credentials for a given Wi-Fi network. */ + command access(invoke: administer) AddOrUpdateWiFiNetwork(AddOrUpdateWiFiNetworkRequest): NetworkConfigResponse = 2; + /** Add or update the credentials for a given Thread network. */ + command access(invoke: administer) AddOrUpdateThreadNetwork(AddOrUpdateThreadNetworkRequest): NetworkConfigResponse = 3; + /** Remove the definition of a given network (including its credentials). */ + command access(invoke: administer) RemoveNetwork(RemoveNetworkRequest): NetworkConfigResponse = 4; + /** Connect to the specified network, using previously-defined credentials. */ + command access(invoke: administer) ConnectNetwork(ConnectNetworkRequest): ConnectNetworkResponse = 6; + /** Modify the order in which networks will be presented in the Networks attribute. */ + command access(invoke: administer) ReorderNetwork(ReorderNetworkRequest): NetworkConfigResponse = 8; + /** Retrieve details about and optionally proof of possession of a network client identity. */ + command access(invoke: administer) QueryIdentity(QueryIdentityRequest): QueryIdentityResponse = 9; +} + +/** The General Diagnostics Cluster, along with other diagnostics clusters, provide a means to acquire standardized diagnostics metrics that MAY be used by a Node to assist a user or Administrative Node in diagnosing potential problems. */ +cluster GeneralDiagnostics = 51 { + revision 2; + + enum BootReasonEnum : enum8 { + kUnspecified = 0; + kPowerOnReboot = 1; + kBrownOutReset = 2; + kSoftwareWatchdogReset = 3; + kHardwareWatchdogReset = 4; + kSoftwareUpdateCompleted = 5; + kSoftwareReset = 6; + } + + enum HardwareFaultEnum : enum8 { + kUnspecified = 0; + kRadio = 1; + kSensor = 2; + kResettableOverTemp = 3; + kNonResettableOverTemp = 4; + kPowerSource = 5; + kVisualDisplayFault = 6; + kAudioOutputFault = 7; + kUserInterfaceFault = 8; + kNonVolatileMemoryError = 9; + kTamperDetected = 10; + } + + enum InterfaceTypeEnum : enum8 { + kUnspecified = 0; + kWiFi = 1; + kEthernet = 2; + kCellular = 3; + kThread = 4; + } + + enum NetworkFaultEnum : enum8 { + kUnspecified = 0; + kHardwareFailure = 1; + kNetworkJammed = 2; + kConnectionFailed = 3; + } + + enum RadioFaultEnum : enum8 { + kUnspecified = 0; + kWiFiFault = 1; + kCellularFault = 2; + kThreadFault = 3; + kNFCFault = 4; + kBLEFault = 5; + kEthernetFault = 6; + } + + bitmap Feature : bitmap32 { + kDataModelTest = 0x1; + } + + struct NetworkInterface { + char_string<32> name = 0; + boolean isOperational = 1; + nullable boolean offPremiseServicesReachableIPv4 = 2; + nullable boolean offPremiseServicesReachableIPv6 = 3; + octet_string<8> hardwareAddress = 4; + octet_string IPv4Addresses[] = 5; + octet_string IPv6Addresses[] = 6; + InterfaceTypeEnum type = 7; + } + + critical event HardwareFaultChange = 0 { + HardwareFaultEnum current[] = 0; + HardwareFaultEnum previous[] = 1; + } + + critical event RadioFaultChange = 1 { + RadioFaultEnum current[] = 0; + RadioFaultEnum previous[] = 1; + } + + critical event NetworkFaultChange = 2 { + NetworkFaultEnum current[] = 0; + NetworkFaultEnum previous[] = 1; + } + + critical event BootReason = 3 { + BootReasonEnum bootReason = 0; + } + + readonly attribute NetworkInterface networkInterfaces[] = 0; + readonly attribute int16u rebootCount = 1; + readonly attribute optional int64u upTime = 2; + readonly attribute optional int32u totalOperationalHours = 3; + readonly attribute optional BootReasonEnum bootReason = 4; + readonly attribute optional HardwareFaultEnum activeHardwareFaults[] = 5; + readonly attribute optional RadioFaultEnum activeRadioFaults[] = 6; + readonly attribute optional NetworkFaultEnum activeNetworkFaults[] = 7; + readonly attribute boolean testEventTriggersEnabled = 8; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct TestEventTriggerRequest { + octet_string<16> enableKey = 0; + int64u eventTrigger = 1; + } + + response struct TimeSnapshotResponse = 2 { + systime_ms systemTimeMs = 0; + nullable posix_ms posixTimeMs = 1; + } + + request struct PayloadTestRequestRequest { + octet_string<16> enableKey = 0; + int8u value = 1; + int16u count = 2; + } + + response struct PayloadTestResponse = 4 { + octet_string payload = 0; + } + + /** Provide a means for certification tests to trigger some test-plan-specific events */ + command access(invoke: manage) TestEventTrigger(TestEventTriggerRequest): DefaultSuccess = 0; + /** Take a snapshot of system time and epoch time. */ + command TimeSnapshot(): TimeSnapshotResponse = 1; + /** Request a variable length payload response. */ + command PayloadTestRequest(PayloadTestRequestRequest): PayloadTestResponse = 3; +} + +/** Commands to trigger a Node to allow a new Administrator to commission it. */ +cluster AdministratorCommissioning = 60 { + revision 1; // NOTE: Default/not specifically set + + enum CommissioningWindowStatusEnum : enum8 { + kWindowNotOpen = 0; + kEnhancedWindowOpen = 1; + kBasicWindowOpen = 2; + } + + enum StatusCode : enum8 { + kBusy = 2; + kPAKEParameterError = 3; + kWindowNotOpen = 4; + } + + bitmap Feature : bitmap32 { + kBasic = 0x1; + } + + readonly attribute CommissioningWindowStatusEnum windowStatus = 0; + readonly attribute nullable fabric_idx adminFabricIndex = 1; + readonly attribute nullable vendor_id adminVendorId = 2; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct OpenCommissioningWindowRequest { + int16u commissioningTimeout = 0; + octet_string PAKEPasscodeVerifier = 1; + int16u discriminator = 2; + int32u iterations = 3; + octet_string<32> salt = 4; + } + + request struct OpenBasicCommissioningWindowRequest { + int16u commissioningTimeout = 0; + } + + /** This command is used by a current Administrator to instruct a Node to go into commissioning mode using enhanced commissioning method. */ + timed command access(invoke: administer) OpenCommissioningWindow(OpenCommissioningWindowRequest): DefaultSuccess = 0; + /** This command is used by a current Administrator to instruct a Node to go into commissioning mode using basic commissioning method, if the node supports it. */ + timed command access(invoke: administer) OpenBasicCommissioningWindow(OpenBasicCommissioningWindowRequest): DefaultSuccess = 1; + /** This command is used by a current Administrator to instruct a Node to revoke any active Open Commissioning Window or Open Basic Commissioning Window command. */ + timed command access(invoke: administer) RevokeCommissioning(): DefaultSuccess = 2; +} + +/** This cluster is used to add or remove Operational Credentials on a Commissionee or Node, as well as manage the associated Fabrics. */ +cluster OperationalCredentials = 62 { + revision 1; // NOTE: Default/not specifically set + + enum CertificateChainTypeEnum : enum8 { + kDACCertificate = 1; + kPAICertificate = 2; + } + + enum NodeOperationalCertStatusEnum : enum8 { + kOK = 0; + kInvalidPublicKey = 1; + kInvalidNodeOpId = 2; + kInvalidNOC = 3; + kMissingCsr = 4; + kTableFull = 5; + kInvalidAdminSubject = 6; + kFabricConflict = 9; + kLabelConflict = 10; + kInvalidFabricIndex = 11; + } + + fabric_scoped struct FabricDescriptorStruct { + octet_string<65> rootPublicKey = 1; + vendor_id vendorID = 2; + fabric_id fabricID = 3; + node_id nodeID = 4; + char_string<32> label = 5; + fabric_idx fabricIndex = 254; + } + + fabric_scoped struct NOCStruct { + fabric_sensitive octet_string noc = 1; + nullable fabric_sensitive octet_string icac = 2; + fabric_idx fabricIndex = 254; + } + + readonly attribute access(read: administer) NOCStruct NOCs[] = 0; + readonly attribute FabricDescriptorStruct fabrics[] = 1; + readonly attribute int8u supportedFabrics = 2; + readonly attribute int8u commissionedFabrics = 3; + readonly attribute octet_string trustedRootCertificates[] = 4; + readonly attribute int8u currentFabricIndex = 5; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct AttestationRequestRequest { + octet_string<32> attestationNonce = 0; + } + + response struct AttestationResponse = 1 { + octet_string<900> attestationElements = 0; + octet_string<64> attestationSignature = 1; + } + + request struct CertificateChainRequestRequest { + CertificateChainTypeEnum certificateType = 0; + } + + response struct CertificateChainResponse = 3 { + octet_string<600> certificate = 0; + } + + request struct CSRRequestRequest { + octet_string<32> CSRNonce = 0; + optional boolean isForUpdateNOC = 1; + } + + response struct CSRResponse = 5 { + octet_string NOCSRElements = 0; + octet_string attestationSignature = 1; + } + + request struct AddNOCRequest { + octet_string<400> NOCValue = 0; + optional octet_string<400> ICACValue = 1; + octet_string<16> IPKValue = 2; + int64u caseAdminSubject = 3; + vendor_id adminVendorId = 4; + } + + request struct UpdateNOCRequest { + octet_string NOCValue = 0; + optional octet_string ICACValue = 1; + } + + response struct NOCResponse = 8 { + NodeOperationalCertStatusEnum statusCode = 0; + optional fabric_idx fabricIndex = 1; + optional char_string<128> debugText = 2; + } + + request struct UpdateFabricLabelRequest { + char_string<32> label = 0; + } + + request struct RemoveFabricRequest { + fabric_idx fabricIndex = 0; + } + + request struct AddTrustedRootCertificateRequest { + octet_string rootCACertificate = 0; + } + + /** Sender is requesting attestation information from the receiver. */ + command access(invoke: administer) AttestationRequest(AttestationRequestRequest): AttestationResponse = 0; + /** Sender is requesting a device attestation certificate from the receiver. */ + command access(invoke: administer) CertificateChainRequest(CertificateChainRequestRequest): CertificateChainResponse = 2; + /** Sender is requesting a certificate signing request (CSR) from the receiver. */ + command access(invoke: administer) CSRRequest(CSRRequestRequest): CSRResponse = 4; + /** Sender is requesting to add the new node operational certificates. */ + command access(invoke: administer) AddNOC(AddNOCRequest): NOCResponse = 6; + /** Sender is requesting to update the node operational certificates. */ + fabric command access(invoke: administer) UpdateNOC(UpdateNOCRequest): NOCResponse = 7; + /** This command SHALL be used by an Administrative Node to set the user-visible Label field for a given Fabric, as reflected by entries in the Fabrics attribute. */ + fabric command access(invoke: administer) UpdateFabricLabel(UpdateFabricLabelRequest): NOCResponse = 9; + /** This command is used by Administrative Nodes to remove a given fabric index and delete all associated fabric-scoped data. */ + command access(invoke: administer) RemoveFabric(RemoveFabricRequest): NOCResponse = 10; + /** This command SHALL add a Trusted Root CA Certificate, provided as its CHIP Certificate representation. */ + command access(invoke: administer) AddTrustedRootCertificate(AddTrustedRootCertificateRequest): DefaultSuccess = 11; +} + +/** The Group Key Management Cluster is the mechanism by which group keys are managed. */ +cluster GroupKeyManagement = 63 { + revision 1; // NOTE: Default/not specifically set + + enum GroupKeySecurityPolicyEnum : enum8 { + kTrustFirst = 0; + kCacheAndSync = 1; + } + + bitmap Feature : bitmap32 { + kCacheAndSync = 0x1; + } + + fabric_scoped struct GroupInfoMapStruct { + group_id groupId = 1; + endpoint_no endpoints[] = 2; + optional char_string<16> groupName = 3; + fabric_idx fabricIndex = 254; + } + + fabric_scoped struct GroupKeyMapStruct { + group_id groupId = 1; + int16u groupKeySetID = 2; + fabric_idx fabricIndex = 254; + } + + struct GroupKeySetStruct { + int16u groupKeySetID = 0; + GroupKeySecurityPolicyEnum groupKeySecurityPolicy = 1; + nullable octet_string<16> epochKey0 = 2; + nullable epoch_us epochStartTime0 = 3; + nullable octet_string<16> epochKey1 = 4; + nullable epoch_us epochStartTime1 = 5; + nullable octet_string<16> epochKey2 = 6; + nullable epoch_us epochStartTime2 = 7; + } + + attribute access(write: manage) GroupKeyMapStruct groupKeyMap[] = 0; + readonly attribute GroupInfoMapStruct groupTable[] = 1; + readonly attribute int16u maxGroupsPerFabric = 2; + readonly attribute int16u maxGroupKeysPerFabric = 3; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct KeySetWriteRequest { + GroupKeySetStruct groupKeySet = 0; + } + + request struct KeySetReadRequest { + int16u groupKeySetID = 0; + } + + response struct KeySetReadResponse = 2 { + GroupKeySetStruct groupKeySet = 0; + } + + request struct KeySetRemoveRequest { + int16u groupKeySetID = 0; + } + + response struct KeySetReadAllIndicesResponse = 5 { + int16u groupKeySetIDs[] = 0; + } + + /** Write a new set of keys for the given key set id. */ + fabric command access(invoke: administer) KeySetWrite(KeySetWriteRequest): DefaultSuccess = 0; + /** Read the keys for a given key set id. */ + fabric command access(invoke: administer) KeySetRead(KeySetReadRequest): KeySetReadResponse = 1; + /** Revoke a Root Key from a Group */ + fabric command access(invoke: administer) KeySetRemove(KeySetRemoveRequest): DefaultSuccess = 3; + /** Return the list of Group Key Sets associated with the accessing fabric */ + fabric command access(invoke: administer) KeySetReadAllIndices(): KeySetReadAllIndicesResponse = 4; +} + +endpoint 0 { + device type ma_rootdevice = 22, version 1; + + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster AccessControl { + emits event AccessControlEntryChanged; + emits event AccessControlExtensionChanged; + callback attribute acl; + callback attribute extension; + callback attribute subjectsPerAccessControlEntry; + callback attribute targetsPerAccessControlEntry; + callback attribute accessControlEntriesPerFabric; + callback attribute attributeList; + ram attribute featureMap default = 0; + callback attribute clusterRevision; + } + + server cluster BasicInformation { + emits event StartUp; + callback attribute dataModelRevision; + callback attribute vendorName; + callback attribute vendorID; + callback attribute productName; + callback attribute productID; + persist attribute nodeLabel; + callback attribute location; + callback attribute hardwareVersion; + callback attribute hardwareVersionString; + callback attribute softwareVersion; + callback attribute softwareVersionString; + callback attribute uniqueID; + callback attribute capabilityMinima; + callback attribute specificationVersion; + callback attribute maxPathsPerInvoke; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 3; + } + + server cluster GeneralCommissioning { + ram attribute breadcrumb default = 0x0000000000000000; + callback attribute basicCommissioningInfo; + callback attribute regulatoryConfig; + callback attribute locationCapability; + callback attribute supportsConcurrentConnection; + ram attribute TCAcceptedVersion; + ram attribute TCMinRequiredVersion; + ram attribute TCAcknowledgements default = 0x0000; + ram attribute TCAcknowledgementsRequired; + ram attribute TCUpdateDeadline; + ram attribute featureMap default = 1; + ram attribute clusterRevision default = 1; + + handle command ArmFailSafe; + handle command ArmFailSafeResponse; + handle command SetRegulatoryConfig; + handle command SetRegulatoryConfigResponse; + handle command CommissioningComplete; + handle command CommissioningCompleteResponse; + handle command SetTCAcknowledgements; + handle command SetTCAcknowledgementsResponse; + } + + server cluster NetworkCommissioning { + ram attribute maxNetworks; + callback attribute networks; + ram attribute interfaceEnabled; + ram attribute lastNetworkingStatus; + ram attribute lastNetworkID; + ram attribute lastConnectErrorValue; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command ScanNetworks; + handle command ScanNetworksResponse; + handle command AddOrUpdateWiFiNetwork; + handle command RemoveNetwork; + handle command NetworkConfigResponse; + handle command ConnectNetwork; + handle command ConnectNetworkResponse; + } + + server cluster GeneralDiagnostics { + emits event BootReason; + callback attribute networkInterfaces; + callback attribute rebootCount; + callback attribute upTime; + callback attribute totalOperationalHours; + callback attribute bootReason; + callback attribute activeHardwareFaults; + callback attribute activeRadioFaults; + callback attribute activeNetworkFaults; + callback attribute testEventTriggersEnabled default = false; + callback attribute featureMap; + callback attribute clusterRevision; + + handle command TestEventTrigger; + handle command TimeSnapshot; + handle command TimeSnapshotResponse; + } + + server cluster AdministratorCommissioning { + callback attribute windowStatus; + callback attribute adminFabricIndex; + callback attribute adminVendorId; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command OpenCommissioningWindow; + handle command OpenBasicCommissioningWindow; + handle command RevokeCommissioning; + } + + server cluster OperationalCredentials { + callback attribute NOCs; + callback attribute fabrics; + callback attribute supportedFabrics; + callback attribute commissionedFabrics; + callback attribute trustedRootCertificates; + callback attribute currentFabricIndex; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command AttestationRequest; + handle command AttestationResponse; + handle command CertificateChainRequest; + handle command CertificateChainResponse; + handle command CSRRequest; + handle command CSRResponse; + handle command AddNOC; + handle command UpdateNOC; + handle command NOCResponse; + handle command UpdateFabricLabel; + handle command RemoveFabric; + handle command AddTrustedRootCertificate; + } + + server cluster GroupKeyManagement { + callback attribute groupKeyMap; + callback attribute groupTable; + callback attribute maxGroupsPerFabric; + callback attribute maxGroupKeysPerFabric; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + + handle command KeySetWrite; + handle command KeySetRead; + handle command KeySetReadResponse; + handle command KeySetRemove; + handle command KeySetReadAllIndices; + handle command KeySetReadAllIndicesResponse; + } +} +endpoint 1 { + device type ma_onofflight = 256, version 1; + + + server cluster Identify { + ram attribute identifyTime default = 0x0; + ram attribute identifyType default = 0x00; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 4; + + handle command Identify; + handle command TriggerEffect; + } + + server cluster Groups { + ram attribute nameSupport default = 0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 4; + + handle command AddGroup; + handle command AddGroupResponse; + handle command ViewGroup; + handle command ViewGroupResponse; + handle command GetGroupMembershipResponse; + handle command GetGroupMembership; + handle command RemoveGroup; + handle command RemoveGroupResponse; + handle command RemoveAllGroups; + handle command AddGroupIfIdentifying; + } + + server cluster OnOff { + ram attribute onOff default = 0; + ram attribute globalSceneControl default = 1; + ram attribute onTime default = 0; + ram attribute offWaitTime default = 0; + ram attribute startUpOnOff; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute attributeList; + ram attribute featureMap default = 1; + ram attribute clusterRevision default = 6; + + handle command Off; + handle command On; + handle command Toggle; + handle command OffWithEffect; + handle command OnWithRecallGlobalScene; + handle command OnWithTimedOff; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + } +} + + diff --git a/examples/terms-and-conditions-app/terms-and-conditions-common/terms-and-conditions-app.zap b/examples/terms-and-conditions-app/terms-and-conditions-common/terms-and-conditions-app.zap new file mode 100644 index 00000000000000..a4a4857d8e7c48 --- /dev/null +++ b/examples/terms-and-conditions-app/terms-and-conditions-common/terms-and-conditions-app.zap @@ -0,0 +1,2660 @@ +{ + "fileFormat": 2, + "featureLevel": 103, + "creator": "zap", + "keyValuePairs": [ + { + "key": "commandDiscovery", + "value": "1" + }, + { + "key": "defaultResponsePolicy", + "value": "always" + }, + { + "key": "manufacturerCodes", + "value": "0x1002" + } + ], + "package": [ + { + "pathRelativity": "relativeToZap", + "path": "../../../src/app/zap-templates/zcl/zcl.json", + "type": "zcl-properties", + "category": "matter", + "version": 1, + "description": "Matter SDK ZCL data" + }, + { + "pathRelativity": "relativeToZap", + "path": "../../../src/app/zap-templates/app-templates.json", + "type": "gen-templates-json", + "category": "matter", + "version": "chip-v1" + } + ], + "endpointTypes": [ + { + "id": 1, + "name": "MA-rootdevice", + "deviceTypeRef": { + "code": 22, + "profileId": 259, + "label": "MA-rootdevice", + "name": "MA-rootdevice", + "deviceTypeOrder": 0 + }, + "deviceTypes": [ + { + "code": 22, + "profileId": 259, + "label": "MA-rootdevice", + "name": "MA-rootdevice", + "deviceTypeOrder": 0 + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 22 + ], + "deviceTypeName": "MA-rootdevice", + "deviceTypeCode": 22, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Access Control", + "code": 31, + "mfgCode": null, + "define": "ACCESS_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "ACL", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Extension", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SubjectsPerAccessControlEntry", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TargetsPerAccessControlEntry", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AccessControlEntriesPerFabric", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "AccessControlEntryChanged", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "AccessControlExtensionChanged", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "Basic Information", + "code": 40, + "mfgCode": null, + "define": "BASIC_INFORMATION_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DataModelRevision", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "VendorName", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "VendorID", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "vendor_id", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductName", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductID", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "NodeLabel", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "NVM", + "singleton": 1, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "Location", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "HardwareVersion", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "HardwareVersionString", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SoftwareVersion", + "code": 9, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SoftwareVersionString", + "code": 10, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "UniqueID", + "code": 18, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CapabilityMinima", + "code": 19, + "mfgCode": null, + "side": "server", + "type": "CapabilityMinimaStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SpecificationVersion", + "code": 21, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxPathsPerInvoke", + "code": 22, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 1, + "bounded": 0, + "defaultValue": "3", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "StartUp", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "General Commissioning", + "code": 48, + "mfgCode": null, + "define": "GENERAL_COMMISSIONING_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "ArmFailSafe", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ArmFailSafeResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "SetRegulatoryConfig", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetRegulatoryConfigResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "CommissioningComplete", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CommissioningCompleteResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "SetTCAcknowledgements", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetTCAcknowledgementsResponse", + "code": 7, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "Breadcrumb", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0000000000000000", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "BasicCommissioningInfo", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "BasicCommissioningInfo", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "RegulatoryConfig", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "RegulatoryLocationTypeEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LocationCapability", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "RegulatoryLocationTypeEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SupportsConcurrentConnection", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TCAcceptedVersion", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TCMinRequiredVersion", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TCAcknowledgements", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "bitmap16", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0000", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TCAcknowledgementsRequired", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TCUpdateDeadline", + "code": 9, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Network Commissioning", + "code": 49, + "mfgCode": null, + "define": "NETWORK_COMMISSIONING_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "ScanNetworks", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ScanNetworksResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "AddOrUpdateWiFiNetwork", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RemoveNetwork", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "NetworkConfigResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ConnectNetwork", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ConnectNetworkResponse", + "code": 7, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "MaxNetworks", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Networks", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "InterfaceEnabled", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LastNetworkingStatus", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "NetworkCommissioningStatusEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LastNetworkID", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "octet_string", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LastConnectErrorValue", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "int32s", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "General Diagnostics", + "code": 51, + "mfgCode": null, + "define": "GENERAL_DIAGNOSTICS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "TestEventTrigger", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "TimeSnapshot", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "TimeSnapshotResponse", + "code": 2, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "NetworkInterfaces", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "RebootCount", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "UpTime", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TotalOperationalHours", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BootReason", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "BootReasonEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ActiveHardwareFaults", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ActiveRadioFaults", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ActiveNetworkFaults", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TestEventTriggersEnabled", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "false", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "BootReason", + "code": 3, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "Administrator Commissioning", + "code": 60, + "mfgCode": null, + "define": "ADMINISTRATOR_COMMISSIONING_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "OpenCommissioningWindow", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "OpenBasicCommissioningWindow", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RevokeCommissioning", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "WindowStatus", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "CommissioningWindowStatusEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AdminFabricIndex", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "fabric_idx", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AdminVendorId", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "vendor_id", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Operational Credentials", + "code": 62, + "mfgCode": null, + "define": "OPERATIONAL_CREDENTIALS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "AttestationRequest", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AttestationResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "CertificateChainRequest", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CertificateChainResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "CSRRequest", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CSRResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "AddNOC", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "UpdateNOC", + "code": 7, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "NOCResponse", + "code": 8, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "UpdateFabricLabel", + "code": 9, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RemoveFabric", + "code": 10, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AddTrustedRootCertificate", + "code": 11, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "NOCs", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Fabrics", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SupportedFabrics", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CommissionedFabrics", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "TrustedRootCertificates", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CurrentFabricIndex", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Group Key Management", + "code": 63, + "mfgCode": null, + "define": "GROUP_KEY_MANAGEMENT_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "KeySetWrite", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetRead", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetReadResponse", + "code": 2, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "KeySetRemove", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetReadAllIndices", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetReadAllIndicesResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "GroupKeyMap", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GroupTable", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxGroupsPerFabric", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxGroupKeysPerFabric", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + } + ] + }, + { + "id": 2, + "name": "Anonymous Endpoint Type", + "deviceTypeRef": { + "code": 256, + "profileId": 259, + "label": "MA-onofflight", + "name": "MA-onofflight", + "deviceTypeOrder": 0 + }, + "deviceTypes": [ + { + "code": 256, + "profileId": 259, + "label": "MA-onofflight", + "name": "MA-onofflight", + "deviceTypeOrder": 0 + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 256 + ], + "deviceTypeName": "MA-onofflight", + "deviceTypeCode": 256, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "4", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Groups", + "code": 4, + "mfgCode": null, + "define": "GROUPS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "AddGroup", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AddGroupResponse", + "code": 0, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ViewGroup", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ViewGroupResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "GetGroupMembershipResponse", + "code": 2, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "GetGroupMembership", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RemoveGroup", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RemoveGroupResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "RemoveAllGroups", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AddGroupIfIdentifying", + "code": 5, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "NameSupport", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "NameSupportBitmap", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "4", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "On/Off", + "code": 6, + "mfgCode": null, + "define": "ON_OFF_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Off", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "On", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Toggle", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "OffWithEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "OnWithRecallGlobalScene", + "code": 65, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "OnWithTimedOff", + "code": 66, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "OnOff", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 0, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GlobalSceneControl", + "code": 16384, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OnTime", + "code": 16385, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OffWaitTime", + "code": 16386, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "StartUpOnOff", + "code": 16387, + "mfgCode": null, + "side": "server", + "type": "StartUpOnOffEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "6", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + } + ] + } + ], + "endpoints": [ + { + "endpointTypeName": "MA-rootdevice", + "endpointTypeIndex": 0, + "profileId": 259, + "endpointId": 0, + "networkId": 0, + "parentEndpointIdentifier": null + }, + { + "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeIndex": 1, + "profileId": 259, + "endpointId": 1, + "networkId": 0, + "parentEndpointIdentifier": null + } + ] +} \ No newline at end of file diff --git a/integrations/docker/images/chip-cert-bins/Dockerfile b/integrations/docker/images/chip-cert-bins/Dockerfile index de1c5166da17c3..f58c4b4e98c5f7 100644 --- a/integrations/docker/images/chip-cert-bins/Dockerfile +++ b/integrations/docker/images/chip-cert-bins/Dockerfile @@ -197,6 +197,7 @@ RUN case ${TARGETPLATFORM} in \ --target linux-x64-fabric-admin-rpc-ipv6only \ --target linux-x64-light-data-model-no-unique-id-ipv6only \ --target linux-x64-network-manager-ipv6only \ + --target linux-x64-terms-and-conditions \ build \ && mv out/linux-x64-chip-tool-ipv6only-platform-mdns/chip-tool out/chip-tool \ && mv out/linux-x64-shell-ipv6only-platform-mdns/chip-shell out/chip-shell \ @@ -221,6 +222,7 @@ RUN case ${TARGETPLATFORM} in \ && mv out/linux-x64-fabric-admin-rpc-ipv6only/fabric-admin out/fabric-admin \ && mv out/linux-x64-light-data-model-no-unique-id-ipv6only/chip-lighting-app out/chip-lighting-data-model-no-unique-id-app \ && mv out/linux-x64-network-manager-ipv6only/matter-network-manager-app out/matter-network-manager-app \ + && mv out/linux-x64-terms-and-conditions/chip-terms-and-conditions-app out/chip-terms-and-conditions-app \ ;; \ "linux/arm64")\ set -x \ @@ -249,6 +251,7 @@ RUN case ${TARGETPLATFORM} in \ --target linux-arm64-fabric-admin-rpc-ipv6only \ --target linux-arm64-light-data-model-no-unique-id-ipv6only \ --target linux-arm64-network-manager-ipv6only \ + --target linux-arm64-terms-and-conditions \ build \ && mv out/linux-arm64-chip-tool-ipv6only-platform-mdns/chip-tool out/chip-tool \ && mv out/linux-arm64-shell-ipv6only-platform-mdns/chip-shell out/chip-shell \ @@ -273,6 +276,7 @@ RUN case ${TARGETPLATFORM} in \ && mv out/linux-arm64-fabric-admin-rpc-ipv6only/fabric-admin out/fabric-admin \ && mv out/linux-arm64-light-data-model-no-unique-id-ipv6only/chip-lighting-app out/chip-lighting-data-model-no-unique-id-app \ && mv out/linux-arm64-network-manager-ipv6only/matter-network-manager-app out/matter-network-manager-app \ + && mv out/linux-arm64-terms-and-conditions/chip-terms-and-conditions-app out/chip-terms-and-conditions-app \ ;; \ *) ;; \ esac @@ -311,6 +315,7 @@ COPY --from=chip-build-cert-bins /root/connectedhomeip/out/fabric-bridge-app fab COPY --from=chip-build-cert-bins /root/connectedhomeip/out/fabric-admin fabric-admin COPY --from=chip-build-cert-bins /root/connectedhomeip/out/chip-lighting-data-model-no-unique-id-app chip-lighting-data-model-no-unique-id-app COPY --from=chip-build-cert-bins /root/connectedhomeip/out/matter-network-manager-app matter-network-manager-app +COPY --from=chip-build-cert-bins /root/connectedhomeip/out/chip-terms-and-conditions-app apps/chip-terms-and-conditions-app # Stage 3.1: Setup the Matter Python environment COPY --from=chip-build-cert-bins /root/connectedhomeip/out/python_lib python_lib diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index 8d1b30234cec87..7e48c2af8812af 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -146,6 +146,7 @@ def BuildHostTarget(): TargetPart('air-quality-sensor', app=HostApp.AIR_QUALITY_SENSOR), TargetPart('network-manager', app=HostApp.NETWORK_MANAGER), TargetPart('energy-management', app=HostApp.ENERGY_MANAGEMENT), + TargetPart('terms-and-conditions', app=HostApp.TERMS_AND_CONDITIONS), ] if (HostBoard.NATIVE.PlatformName() == 'darwin'): @@ -198,6 +199,7 @@ def BuildHostTarget(): target.AppendModifier('data-model-disabled', data_model_interface="disabled").ExceptIfRe('-data-model-(check|enabled)') target.AppendModifier('data-model-enabled', data_model_interface="enabled").ExceptIfRe('-data-model-(check|disabled)') target.AppendModifier('check-failure-die', chip_data_model_check_die_on_failure=True).OnlyIfRe('-data-model-check') + target.AppendModifier('terms-and-conditions', terms_and_conditions_required=True) return target diff --git a/scripts/build/builders/host.py b/scripts/build/builders/host.py index 624bd9ad748cd4..9b82bc37234174 100644 --- a/scripts/build/builders/host.py +++ b/scripts/build/builders/host.py @@ -84,6 +84,7 @@ class HostApp(Enum): AIR_QUALITY_SENSOR = auto() NETWORK_MANAGER = auto() ENERGY_MANAGEMENT = auto() + TERMS_AND_CONDITIONS = auto() def ExamplePath(self): if self == HostApp.ALL_CLUSTERS: @@ -154,6 +155,8 @@ def ExamplePath(self): return 'network-manager-app/linux' elif self == HostApp.ENERGY_MANAGEMENT: return 'energy-management-app/linux' + elif self == HostApp.TERMS_AND_CONDITIONS: + return 'terms-and-conditions-app/linux' else: raise Exception('Unknown app type: %r' % self) @@ -266,6 +269,9 @@ def OutputNames(self): elif self == HostApp.ENERGY_MANAGEMENT: yield 'chip-energy-management-app' yield 'chip-energy-management-app.map' + elif self == HostApp.TERMS_AND_CONDITIONS: + yield 'chip-terms-and-conditions-app' + yield 'chip-terms-and-conditions-app.map' else: raise Exception('Unknown app type: %r' % self) @@ -324,7 +330,8 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, chip_casting_simplified: Optional[bool] = None, data_model_interface: Optional[str] = None, chip_data_model_check_die_on_failure: Optional[bool] = None, - disable_shell=False + disable_shell=False, + terms_and_conditions_required: Optional[bool] = None, ): super(HostBuilder, self).__init__( root=os.path.join(root, 'examples', app.ExamplePath()), @@ -456,6 +463,12 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, if chip_casting_simplified is not None: self.extra_gn_options.append(f'chip_casting_simplified={str(chip_casting_simplified).lower()}') + if terms_and_conditions_required is not None: + if terms_and_conditions_required: + self.extra_gn_options.append('chip_terms_and_conditions_required=true') + else: + self.extra_gn_options.append('chip_terms_and_conditions_required=false') + if self.board == HostBoard.ARM64: if not use_clang: raise Exception("Cross compile only supported using clang") diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt index 93e375c857a83f..82c249bd615f62 100644 --- a/scripts/build/testdata/all_targets_linux_x64.txt +++ b/scripts/build/testdata/all_targets_linux_x64.txt @@ -9,7 +9,7 @@ efr32-{brd2704b,brd4316a,brd4317a,brd4318a,brd4319a,brd4186a,brd4187a,brd2601b,b esp32-{m5stack,c3devkit,devkitc,qemu}-{all-clusters,all-clusters-minimal,energy-management,ota-provider,ota-requestor,shell,light,lock,bridge,temperature-measurement,ota-requestor,tests}[-rpc][-ipv6only][-tracing][-data-model-disabled][-data-model-enabled] genio-lighting-app linux-fake-tests[-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-pw-fuzztest][-coverage][-dmalloc][-clang] -linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,light-data-model-no-unique-id,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,fabric-admin,fabric-bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,microwave-oven,refrigerator,rvc,air-purifier,lit-icd,air-quality-sensor,network-manager,energy-management}[-nodeps][-nlfaultinject][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-no-shell][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-pw-fuzztest][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui][-evse-test-event][-enable-dnssd-tests][-disable-dnssd-tests][-chip-casting-simplified][-data-model-check][-data-model-disabled][-data-model-enabled][-check-failure-die] +linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,kotlin-matter-controller,minmdns,light,light-data-model-no-unique-id,lock,shell,ota-provider,ota-requestor,simulated-app1,simulated-app2,python-bindings,tv-app,tv-casting-app,bridge,fabric-admin,fabric-bridge,tests,chip-cert,address-resolve-tool,contact-sensor,dishwasher,microwave-oven,refrigerator,rvc,air-purifier,lit-icd,air-quality-sensor,network-manager,energy-management,terms-and-conditions}[-nodeps][-nlfaultinject][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-no-shell][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-ossfuzz][-pw-fuzztest][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui][-evse-test-event][-enable-dnssd-tests][-disable-dnssd-tests][-chip-casting-simplified][-data-model-check][-data-model-disabled][-data-model-enabled][-check-failure-die][-terms-and-conditions] linux-x64-efr32-test-runner[-clang] imx-{chip-tool,lighting-app,thermostat,all-clusters-app,all-clusters-minimal-app,ota-provider-app}[-release] infineon-psoc6-{lock,light,all-clusters,all-clusters-minimal}[-ota][-updateimage][-trustm] diff --git a/scripts/tests/local.py b/scripts/tests/local.py index 75509f65e00510..49ee3abbe76841 100755 --- a/scripts/tests/local.py +++ b/scripts/tests/local.py @@ -354,6 +354,7 @@ def as_runner(path): CHIP_RVC_APP: {as_runner(f'out/{target_prefix}-rvc-no-ble-clang-boringssl/chip-rvc-app')} NETWORK_MANAGEMENT_APP: { as_runner(f'out/{target_prefix}-network-manager-ipv6only-no-ble-clang-boringssl/matter-network-manager-app')} + TERMS_AND_CONDITIONS_APP: {as_runner(f'out/{target_prefix}-terms-and-conditions/chip-terms-and-conditions-app')} TRACE_APP: out/trace_data/app-{{SCRIPT_BASE_NAME}} TRACE_TEST_JSON: out/trace_data/test-{{SCRIPT_BASE_NAME}} TRACE_TEST_PERFETTO: out/trace_data/test-{{SCRIPT_BASE_NAME}} diff --git a/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/IMClusterCommandHandler.cpp b/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/IMClusterCommandHandler.cpp index bd3030cf63ddc9..fb772205ed90bd 100644 --- a/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/IMClusterCommandHandler.cpp +++ b/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/IMClusterCommandHandler.cpp @@ -545,62 +545,6 @@ void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandP } // namespace FaultInjection -namespace GeneralCommissioning { - -void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandPath & aCommandPath, TLV::TLVReader & aDataTlv) -{ - CHIP_ERROR TLVError = CHIP_NO_ERROR; - bool wasHandled = false; - { - switch (aCommandPath.mCommandId) - { - case Commands::ArmFailSafe::Id: { - Commands::ArmFailSafe::DecodableType commandData; - TLVError = DataModel::Decode(aDataTlv, commandData); - if (TLVError == CHIP_NO_ERROR) - { - wasHandled = emberAfGeneralCommissioningClusterArmFailSafeCallback(apCommandObj, aCommandPath, commandData); - } - break; - } - case Commands::SetRegulatoryConfig::Id: { - Commands::SetRegulatoryConfig::DecodableType commandData; - TLVError = DataModel::Decode(aDataTlv, commandData); - if (TLVError == CHIP_NO_ERROR) - { - wasHandled = emberAfGeneralCommissioningClusterSetRegulatoryConfigCallback(apCommandObj, aCommandPath, commandData); - } - break; - } - case Commands::CommissioningComplete::Id: { - Commands::CommissioningComplete::DecodableType commandData; - TLVError = DataModel::Decode(aDataTlv, commandData); - if (TLVError == CHIP_NO_ERROR) - { - wasHandled = - emberAfGeneralCommissioningClusterCommissioningCompleteCallback(apCommandObj, aCommandPath, commandData); - } - break; - } - default: { - // Unrecognized command ID, error status will apply. - apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::UnsupportedCommand); - ChipLogError(Zcl, "Unknown command " ChipLogFormatMEI " for cluster " ChipLogFormatMEI, - ChipLogValueMEI(aCommandPath.mCommandId), ChipLogValueMEI(aCommandPath.mClusterId)); - return; - } - } - } - - if (CHIP_NO_ERROR != TLVError || !wasHandled) - { - apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::InvalidCommand); - ChipLogProgress(Zcl, "Failed to dispatch command, TLVError=%" CHIP_ERROR_FORMAT, TLVError.Format()); - } -} - -} // namespace GeneralCommissioning - namespace GeneralDiagnostics { void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandPath & aCommandPath, TLV::TLVReader & aDataTlv) @@ -1958,9 +1902,6 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, TLV: case Clusters::FaultInjection::Id: Clusters::FaultInjection::DispatchServerCommand(apCommandObj, aCommandPath, aReader); break; - case Clusters::GeneralCommissioning::Id: - Clusters::GeneralCommissioning::DispatchServerCommand(apCommandObj, aCommandPath, aReader); - break; case Clusters::GeneralDiagnostics::Id: Clusters::GeneralDiagnostics::DispatchServerCommand(apCommandObj, aCommandPath, aReader); break; diff --git a/scripts/tools/zap/tests/outputs/lighting-app/app-templates/IMClusterCommandHandler.cpp b/scripts/tools/zap/tests/outputs/lighting-app/app-templates/IMClusterCommandHandler.cpp index aee293138b6ad5..6baabbe22362bf 100644 --- a/scripts/tools/zap/tests/outputs/lighting-app/app-templates/IMClusterCommandHandler.cpp +++ b/scripts/tools/zap/tests/outputs/lighting-app/app-templates/IMClusterCommandHandler.cpp @@ -369,62 +369,6 @@ void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandP } // namespace EthernetNetworkDiagnostics -namespace GeneralCommissioning { - -void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandPath & aCommandPath, TLV::TLVReader & aDataTlv) -{ - CHIP_ERROR TLVError = CHIP_NO_ERROR; - bool wasHandled = false; - { - switch (aCommandPath.mCommandId) - { - case Commands::ArmFailSafe::Id: { - Commands::ArmFailSafe::DecodableType commandData; - TLVError = DataModel::Decode(aDataTlv, commandData); - if (TLVError == CHIP_NO_ERROR) - { - wasHandled = emberAfGeneralCommissioningClusterArmFailSafeCallback(apCommandObj, aCommandPath, commandData); - } - break; - } - case Commands::SetRegulatoryConfig::Id: { - Commands::SetRegulatoryConfig::DecodableType commandData; - TLVError = DataModel::Decode(aDataTlv, commandData); - if (TLVError == CHIP_NO_ERROR) - { - wasHandled = emberAfGeneralCommissioningClusterSetRegulatoryConfigCallback(apCommandObj, aCommandPath, commandData); - } - break; - } - case Commands::CommissioningComplete::Id: { - Commands::CommissioningComplete::DecodableType commandData; - TLVError = DataModel::Decode(aDataTlv, commandData); - if (TLVError == CHIP_NO_ERROR) - { - wasHandled = - emberAfGeneralCommissioningClusterCommissioningCompleteCallback(apCommandObj, aCommandPath, commandData); - } - break; - } - default: { - // Unrecognized command ID, error status will apply. - apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::UnsupportedCommand); - ChipLogError(Zcl, "Unknown command " ChipLogFormatMEI " for cluster " ChipLogFormatMEI, - ChipLogValueMEI(aCommandPath.mCommandId), ChipLogValueMEI(aCommandPath.mClusterId)); - return; - } - } - } - - if (CHIP_NO_ERROR != TLVError || !wasHandled) - { - apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::InvalidCommand); - ChipLogProgress(Zcl, "Failed to dispatch command, TLVError=%" CHIP_ERROR_FORMAT, TLVError.Format()); - } -} - -} // namespace GeneralCommissioning - namespace GeneralDiagnostics { void DispatchServerCommand(CommandHandler * apCommandObj, const ConcreteCommandPath & aCommandPath, TLV::TLVReader & aDataTlv) @@ -1115,9 +1059,6 @@ void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, TLV: case Clusters::EthernetNetworkDiagnostics::Id: Clusters::EthernetNetworkDiagnostics::DispatchServerCommand(apCommandObj, aCommandPath, aReader); break; - case Clusters::GeneralCommissioning::Id: - Clusters::GeneralCommissioning::DispatchServerCommand(apCommandObj, aCommandPath, aReader); - break; case Clusters::GeneralDiagnostics::Id: Clusters::GeneralDiagnostics::DispatchServerCommand(apCommandObj, aCommandPath, aReader); break; diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 98e7225549aaa4..3451f452f65844 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -76,6 +76,7 @@ buildconfig_header("app_buildconfig") { "CHIP_DEVICE_CONFIG_DYNAMIC_SERVER=${chip_build_controller_dynamic_server}", "CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP=${chip_enable_busy_handling_for_operational_session_setup}", "CHIP_CONFIG_DATA_MODEL_CHECK_DIE_ON_FAILURE=${chip_data_model_check_die_on_failure}", + "CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED=${chip_terms_and_conditions_required}", ] if (chip_use_data_model_interface == "disabled") { diff --git a/src/app/FailSafeContext.cpp b/src/app/FailSafeContext.cpp index 95a5b267f2aa5e..372ee323930d67 100644 --- a/src/app/FailSafeContext.cpp +++ b/src/app/FailSafeContext.cpp @@ -86,9 +86,12 @@ void FailSafeContext::ScheduleFailSafeCleanup(FabricIndex fabricIndex, bool addN SetFailSafeArmed(false); ChipDeviceEvent event{ .Type = DeviceEventType::kFailSafeTimerExpired, - .FailSafeTimerExpired = { .fabricIndex = fabricIndex, - .addNocCommandHasBeenInvoked = addNocCommandInvoked, - .updateNocCommandHasBeenInvoked = updateNocCommandInvoked } }; + .FailSafeTimerExpired = { + .fabricIndex = fabricIndex, + .addNocCommandHasBeenInvoked = addNocCommandInvoked, + .updateNocCommandHasBeenInvoked = updateNocCommandInvoked, + .updateTermsAndConditionsHasBeenInvoked = mUpdateTermsAndConditionsHasBeenInvoked, + } }; CHIP_ERROR status = PlatformMgr().PostEvent(&event); if (status != CHIP_NO_ERROR) diff --git a/src/app/FailSafeContext.h b/src/app/FailSafeContext.h index 48e11e0845395b..af177bd2a8d5fc 100644 --- a/src/app/FailSafeContext.h +++ b/src/app/FailSafeContext.h @@ -56,6 +56,7 @@ class FailSafeContext void SetUpdateNocCommandInvoked() { mUpdateNocCommandHasBeenInvoked = true; } void SetAddTrustedRootCertInvoked() { mAddTrustedRootCertHasBeenInvoked = true; } void SetCsrRequestForUpdateNoc(bool isForUpdateNoc) { mIsCsrRequestForUpdateNoc = isForUpdateNoc; } + void SetUpdateTermsAndConditionsHasBeenInvoked() { mUpdateTermsAndConditionsHasBeenInvoked = true; } /** * @brief @@ -91,6 +92,7 @@ class FailSafeContext bool UpdateNocCommandHasBeenInvoked() const { return mUpdateNocCommandHasBeenInvoked; } bool AddTrustedRootCertHasBeenInvoked() const { return mAddTrustedRootCertHasBeenInvoked; } bool IsCsrRequestForUpdateNoc() const { return mIsCsrRequestForUpdateNoc; } + bool UpdateTermsAndConditionsHasBeenInvoked() { return mUpdateTermsAndConditionsHasBeenInvoked; } FabricIndex GetFabricIndex() const { @@ -109,8 +111,9 @@ class FailSafeContext bool mUpdateNocCommandHasBeenInvoked = false; bool mAddTrustedRootCertHasBeenInvoked = false; // The fact of whether a CSR occurred at all is stored elsewhere. - bool mIsCsrRequestForUpdateNoc = false; - FabricIndex mFabricIndex = kUndefinedFabricIndex; + bool mIsCsrRequestForUpdateNoc = false; + FabricIndex mFabricIndex = kUndefinedFabricIndex; + bool mUpdateTermsAndConditionsHasBeenInvoked = false; /** * @brief @@ -140,11 +143,12 @@ class FailSafeContext { SetFailSafeArmed(false); - mAddNocCommandHasBeenInvoked = false; - mUpdateNocCommandHasBeenInvoked = false; - mAddTrustedRootCertHasBeenInvoked = false; - mFailSafeBusy = false; - mIsCsrRequestForUpdateNoc = false; + mAddNocCommandHasBeenInvoked = false; + mUpdateNocCommandHasBeenInvoked = false; + mAddTrustedRootCertHasBeenInvoked = false; + mFailSafeBusy = false; + mIsCsrRequestForUpdateNoc = false; + mUpdateTermsAndConditionsHasBeenInvoked = false; } void FailSafeTimerExpired(); diff --git a/src/app/chip_data_model.cmake b/src/app/chip_data_model.cmake index e2d05adb01e4a1..56b70dced29ead 100644 --- a/src/app/chip_data_model.cmake +++ b/src/app/chip_data_model.cmake @@ -79,8 +79,9 @@ function(chip_configure_data_model APP_TARGET) # CMAKE data model auto-includes the server side implementation target_sources(${APP_TARGET} ${SCOPE} ${CHIP_APP_BASE_DIR}/server/AclStorage.cpp - ${CHIP_APP_BASE_DIR}/server/DefaultAclStorage.cpp ${CHIP_APP_BASE_DIR}/server/CommissioningWindowManager.cpp + ${CHIP_APP_BASE_DIR}/server/DefaultAclStorage.cpp + ${CHIP_APP_BASE_DIR}/server/DefaultTermsAndConditionsProvider.cpp ${CHIP_APP_BASE_DIR}/server/Dnssd.cpp ${CHIP_APP_BASE_DIR}/server/EchoHandler.cpp ${CHIP_APP_BASE_DIR}/server/OnboardingCodesUtil.cpp diff --git a/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp b/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp index 4bf97face53740..04f7e4e5df15e4 100644 --- a/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp +++ b/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp @@ -1,6 +1,6 @@ /** * - * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2021-2024 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. @@ -21,20 +21,31 @@ ******************************************************************************* ******************************************************************************/ +#include "general-commissioning-server.h" + #include #include +#include #include #include +#include +#include #include +#include #include #include -#include -#include +#include #include #include #include #include #include +#include + +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED +#include //nogncheck +#include //nogncheck +#endif using namespace chip; using namespace chip::app; @@ -42,6 +53,7 @@ using namespace chip::app::Clusters; using namespace chip::app::Clusters::GeneralCommissioning; using namespace chip::app::Clusters::GeneralCommissioning::Attributes; using namespace chip::DeviceLayer; +using chip::app::Clusters::GeneralCommissioning::CommissioningErrorEnum; using Transport::SecureSession; using Transport::Session; @@ -50,18 +62,21 @@ using Transport::Session; { \ if (!::chip::ChipError::IsSuccess(expr)) \ { \ - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::code, #expr); \ - return true; \ + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::code, #expr); \ + return; \ } \ } while (false) namespace { -class GeneralCommissioningAttrAccess : public AttributeAccessInterface +class GeneralCommissioningGlobalInstance : public AttributeAccessInterface, public CommandHandlerInterface { public: // Register for the GeneralCommissioning cluster on all endpoints. - GeneralCommissioningAttrAccess() : AttributeAccessInterface(Optional::Missing(), GeneralCommissioning::Id) {} + GeneralCommissioningGlobalInstance() : + AttributeAccessInterface(Optional::Missing(), GeneralCommissioning::Id), + CommandHandlerInterface(Optional::Missing(), GeneralCommissioning::Id) + {} CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; @@ -69,11 +84,20 @@ class GeneralCommissioningAttrAccess : public AttributeAccessInterface CHIP_ERROR ReadIfSupported(CHIP_ERROR (ConfigurationManager::*getter)(uint8_t &), AttributeValueEncoder & aEncoder); CHIP_ERROR ReadBasicCommissioningInfo(AttributeValueEncoder & aEncoder); CHIP_ERROR ReadSupportsConcurrentConnection(AttributeValueEncoder & aEncoder); + + void InvokeCommand(HandlerContext & ctx) override; + + void HandleArmFailSafe(HandlerContext & ctx, const Commands::ArmFailSafe::DecodableType & commandData); + void HandleCommissioningComplete(HandlerContext & ctx, const Commands::CommissioningComplete::DecodableType & commandData); + void HandleSetRegulatoryConfig(HandlerContext & ctx, const Commands::SetRegulatoryConfig::DecodableType & commandData); +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + void HandleSetTCAcknowledgements(HandlerContext & ctx, const Commands::SetTCAcknowledgements::DecodableType & commandData); +#endif }; -GeneralCommissioningAttrAccess gAttrAccess; +GeneralCommissioningGlobalInstance gGeneralCommissioningInstance; -CHIP_ERROR GeneralCommissioningAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +CHIP_ERROR GeneralCommissioningGlobalInstance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) { if (aPath.mClusterId != GeneralCommissioning::Id) { @@ -83,6 +107,14 @@ CHIP_ERROR GeneralCommissioningAttrAccess::Read(const ConcreteReadAttributePath switch (aPath.mAttributeId) { + case FeatureMap::Id: { + BitFlags features; +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + features.Set(GeneralCommissioning::Feature::kTermsAndConditions); +#endif // CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + return aEncoder.Encode(features); + } + case RegulatoryConfig::Id: { return ReadIfSupported(&ConfigurationManager::GetRegulatoryLocation, aEncoder); } @@ -95,6 +127,58 @@ CHIP_ERROR GeneralCommissioningAttrAccess::Read(const ConcreteReadAttributePath case SupportsConcurrentConnection::Id: { return ReadSupportsConcurrentConnection(aEncoder); } +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + case TCAcceptedVersion::Id: { + TermsAndConditionsProvider * tcProvider = TermsAndConditionsManager::GetInstance(); + Optional outTermsAndConditions; + + VerifyOrReturnError(nullptr != tcProvider, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + ReturnErrorOnFailure(tcProvider->GetAcceptance(outTermsAndConditions)); + + return aEncoder.Encode(outTermsAndConditions.ValueOr(TermsAndConditions(0, 0)).GetVersion()); + } + case TCMinRequiredVersion::Id: { + TermsAndConditionsProvider * tcProvider = TermsAndConditionsManager::GetInstance(); + Optional outTermsAndConditions; + + VerifyOrReturnError(nullptr != tcProvider, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + ReturnErrorOnFailure(tcProvider->GetRequirements(outTermsAndConditions)); + + return aEncoder.Encode(outTermsAndConditions.ValueOr(TermsAndConditions(0, 0)).GetVersion()); + } + case TCAcknowledgements::Id: { + TermsAndConditionsProvider * tcProvider = TermsAndConditionsManager::GetInstance(); + Optional outTermsAndConditions; + + VerifyOrReturnError(nullptr != tcProvider, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + ReturnErrorOnFailure(tcProvider->GetAcceptance(outTermsAndConditions)); + + return aEncoder.Encode(outTermsAndConditions.ValueOr(TermsAndConditions(0, 0)).GetValue()); + } + case TCAcknowledgementsRequired::Id: { + TermsAndConditionsProvider * tcProvider = TermsAndConditionsManager::GetInstance(); + bool acknowledgementsRequired; + + VerifyOrReturnError(nullptr != tcProvider, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + ReturnErrorOnFailure(tcProvider->GetAcknowledgementsRequired(acknowledgementsRequired)); + + return aEncoder.Encode(acknowledgementsRequired); + } + case TCUpdateDeadline::Id: { + TermsAndConditionsProvider * tcProvider = TermsAndConditionsManager::GetInstance(); + Optional outUpdateAcceptanceDeadline; + + VerifyOrReturnError(nullptr != tcProvider, CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + ReturnErrorOnFailure(tcProvider->GetUpdateAcceptanceDeadline(outUpdateAcceptanceDeadline)); + + if (!outUpdateAcceptanceDeadline.HasValue()) + { + return aEncoder.EncodeNull(); + } + + return aEncoder.Encode(outUpdateAcceptanceDeadline.Value()); + } +#endif // CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED default: { break; } @@ -102,10 +186,10 @@ CHIP_ERROR GeneralCommissioningAttrAccess::Read(const ConcreteReadAttributePath return CHIP_NO_ERROR; } -CHIP_ERROR GeneralCommissioningAttrAccess::ReadIfSupported(CHIP_ERROR (ConfigurationManager::*getter)(uint8_t &), - AttributeValueEncoder & aEncoder) +CHIP_ERROR GeneralCommissioningGlobalInstance::ReadIfSupported(CHIP_ERROR (ConfigurationManager::*getter)(uint8_t &), + AttributeValueEncoder & aEncoder) { - uint8_t data; + uint8_t data = 0; CHIP_ERROR err = (DeviceLayer::ConfigurationMgr().*getter)(data); if (err == CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE) { @@ -115,25 +199,24 @@ CHIP_ERROR GeneralCommissioningAttrAccess::ReadIfSupported(CHIP_ERROR (Configura { return err; } - return aEncoder.Encode(data); } -CHIP_ERROR GeneralCommissioningAttrAccess::ReadBasicCommissioningInfo(AttributeValueEncoder & aEncoder) +CHIP_ERROR GeneralCommissioningGlobalInstance::ReadBasicCommissioningInfo(AttributeValueEncoder & aEncoder) { - BasicCommissioningInfo::TypeInfo::Type basicCommissioningInfo; + BasicCommissioningInfo::TypeInfo::Type info; // TODO: The commissioner might use the critical parameters in BasicCommissioningInfo to initialize // the CommissioningParameters at the beginning of commissioning flow. - basicCommissioningInfo.failSafeExpiryLengthSeconds = CHIP_DEVICE_CONFIG_FAILSAFE_EXPIRY_LENGTH_SEC; - basicCommissioningInfo.maxCumulativeFailsafeSeconds = CHIP_DEVICE_CONFIG_MAX_CUMULATIVE_FAILSAFE_SEC; + info.failSafeExpiryLengthSeconds = CHIP_DEVICE_CONFIG_FAILSAFE_EXPIRY_LENGTH_SEC; + info.maxCumulativeFailsafeSeconds = CHIP_DEVICE_CONFIG_MAX_CUMULATIVE_FAILSAFE_SEC; static_assert(CHIP_DEVICE_CONFIG_MAX_CUMULATIVE_FAILSAFE_SEC >= CHIP_DEVICE_CONFIG_FAILSAFE_EXPIRY_LENGTH_SEC, "Max cumulative failsafe seconds must be larger than failsafe expiry length seconds"); - return aEncoder.Encode(basicCommissioningInfo); + return aEncoder.Encode(info); } -CHIP_ERROR GeneralCommissioningAttrAccess::ReadSupportsConcurrentConnection(AttributeValueEncoder & aEncoder) +CHIP_ERROR GeneralCommissioningGlobalInstance::ReadSupportsConcurrentConnection(AttributeValueEncoder & aEncoder) { SupportsConcurrentConnection::TypeInfo::Type supportsConcurrentConnection; @@ -144,10 +227,105 @@ CHIP_ERROR GeneralCommissioningAttrAccess::ReadSupportsConcurrentConnection(Attr return aEncoder.Encode(supportsConcurrentConnection); } -} // anonymous namespace +void GeneralCommissioningGlobalInstance::InvokeCommand(HandlerContext & handlerContext) +{ + switch (handlerContext.mRequestPath.mCommandId) + { + case Commands::ArmFailSafe::Id: + CommandHandlerInterface::HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleArmFailSafe(ctx, commandData); }); + break; + + case Commands::CommissioningComplete::Id: + CommandHandlerInterface::HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleCommissioningComplete(ctx, commandData); }); + break; -bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler * commandObj, - const app::ConcreteCommandPath & commandPath, + case Commands::SetRegulatoryConfig::Id: + CommandHandlerInterface::HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleSetRegulatoryConfig(ctx, commandData); }); + break; + +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + case Commands::SetTCAcknowledgements::Id: + CommandHandlerInterface::HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleSetTCAcknowledgements(ctx, commandData); }); + break; +#endif + } +} + +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED +typedef struct sTermsAndConditionsState +{ + Optional acceptance; + bool acknowledgementsRequired; + Optional requirements; + Optional updateAcceptanceDeadline; +} TermsAndConditionsState; + +CHIP_ERROR GetTermsAndConditionsAttributeState(TermsAndConditionsProvider * tcProvider, + TermsAndConditionsState & outTermsAndConditionsState) +{ + TermsAndConditionsState termsAndConditionsState; + + ReturnErrorOnFailure(tcProvider->GetAcceptance(termsAndConditionsState.acceptance)); + ReturnErrorOnFailure(tcProvider->GetAcknowledgementsRequired(termsAndConditionsState.acknowledgementsRequired)); + ReturnErrorOnFailure(tcProvider->GetRequirements(termsAndConditionsState.requirements)); + ReturnErrorOnFailure(tcProvider->GetUpdateAcceptanceDeadline(termsAndConditionsState.updateAcceptanceDeadline)); + + outTermsAndConditionsState = termsAndConditionsState; + return CHIP_NO_ERROR; +} + +void NotifyTermsAndConditionsAttributeChangeIfRequired(const TermsAndConditionsState & initialState, + const TermsAndConditionsState & updatedState) +{ + // Notify on TCAcknowledgementsRequired change + if (initialState.acknowledgementsRequired != updatedState.acknowledgementsRequired) + { + MatterReportingAttributeChangeCallback(kRootEndpointId, GeneralCommissioning::Id, TCAcknowledgementsRequired::Id); + } + + // Notify on TCAcceptedVersion change + if ((initialState.acceptance.HasValue() != updatedState.acceptance.HasValue()) || + (initialState.acceptance.HasValue() && + (initialState.acceptance.Value().GetVersion() != updatedState.acceptance.Value().GetVersion()))) + { + MatterReportingAttributeChangeCallback(kRootEndpointId, GeneralCommissioning::Id, TCAcceptedVersion::Id); + } + + // Notify on TCAcknowledgements change + if ((initialState.acceptance.HasValue() != updatedState.acceptance.HasValue()) || + (initialState.acceptance.HasValue() && + (initialState.acceptance.Value().GetValue() != updatedState.acceptance.Value().GetValue()))) + { + MatterReportingAttributeChangeCallback(kRootEndpointId, GeneralCommissioning::Id, TCAcknowledgements::Id); + } + + // Notify on TCRequirements change + if ((initialState.requirements.HasValue() != updatedState.requirements.HasValue()) || + (initialState.requirements.HasValue() && + (initialState.requirements.Value().GetVersion() != updatedState.requirements.Value().GetVersion() || + initialState.requirements.Value().GetValue() != updatedState.requirements.Value().GetValue()))) + { + MatterReportingAttributeChangeCallback(kRootEndpointId, GeneralCommissioning::Id, TCMinRequiredVersion::Id); + } + + // Notify on TCUpdateDeadline change + if ((initialState.updateAcceptanceDeadline.HasValue() != updatedState.updateAcceptanceDeadline.HasValue()) || + (initialState.updateAcceptanceDeadline.HasValue() && + (initialState.updateAcceptanceDeadline.Value() != updatedState.updateAcceptanceDeadline.Value()))) + { + MatterReportingAttributeChangeCallback(kRootEndpointId, GeneralCommissioning::Id, TCUpdateDeadline::Id); + } +} +#endif // CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + +void GeneralCommissioningGlobalInstance::HandleArmFailSafe(HandlerContext & ctx, const Commands::ArmFailSafe::DecodableType & commandData) { MATTER_TRACE_SCOPE("ArmFailSafe", "GeneralCommissioning"); @@ -163,8 +341,7 @@ bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler * * If the fail-safe timer was currently armed, and current accessing fabric matches the fail-safe * context’s Fabric Index, then the fail-safe timer SHALL be re-armed. */ - - FabricIndex accessingFabricIndex = commandObj->GetAccessingFabricIndex(); + FabricIndex accessingFabricIndex = ctx.mCommandHandler.GetAccessingFabricIndex(); // We do not allow CASE connections to arm the failsafe for the first time while the commissioning window is open in order // to allow commissioners the opportunity to obtain this failsafe for the purpose of commissioning @@ -175,10 +352,9 @@ bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler * // to allow commissioners the opportunity to obtain this failsafe for the purpose of commissioning if (!failSafeContext.IsFailSafeArmed() && Server::GetInstance().GetCommissioningWindowManager().IsCommissioningWindowOpen() && - commandObj->GetSubjectDescriptor().authMode == Access::AuthMode::kCase) + ctx.mCommandHandler.GetSubjectDescriptor().authMode == Access::AuthMode::kCase) { response.errorCode = CommissioningErrorEnum::kBusyWithOtherAdmin; - commandObj->AddResponse(commandPath, response); } else if (commandData.expiryLengthSeconds == 0) { @@ -187,30 +363,26 @@ bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler * // Don't set the breadcrumb, since expiring the failsafe should // reset it anyway. response.errorCode = CommissioningErrorEnum::kOk; - commandObj->AddResponse(commandPath, response); } else { CheckSuccess( failSafeContext.ArmFailSafe(accessingFabricIndex, System::Clock::Seconds16(commandData.expiryLengthSeconds)), Failure); - Breadcrumb::Set(commandPath.mEndpointId, commandData.breadcrumb); + Breadcrumb::Set(ctx.mRequestPath.mEndpointId, commandData.breadcrumb); response.errorCode = CommissioningErrorEnum::kOk; - commandObj->AddResponse(commandPath, response); } } else { response.errorCode = CommissioningErrorEnum::kBusyWithOtherAdmin; - commandObj->AddResponse(commandPath, response); } - return true; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } -bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback( - app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, - const Commands::CommissioningComplete::DecodableType & commandData) +void GeneralCommissioningGlobalInstance::HandleCommissioningComplete( + HandlerContext & ctx, const Commands::CommissioningComplete::DecodableType & commandData) { MATTER_TRACE_SCOPE("CommissioningComplete", "GeneralCommissioning"); @@ -221,130 +393,285 @@ bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback( ChipLogProgress(FailSafe, "GeneralCommissioning: Received CommissioningComplete"); Commands::CommissioningCompleteResponse::Type response; + CHIP_ERROR err; + + // Fail-safe must be armed if (!failSafe.IsFailSafeArmed()) { response.errorCode = CommissioningErrorEnum::kNoFailSafe; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + return; } - else + +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + TermsAndConditionsProvider * tcProvider = TermsAndConditionsManager::GetInstance(); + + // Ensure required terms and conditions have been accepted, then attempt to commit + if (nullptr != tcProvider) { - SessionHandle handle = commandObj->GetExchangeContext()->GetSessionHandle(); - // If not a CASE session, or the fabric does not match the fail-safe, - // error out. - if (handle->GetSessionType() != Session::SessionType::kSecure || - handle->AsSecureSession()->GetSecureSessionType() != SecureSession::Type::kCASE || - !failSafe.MatchesFabricIndex(commandObj->GetAccessingFabricIndex())) + Optional requiredTermsAndConditionsMaybe; + Optional acceptedTermsAndConditionsMaybe; + + CheckSuccess(tcProvider->GetRequirements(requiredTermsAndConditionsMaybe), Failure); + CheckSuccess(tcProvider->GetAcceptance(acceptedTermsAndConditionsMaybe), Failure); + + if (requiredTermsAndConditionsMaybe.HasValue() && !acceptedTermsAndConditionsMaybe.HasValue()) { - response.errorCode = CommissioningErrorEnum::kInvalidAuthentication; - ChipLogError(FailSafe, "GeneralCommissioning: Got commissioning complete in invalid security context"); + response.errorCode = CommissioningErrorEnum::kTCAcknowledgementsNotReceived; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + return; } - else + + if (requiredTermsAndConditionsMaybe.HasValue() && acceptedTermsAndConditionsMaybe.HasValue()) { - if (failSafe.NocCommandHasBeenInvoked()) + TermsAndConditions requiredTermsAndConditions = requiredTermsAndConditionsMaybe.Value(); + TermsAndConditions acceptedTermsAndConditions = acceptedTermsAndConditionsMaybe.Value(); + + if (!requiredTermsAndConditions.ValidateVersion(acceptedTermsAndConditions)) { - CHIP_ERROR err = fabricTable.CommitPendingFabricData(); - if (err != CHIP_NO_ERROR) - { - // No need to revert on error: CommitPendingFabricData always reverts if not fully successful. - ChipLogError(FailSafe, "GeneralCommissioning: Failed to commit pending fabric data: %" CHIP_ERROR_FORMAT, - err.Format()); - } - else - { - ChipLogProgress(FailSafe, "GeneralCommissioning: Successfully commited pending fabric data"); - } - CheckSuccess(err, Failure); + response.errorCode = CommissioningErrorEnum::kTCMinVersionNotMet; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + return; } - /* - * Pass fabric of commissioner to DeviceControlSvr. - * This allows device to send messages back to commissioner. - * Once bindings are implemented, this may no longer be needed. - */ - failSafe.DisarmFailSafe(); - CheckSuccess( - devCtrl->PostCommissioningCompleteEvent(handle->AsSecureSession()->GetPeerNodeId(), handle->GetFabricIndex()), - Failure); + if (!requiredTermsAndConditions.ValidateValue(acceptedTermsAndConditions)) + { + response.errorCode = CommissioningErrorEnum::kRequiredTCNotAccepted; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + return; + } + } - Breadcrumb::Set(commandPath.mEndpointId, 0); - response.errorCode = CommissioningErrorEnum::kOk; + if (failSafe.UpdateTermsAndConditionsHasBeenInvoked()) + { + // Commit terms and conditions acceptance on commissioning complete + err = tcProvider->CommitAcceptance(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(FailSafe, "GeneralCommissioning: Failed to commit terms and conditions: %" CHIP_ERROR_FORMAT, + err.Format()); + } + else + { + ChipLogProgress(FailSafe, "GeneralCommissioning: Successfully committed terms and conditions"); + } + CheckSuccess(err, Failure); } } +#endif // CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED - commandObj->AddResponse(commandPath, response); + SessionHandle handle = ctx.mCommandHandler.GetExchangeContext()->GetSessionHandle(); - return true; + // Ensure it's a valid CASE session + if (handle->GetSessionType() != Session::SessionType::kSecure || + handle->AsSecureSession()->GetSecureSessionType() != SecureSession::Type::kCASE || + !failSafe.MatchesFabricIndex(ctx.mCommandHandler.GetAccessingFabricIndex())) + { + response.errorCode = CommissioningErrorEnum::kInvalidAuthentication; + ChipLogError(FailSafe, "GeneralCommissioning: Got commissioning complete in invalid security context"); + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + return; + } + + // Handle NOC commands + if (failSafe.NocCommandHasBeenInvoked()) + { + err = fabricTable.CommitPendingFabricData(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(FailSafe, "GeneralCommissioning: Failed to commit pending fabric data: %" CHIP_ERROR_FORMAT, err.Format()); + // CommitPendingFabricData reverts on error, no need to revert explicitly + } + else + { + ChipLogProgress(FailSafe, "GeneralCommissioning: Successfully committed pending fabric data"); + } + CheckSuccess(err, Failure); + } + + // Disarm the fail-safe and notify the DeviceControlServer + failSafe.DisarmFailSafe(); + err = devCtrl->PostCommissioningCompleteEvent(handle->AsSecureSession()->GetPeerNodeId(), handle->GetFabricIndex()); + CheckSuccess(err, Failure); + + Breadcrumb::Set(ctx.mRequestPath.mEndpointId, 0); + response.errorCode = CommissioningErrorEnum::kOk; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } -bool emberAfGeneralCommissioningClusterSetRegulatoryConfigCallback(app::CommandHandler * commandObj, - const app::ConcreteCommandPath & commandPath, +void GeneralCommissioningGlobalInstance::HandleSetRegulatoryConfig(HandlerContext & ctx, const Commands::SetRegulatoryConfig::DecodableType & commandData) { MATTER_TRACE_SCOPE("SetRegulatoryConfig", "GeneralCommissioning"); DeviceControlServer * server = &DeviceLayer::DeviceControlServer::DeviceControlSvr(); Commands::SetRegulatoryConfigResponse::Type response; - auto & countryCode = commandData.countryCode; - bool isValidLength = countryCode.size() == DeviceLayer::ConfigurationManager::kMaxLocationLength; - if (!isValidLength) + + if (countryCode.size() != ConfigurationManager::kMaxLocationLength) { ChipLogError(Zcl, "Invalid country code: '%.*s'", static_cast(countryCode.size()), countryCode.data()); - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::ConstraintError); - return true; + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::ConstraintError); + return; } if (commandData.newRegulatoryConfig > RegulatoryLocationTypeEnum::kIndoorOutdoor) { response.errorCode = CommissioningErrorEnum::kValueOutsideRange; - // TODO: How does using the country code in debug text make sense, if - // the real issue is the newRegulatoryConfig value? - response.debugText = countryCode; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + return; } - else + + uint8_t locationCapability; + if (ConfigurationMgr().GetLocationCapability(locationCapability) != CHIP_NO_ERROR) { - uint8_t locationCapability; - uint8_t location = to_underlying(commandData.newRegulatoryConfig); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::Failure); + return; + } + + uint8_t location = to_underlying(commandData.newRegulatoryConfig); - CheckSuccess(ConfigurationMgr().GetLocationCapability(locationCapability), Failure); + // If the LocationCapability attribute is not Indoor/Outdoor and the NewRegulatoryConfig value received does not match + // either the Indoor or Outdoor fixed value in LocationCapability. + if ((locationCapability != to_underlying(RegulatoryLocationTypeEnum::kIndoorOutdoor)) && (location != locationCapability)) + { + response.errorCode = CommissioningErrorEnum::kValueOutsideRange; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + return; + } - // If the LocationCapability attribute is not Indoor/Outdoor and the NewRegulatoryConfig value received does not match - // either the Indoor or Outdoor fixed value in LocationCapability. - if ((locationCapability != to_underlying(RegulatoryLocationTypeEnum::kIndoorOutdoor)) && (location != locationCapability)) + CheckSuccess(server->SetRegulatoryConfig(location, countryCode), Failure); + Breadcrumb::Set(ctx.mRequestPath.mEndpointId, commandData.breadcrumb); + response.errorCode = CommissioningErrorEnum::kOk; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); +} + +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED +void GeneralCommissioningGlobalInstance::HandleSetTCAcknowledgements( + HandlerContext & ctx, const Commands::SetTCAcknowledgements::DecodableType & commandData) +{ + MATTER_TRACE_SCOPE("SetTCAcknowledgements", "GeneralCommissioning"); + + auto & failSafeContext = Server::GetInstance().GetFailSafeContext(); + TermsAndConditionsProvider * tcProvider = TermsAndConditionsManager::GetInstance(); + + if (nullptr == tcProvider) + { + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::Failure); + return; + } + + Optional requiredTermsAndConditionsMaybe; + Optional previousAcceptedTermsAndConditionsMaybe; + CheckSuccess(tcProvider->GetRequirements(requiredTermsAndConditionsMaybe), Failure); + CheckSuccess(tcProvider->GetAcceptance(previousAcceptedTermsAndConditionsMaybe), Failure); + TermsAndConditions acceptedTermsAndConditions = TermsAndConditions(commandData.TCUserResponse, commandData.TCVersion); + Optional acceptedTermsAndConditionsPresent = Optional(acceptedTermsAndConditions); + + Commands::SetTCAcknowledgementsResponse::Type response; + + if (requiredTermsAndConditionsMaybe.HasValue()) + { + TermsAndConditions requiredTermsAndConditions = requiredTermsAndConditionsMaybe.Value(); + + if (!requiredTermsAndConditions.ValidateVersion(acceptedTermsAndConditions)) { - response.errorCode = CommissioningErrorEnum::kValueOutsideRange; - // TODO: How does using the country code in debug text make sense, if - // the real issue is the newRegulatoryConfig value? - response.debugText = countryCode; + response.errorCode = CommissioningErrorEnum::kTCMinVersionNotMet; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + return; } - else + + if (!requiredTermsAndConditions.ValidateValue(acceptedTermsAndConditions)) { - CheckSuccess(server->SetRegulatoryConfig(location, countryCode), Failure); - Breadcrumb::Set(commandPath.mEndpointId, commandData.breadcrumb); - response.errorCode = CommissioningErrorEnum::kOk; + response.errorCode = CommissioningErrorEnum::kRequiredTCNotAccepted; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + return; } } - commandObj->AddResponse(commandPath, response); + if (previousAcceptedTermsAndConditionsMaybe != acceptedTermsAndConditionsPresent) + { + TermsAndConditionsState initialState, updatedState; + CheckSuccess(GetTermsAndConditionsAttributeState(tcProvider, initialState), Failure); + CheckSuccess(tcProvider->SetAcceptance(acceptedTermsAndConditionsPresent), Failure); + CheckSuccess(GetTermsAndConditionsAttributeState(tcProvider, updatedState), Failure); + NotifyTermsAndConditionsAttributeChangeIfRequired(initialState, updatedState); + + // Commit or defer based on fail-safe state + if (!failSafeContext.IsFailSafeArmed()) + { + CheckSuccess(tcProvider->CommitAcceptance(), Failure); + } + else + { + failSafeContext.SetUpdateTermsAndConditionsHasBeenInvoked(); + } + } - return true; + response.errorCode = CommissioningErrorEnum::kOk; + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); } +#endif // CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED -namespace { -void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) +void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t) { if (event->Type == DeviceLayer::DeviceEventType::kFailSafeTimerExpired) { // Spec says to reset Breadcrumb attribute to 0. Breadcrumb::Set(0, 0); + +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + if (event->FailSafeTimerExpired.updateTermsAndConditionsHasBeenInvoked) + { + // Clear terms and conditions acceptance on failsafe timer expiration + TermsAndConditionsProvider * tcProvider = TermsAndConditionsManager::GetInstance(); + TermsAndConditionsState initialState, updatedState; + VerifyOrReturn(nullptr != tcProvider); + VerifyOrReturn(CHIP_NO_ERROR == GetTermsAndConditionsAttributeState(tcProvider, initialState)); + VerifyOrReturn(CHIP_NO_ERROR == tcProvider->RevertAcceptance()); + VerifyOrReturn(CHIP_NO_ERROR == GetTermsAndConditionsAttributeState(tcProvider, updatedState)); + NotifyTermsAndConditionsAttributeChangeIfRequired(initialState, updatedState); + } +#endif // CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED } } } // anonymous namespace +class GeneralCommissioningFabricTableDelegate : public chip::FabricTable::Delegate +{ +public: + // Gets called when a fabric is deleted + void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override + { +#if CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + // If the FabricIndex matches the last remaining entry in the Fabrics list, then the device SHALL delete all Matter + // related data on the node which was created since it was commissioned. + if (Server::GetInstance().GetFabricTable().FabricCount() == 0) + { + ChipLogProgress(Zcl, "general-commissioning-server: Last Fabric index 0x%x was removed", + static_cast(fabricIndex)); + + TermsAndConditionsProvider * tcProvider = TermsAndConditionsManager::GetInstance(); + TermsAndConditionsState initialState, updatedState; + VerifyOrReturn(nullptr != tcProvider); + VerifyOrReturn(CHIP_NO_ERROR == GetTermsAndConditionsAttributeState(tcProvider, initialState)); + VerifyOrReturn(CHIP_NO_ERROR == tcProvider->ResetAcceptance()); + VerifyOrReturn(CHIP_NO_ERROR == GetTermsAndConditionsAttributeState(tcProvider, updatedState)); + NotifyTermsAndConditionsAttributeChangeIfRequired(initialState, updatedState); + } +#endif // CHIP_CONFIG_TERMS_AND_CONDITIONS_REQUIRED + } +}; + void MatterGeneralCommissioningPluginServerInitCallback() { Breadcrumb::Set(0, 0); - AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); + AttributeAccessInterfaceRegistry::Instance().Register(&gGeneralCommissioningInstance); + ReturnOnFailure(CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(&gGeneralCommissioningInstance)); DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler); + + static GeneralCommissioningFabricTableDelegate fabricDelegate; + Server::GetInstance().GetFabricTable().AddFabricDelegate(&fabricDelegate); } namespace chip { diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml index 61e7c45582268a..27e428647ae285 100644 --- a/src/app/common/templates/config-data.yaml +++ b/src/app/common/templates/config-data.yaml @@ -47,6 +47,7 @@ CommandHandlerInterfaceOnlyClusters: - Thread Network Directory - Water Heater Management - Water Heater Mode + - General Commissioning # We need a more configurable way of deciding which clusters have which init functions.... # See https://github.com/project-chip/connectedhomeip/issues/4369 diff --git a/src/app/common_flags.gni b/src/app/common_flags.gni index d3e7ce34bf0338..e74be028063a89 100644 --- a/src/app/common_flags.gni +++ b/src/app/common_flags.gni @@ -40,4 +40,8 @@ declare_args() { # If/once the chip_use_data_model_interface flag is removed or does not support # a `check` option, this should alwo be removed chip_data_model_check_die_on_failure = false + + # Controls whether the device commissioning process requires the user to + # acknowledge terms and conditions during commissioning. + chip_terms_and_conditions_required = false } diff --git a/src/app/server/BUILD.gn b/src/app/server/BUILD.gn index 4addf8eea938e3..fa2a4528c29b37 100644 --- a/src/app/server/BUILD.gn +++ b/src/app/server/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Project CHIP Authors +# Copyright (c) 2020-2024 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. @@ -25,6 +25,22 @@ config("server_config") { } } +source_set("terms_and_conditions") { + sources = [ + "DefaultTermsAndConditionsProvider.cpp", + "DefaultTermsAndConditionsProvider.h", + "TermsAndConditionsManager.cpp", + "TermsAndConditionsManager.h", + "TermsAndConditionsProvider.h", + ] + + public_deps = [ + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/support", + "${chip_root}/src/protocols", + ] +} + static_library("server") { output_name = "libCHIPAppServer" @@ -66,6 +82,10 @@ static_library("server") { "${chip_root}/src/transport", ] + if (chip_terms_and_conditions_required) { + public_deps += [ ":terms_and_conditions" ] + } + # TODO: Server.cpp uses TestGroupData.h. Unsure why test code would be in such a central place # This dependency is split since it should probably be removed (or naming should # be updated if this is not really "testing" even though headers are Test*.h) diff --git a/src/app/server/DefaultTermsAndConditionsProvider.cpp b/src/app/server/DefaultTermsAndConditionsProvider.cpp new file mode 100644 index 00000000000000..4a05a7d3ff8b87 --- /dev/null +++ b/src/app/server/DefaultTermsAndConditionsProvider.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2024 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 "DefaultTermsAndConditionsProvider.h" +#include "TermsAndConditionsProvider.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +constexpr chip::TLV::Tag kSerializationVersionTag = chip::TLV::ContextTag(1); +constexpr chip::TLV::Tag kAcceptedAcknowledgementsTag = chip::TLV::ContextTag(2); +constexpr chip::TLV::Tag kAcceptedAcknowledgementsVersionTag = chip::TLV::ContextTag(3); +constexpr uint8_t kSerializationSchemaMinimumVersion = 1; +constexpr uint8_t kSerializationSchemaCurrentVersion = 1; + +constexpr size_t kEstimatedTlvBufferSize = chip::TLV::EstimateStructOverhead(sizeof(uint8_t), // SerializationVersion + sizeof(uint16_t), // AcceptedAcknowledgements + sizeof(uint16_t) // AcceptedAcknowledgementsVersion + ) * + 4; // Extra space for rollback compatibility +} // namespace + +CHIP_ERROR chip::app::DefaultTermsAndConditionsStorageDelegate::Init(PersistentStorageDelegate * inPersistentStorageDelegate) +{ + VerifyOrReturnValue(nullptr != inPersistentStorageDelegate, CHIP_ERROR_INVALID_ARGUMENT); + + mStorageDelegate = inPersistentStorageDelegate; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsStorageDelegate::Delete() +{ + VerifyOrReturnValue(nullptr != mStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + const chip::StorageKeyName kStorageKey = chip::DefaultStorageKeyAllocator::TermsAndConditionsAcceptance(); + ReturnErrorOnFailure(mStorageDelegate->SyncDeleteKeyValue(kStorageKey.KeyName())); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsStorageDelegate::Get(Optional & outTermsAndConditions) +{ + VerifyOrReturnValue(nullptr != mStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + uint8_t serializationVersion = 0; + uint16_t acknowledgements = 0; + uint16_t acknowledgementsVersion = 0; + + chip::TLV::TLVReader tlvReader; + chip::TLV::TLVType tlvContainer; + + uint8_t buffer[kEstimatedTlvBufferSize] = { 0 }; + uint16_t bufferSize = sizeof(buffer); + + const chip::StorageKeyName kStorageKey = chip::DefaultStorageKeyAllocator::TermsAndConditionsAcceptance(); + + CHIP_ERROR err = mStorageDelegate->SyncGetKeyValue(kStorageKey.KeyName(), &buffer, bufferSize); + VerifyOrReturnValue(CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == err || CHIP_NO_ERROR == err, err); + + if (CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == err) + { + outTermsAndConditions.ClearValue(); + return CHIP_NO_ERROR; + } + + tlvReader.Init(buffer, bufferSize); + ReturnErrorOnFailure(tlvReader.Next(chip::TLV::kTLVType_Structure, chip::TLV::AnonymousTag())); + ReturnErrorOnFailure(tlvReader.EnterContainer(tlvContainer)); + ReturnErrorOnFailure(tlvReader.Next(kSerializationVersionTag)); + ReturnErrorOnFailure(tlvReader.Get(serializationVersion)); + + if (serializationVersion < kSerializationSchemaMinimumVersion) + { + ChipLogError(AppServer, "The terms and conditions datastore schema (%hhu) is newer than oldest compatible schema (%hhu)", + serializationVersion, kSerializationSchemaMinimumVersion); + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + + if (serializationVersion != kSerializationSchemaCurrentVersion) + { + ChipLogDetail(AppServer, "The terms and conditions datastore schema (%hhu) differs from current schema (%hhu)", + serializationVersion, kSerializationSchemaCurrentVersion); + } + + ReturnErrorOnFailure(tlvReader.Next(kAcceptedAcknowledgementsTag)); + ReturnErrorOnFailure(tlvReader.Get(acknowledgements)); + ReturnErrorOnFailure(tlvReader.Next(kAcceptedAcknowledgementsVersionTag)); + ReturnErrorOnFailure(tlvReader.Get(acknowledgementsVersion)); + ReturnErrorOnFailure(tlvReader.ExitContainer(tlvContainer)); + + outTermsAndConditions = Optional(TermsAndConditions(acknowledgements, acknowledgementsVersion)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsStorageDelegate::Set(const TermsAndConditions & inTermsAndConditions) +{ + uint8_t buffer[kEstimatedTlvBufferSize] = { 0 }; + chip::TLV::TLVWriter tlvWriter; + chip::TLV::TLVType tlvContainer; + + VerifyOrReturnValue(nullptr != mStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + tlvWriter.Init(buffer); + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, tlvContainer)); + ReturnErrorOnFailure(tlvWriter.Put(kSerializationVersionTag, kSerializationSchemaCurrentVersion)); + ReturnErrorOnFailure(tlvWriter.Put(kAcceptedAcknowledgementsTag, inTermsAndConditions.GetValue())); + ReturnErrorOnFailure(tlvWriter.Put(kAcceptedAcknowledgementsVersionTag, inTermsAndConditions.GetVersion())); + ReturnErrorOnFailure(tlvWriter.EndContainer(tlvContainer)); + ReturnErrorOnFailure(tlvWriter.Finalize()); + + const chip::StorageKeyName kStorageKey = chip::DefaultStorageKeyAllocator::TermsAndConditionsAcceptance(); + ReturnErrorOnFailure( + mStorageDelegate->SyncSetKeyValue(kStorageKey.KeyName(), buffer, static_cast(tlvWriter.GetLengthWritten()))); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::Init( + TermsAndConditionsStorageDelegate * inStorageDelegate, + const chip::Optional & inRequiredTermsAndConditions) +{ + VerifyOrReturnValue(nullptr != inStorageDelegate, CHIP_ERROR_INVALID_ARGUMENT); + + mTermsAndConditionsStorageDelegate = inStorageDelegate; + mRequiredAcknowledgements = inRequiredTermsAndConditions; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::CommitAcceptance() +{ + VerifyOrReturnValue(nullptr != mTermsAndConditionsStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + // No terms and conditions to commit + VerifyOrReturnValue(mTemporalAcceptance.HasValue(), CHIP_NO_ERROR); + + ReturnErrorOnFailure(mTermsAndConditionsStorageDelegate->Set(mTemporalAcceptance.Value())); + ChipLogProgress(AppServer, "Terms and conditions have been committed"); + mTemporalAcceptance.ClearValue(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetAcceptance(Optional & outTermsAndConditions) const +{ + VerifyOrReturnValue(nullptr != mTermsAndConditionsStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + // Return the in-memory acceptance state + if (mTemporalAcceptance.HasValue()) + { + outTermsAndConditions = mTemporalAcceptance; + return CHIP_NO_ERROR; + } + + // Otherwise, try to get the persisted acceptance state + CHIP_ERROR err = mTermsAndConditionsStorageDelegate->Get(outTermsAndConditions); + VerifyOrReturnValue(CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == err || CHIP_NO_ERROR == err, err); + + if (CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == err) + { + ChipLogError(AppServer, "No terms and conditions have been accepted"); + outTermsAndConditions.ClearValue(); + return CHIP_NO_ERROR; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetAcknowledgementsRequired(bool & outAcknowledgementsRequired) const +{ + Optional requiredTermsAndConditionsMaybe; + ReturnErrorOnFailure(GetRequirements(requiredTermsAndConditionsMaybe)); + + if (!requiredTermsAndConditionsMaybe.HasValue()) + { + outAcknowledgementsRequired = false; + return CHIP_NO_ERROR; + } + + Optional acceptedTermsAndConditionsMaybe; + ReturnErrorOnFailure(GetAcceptance(acceptedTermsAndConditionsMaybe)); + + if (!acceptedTermsAndConditionsMaybe.HasValue()) + { + outAcknowledgementsRequired = true; + return CHIP_NO_ERROR; + } + + TermsAndConditions requiredTermsAndConditions = requiredTermsAndConditionsMaybe.Value(); + TermsAndConditions acceptedTermsAndConditions = acceptedTermsAndConditionsMaybe.Value(); + + bool requiredTermsAndConditionsAreAccepted = requiredTermsAndConditions.Validate(acceptedTermsAndConditions); + outAcknowledgementsRequired = !requiredTermsAndConditionsAreAccepted; + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetRequirements(Optional & outTermsAndConditions) const +{ + outTermsAndConditions = mRequiredAcknowledgements; + return CHIP_NO_ERROR; +} + +CHIP_ERROR +chip::app::DefaultTermsAndConditionsProvider::GetUpdateAcceptanceDeadline(Optional & outUpdateAcceptanceDeadline) const +{ + // No-op stub implementation. This feature is not implemented in this default implementation. + outUpdateAcceptanceDeadline = Optional(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::ResetAcceptance() +{ + VerifyOrReturnValue(nullptr != mTermsAndConditionsStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + (void) mTermsAndConditionsStorageDelegate->Delete(); + ReturnErrorOnFailure(RevertAcceptance()); + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::RevertAcceptance() +{ + mTemporalAcceptance.ClearValue(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::SetAcceptance(const Optional & inTermsAndConditions) +{ + mTemporalAcceptance = inTermsAndConditions; + return CHIP_NO_ERROR; +} diff --git a/src/app/server/DefaultTermsAndConditionsProvider.h b/src/app/server/DefaultTermsAndConditionsProvider.h new file mode 100644 index 00000000000000..8bc3d0761b3e21 --- /dev/null +++ b/src/app/server/DefaultTermsAndConditionsProvider.h @@ -0,0 +1,152 @@ +/* + * + * Copyright (c) 2024 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. + */ + +#pragma once + +#include "TermsAndConditionsProvider.h" + +#include + +#include +#include +#include + +namespace chip { +namespace app { + +/** + * @brief Abstract interface for storing and retrieving terms and conditions acceptance status. + * + * This class defines the methods required to interact with the underlying storage system + * for saving, retrieving, and deleting the user's acceptance of terms and conditions. + */ +class TermsAndConditionsStorageDelegate +{ +public: + virtual ~TermsAndConditionsStorageDelegate() = default; + + /** + * @brief Deletes the persisted terms and conditions acceptance status from storage. + * + * This method deletes the stored record of the user's acceptance of the terms and conditions, + * effectively resetting their acceptance status in the persistent storage. + * + * @retval CHIP_NO_ERROR if the record was successfully deleted. + * @retval CHIP_ERROR_UNINITIALIZED if the storage delegate is not properly initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR Delete() = 0; + + /** + * @brief Retrieves the persisted terms and conditions acceptance status from storage. + * + * This method attempts to retrieve the previously accepted terms and conditions from + * persistent storage. If no such record exists, it returns an empty `Optional`. + * + * @param[out] outTermsAndConditions The retrieved terms and conditions, if any exist. + * + * @retval CHIP_NO_ERROR if the terms were successfully retrieved. + * @retval CHIP_ERROR_UNINITIALIZED if the storage delegate is not properly initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR Get(Optional & outTermsAndConditions) = 0; + + /** + * @brief Persists the user's acceptance of the terms and conditions. + * + * This method stores the provided terms and conditions acceptance status in persistent + * storage, allowing the user's acceptance to be retrieved later. + * + * @param[in] inTermsAndConditions The terms and conditions to be saved. + * + * @retval CHIP_NO_ERROR if the terms were successfully stored. + * @retval CHIP_ERROR_UNINITIALIZED if the storage delegate is not properly initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR Set(const TermsAndConditions & inTermsAndConditions) = 0; +}; + +/** + * @brief Default implementation of the TermsAndConditionsStorageDelegate using a persistent storage backend. + * + * This class provides an implementation of the TermsAndConditionsStorageDelegate interface, storing + * and retrieving the user's terms and conditions acceptance from persistent storage. It requires a + * PersistentStorageDelegate to interface with the storage system. + */ +class DefaultTermsAndConditionsStorageDelegate : public TermsAndConditionsStorageDelegate +{ +public: + /** + * @brief Initializes the storage delegate with a persistent storage backend. + * + * This method initializes the storage delegate with the provided persistent storage delegate. + * The storage delegate must be initialized before performing any operations. + * + * @param[in] inPersistentStorageDelegate The storage backend used for saving and retrieving data. + * + * @retval CHIP_NO_ERROR if the storage delegate was successfully initialized. + * @retval CHIP_ERROR_INVALID_ARGUMENT if the provided storage delegate is null. + */ + CHIP_ERROR Init(PersistentStorageDelegate * inPersistentStorageDelegate); + + CHIP_ERROR Delete() override; + + CHIP_ERROR Get(Optional & inTermsAndConditions) override; + + CHIP_ERROR Set(const TermsAndConditions & inTermsAndConditions) override; + +private: + PersistentStorageDelegate * mStorageDelegate = nullptr; +}; + +class DefaultTermsAndConditionsProvider : public TermsAndConditionsProvider +{ +public: + /** + * @brief Initializes the TermsAndConditionsProvider. + * + * @param[in] inStorageDelegate Storage delegate dependency. + * @param[in] inRequiredTermsAndConditions The required terms and conditions that must be met. + */ + CHIP_ERROR Init(TermsAndConditionsStorageDelegate * inStorageDelegate, + const Optional & inRequiredTermsAndConditions); + + CHIP_ERROR CommitAcceptance() override; + + CHIP_ERROR GetAcceptance(Optional & outTermsAndConditions) const override; + + CHIP_ERROR GetAcknowledgementsRequired(bool & outAcknowledgementsRequired) const override; + + CHIP_ERROR GetRequirements(Optional & outTermsAndConditions) const override; + + CHIP_ERROR GetUpdateAcceptanceDeadline(Optional & outUpdateAcceptanceDeadline) const override; + + CHIP_ERROR ResetAcceptance() override; + + CHIP_ERROR RevertAcceptance() override; + + CHIP_ERROR SetAcceptance(const Optional & inTermsAndConditions) override; + +private: + TermsAndConditionsStorageDelegate * mTermsAndConditionsStorageDelegate; + Optional mTemporalAcceptance; + Optional mRequiredAcknowledgements; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/TermsAndConditionsManager.cpp b/src/app/server/TermsAndConditionsManager.cpp new file mode 100644 index 00000000000000..4946bf37d2611f --- /dev/null +++ b/src/app/server/TermsAndConditionsManager.cpp @@ -0,0 +1,80 @@ +/* + * + * Copyright (c) 2024 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 "TermsAndConditionsManager.h" + +#include "DefaultTermsAndConditionsProvider.h" + +static chip::app::TermsAndConditionsManager sTermsAndConditionsManager; +static chip::app::DefaultTermsAndConditionsProvider sTermsAndConditionsProviderInstance; +static chip::app::DefaultTermsAndConditionsStorageDelegate sTermsAndConditionsStorageDelegateInstance; + +chip::app::TermsAndConditionsManager * chip::app::TermsAndConditionsManager::GetInstance() +{ + return &sTermsAndConditionsManager; +} + +CHIP_ERROR chip::app::TermsAndConditionsManager::Init(chip::PersistentStorageDelegate * inPersistentStorageDelegate, + const Optional & inRequiredTermsAndConditions) +{ + ReturnErrorOnFailure(sTermsAndConditionsStorageDelegateInstance.Init(inPersistentStorageDelegate)); + ReturnErrorOnFailure( + sTermsAndConditionsProviderInstance.Init(&sTermsAndConditionsStorageDelegateInstance, inRequiredTermsAndConditions)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::TermsAndConditionsManager::CommitAcceptance() +{ + return sTermsAndConditionsProviderInstance.CommitAcceptance(); +} + +CHIP_ERROR chip::app::TermsAndConditionsManager::GetAcceptance(Optional & outTermsAndConditions) const +{ + return sTermsAndConditionsProviderInstance.GetAcceptance(outTermsAndConditions); +} + +CHIP_ERROR chip::app::TermsAndConditionsManager::GetAcknowledgementsRequired(bool & outAcknowledgementsRequired) const +{ + return sTermsAndConditionsProviderInstance.GetAcknowledgementsRequired(outAcknowledgementsRequired); +} + +CHIP_ERROR chip::app::TermsAndConditionsManager::GetRequirements(Optional & outTermsAndConditions) const +{ + return sTermsAndConditionsProviderInstance.GetRequirements(outTermsAndConditions); +} + +CHIP_ERROR chip::app::TermsAndConditionsManager::GetUpdateAcceptanceDeadline(Optional & outUpdateAcceptanceDeadline) const +{ + return sTermsAndConditionsProviderInstance.GetUpdateAcceptanceDeadline(outUpdateAcceptanceDeadline); +} + +CHIP_ERROR chip::app::TermsAndConditionsManager::ResetAcceptance() +{ + return sTermsAndConditionsProviderInstance.ResetAcceptance(); +} + +CHIP_ERROR chip::app::TermsAndConditionsManager::RevertAcceptance() +{ + return sTermsAndConditionsProviderInstance.RevertAcceptance(); +} + +CHIP_ERROR chip::app::TermsAndConditionsManager::SetAcceptance(const Optional & inTermsAndConditions) +{ + return sTermsAndConditionsProviderInstance.SetAcceptance(inTermsAndConditions); +} diff --git a/src/app/server/TermsAndConditionsManager.h b/src/app/server/TermsAndConditionsManager.h new file mode 100644 index 00000000000000..02101bbec46425 --- /dev/null +++ b/src/app/server/TermsAndConditionsManager.h @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2024 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. + */ + +#pragma once + +#include + +#include "TermsAndConditionsProvider.h" + +namespace chip { +namespace app { + +class TermsAndConditionsManager : public TermsAndConditionsProvider +{ +public: + static TermsAndConditionsManager * GetInstance(); + CHIP_ERROR Init(PersistentStorageDelegate * inPersistentStorageDelegate, + const Optional & inRequiredTermsAndConditions); + CHIP_ERROR CommitAcceptance(); + CHIP_ERROR GetAcceptance(Optional & outTermsAndConditions) const; + CHIP_ERROR GetAcknowledgementsRequired(bool & outAcknowledgementsRequired) const; + CHIP_ERROR GetRequirements(Optional & outTermsAndConditions) const; + CHIP_ERROR GetUpdateAcceptanceDeadline(Optional & outUpdateAcceptanceDeadline) const; + CHIP_ERROR ResetAcceptance(); + CHIP_ERROR RevertAcceptance(); + CHIP_ERROR SetAcceptance(const Optional & inTermsAndConditions); +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/TermsAndConditionsProvider.h b/src/app/server/TermsAndConditionsProvider.h new file mode 100644 index 00000000000000..cb0b6f0b8f088a --- /dev/null +++ b/src/app/server/TermsAndConditionsProvider.h @@ -0,0 +1,224 @@ +/* + * + * Copyright (c) 2024 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. + */ + +#pragma once + +#include + +#include +#include + +namespace chip { +namespace app { + +/** + * @brief Represents a pair of terms and conditions value and version. + * + * This class encapsulates terms and conditions with methods to validate a user's accepted value and version against required + * criteria. + */ +class TermsAndConditions +{ +public: + TermsAndConditions(uint16_t inValue, uint16_t inVersion) : value(inValue), version(inVersion) {} + + bool operator==(const TermsAndConditions & other) const { return value == other.value && version == other.version; } + bool operator!=(const TermsAndConditions & other) const { return !(*this == other); } + + /** + * @brief Retrieves the terms and conditions value (accepted bits). + * + * @return The value of the terms and conditions. + */ + uint16_t GetValue() const { return value; } + + /** + * @brief Retrieves the terms and conditions version. + * + * @return The version of the terms and conditions. + */ + uint16_t GetVersion() const { return version; } + + /** + * @brief Validates the terms and conditions value. + * + * Checks whether all required bits are set in the accepted terms and conditions. + * + * @param acceptedTermsAndConditions The user's accepted terms and conditions. + * @return True if all required bits are set, false otherwise. + */ + bool ValidateValue(const TermsAndConditions & acceptedTermsAndConditions) const + { + // Check if all required bits are set in the user-accepted value. + return ((value & acceptedTermsAndConditions.GetValue()) == value); + } + + /** + * @brief Validates the terms and conditions version. + * + * Checks whether the accepted version is greater than or equal to the required version. + * + * @param acceptedTermsAndConditions The user's accepted terms and conditions. + * @return True if the accepted version is valid, false otherwise. + */ + bool ValidateVersion(const TermsAndConditions & acceptedTermsAndConditions) const + { + // Check if the version is below the minimum required version. + return (acceptedTermsAndConditions.GetVersion() >= version); + } + + /** + * @brief Validates the terms and conditions. + * + * Combines validation of both value and version to ensure compliance with requirements. + * + * @param acceptedTermsAndConditions The user's accepted terms and conditions. + * @return True if both value and version validations pass, false otherwise. + */ + bool Validate(const TermsAndConditions & acceptedTermsAndConditions) const + { + return ValidateVersion(acceptedTermsAndConditions) && ValidateValue(acceptedTermsAndConditions); + } + +private: + const uint16_t value; + const uint16_t version; +}; + +/** + * @brief Data access layer for handling the required terms and conditions and managing user acceptance status. + * + * This class provides methods to manage the acceptance of terms and conditions, including storing, retrieving, + * and verifying the acceptance status. It also supports temporary in-memory storage and persistent storage for + * accepted terms and conditions. + */ +class TermsAndConditionsProvider +{ +public: + virtual ~TermsAndConditionsProvider() = default; + + /** + * @brief Persists the acceptance of the terms and conditions. + * + * This method commits the in-memory acceptance status to persistent storage. It stores the acceptance + * status in a permanent location and clears the temporary in-memory acceptance state after committing. + * + * @retval CHIP_NO_ERROR if the terms were successfully persisted. + * @retval CHIP_ERROR_UNINITIALIZED if the module has not been initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR CommitAcceptance() = 0; + + /** + * @brief Retrieves the current acceptance status of the terms and conditions. + * + * This method checks the temporary in-memory acceptance state first. If no in-memory state is found, + * it attempts to retrieve the acceptance status from persistent storage. If no terms have been accepted, + * it returns an empty `Optional`. + * + * @param[out] outTermsAndConditions The current accepted terms and conditions, if any. + * + * @retval CHIP_NO_ERROR if the terms were successfully retrieved or no terms were found. + * @retval CHIP_ERROR_UNINITIALIZED if the module has not been initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR GetAcceptance(Optional & outTermsAndConditions) const = 0; + + /** + * @brief Determines if acknowledgments are required. + * + * @param[out] outAcknowledgementsRequired True if acknowledgments are required, false otherwise. + * + * @retval CHIP_NO_ERROR if successful. + * @retval CHIP_ERROR_UNINITIALIZED if the module has not been initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR GetAcknowledgementsRequired(bool & outAcknowledgementsRequired) const = 0; + + /** + * @brief Retrieves the requirements for the terms and conditions. + * + * This method retrieves the required terms and conditions that must be accepted by the user. These + * requirements are set by the provider and used to validate the acceptance. + * + * @param[out] outTermsAndConditions The required terms and conditions. + * + * @retval CHIP_NO_ERROR if the required terms were successfully retrieved. + * @retval CHIP_ERROR_UNINITIALIZED if the module has not been initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR GetRequirements(Optional & outTermsAndConditions) const = 0; + + /** + * @brief Retrieves the deadline for accepting updated terms and conditions. + * + * This method retrieves the deadline by which the user must accept updated terms and conditions. + * If no deadline is set, it returns an empty `Optional`. + * + * @param[out] outUpdateAcceptanceDeadline The deadline (in seconds) by which updated terms must be accepted. + * Returns empty Optional if no deadline is set. + * + * @retval CHIP_NO_ERROR if the deadline was successfully retrieved or no deadline was found. + * @retval CHIP_ERROR_UNINITIALIZED if the module has not been initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR GetUpdateAcceptanceDeadline(Optional & outUpdateAcceptanceDeadline) const = 0; + + /** + * @brief Resets the persisted acceptance status. + * + * This method deletes the persisted acceptance of the terms and conditions from storage, effectively + * resetting the stored acceptance status. Any in-memory temporary acceptance will also be cleared + * through this method. + * + * @retval CHIP_NO_ERROR if the terms were successfully reset. + * @retval CHIP_ERROR_UNINITIALIZED if the module has not been initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR ResetAcceptance() = 0; + + /** + * @brief Clears the in-memory temporary acceptance status. + * + * This method clears any temporary acceptance of the terms and conditions that is held in-memory. It does + * not affect the persisted state stored in storage. + * + * @retval CHIP_NO_ERROR if the in-memory acceptance state was successfully cleared. + * @retval CHIP_ERROR_UNINITIALIZED if the module has not been initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR RevertAcceptance() = 0; + + /** + * @brief Sets the temporary in-memory acceptance status of the terms and conditions. + * + * This method stores the provided terms and conditions acceptance status in-memory. It does not persist + * the acceptance status to storage. To persist the acceptance, call `CommitAcceptance()` after this method. + * + * @param[in] inTermsAndConditions The terms and conditions to be accepted temporarily. + * + * @retval CHIP_NO_ERROR if the terms were successfully stored in-memory. + * @retval CHIP_ERROR_INVALID_ARGUMENT if the provided terms and conditions are invalid. + * @retval CHIP_ERROR_UNINITIALIZED if the module has not been initialized. + * @retval CHIP_ERROR_* for other errors. + */ + virtual CHIP_ERROR SetAcceptance(const Optional & inTermsAndConditions) = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 09dcb304ea29f2..104fae6cc05158 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Project CHIP Authors +# Copyright (c) 2020-2024 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. @@ -201,6 +201,7 @@ chip_test_suite("tests") { "TestConcreteAttributePath.cpp", "TestDataModelSerialization.cpp", "TestDefaultOTARequestorStorage.cpp", + "TestDefaultTermsAndConditionsProvider.cpp", "TestDefaultThreadNetworkDirectoryStorage.cpp", "TestEventLoggingNoUTCTime.cpp", "TestEventOverflow.cpp", @@ -239,6 +240,8 @@ chip_test_suite("tests") { "${chip_root}/src/app/common:cluster-objects", "${chip_root}/src/app/icd/client:handler", "${chip_root}/src/app/icd/client:manager", + "${chip_root}/src/app/server", + "${chip_root}/src/app/server:terms_and_conditions", "${chip_root}/src/app/tests:helpers", "${chip_root}/src/app/util/mock:mock_codegen_data_model", "${chip_root}/src/app/util/mock:mock_ember", diff --git a/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp b/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp new file mode 100644 index 00000000000000..dc2a66278aa5d2 --- /dev/null +++ b/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp @@ -0,0 +1,435 @@ +/* + * + * Copyright (c) 2024 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 "app/server/DefaultTermsAndConditionsProvider.h" +#include "app/server/TermsAndConditionsProvider.h" +#include "pw_unit_test/framework.h" + +#include +#include +#include +#include + +class TestTermsAndConditionsStorageDelegate : public chip::app::TermsAndConditionsStorageDelegate +{ +public: + TestTermsAndConditionsStorageDelegate(chip::Optional & initialTermsAndConditions) : + mTermsAndConditions(initialTermsAndConditions) + {} + + CHIP_ERROR Delete() + { + mTermsAndConditions.ClearValue(); + return CHIP_NO_ERROR; + } + + CHIP_ERROR Get(chip::Optional & outTermsAndConditions) + { + outTermsAndConditions = mTermsAndConditions; + return CHIP_NO_ERROR; + } + + CHIP_ERROR Set(const chip::app::TermsAndConditions & inTermsAndConditions) + { + mTermsAndConditions = chip::Optional(inTermsAndConditions); + return CHIP_NO_ERROR; + } + +private: + chip::Optional & mTermsAndConditions; +}; + +TEST(DefaultTermsAndConditionsProvider, TestInitSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); +} + +TEST(DefaultTermsAndConditionsProvider, TestNoRequirementsGetRequirementsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + chip::Optional requiredTermsAndConditions = chip::Optional(); + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(outTermsAndConditions.HasValue()); +} + +TEST(DefaultTermsAndConditionsProvider, TestNeverAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(0b1111'1111'1111'1111, 1)); + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(outTermsAndConditions.HasValue()); +} + +TEST(DefaultTermsAndConditionsProvider, TestTermsAcceptedPersistsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional newTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + + err = defaultTermsAndConditionsProvider.SetAcceptance(newTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().GetValue()); + EXPECT_EQ(1, outTermsAndConditions.Value().GetVersion()); + + err = defaultTermsAndConditionsProvider.CommitAcceptance(); + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().GetValue()); + EXPECT_EQ(1, outTermsAndConditions.Value().GetVersion()); + + chip::app::DefaultTermsAndConditionsProvider anotherTncProvider; + err = anotherTncProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = anotherTncProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().GetValue()); + EXPECT_EQ(1, outTermsAndConditions.Value().GetVersion()); +} + +TEST(DefaultTermsAndConditionsProvider, TestTermsRequiredGetRequirementsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = defaultTermsAndConditionsProvider.GetRequirements(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().GetValue()); + EXPECT_EQ(1, outTermsAndConditions.Value().GetVersion()); +} + +TEST(DefaultTermsAndConditionsProvider, TestSetAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional acceptedTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.SetAcceptance(acceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = defaultTermsAndConditionsProvider.GetRequirements(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().GetValue()); + EXPECT_EQ(1, outTermsAndConditions.Value().GetVersion()); +} + +TEST(DefaultTermsAndConditionsProvider, TestRevertAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional acceptedTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.SetAcceptance(acceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = defaultTermsAndConditionsProvider.GetRequirements(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().GetValue()); + EXPECT_EQ(1, outTermsAndConditions.Value().GetVersion()); + + err = defaultTermsAndConditionsProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outAcceptance2; + err = defaultTermsAndConditionsProvider.GetAcceptance(outAcceptance2); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(outAcceptance2.HasValue()); +} + +TEST(DefaultTermsAndConditionsProvider, TestAcceptanceRequiredTermsMissingFailure) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional acceptedTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.SetAcceptance(acceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outAcknowledgementTermsAndConditions; + err = defaultTermsAndConditionsProvider.GetAcceptance(outAcknowledgementTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcknowledgementTermsAndConditions.Value().GetValue()); + EXPECT_EQ(1, outAcknowledgementTermsAndConditions.Value().GetVersion()); + + err = defaultTermsAndConditionsProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outRequiredTermsAndConditions; + err = defaultTermsAndConditionsProvider.GetRequirements(outRequiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outRequiredTermsAndConditions.Value().GetValue()); + EXPECT_EQ(1, outRequiredTermsAndConditions.Value().GetVersion()); +} + +TEST(DefaultTermsAndConditionsProvider, TestAcceptanceCommitCheckSetRevertCheckExpectCommitValue) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // Initialize unit under test + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // Set acceptance + chip::Optional acceptedTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(0b1, 1)); + err = defaultTermsAndConditionsProvider.SetAcceptance(acceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // Commit value + err = defaultTermsAndConditionsProvider.CommitAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // Check commit value + chip::Optional outTermsAndConditions; + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(outTermsAndConditions.HasValue()); + EXPECT_EQ(outTermsAndConditions.Value().GetValue(), acceptedTermsAndConditions.Value().GetValue()); + EXPECT_EQ(outTermsAndConditions.Value().GetVersion(), acceptedTermsAndConditions.Value().GetVersion()); + + // Set updated value + chip::Optional updatedTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(0b11, 2)); + err = defaultTermsAndConditionsProvider.SetAcceptance(updatedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // Check updated value + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(outTermsAndConditions.HasValue()); + EXPECT_EQ(outTermsAndConditions.Value().GetValue(), updatedTermsAndConditions.Value().GetValue()); + EXPECT_EQ(outTermsAndConditions.Value().GetVersion(), updatedTermsAndConditions.Value().GetVersion()); + + // Revert updated value + err = defaultTermsAndConditionsProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // Check committed value + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(outTermsAndConditions.HasValue()); + EXPECT_EQ(outTermsAndConditions.Value().GetValue(), acceptedTermsAndConditions.Value().GetValue()); + EXPECT_EQ(outTermsAndConditions.Value().GetVersion(), acceptedTermsAndConditions.Value().GetVersion()); +} + +TEST(DefaultTermsAndConditionsProvider, TestRevertAcceptanceWhileMissing) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate testPersistentStorageDelegate; + chip::app::DefaultTermsAndConditionsStorageDelegate defaultTermsAndConditionsStorageDelegate; + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + + chip::Optional outTermsAndConditions; + + err = defaultTermsAndConditionsStorageDelegate.Init(&testPersistentStorageDelegate); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // Initialize unit under test [No conditions previously accepted] + err = defaultTermsAndConditionsProvider.Init(&defaultTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // [Fail-safe started] No conditions set during the fail-safe. No commit. + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(outTermsAndConditions.HasValue()); + + // [Fail-safe expires] Revert is called. + err = defaultTermsAndConditionsProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // [New fail safe started (to retry the commissioning operations)] Confirm acceptance returns previous values (empty) + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(outTermsAndConditions.HasValue()); +} + +TEST(DefaultTermsAndConditionsProvider, TestRevertAcceptanceWhenPreviouslyAccepted) +{ + CHIP_ERROR err; + + // Initialize unit under test [Conditions previously accepted] + chip::Optional initialTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(0b11, 2)); + TestTermsAndConditionsStorageDelegate testTermsAndConditionsStorageDelegate(initialTermsAndConditions); + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + err = defaultTermsAndConditionsProvider.Init(&testTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // [Fail-safe started] No conditions set during the fail-safe. No commit. + + // [Fail-safe expires] Revert is called. + err = defaultTermsAndConditionsProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + + // [New fail safe started (to retry the commissioning operations)] Confirm acceptance returns previous values (accepted) + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(outTermsAndConditions.HasValue()); + EXPECT_EQ(outTermsAndConditions.Value().GetValue(), 1); + EXPECT_EQ(outTermsAndConditions.Value().GetVersion(), 1); +} + +TEST(DefaultTermsAndConditionsProvider, TestRevertAcceptanceWhenPreviouslyAcceptedThenUpdatedUnderFailsafe) +{ + CHIP_ERROR err; + + // Initialize unit under test dependency + chip::Optional initiallyAcceptedTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(1, 1)); + TestTermsAndConditionsStorageDelegate testTermsAndConditionsStorageDelegate(initiallyAcceptedTermsAndConditions); + + // Initialize unit under test [Conditions previously accepted] + chip::Optional requiredTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(0b11, 2)); + chip::app::DefaultTermsAndConditionsProvider defaultTermsAndConditionsProvider; + err = defaultTermsAndConditionsProvider.Init(&testTermsAndConditionsStorageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // [Fail-safe started] Acceptance updated. + chip::Optional updatedAcceptedTermsAndConditions = + chip::Optional(chip::app::TermsAndConditions(0b111, 3)); + err = defaultTermsAndConditionsProvider.SetAcceptance(updatedAcceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + // [Fail-safe expires] Revert is called. + err = defaultTermsAndConditionsProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + + // [New fail safe started (to retry the commissioning operations)] Confirm acceptance returns previous values (accepted) + err = defaultTermsAndConditionsProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(outTermsAndConditions.HasValue()); + EXPECT_EQ(outTermsAndConditions.Value().GetValue(), initiallyAcceptedTermsAndConditions.Value().GetValue()); + EXPECT_EQ(outTermsAndConditions.Value().GetVersion(), initiallyAcceptedTermsAndConditions.Value().GetVersion()); +} diff --git a/src/app/tests/suites/certification/PICS.yaml b/src/app/tests/suites/certification/PICS.yaml index 4f7539e2682e63..ea77b7c00d72f9 100644 --- a/src/app/tests/suites/certification/PICS.yaml +++ b/src/app/tests/suites/certification/PICS.yaml @@ -3625,6 +3625,23 @@ PICS: CommissioningCompleteResponse command?" id: CGEN.S.C05.Tx + # + # server / features + # + - label: + "Does the device implement the General Commissioning cluster's terms + and conditions feature?" + id: CGEN.S.F00 + + - label: "The device's failsafe expiration limit." + id: PIXIT.CGEN.FailsafeExpiryLengthSeconds + + - label: "The device's required terms and conditions acknowledgements." + id: PIXIT.CGEN.RequiredTCAcknowledgements + + - label: "The device's required minimum terms and conditions revision." + id: PIXIT.CGEN.TCRevision + # General Diagnostics Cluster Test Plan - label: "Does the device implement the General Diagnostics cluster as a diff --git a/src/app/tests/suites/certification/ci-pics-values b/src/app/tests/suites/certification/ci-pics-values index b8dcbd8f83f76c..f507bce0dd38b5 100644 --- a/src/app/tests/suites/certification/ci-pics-values +++ b/src/app/tests/suites/certification/ci-pics-values @@ -729,6 +729,7 @@ SWTCH.C.AO-READ=0 SWTCH.C.AO-WRITE=0 # General Commissioning Cluster +CGEN.C=1 CGEN.S=1 CGEN.S.A0000=1 CGEN.S.A0001=1 @@ -742,7 +743,13 @@ CGEN.S.C03.Tx=1 CGEN.S.C04.Rsp=1 CGEN.S.C05.Tx=1 -CGEN.C=1 +#Feature +CGEN.S.F00=1 + +#PIXIT +PIXIT.CGEN.FailsafeExpiryLengthSeconds=0 +PIXIT.CGEN.RequiredTCAcknowledgements=1 +PIXIT.CGEN.TCRevision=1 # LAUNDRY WASHER MODE CLUSTER LWM.S=1 diff --git a/src/app/zap-templates/zcl/data-model/chip/general-commissioning-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/general-commissioning-cluster.xml index ac798d929e069a..1a02c99d57adcc 100644 --- a/src/app/zap-templates/zcl/data-model/chip/general-commissioning-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/general-commissioning-cluster.xml @@ -77,7 +77,8 @@ limitations under the License. TCAcknowledgementsRequired - + + TCUpdateDeadline diff --git a/src/controller/AutoCommissioner.cpp b/src/controller/AutoCommissioner.cpp index 64f132c3124cda..153956e72ae5b7 100644 --- a/src/controller/AutoCommissioner.cpp +++ b/src/controller/AutoCommissioner.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2021-2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -365,6 +365,8 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(Commissio case CommissioningStage::kArmFailsafe: return CommissioningStage::kConfigRegulatory; case CommissioningStage::kConfigRegulatory: + return CommissioningStage::kConfigureTCAcknowledgments; + case CommissioningStage::kConfigureTCAcknowledgments: if (mDeviceCommissioningInfo.requiresUTC) { return CommissioningStage::kConfigureUTCTime; diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 346867226e4261..e5c3d282413528 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2020-2024 Project CHIP Authors * Copyright (c) 2013-2017 Nest Labs, Inc. * All rights reserved. * @@ -2688,6 +2688,22 @@ void DeviceCommissioner::OnSetRegulatoryConfigResponse( commissioner->CommissioningStageComplete(err, report); } +void DeviceCommissioner::OnSetTCAcknowledgementsResponse( + void * context, const GeneralCommissioning::Commands::SetTCAcknowledgementsResponse::DecodableType & data) +{ + CommissioningDelegate::CommissioningReport report; + CHIP_ERROR err = CHIP_NO_ERROR; + + ChipLogProgress(Controller, "Received SetTCAcknowledgements response errorCode=%u", to_underlying(data.errorCode)); + if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk) + { + err = CHIP_ERROR_INTERNAL; + report.Set(data.errorCode); + } + DeviceCommissioner * commissioner = static_cast(context); + commissioner->CommissioningStageComplete(err, report); +} + void DeviceCommissioner::OnSetTimeZoneResponse(void * context, const TimeSynchronization::Commands::SetTimeZoneResponse::DecodableType & data) { @@ -3160,6 +3176,32 @@ void DeviceCommissioner::PerformCommissioningStep(DeviceProxy * proxy, Commissio } } break; + case CommissioningStage::kConfigureTCAcknowledgments: { + ChipLogProgress(Controller, "Setting Terms and Conditions"); + + if (!params.GetTermsAndConditionsAcknowledgement().HasValue()) + { + ChipLogProgress(Controller, "Setting Terms and Conditions: Skipped"); + CommissioningStageComplete(CHIP_NO_ERROR); + return; + } + + GeneralCommissioning::Commands::SetTCAcknowledgements::Type request; + TermsAndConditionsAcknowledgement termsAndConditionsAcknowledgement = params.GetTermsAndConditionsAcknowledgement().Value(); + request.TCUserResponse = termsAndConditionsAcknowledgement.acceptedTermsAndConditions; + request.TCVersion = termsAndConditionsAcknowledgement.acceptedTermsAndConditionsVersion; + + ChipLogProgress(Controller, "Setting Terms and Conditions: %hu, %hu", request.TCUserResponse, request.TCVersion); + CHIP_ERROR err = + SendCommissioningCommand(proxy, request, OnSetTCAcknowledgementsResponse, OnBasicFailure, endpoint, timeout); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to send SetTCAcknowledgements command: %" CHIP_ERROR_FORMAT, err.Format()); + CommissioningStageComplete(err); + return; + } + break; + } case CommissioningStage::kSendPAICertificateRequest: { ChipLogProgress(Controller, "Sending request for PAI certificate"); CHIP_ERROR err = SendCertificateChainRequestCommand(proxy, CertificateType::kPAI, timeout); diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index 2ec020340ee98d..c8b522caa3bec4 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2020-2024 Project CHIP Authors * Copyright (c) 2013-2017 Nest Labs, Inc. * All rights reserved. * @@ -947,6 +947,9 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, static void OnSetRegulatoryConfigResponse( void * context, const chip::app::Clusters::GeneralCommissioning::Commands::SetRegulatoryConfigResponse::DecodableType & data); + static void OnSetTCAcknowledgementsResponse( + void * context, + const chip::app::Clusters::GeneralCommissioning::Commands::SetTCAcknowledgementsResponse::DecodableType & data); static void OnSetUTCError(void * context, CHIP_ERROR error); static void OnSetTimeZoneResponse(void * context, diff --git a/src/controller/CommissioningDelegate.cpp b/src/controller/CommissioningDelegate.cpp index d6acac6bc00bd4..9b2add8212b7cb 100644 --- a/src/controller/CommissioningDelegate.cpp +++ b/src/controller/CommissioningDelegate.cpp @@ -46,6 +46,9 @@ const char * StageToString(CommissioningStage stage) case kConfigRegulatory: return "ConfigRegulatory"; + case kConfigureTCAcknowledgments: + return "ConfigureTCAcknowledgments"; + case kConfigureUTCTime: return "ConfigureUTCTime"; diff --git a/src/controller/CommissioningDelegate.h b/src/controller/CommissioningDelegate.h index f0f98961adf3c3..9fda2a50b7c684 100644 --- a/src/controller/CommissioningDelegate.h +++ b/src/controller/CommissioningDelegate.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2021-2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -70,10 +70,8 @@ enum CommissioningStage : uint8_t ///< Commissioning Complete command kSendComplete, ///< Send CommissioningComplete (0x30:4) command to the device kICDSendStayActive, ///< Send Keep Alive to ICD - kCleanup, ///< Call delegates with status, free memory, clear timers and state /// Send ScanNetworks (0x31:0) command to the device. /// ScanNetworks can happen anytime after kArmFailsafe. - /// However, the cirque tests fail if it is earlier in the list kScanNetworks, /// Waiting for the higher layer to provide network credentials before continuing the workflow. /// Call CHIPDeviceController::NetworkCredentialsReady() when CommissioningParameters is populated with @@ -82,7 +80,9 @@ enum CommissioningStage : uint8_t kPrimaryOperationalNetworkFailed, ///< Indicate that the primary operational network (on root endpoint) failed, should remove ///< the primary network config later. kRemoveWiFiNetworkConfig, ///< Remove Wi-Fi network config. - kRemoveThreadNetworkConfig ///< Remove Thread network config. + kRemoveThreadNetworkConfig, ///< Remove Thread network config. + kConfigureTCAcknowledgments, ///< Send SetTCAcknowledgements (0x30:6) command to the device + kCleanup, ///< Call delegates with status, free memory, clear timers and state }; enum class ICDRegistrationStrategy : uint8_t @@ -105,6 +105,12 @@ struct WiFiCredentials WiFiCredentials(ByteSpan newSsid, ByteSpan newCreds) : ssid(newSsid), credentials(newCreds) {} }; +struct TermsAndConditionsAcknowledgement +{ + uint16_t acceptedTermsAndConditions; + uint16_t acceptedTermsAndConditionsVersion; +}; + struct NOCChainGenerationParameters { ByteSpan nocsrElements; @@ -169,6 +175,11 @@ class CommissioningParameters // The country code to be used for the node, if set. Optional GetCountryCode() const { return mCountryCode; } + Optional GetTermsAndConditionsAcknowledgement() const + { + return mTermsAndConditionsAcknowledgement; + } + // Time zone to set for the node // If required, this will be truncated to fit the max size allowable on the node Optional> GetTimeZone() const @@ -341,6 +352,13 @@ class CommissioningParameters return *this; } + CommissioningParameters & + SetTermsAndConditionsAcknowledgement(TermsAndConditionsAcknowledgement termsAndConditionsAcknowledgement) + { + mTermsAndConditionsAcknowledgement.SetValue(termsAndConditionsAcknowledgement); + return *this; + } + // The lifetime of the list buffer needs to exceed the lifetime of the CommissioningParameters object. CommissioningParameters & SetTimeZone(app::DataModel::List timeZone) @@ -612,6 +630,7 @@ class CommissioningParameters Optional mAttestationNonce; Optional mWiFiCreds; Optional mCountryCode; + Optional mTermsAndConditionsAcknowledgement; Optional mThreadOperationalDataset; Optional mNOCChainGenerationParameters; Optional mRootCert; diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index b31cb5b7b8a4e6..739dac9cb7ffdb 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -182,6 +182,10 @@ PyChipError pychip_DeviceController_OpenCommissioningWindow(chip::Controller::De bool pychip_DeviceController_GetIPForDiscoveredDevice(chip::Controller::DeviceCommissioner * devCtrl, int idx, char * addrStr, uint32_t len); +PyChipError pychip_DeviceController_SetTermsAcknowledgements(uint16_t tcVersion, uint16_t tcUserResponse); + +PyChipError pychip_DeviceController_SetSkipCommissioningComplete(bool skipCommissioningComplete); + // Pairing Delegate PyChipError pychip_ScriptDevicePairingDelegate_SetKeyExchangeCallback(chip::Controller::ScriptDevicePairingDelegate * pairingDelegate, @@ -570,6 +574,19 @@ PyChipError pychip_DeviceController_SetDefaultNtp(const char * defaultNTP) return ToPyChipError(CHIP_NO_ERROR); } +PyChipError pychip_DeviceController_SetTermsAcknowledgements(uint16_t tcVersion, uint16_t tcUserResponse) +{ + sCommissioningParameters.SetTermsAndConditionsAcknowledgement( + { .acceptedTermsAndConditions = tcUserResponse, .acceptedTermsAndConditionsVersion = tcVersion }); + return ToPyChipError(CHIP_NO_ERROR); +} + +PyChipError pychip_DeviceController_SetSkipCommissioningComplete(bool skipCommissioningComplete) +{ + sCommissioningParameters.SetSkipCommissioningComplete(skipCommissioningComplete); + return ToPyChipError(CHIP_NO_ERROR); +} + PyChipError pychip_DeviceController_SetTrustedTimeSource(chip::NodeId nodeId, chip::EndpointId endpoint) { chip::app::Clusters::TimeSynchronization::Structs::FabricScopedTrustedTimeSourceStruct::Type timeSource = { .nodeID = nodeId, diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 8c751f7f791dd0..8e39bc4ac504c9 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -1974,6 +1974,12 @@ def _InitLib(self): self._dmLib.pychip_CreateManualCode.restype = PyChipError self._dmLib.pychip_CreateManualCode.argtypes = [c_uint16, c_uint32, c_char_p, c_size_t, POINTER(c_size_t)] + self._dmLib.pychip_DeviceController_SetSkipCommissioningComplete.restype = PyChipError + self._dmLib.pychip_DeviceController_SetSkipCommissioningComplete.argtypes = [c_bool] + + self._dmLib.pychip_DeviceController_SetTermsAcknowledgements.restype = PyChipError + self._dmLib.pychip_DeviceController_SetTermsAcknowledgements.argtypes = [c_uint16, c_uint16] + class ChipDeviceController(ChipDeviceControllerBase): ''' The ChipDeviceCommissioner binding, named as ChipDeviceController @@ -2102,6 +2108,20 @@ def SetDSTOffset(self, offset: int, validStarting: int, validUntil: int): lambda: self._dmLib.pychip_DeviceController_SetDSTOffset(offset, validStarting, validUntil) ).raise_on_error() + def SetTCAcknowledgements(self, tcAcceptedVersion: int, tcUserResponse: int): + ''' Set the TC acknowledgements to set during commissioning''' + self.CheckIsActive() + self._ChipStack.Call( + lambda: self._dmLib.pychip_DeviceController_SetTermsAcknowledgements(tcAcceptedVersion, tcUserResponse) + ).raise_on_error() + + def SetSkipCommissioningComplete(self, skipCommissioningComplete: bool): + ''' Set whether to skip the commissioning complete callback''' + self.CheckIsActive() + self._ChipStack.Call( + lambda: self._dmLib.pychip_DeviceController_SetSkipCommissioningComplete(skipCommissioningComplete) + ).raise_on_error() + def SetDefaultNTP(self, defaultNTP: str): ''' Set the DefaultNTP to set during commissioning''' self.CheckIsActive() diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py index 3ae9116defc810..64578003a0b5eb 100644 --- a/src/controller/python/chip/clusters/Objects.py +++ b/src/controller/python/chip/clusters/Objects.py @@ -6551,21 +6551,22 @@ def descriptor(cls) -> ClusterObjectDescriptor: ClusterObjectFieldDescriptor(Label="clusterRevision", Tag=0x0000FFFD, Type=uint), ]) - breadcrumb: uint = 0 - basicCommissioningInfo: GeneralCommissioning.Structs.BasicCommissioningInfo = None - regulatoryConfig: GeneralCommissioning.Enums.RegulatoryLocationTypeEnum = 0 - locationCapability: GeneralCommissioning.Enums.RegulatoryLocationTypeEnum = 0 - supportsConcurrentConnection: bool = False - TCAcceptedVersion: typing.Optional[uint] = None - TCMinRequiredVersion: typing.Optional[uint] = None - TCAcknowledgements: typing.Optional[uint] = None - TCAcknowledgementsRequired: typing.Optional[bool] = None - TCUpdateDeadline: typing.Union[None, Nullable, uint] = None - generatedCommandList: typing.List[uint] = field(default_factory=lambda: []) - acceptedCommandList: typing.List[uint] = field(default_factory=lambda: []) - attributeList: typing.List[uint] = field(default_factory=lambda: []) - featureMap: uint = 0 - clusterRevision: uint = 0 + breadcrumb: 'uint' = None + basicCommissioningInfo: 'GeneralCommissioning.Structs.BasicCommissioningInfo' = None + regulatoryConfig: 'GeneralCommissioning.Enums.RegulatoryLocationTypeEnum' = None + locationCapability: 'GeneralCommissioning.Enums.RegulatoryLocationTypeEnum' = None + supportsConcurrentConnection: 'bool' = None + TCAcceptedVersion: 'typing.Optional[uint]' = None + TCMinRequiredVersion: 'typing.Optional[uint]' = None + TCAcknowledgements: 'typing.Optional[uint]' = None + TCAcknowledgementsRequired: 'typing.Optional[bool]' = None + TCUpdateDeadline: 'typing.Union[None, Nullable, uint]' = None + generatedCommandList: 'typing.List[uint]' = None + acceptedCommandList: 'typing.List[uint]' = None + eventList: 'typing.List[uint]' = None + attributeList: 'typing.List[uint]' = None + featureMap: 'uint' = None + clusterRevision: 'uint' = None class Enums: class CommissioningErrorEnum(MatterIntEnum): @@ -6910,11 +6911,7 @@ def attribute_id(cls) -> int: def attribute_type(cls) -> ClusterObjectFieldDescriptor: return ClusterObjectFieldDescriptor(Type=typing.Union[None, Nullable, uint]) -<<<<<<< HEAD - value: 'typing.Optional[uint]' = None -======= - value: typing.Union[None, Nullable, uint] = None ->>>>>>> eea382e1b4 (Update TCUpdateDeadline to be nullable to match spec (#37438)) + value: 'typing.Union[None, Nullable, uint]' = None @dataclass class GeneratedCommandList(ClusterAttributeDescriptor): @@ -50734,3 +50731,4 @@ def descriptor(cls) -> ClusterObjectDescriptor: count: 'uint' = 0 fabricIndex: 'uint' = 0 + diff --git a/src/controller/python/chip/commissioning/__init__.py b/src/controller/python/chip/commissioning/__init__.py index c1105511c67b85..15cbf33a770704 100644 --- a/src/controller/python/chip/commissioning/__init__.py +++ b/src/controller/python/chip/commissioning/__init__.py @@ -72,6 +72,12 @@ class WiFiCredentials: passphrase: bytes +@dataclasses.dataclass +class TermsAndConditionsParameters: + version: int + user_response: int + + @dataclasses.dataclass class Parameters: pase_param: Union[PaseOverBLEParameters, PaseOverIPParameters] @@ -80,6 +86,7 @@ class Parameters: commissionee_info: CommissioneeInfo wifi_credentials: WiFiCredentials thread_credentials: bytes + tc_acknowledgements: Optional[TermsAndConditionsParameters] = None failsafe_expiry_length_seconds: int = 600 diff --git a/src/controller/python/chip/commissioning/commissioning_flow_blocks.py b/src/controller/python/chip/commissioning/commissioning_flow_blocks.py index 7d0d11b37fab1c..cc1999167a8eed 100644 --- a/src/controller/python/chip/commissioning/commissioning_flow_blocks.py +++ b/src/controller/python/chip/commissioning/commissioning_flow_blocks.py @@ -240,6 +240,15 @@ async def send_regulatory_config(self, parameter: commissioning.Parameters, node if response.errorCode != 0: raise commissioning.CommissionFailure(repr(response)) + async def send_terms_and_conditions_acknowledgements(self, parameter: commissioning.Parameters, node_id: int): + self._logger.info("Settings Terms and Conditions") + if parameter.tc_acknowledgements: + response = await self._devCtrl.SendCommand(node_id, commissioning.ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=parameter.tc_acknowledgements.version, TCUserResponse=parameter.tc_acknowledgements.user_response + )) + if response.errorCode != 0: + raise commissioning.CommissionFailure(repr(response)) + async def complete_commission(self, node_id: int): response = await self._devCtrl.SendCommand(node_id, commissioning.ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Commands.CommissioningComplete()) if response.errorCode != 0: diff --git a/src/include/platform/CHIPDeviceEvent.h b/src/include/platform/CHIPDeviceEvent.h index 09f4c46b652920..9618d93f5faa05 100644 --- a/src/include/platform/CHIPDeviceEvent.h +++ b/src/include/platform/CHIPDeviceEvent.h @@ -534,6 +534,7 @@ struct ChipDeviceEvent final FabricIndex fabricIndex; bool addNocCommandHasBeenInvoked; bool updateNocCommandHasBeenInvoked; + bool updateTermsAndConditionsHasBeenInvoked; } FailSafeTimerExpired; struct diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index 9ed8a2f56cfd77..b0de78d085e48d 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2021-2024 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. @@ -256,6 +256,10 @@ class DefaultStorageKeyAllocator // when new fabric is created, this list needs to be updated, // when client init DefaultICDClientStorage, this table needs to be loaded. static StorageKeyName ICDFabricList() { return StorageKeyName::FromConst("g/icdfl"); } + + // Terms and Conditions Acceptance Key + // Stores the terms and conditions acceptance including terms and conditions revision, TLV encoded + static StorageKeyName TermsAndConditionsAcceptance() { return StorageKeyName::FromConst("g/tc"); } }; } // namespace chip diff --git a/src/python_testing/TC_CGEN_2_10.py b/src/python_testing/TC_CGEN_2_10.py new file mode 100644 index 00000000000000..90644074146916 --- /dev/null +++ b/src/python_testing/TC_CGEN_2_10.py @@ -0,0 +1,144 @@ +# +# Copyright (c) 2025 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. +# + + +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${TERMS_AND_CONDITIONS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --tc-min-required-version 1 --tc-required-acknowledgements 1 --custom-flow 2 --capabilities 6 +# test-runner-run/run1/script-args: --PICS src/app/tests/suites/certification/ci-pics-values --in-test-commissioning-method on-network --tc-version-to-simulate 1 --tc-user-response-to-simulate 1 --qr-code MT:-24J0AFN00KA0648G00 --trace-to json:log +# === END CI TEST ARGUMENTS === + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.commissioning import ROOT_ENDPOINT_ID +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_CGEN_2_10(MatterBaseTest): + def desc_TC_CGEN_2_10(self) -> str: + return "[TC-CGEN-2.10] Verification that required terms can't be unset from TCAcknowledgements with SetTCAcknowledgements [DUT as Server]" + + def pics_TC_CGEN_2_10(self) -> list[str]: + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["CGEN.S", "CGEN.S.F00"] + + def steps_TC_CGEN_2_10(self) -> list[TestStep]: + return [ + TestStep(0, description="", expectation="", is_commissioning=False), + TestStep(1, "TH reads from the DUT the attribute TCAcceptedVersion. Store the value as acceptedVersion."), + TestStep(2, "TH reads from the DUT the attribute TCAcknowledgements. Store the value as userAcknowledgements."), + TestStep(3, "TH Sends the SetTCAcknowledgements command to the DUT with the fields set as follows:\n* TCVersion: 0\n* TCUserResponse: 65535"), + TestStep(4, "TH reads from the DUT the attribute TCAcceptedVersion."), + TestStep(5, "TH reads from the DUT the attribute TCAcknowledgements."), + TestStep(6, "TH Sends the SetTCAcknowledgements command to the DUT with the fields set as follows:\n* TCVersion: acceptedVersion + 1\n* TCUserResponse: 0"), + TestStep(7, "TH reads from the DUT the attribute TCAcceptedVersion."), + TestStep(8, "TH reads from the DUT the attribute TCAcknowledgements."), + ] + + @async_test_body + async def test_TC_CGEN_2_10(self): + commissioner: ChipDeviceCtrl.ChipDeviceController = self.default_controller + + self.step(0) + if not self.check_pics("CGEN.S.F00"): + asserts.skip('Root endpoint does not support the [commissioning] feature under test') + return + + # Step 1: Begin commissioning with PASE and failsafe + commissioner.SetSkipCommissioningComplete(True) + self.matter_test_config.commissioning_method = self.matter_test_config.in_test_commissioning_method + self.matter_test_config.tc_version_to_simulate = None + self.matter_test_config.tc_user_response_to_simulate = None + await self.commission_devices() + + # Step 1: Read TCAcceptedVersion + self.step(1) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion)]) + accepted_version = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion] + + # Step 2: Read TCAcknowledgements + self.step(2) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcknowledgements)]) + user_acknowledgements = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcknowledgements] + + # Step 3: Send SetTCAcknowledgements with invalid version + self.step(3) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(TCVersion=0, TCUserResponse=65535), + ) + + # Verify TCMinVersionNotMet error + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kTCMinVersionNotMet, + "Expected TCMinVersionNotMet error", + ) + + # Step 4: Verify TCAcceptedVersion unchanged + self.step(4) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion)]) + current_version = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion] + asserts.assert_equal(current_version, accepted_version, "TCAcceptedVersion changed unexpectedly") + + # Step 5: Verify TCAcknowledgements unchanged + self.step(5) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcknowledgements)]) + current_acknowledgements = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcknowledgements] + asserts.assert_equal(current_acknowledgements, user_acknowledgements, "TCAcknowledgements changed unexpectedly") + + # Step 6: Send SetTCAcknowledgements with invalid response + self.step(6) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=accepted_version + 1, TCUserResponse=0 + ), + ) + + # Verify RequiredTCNotAccepted error + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kRequiredTCNotAccepted, + "Expected RequiredTCNotAccepted error", + ) + + # Step 7: Verify TCAcceptedVersion still unchanged + self.step(7) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion)]) + current_version = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion] + asserts.assert_equal(current_version, accepted_version, "TCAcceptedVersion changed unexpectedly after second attempt") + + # Step 8: Verify TCAcknowledgements still unchanged + self.step(8) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcknowledgements)]) + current_acknowledgements = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcknowledgements] + asserts.assert_equal( + current_acknowledgements, + user_acknowledgements, + "TCAcknowledgements changed unexpectedly after second attempt", + ) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_CGEN_2_11.py b/src/python_testing/TC_CGEN_2_11.py new file mode 100644 index 00000000000000..aa61a5db4f66f6 --- /dev/null +++ b/src/python_testing/TC_CGEN_2_11.py @@ -0,0 +1,167 @@ +# +# Copyright (c) 2025 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. +# + +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${TERMS_AND_CONDITIONS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --tc-min-required-version 1 --tc-required-acknowledgements 1 --custom-flow 2 --capabilities 6 +# test-runner-run/run1/script-args: --PICS src/app/tests/suites/certification/ci-pics-values --in-test-commissioning-method on-network --int-arg PIXIT.CGEN.FailsafeExpiryLengthSeconds:900 PIXIT.CGEN.RequiredTCAcknowledgements:1 PIXIT.CGEN.TCRevision:1 --qr-code MT:-24J0AFN00KA0648G00 --trace-to json:log +# === END CI TEST ARGUMENTS === + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.commissioning import ROOT_ENDPOINT_ID +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_CGEN_2_11(MatterBaseTest): + def desc_TC_CGEN_2_11(self) -> str: + return "[TC-CGEN-2.11] Verification that TCAcknowledgements and TCAcceptedVersion can be updated after being commissioned [DUT as Server]" + + def pics_TC_CGEN_2_11(self) -> list[str]: + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["CGEN.S", "CGEN.S.F00"] + + def steps_TC_CGEN_2_11(self) -> list[TestStep]: + return [ + TestStep(0, description="", expectation="", is_commissioning=False), + TestStep(1, "TH begins commissioning the DUT and performs the following steps in order:\n* Security setup using PASE\n* Setup fail-safe timer, with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1\n* Configure information- UTC time, regulatory, etc."), + TestStep(2, "TH sends SetTCAcknowledgements to DUT with the following values:\n* TCVersion: PIXIT.CGEN.TCRevision\n* TCUserResponse: PIXIT.CGEN.RequiredTCAcknowledgements"), + TestStep(3, "TH sends CommissioningComplete to DUT."), + TestStep(4, "TH Sends the SetTCAcknowledgements command to the DUT with the fields set as follows:\n* TCVersion: PIXIT.CGEN.TCRevision + 1\n* TCUserResponse: PIXIT.CGEN.RequiredTCAcknowledgements"), + TestStep(5, "TH reads from the DUT the attribute TCAcceptedVersion."), + TestStep(6, "TH Sends the SetTCAcknowledgements command to the DUT with the fields set as follows:\n* TCVersion: PIXIT.CGEN.TCRevision + 1\n* TCUserResponse: 65535"), + TestStep(7, "TH reads from the DUT the attribute TCAcknowledgements."), + ] + + @async_test_body + async def test_TC_CGEN_2_11(self): + commissioner: ChipDeviceCtrl.ChipDeviceController = self.default_controller + failsafe_expiry_length_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] + tc_version_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.TCRevision'] + tc_user_response_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.RequiredTCAcknowledgements'] + + self.step(0) + if not self.check_pics("CGEN.S.F00"): + asserts.skip('Root endpoint does not support the [commissioning] feature under test') + return + + # Step 1: Begin commissioning with PASE and failsafe + self.step(1) + commissioner.SetSkipCommissioningComplete(True) + self.matter_test_config.commissioning_method = self.matter_test_config.in_test_commissioning_method + self.matter_test_config.tc_version_to_simulate = None + self.matter_test_config.tc_user_response_to_simulate = None + await self.commission_devices() + + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.ArmFailSafe( + expiryLengthSeconds=failsafe_expiry_length_seconds, breadcrumb=1), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "ArmFailSafeResponse error code is not OK.", + ) + + # Step 2: Send initial SetTCAcknowledgements + self.step(2) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=tc_version_to_simulate, TCUserResponse=tc_user_response_to_simulate + ), + ) + + # Verify initial SetTCAcknowledgements response + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Initial SetTCAcknowledgements failed", + ) + + # Step 3: Send CommissioningComplete + self.step(3) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.CommissioningComplete(), + ) + + # Verify CommissioningComplete response + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "CommissioningComplete failed", + ) + + # Step 4: Send SetTCAcknowledgements with updated version + self.step(4) + updated_tc_version = tc_version_to_simulate + 1 + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=updated_tc_version, TCUserResponse=tc_user_response_to_simulate + ), + ) + + # Verify SetTCAcknowledgements response with updated version + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "SetTCAcknowledgements with updated version failed", + ) + + # Step 5: Verify TCAcceptedVersion is updated + self.step(5) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion)]) + current_version = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion] + asserts.assert_equal(current_version, updated_tc_version, "TCAcceptedVersion not updated correctly") + + # Step 6: Send SetTCAcknowledgements with maximum acknowledgements + self.step(6) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=updated_tc_version, TCUserResponse=65535 + ), + ) + + # Verify SetTCAcknowledgements response with maximum acknowledgements + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "SetTCAcknowledgements with maximum acknowledgements failed", + ) + + # Step 7: Verify TCAcknowledgements is updated + self.step(7) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcknowledgements)]) + current_acknowledgements = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcknowledgements] + asserts.assert_equal(current_acknowledgements, 65535, "TCAcknowledgements not updated to maximum value") + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_CGEN_2_5.py b/src/python_testing/TC_CGEN_2_5.py new file mode 100644 index 00000000000000..6d25fabae6a509 --- /dev/null +++ b/src/python_testing/TC_CGEN_2_5.py @@ -0,0 +1,186 @@ +# +# Copyright (c) 2025 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. +# +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${TERMS_AND_CONDITIONS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --tc-min-required-version 1 --tc-required-acknowledgements 1 --custom-flow 2 --capabilities 6 +# test-runner-run/run1/script-args: --PICS src/app/tests/suites/certification/ci-pics-values --in-test-commissioning-method on-network --int-arg PIXIT.CGEN.FailsafeExpiryLengthSeconds:900 PIXIT.CGEN.RequiredTCAcknowledgements:1 PIXIT.CGEN.TCRevision:1 --qr-code MT:-24J0AFN00KA0648G00 --trace-to json:log +# === END CI TEST ARGUMENTS === + +import chip.clusters as Clusters +import matter_asserts +from chip import ChipDeviceCtrl +from chip.clusters.Types import Nullable +from chip.commissioning import ROOT_ENDPOINT_ID +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_CGEN_2_5(MatterBaseTest): + def desc_TC_CGEN_2_5(self) -> str: + return "[TC-CGEN-2.5] Verification for SetTCAcknowledgements [DUT as Server]" + + def pics_TC_CGEN_2_5(self) -> list[str]: + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["CGEN.S", "CGEN.S.F00"] + + def steps_TC_CGEN_2_5(self) -> list[TestStep]: + return [ + TestStep(0, description="", expectation="", is_commissioning=False), + TestStep(1, "TH begins commissioning the DUT and performs the following steps in order:\n* Security setup using PASE\n* Setup fail-safe timer, with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1\n* Configure information- UTC time, regulatory, etc."), + TestStep(2, "TH reads TCAcknowledgementsRequired attribute from the DUT"), + TestStep(3, "TH reads TCUpdateDeadline attribute from the DUT"), + TestStep(4, "TH reads the FeatureMap from the General Commissioning Cluster."), + TestStep(5, "TH sends SetTCAcknowledgements to DUT with the following values:\n* TCVersion: PIXIT.CGEN.TCRevision\n* TCUserResponse: PIXIT.CGEN.RequiredTCAcknowledgements"), + TestStep(6, "TH reads TCAcknowledgementsRequired attribute from the DUT"), + TestStep(7, "TH continues commissioning with the DUT and performs the steps from 'Operation CSR exchange' through 'Security setup using CASE'"), + TestStep(8, "TH sends CommissioningComplete to DUT."), + TestStep(9, "TH reads from the DUT the attribute TCAcceptedVersion."), + TestStep(10, "TH reads from the DUT the attribute TCAcknowledgements."), + TestStep(11, "TH reads from the DUT the attribute TCMinRequiredVersion."), + TestStep(12, "TH reads from the DUT the attribute TCAcknowledgementsRequired."), + ] + + @async_test_body + async def test_TC_CGEN_2_5(self): + commissioner: ChipDeviceCtrl.ChipDeviceController = self.default_controller + + failsafe_expiry_length_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] + tc_version_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.TCRevision'] + tc_user_response_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.RequiredTCAcknowledgements'] + + self.step(0) + if not self.check_pics("CGEN.S.F00"): + asserts.skip('Root endpoint does not support the [commissioning] feature under test') + return + + # Step 1: Begin commissioning with PASE and failsafe + self.step(1) + commissioner.SetSkipCommissioningComplete(True) + self.matter_test_config.commissioning_method = self.matter_test_config.in_test_commissioning_method + self.matter_test_config.tc_version_to_simulate = None + self.matter_test_config.tc_user_response_to_simulate = None + await self.commission_devices() + + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.ArmFailSafe( + expiryLengthSeconds=failsafe_expiry_length_seconds, breadcrumb=1), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "ArmFailSafeResponse error code is not OK.", + ) + + # Step 2: Read TCAcknowledgementsRequired + self.step(2) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcknowledgementsRequired)]) + tc_acknowledgements_required = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcknowledgementsRequired] + asserts.assert_equal(tc_acknowledgements_required, True, "TCAcknowledgementsRequired should be True.") + + # Step 3: Read TCUpdateDeadline + self.step(3) + response = await commissioner.ReadAttribute( + nodeid=self.dut_node_id, + attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCUpdateDeadline)], + ) + tc_update_deadline = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCUpdateDeadline] + + # Validate the value is of type Optional[uint32], e.g. either Nullable or within the 32-bit range. + if not isinstance(tc_update_deadline, Nullable): + matter_asserts.assert_valid_uint32(tc_update_deadline, "TCUpdateDeadline exceeds uint32 range") + + # Step 4: Verify TC feature flag in FeatureMap + self.step(4) + response = await commissioner.ReadAttribute( + nodeid=self.dut_node_id, + attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.FeatureMap)], + ) + feature_map = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.FeatureMap] + asserts.assert_equal(feature_map & Clusters.GeneralCommissioning.Bitmaps.Feature.kTermsAndConditions, + Clusters.GeneralCommissioning.Bitmaps.Feature.kTermsAndConditions, "TC feature flag is not set.") + + # Step 5: Send SetTCAcknowledgements + self.step(5) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=tc_version_to_simulate, TCUserResponse=tc_user_response_to_simulate + ), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "SetTCAcknowledgementsResponse error code is not OK.", + ) + + # Step 6: Verify TCAcknowledgementsRequired is False + self.step(6) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcknowledgementsRequired)]) + tc_acknowledgements_required = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcknowledgementsRequired] + asserts.assert_equal(tc_acknowledgements_required, False, "TCAcknowledgementsRequired should be False.") + + # Step 7: Continue with CSR and CASE setup + self.step(7) + + # Step 8: Send CommissioningComplete + self.step(8) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.CommissioningComplete(), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "CommissioningCompleteResponse error code is not OK.", + ) + + # Step 9: Verify TCAcceptedVersion + self.step(9) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion)]) + accepted_version = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion] + asserts.assert_equal(accepted_version, tc_version_to_simulate, "TCAcceptedVersion does not match expected value.") + matter_asserts.assert_valid_uint16(accepted_version, "TCAcceptedVersion is not a uint16 type.") + + # Step 10: Verify TCAcknowledgements + self.step(10) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcknowledgements)]) + acknowledgements = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcknowledgements] + asserts.assert_equal(acknowledgements, tc_user_response_to_simulate, "TCAcknowledgements does not match expected value.") + matter_asserts.assert_valid_uint16(accepted_version, "TCAcknowledgements is not a map16 type.") + + # Step 11: Verify TCMinRequiredVersion + self.step(11) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCMinRequiredVersion)]) + min_required_version = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCMinRequiredVersion] + matter_asserts.assert_valid_uint16(min_required_version, "TCMinRequiredVersion is not a uint16 type.") + + # Step 12: Verify TCAcknowledgementsRequired is False again + self.step(12) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCAcknowledgementsRequired)]) + tc_acknowledgements_required = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCAcknowledgementsRequired] + asserts.assert_equal(tc_acknowledgements_required, False, "TCAcknowledgementsRequired should be False.") + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_CGEN_2_6.py b/src/python_testing/TC_CGEN_2_6.py new file mode 100644 index 00000000000000..125c1b7b03c5ce --- /dev/null +++ b/src/python_testing/TC_CGEN_2_6.py @@ -0,0 +1,84 @@ +# +# Copyright (c) 2025 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. +# + +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${TERMS_AND_CONDITIONS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --tc-min-required-version 1 --tc-required-acknowledgements 1 --custom-flow 2 --capabilities 6 +# test-runner-run/run1/script-args: --PICS src/app/tests/suites/certification/ci-pics-values --in-test-commissioning-method on-network --qr-code MT:-24J0AFN00KA0648G00 --trace-to json:log +# === END CI TEST ARGUMENTS === + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.commissioning import ROOT_ENDPOINT_ID +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_CGEN_2_6(MatterBaseTest): + def desc_TC_CGEN_2_6(self) -> str: + return "[TC-CGEN-2.6] Verification for CommissioningComplete no terms accepted when required [DUT as Server]" + + def pics_TC_CGEN_2_6(self) -> list[str]: + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["CGEN.S", "CGEN.S.F00"] + + def steps_TC_CGEN_2_6(self) -> list[TestStep]: + return [ + TestStep(0, description="", expectation="", is_commissioning=False), + TestStep(1, "TH starts commissioning the DUT. It performs all commissioning steps from 'Device discovery and establish commissioning channel' to 'Security setup using CASE', except for TC configuration with SetTCAcknowledgements."), + TestStep(2, "TH sends CommissioningComplete to DUT."), + ] + + @async_test_body + async def test_TC_CGEN_2_6(self): + commissioner: ChipDeviceCtrl.ChipDeviceController = self.default_controller + + self.step(0) + if not self.check_pics("CGEN.S.F00"): + asserts.skip('Root endpoint does not support the [commissioning] feature under test') + return + + # Step 1: Commission device without setting TC acknowledgements + self.step(1) + # Don't set TCs for the next commissioning and skip CommissioningComplete so we can manually call CommissioningComplete to check the response error code + commissioner.SetSkipCommissioningComplete(True) + self.matter_test_config.commissioning_method = self.matter_test_config.in_test_commissioning_method + self.matter_test_config.tc_version_to_simulate = None + self.matter_test_config.tc_user_response_to_simulate = None + await self.commission_devices() + + # Step 2: Send CommissioningComplete and verify error response + self.step(2) + response: Clusters.GeneralCommissioning.Commands.CommissioningCompleteResponse = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.CommissioningComplete(), + ) + + # Verify that DUT sends CommissioningCompleteResponse Command to TH with ErrorCode as 'TCAcknowledgementsNotReceived'(6) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kTCAcknowledgementsNotReceived, + "Expected TCAcknowledgementsNotReceived error code", + ) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_CGEN_2_7.py b/src/python_testing/TC_CGEN_2_7.py new file mode 100644 index 00000000000000..c30190c3374e15 --- /dev/null +++ b/src/python_testing/TC_CGEN_2_7.py @@ -0,0 +1,161 @@ +# +# Copyright (c) 2025 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. +# + +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${TERMS_AND_CONDITIONS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --tc-min-required-version 1 --tc-required-acknowledgements 1 --custom-flow 2 --capabilities 6 +# test-runner-run/run1/script-args: --PICS src/app/tests/suites/certification/ci-pics-values --in-test-commissioning-method on-network --int-arg PIXIT.CGEN.FailsafeExpiryLengthSeconds:900 PIXIT.CGEN.RequiredTCAcknowledgements:1 PIXIT.CGEN.TCRevision:1 --qr-code MT:-24J0AFN00KA0648G00 --trace-to json:log +# === END CI TEST ARGUMENTS === + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.commissioning import ROOT_ENDPOINT_ID +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_CGEN_2_7(MatterBaseTest): + def desc_TC_CGEN_2_7(self) -> str: + return "[TC-CGEN-2.7] Verification for CommissioningComplete when SetTCAcknowledgements provides invalid terms [DUT as Server]" + + def pics_TC_CGEN_2_7(self) -> list[str]: + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["CGEN.S", "CGEN.S.F00"] + + def steps_TC_CGEN_2_7(self) -> list[TestStep]: + return [ + TestStep(0, description="", expectation="", is_commissioning=False), + TestStep(1, "TH begins commissioning the DUT and performs the following steps in order:\n* Security setup using PASE\n* Setup fail-safe timer, with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1\n* Configure information- UTC time, regulatory, etc."), + TestStep(2, "TH reads from the DUT the attribute TCMinRequiredVersion. Store the value as minVersion."), + TestStep(3, "TH sends SetTCAcknowledgements to DUT with the following values:\n* TCVersion: minVersion\n* TCUserResponse: 0"), + TestStep(4, "TH continues commissioning with the DUT and performs the steps from 'Operation CSR exchange' through 'Security setup using CASE'"), + TestStep(5, "TH sends CommissioningComplete to DUT."), + TestStep(6, "TH sends SetTCAcknowledgements to DUT with the following values:\n* TCVersion: PIXIT.CGEN.TCRevision\n* TCUserResponse: PIXIT.CGEN.RequiredTCAcknowledgements"), + TestStep(7, "TH sends CommissioningComplete to DUT."), + ] + + @async_test_body + async def test_TC_CGEN_2_7(self): + commissioner: ChipDeviceCtrl.ChipDeviceController = self.default_controller + failsafe_expiry_length_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] + tc_version_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.TCRevision'] + tc_user_response_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.RequiredTCAcknowledgements'] + + self.step(0) + if not self.check_pics("CGEN.S.F00"): + asserts.skip('Root endpoint does not support the [commissioning] feature under test') + return + + # Step 1: Begin commissioning with PASE and failsafe + self.step(1) + commissioner.SetSkipCommissioningComplete(True) + self.matter_test_config.commissioning_method = self.matter_test_config.in_test_commissioning_method + self.matter_test_config.tc_version_to_simulate = None + self.matter_test_config.tc_user_response_to_simulate = None + await self.commission_devices() + + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.ArmFailSafe( + expiryLengthSeconds=failsafe_expiry_length_seconds, breadcrumb=1), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "ArmFailSafeResponse error code is not OK.", + ) + + # Step 2: Read TCMinRequiredVersion + self.step(2) + response = await commissioner.ReadAttribute(nodeid=self.dut_node_id, attributes=[(ROOT_ENDPOINT_ID, Clusters.GeneralCommissioning.Attributes.TCMinRequiredVersion)]) + min_version = response[ROOT_ENDPOINT_ID][Clusters.GeneralCommissioning][Clusters.GeneralCommissioning.Attributes.TCMinRequiredVersion] + + # Step 3: Send SetTCAcknowledgements with invalid response + self.step(3) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=min_version, TCUserResponse=0 + ), + ) + + # Verify error code is RequiredTCNotAccepted + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kRequiredTCNotAccepted, + "Expected RequiredTCNotAccepted error code", + ) + + # Step 4: Continue with CSR and CASE setup + self.step(4) + # Note: CSR and CASE setup is handled by the commissioning process + + # Step 5: Send CommissioningComplete and verify it fails + self.step(5) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.CommissioningComplete(), + ) + + # Verify error code is TCAcknowledgementsNotReceived + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kTCAcknowledgementsNotReceived, + "Expected TCAcknowledgementsNotReceived error code", + ) + + # Step 6: Send SetTCAcknowledgements with valid values + self.step(6) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=tc_version_to_simulate, TCUserResponse=tc_user_response_to_simulate + ), + ) + + # Verify error code is OK + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Expected OK response for valid TC acknowledgements", + ) + + # Step 7: Send CommissioningComplete and verify success + self.step(7) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.CommissioningComplete(), + ) + + # Verify error code is OK + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "Expected OK response for CommissioningComplete", + ) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_CGEN_2_8.py b/src/python_testing/TC_CGEN_2_8.py new file mode 100644 index 00000000000000..cd419d104b5af8 --- /dev/null +++ b/src/python_testing/TC_CGEN_2_8.py @@ -0,0 +1,153 @@ +# +# Copyright (c) 2025 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. +# + +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${TERMS_AND_CONDITIONS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --tc-min-required-version 1 --tc-required-acknowledgements 1 --custom-flow 2 --capabilities 6 +# test-runner-run/run1/script-args: --PICS src/app/tests/suites/certification/ci-pics-values --in-test-commissioning-method on-network --int-arg PIXIT.CGEN.FailsafeExpiryLengthSeconds:900 PIXIT.CGEN.RequiredTCAcknowledgements:1 PIXIT.CGEN.TCRevision:1 --qr-code MT:-24J0AFN00KA0648G00 --trace-to json:log +# === END CI TEST ARGUMENTS === + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.commissioning import ROOT_ENDPOINT_ID +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_CGEN_2_8(MatterBaseTest): + def desc_TC_CGEN_2_8(self) -> str: + return "[TC-CGEN-2.8] Verification that TCAcknowledgements is reset after Factory Reset [DUT as Server]" + + def pics_TC_CGEN_2_8(self) -> list[str]: + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["CGEN.S", "CGEN.S.F00"] + + def steps_TC_CGEN_2_8(self) -> list[TestStep]: + return [ + TestStep(0, description="", expectation="", is_commissioning=False), + TestStep(1, "TH begins commissioning the DUT and performs the following steps in order:\n* Security setup using PASE\n* Setup fail-safe timer, with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1\n* Configure information- UTC time, regulatory, etc."), + TestStep(2, "TH sends SetTCAcknowledgements to DUT with the following values:\n* TCVersion: PIXIT.CGEN.TCRevision\n* TCUserResponse: PIXIT.CGEN.RequiredTCAcknowledgements"), + TestStep(3, "TH continues commissioning steps with the DUT and performs steps 'Operation CSR exchange' through 'Security setup using CASE'"), + TestStep(4, "TH sends CommissioningComplete to DUT."), + TestStep(5, "DUT is factory reset."), + TestStep(6, "Perform the necessary actions to put the DUT into a commissionable state."), + TestStep(7, "TH begins commissioning the DUT and performs the steps 'Device discovery and establish commissioning channel' through 'Security setup using CASE', skipping 'Configure information- TC Acknowledgements'"), + TestStep(8, "TH sends CommissioningComplete to DUT."), + ] + + @async_test_body + async def test_TC_CGEN_2_8(self): + commissioner: ChipDeviceCtrl.ChipDeviceController = self.default_controller + failsafe_expiry_length_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] + tc_version_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.TCRevision'] + tc_user_response_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.RequiredTCAcknowledgements'] + + self.step(0) + if not self.check_pics("CGEN.S.F00"): + asserts.skip('Root endpoint does not support the [commissioning] feature under test') + return + + # Step 1: Begin commissioning with PASE and failsafe + self.step(1) + commissioner.SetSkipCommissioningComplete(True) + self.matter_test_config.commissioning_method = self.matter_test_config.in_test_commissioning_method + self.matter_test_config.tc_version_to_simulate = None + self.matter_test_config.tc_user_response_to_simulate = None + await self.commission_devices() + + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.ArmFailSafe( + expiryLengthSeconds=failsafe_expiry_length_seconds, breadcrumb=1), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "ArmFailSafeResponse error code is not OK.", + ) + + # Step 2: Send SetTCAcknowledgements + self.step(2) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=tc_version_to_simulate, TCUserResponse=tc_user_response_to_simulate + ), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "SetTCAcknowledgements failed", + ) + + # Step 3: Continue with CSR and CASE setup + self.step(3) + # Note: CSR and CASE setup is handled by the commissioning process + + # Step 4: Send CommissioningComplete + self.step(4) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.CommissioningComplete(), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "First CommissioningComplete failed", + ) + + # Step 5: Factory reset is handled by test operator + self.step(5) + if not self.check_pics('PICS_USER_PROMPT'): + self.skip_all_remaining_steps(6) + return + + self.wait_for_user_input(prompt_msg="Manually trigger factory reset on the DUT, then continue") + + # Step 6: Put device in commissioning mode (requiring user input, so skip in CI) + self.step(6) + self.wait_for_user_input(prompt_msg="Manually set the DUT into commissioning mode, then continue") + + # Step 7: Commission without TC acknowledgements + self.step(7) + commissioner.SetSkipCommissioningComplete(True) + self.matter_test_config.tc_version_to_simulate = None + self.matter_test_config.tc_user_response_to_simulate = None + await self.commission_devices() + + # Step 8: Verify CommissioningComplete fails + self.step(8) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.CommissioningComplete(), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kTCAcknowledgementsNotReceived, + "Expected TCAcknowledgementsNotReceived error after factory reset", + ) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_CGEN_2_9.py b/src/python_testing/TC_CGEN_2_9.py new file mode 100644 index 00000000000000..90d30582f211a5 --- /dev/null +++ b/src/python_testing/TC_CGEN_2_9.py @@ -0,0 +1,182 @@ +# +# Copyright (c) 2025 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. +# + +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${TERMS_AND_CONDITIONS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --tc-min-required-version 1 --tc-required-acknowledgements 1 --custom-flow 2 --capabilities 6 +# test-runner-run/run1/script-args: --PICS src/app/tests/suites/certification/ci-pics-values --in-test-commissioning-method on-network --int-arg PIXIT.CGEN.FailsafeExpiryLengthSeconds:900 PIXIT.CGEN.RequiredTCAcknowledgements:1 PIXIT.CGEN.TCRevision:1 --qr-code MT:-24J0AFN00KA0648G00 --trace-to json:log +# === END CI TEST ARGUMENTS === + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.commissioning import ROOT_ENDPOINT_ID +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_CGEN_2_9(MatterBaseTest): + + async def remove_commissioner_fabric(self): + commissioner: ChipDeviceCtrl.ChipDeviceController = self.default_controller + + fabrics: list[Clusters.OperationalCredentials.Structs.FabricDescriptorStruct] = await self.read_single_attribute( + dev_ctrl=commissioner, + node_id=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + attribute=Clusters.OperationalCredentials.Attributes.Fabrics) + + # Re-order the list of fabrics so that the test harness admin fabric is removed last + commissioner_fabric = next((fabric for fabric in fabrics if fabric.fabricIndex == commissioner.fabricId), None) + fabrics.remove(commissioner_fabric) + fabrics.append(commissioner_fabric) + + for fabric in fabrics: + response: Clusters.OperationalCredentials.Commands.NOCResponse = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.OperationalCredentials.Commands.RemoveFabric(fabric.fabricIndex), + ) + asserts.assert_equal(response.statusCode, Clusters.OperationalCredentials.Enums.NodeOperationalCertStatusEnum.kOk) + + def desc_TC_CGEN_2_9(self) -> str: + return "[TC-CGEN-2.9] Verification that TCAcknowledgements is reset after all fabrics removed [DUT as Server]" + + def pics_TC_CGEN_2_9(self) -> list[str]: + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["CGEN.S", "CGEN.S.F00"] + + def steps_TC_CGEN_2_9(self) -> list[TestStep]: + return [ + TestStep(0, description="", expectation="", is_commissioning=False), + TestStep(1, "TH begins commissioning the DUT and performs the following steps in order:\n* Security setup using PASE\n* Setup fail-safe timer, with ExpiryLengthSeconds field set to PIXIT.CGEN.FailsafeExpiryLengthSeconds and the Breadcrumb value as 1\n* Configure information- UTC time, regulatory, etc."), + TestStep(2, "TH sends SetTCAcknowledgements to DUT with the following values:\n* TCVersion: PIXIT.CGEN.TCRevision\n* TCUserResponse: PIXIT.CGEN.RequiredTCAcknowledgements"), + TestStep(3, "TH continues commissioning with the DUT and performs the steps from 'Operation CSR exchange' through 'Security setup using CASE'"), + TestStep(4, "TH sends CommissioningComplete to DUT."), + TestStep(5, "TH removes all fabrics from DUT with RemoveFabric."), + TestStep(6, "Perform the necessary actions to put the DUT into a commissionable state."), + TestStep(7, "TH begins commissioning the DUT and performs all steps from 'Device discovery and establish commissioning channel' through 'Security setup using CASE', skipping the 'Configure TC acknowledgements' step"), + TestStep(8, "TH sends CommissioningComplete to DUT."), + ] + + @async_test_body + async def test_TC_CGEN_2_9(self): + commissioner: ChipDeviceCtrl.ChipDeviceController = self.default_controller + failsafe_expiry_length_seconds = self.matter_test_config.global_test_params['PIXIT.CGEN.FailsafeExpiryLengthSeconds'] + tc_version_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.TCRevision'] + tc_user_response_to_simulate = self.matter_test_config.global_test_params['PIXIT.CGEN.RequiredTCAcknowledgements'] + + self.step(0) + if not self.check_pics("CGEN.S.F00"): + asserts.skip('Root endpoint does not support the [commissioning] feature under test') + return + + # Step 1: Begin commissioning with PASE and failsafe + self.step(1) + commissioner.SetSkipCommissioningComplete(True) + self.matter_test_config.commissioning_method = self.matter_test_config.in_test_commissioning_method + self.matter_test_config.tc_version_to_simulate = None + self.matter_test_config.tc_user_response_to_simulate = None + await self.commission_devices() + + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.ArmFailSafe( + expiryLengthSeconds=failsafe_expiry_length_seconds, breadcrumb=1), + ) + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "ArmFailSafeResponse error code is not OK.", + ) + + # Step 2: Send SetTCAcknowledgements + self.step(2) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements( + TCVersion=tc_version_to_simulate, TCUserResponse=tc_user_response_to_simulate + ), + ) + + # Verify SetTCAcknowledgements response + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "SetTCAcknowledgements failed", + ) + + # Step 3: Continue with CSR and CASE setup + self.step(3) + # Note: CSR and CASE setup is handled by the commissioning process + + # Step 4: Send CommissioningComplete + self.step(4) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.CommissioningComplete(), + ) + + # Verify CommissioningComplete response + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, + "First CommissioningComplete failed", + ) + + # Step 5: Remove all fabrics + self.step(5) + await self.remove_commissioner_fabric() + + # Step 6: Put device in commissioning mode (requiring user input, so skip in CI) + self.step(6) + if not self.check_pics('PICS_USER_PROMPT'): + self.skip_all_remaining_steps(7) + return + + self.wait_for_user_input(prompt_msg="Set the DUT into commissioning mode") + + # Step 7: Commission without TC acknowledgements + self.step(7) + commissioner.SetSkipCommissioningComplete(True) + self.matter_test_config.tc_version_to_simulate = None + self.matter_test_config.tc_user_response_to_simulate = None + await self.commission_devices() + + # Step 8: Verify CommissioningComplete fails + self.step(8) + response = await commissioner.SendCommand( + nodeid=self.dut_node_id, + endpoint=ROOT_ENDPOINT_ID, + payload=Clusters.GeneralCommissioning.Commands.CommissioningComplete(), + ) + + # Verify CommissioningComplete fails with correct error + asserts.assert_equal( + response.errorCode, + Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kTCAcknowledgementsNotReceived, + "Expected TCAcknowledgementsNotReceived error after fabric removal", + ) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/execute_python_tests.py b/src/python_testing/execute_python_tests.py index 2b887c5f87ff8c..7ce761a1b6e4d3 100644 --- a/src/python_testing/execute_python_tests.py +++ b/src/python_testing/execute_python_tests.py @@ -93,6 +93,7 @@ def main(search_directory, env_file): "choice_conformance_support.py", # Test support/shared code script, not a standalone test "conformance_support.py", # Test support/shared code script, not a standalone test "global_attribute_ids.py", # Test support/shared code script, not a standalone test + "matter_asserts.py", # Test support/shared code script, not a standalone test "matter_testing_support.py", # Test support/shared code script, not a standalone test "pics_support.py", # Test support/shared code script, not a standalone test "spec_parsing_support.py", # Test support/shared code script, not a standalone test diff --git a/src/python_testing/matter_asserts.py b/src/python_testing/matter_asserts.py new file mode 100644 index 00000000000000..2371b21557d8bd --- /dev/null +++ b/src/python_testing/matter_asserts.py @@ -0,0 +1,336 @@ +""" +Matter-specific assertions building on top of Mobly asserts. +""" + +from typing import Any, List, Optional, Type, TypeVar + +from mobly import asserts + +T = TypeVar('T') + + +# Internal helper functions + +def is_valid_uint_value(value: Any, bit_count: int = 64) -> bool: + """ + Checks if 'value' is a non-negative integer that fits into 'bit_count' bits. + For example, bit_count=32 => 0 <= value <= 0xFFFFFFFF + """ + if not isinstance(value, int): + return False + if value < 0: + return False + return value < (1 << bit_count) + + +def is_valid_int_value(value: Any, bit_count: int = 8) -> bool: + """ + Checks if 'value' is a signed integer that fits into 'bit_count' bits. + For example, for int8: -128 <= value <= 127. + """ + min_val = -(1 << (bit_count - 1)) + max_val = (1 << (bit_count - 1)) - 1 + return isinstance(value, int) and (min_val <= value <= max_val) + + +def is_valid_bool_value(value: Any) -> bool: + """ + Checks if 'value' is a boolean. + """ + return isinstance(value, bool) + + +# Integer assertions + +def assert_valid_uint64(value: Any, description: str) -> None: + """ + Asserts that the value is a valid uint64 (0 <= value < 2^64). + """ + asserts.assert_true(is_valid_uint_value(value, bit_count=64), + f"{description} must be a valid uint64 integer") + + +def assert_valid_uint32(value: Any, description: str) -> None: + """ + Asserts that the value is a valid uint32 (0 <= value < 2^32). + """ + asserts.assert_true(is_valid_uint_value(value, bit_count=32), + f"{description} must be a valid uint32 integer") + + +def assert_valid_uint16(value: Any, description: str) -> None: + """ + Asserts that the value is a valid uint16 (0 <= value < 2^16). + """ + asserts.assert_true(is_valid_uint_value(value, bit_count=16), + f"{description} must be a valid uint16 integer") + + +def assert_valid_uint8(value: Any, description: str) -> None: + """ + Asserts that the value is a valid uint8 (0 <= value < 2^8). + """ + asserts.assert_true(is_valid_uint_value(value, bit_count=8), + f"{description} must be a valid uint8 integer") + + +def assert_valid_int64(value: Any, description: str) -> None: + """ + Asserts that the value is a valid int64 (-2^63 <= value <= 2^63-1). + """ + asserts.assert_true(is_valid_int_value(value, bit_count=64), + f"{description} must be a valid int64 integer") + + +def assert_valid_int32(value: Any, description: str) -> None: + """ + Asserts that the value is a valid int32 (-2^31 <= value <= 2^31-1). + """ + asserts.assert_true(is_valid_int_value(value, bit_count=32), + f"{description} must be a valid int32 integer") + + +def assert_valid_int16(value: Any, description: str) -> None: + """ + Asserts that the value is a valid int16 (-2^15 <= value <= 2^15-1). + """ + asserts.assert_true(is_valid_int_value(value, bit_count=16), + f"{description} must be a valid int16 integer") + + +def assert_valid_int8(value: Any, description: str) -> None: + """ + Asserts that the value is a valid int8 (-128 <= value <= 127). + """ + asserts.assert_true(is_valid_int_value(value, bit_count=8), + f"{description} must be a valid int8 integer") + + +def assert_valid_bool(value: Any, description: str) -> None: + """ + Asserts that the value is a valid bool (True/False). + """ + asserts.assert_true(is_valid_bool_value(value), + f"{description} must be a valid bool (True/False)") + + +def assert_int_in_range(value: Any, min_value: int, max_value: int, description: str) -> None: + """ + Asserts that the value is an integer within the specified range (inclusive). + + Args: + value: The value to check + min_value: Minimum allowed value (inclusive) + max_value: Maximum allowed value (inclusive) + description: User-defined description for error messages + + Raises: + AssertionError: If value is not an integer or outside the specified range + """ + asserts.assert_true(isinstance(value, int), f"{description} must be an integer") + asserts.assert_greater_equal(value, min_value, f"{description} must be greater than or equal to {min_value}") + asserts.assert_less_equal(value, max_value, f"{description} must be less than or equal to {max_value}") + + +# List assertions + +def assert_list(value: Any, description: str, min_length: Optional[int] = None, max_length: Optional[int] = None) -> None: + """ + Asserts that the value is a list with optional length constraints. + + Args: + value: The value to check + description: User-defined description for error messages + min_length: Optional minimum length (inclusive) + max_length: Optional maximum length (inclusive) + + Raises: + AssertionError: If value is not a list or fails length constraints + """ + asserts.assert_true(isinstance(value, list), f"{description} must be a list") + + if min_length is not None: + asserts.assert_greater_equal(len(value), min_length, + f"{description} must have at least {min_length} elements") + + if max_length is not None: + asserts.assert_less_equal(len(value), max_length, + f"{description} must not exceed {max_length} elements") + + +def assert_list_element_type(value: List[Any], description: str, expected_type: Type[T]) -> None: + """ + Asserts that all elements in the list are of the expected type. + + Args: + value: The list to validate + description: User-defined description for error messages + expected_type: The type that all elements should match + + Raises: + AssertionError: If value is not a list or contains elements of wrong type + """ + assert_list(value, description) + for i, item in enumerate(value): + asserts.assert_true(isinstance(item, expected_type), + f"{description}[{i}] must be of type {expected_type.__name__}") + + +# String assertions + +def assert_is_string(value: Any, description: str) -> None: + """ + Asserts that the value is a string. + + Args: + value: The value to check + description: User-defined description for error messages + + Raises: + AssertionError: If value is not a string + """ + asserts.assert_true(isinstance(value, str), f"{description} must be a string") + + +def assert_string_length(value: Any, description: str, min_length: Optional[int] = None, max_length: Optional[int] = None) -> None: + """ + Asserts that the string length is within the specified bounds. + + Args: + value: The value to check + description: User-defined description for error messages + min_length: Optional minimum length (inclusive) + max_length: Optional maximum length (inclusive) + + Raises: + AssertionError: If value is not a string or fails length constraints + + Note: + - Use min_length=1 instead of assert_non_empty_string when you want to ensure non-emptiness + - Use min_length=None, max_length=None to only validate string type (same as assert_is_string) + """ + assert_is_string(value, description) + if min_length is not None: + asserts.assert_greater_equal(len(value), min_length, + f"{description} length must be at least {min_length} characters") + if max_length is not None: + asserts.assert_less_equal(len(value), max_length, + f"{description} length must not exceed {max_length} characters") + + +def assert_non_empty_string(value: Any, description: str) -> None: + """ + Asserts that the value is a non-empty string. + + Args: + value: The value to check + description: User-defined description for error messages + + Raises: + AssertionError: If value is not a string or is empty + """ + assert_string_length(value, description, min_length=1) + + +def assert_is_octstr(value: Any, description: str) -> None: + """ + Asserts that the value is a octet string. + + Args: + value: The value to check + description: User-defined description for error messages + + Raises: + AssertionError: If value is not a octet string (bytes) + """ + asserts.assert_true(isinstance(value, bytes), f"{description} must be a octet string (bytes)") + + +# Matter-specific assertions + +def assert_string_matches_pattern(value: str, description: str, pattern: str) -> None: + """ + Asserts that the string matches the given regex pattern. + + Args: + value: The string to check + description: User-defined description for error messages + pattern: Regular expression pattern to match against + + Raises: + AssertionError: If value is not a string or doesn't match the pattern + """ + import re + assert_is_string(value, description) + asserts.assert_true(bool(re.match(pattern, value)), + f"{description} must match pattern: {pattern}") + + +def assert_valid_attribute_id(id: int, allow_test: bool = False) -> None: + """ + Asserts that the given ID is a valid attribute ID. + + Args: + id: The attribute ID to validate + allow_test: Whether to allow test attribute IDs + + Raises: + AssertionError: If the ID is not a valid attribute ID + """ + from chip.testing.global_attribute_ids import is_valid_attribute_id + asserts.assert_true(is_valid_attribute_id(id, allow_test), + f"Invalid attribute ID: {hex(id)}") + + +def assert_standard_attribute_id(id: int) -> None: + """ + Asserts that the given ID is a standard attribute ID. + + Args: + id: The attribute ID to validate + + Raises: + AssertionError: If the ID is not a standard attribute ID + """ + from chip.testing.global_attribute_ids import is_standard_attribute_id + asserts.assert_true(is_standard_attribute_id(id), + f"Not a standard attribute ID: {hex(id)}") + + +def assert_valid_command_id(id: int, allow_test: bool = False) -> None: + """ + Asserts that the given ID is a valid command ID. + + Args: + id: The command ID to validate + allow_test: Whether to allow test command IDs + + Raises: + AssertionError: If the ID is not a valid command ID + """ + from chip.testing.global_attribute_ids import is_valid_command_id + asserts.assert_true(is_valid_command_id(id, allow_test), + f"Invalid command ID: {hex(id)}") + + +def assert_standard_command_id(id: int) -> None: + """ + Asserts that the given ID is a standard command ID. + + Args: + id: The command ID to validate + + Raises: + AssertionError: If the ID is not a standard command ID + """ + from chip.testing.global_attribute_ids import is_standard_command_id + asserts.assert_true(is_standard_command_id(id), + f"Not a standard command ID: {hex(id)}") + + +def assert_valid_enum(value: Any, description: str, enum_type: type) -> None: + """ + Asserts that 'value' is a valid instance of the specified enum type. + """ + asserts.assert_true(isinstance(value, enum_type), + f"{description} must be of type {enum_type.__name__}") diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index 14b4c4d32466d7..96f76479fc8aa6 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Project CHIP Authors +# Copyright (c) 2022-2025 Project CHIP Authors # All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -220,6 +220,100 @@ def get_wait_seconds_from_set_time(set_time_matter_us: int, wait_seconds: int): return wait_seconds - seconds_passed +@dataclass +class SetupPayloadInfo: + filter_type: discovery.FilterType = discovery.FilterType.LONG_DISCRIMINATOR + filter_value: int = 0 + passcode: int = 0 + + +@dataclass +class CommissioningInfo: + commissionee_ip_address_just_for_testing: Optional[str] = None + commissioning_method: Optional[str] = None + thread_operational_dataset: Optional[str] = None + wifi_passphrase: Optional[str] = None + wifi_ssid: Optional[str] = None + # Accepted Terms and Conditions if used + tc_version_to_simulate: int = None + tc_user_response_to_simulate: int = None + + +async def commission_device( + dev_ctrl: ChipDeviceCtrl.ChipDeviceController, node_id: int, info: SetupPayloadInfo, commissioning_info: CommissioningInfo +) -> bool: + if commissioning_info.tc_version_to_simulate is not None and commissioning_info.tc_user_response_to_simulate is not None: + logging.debug( + f"Setting TC Acknowledgements to version {commissioning_info.tc_version_to_simulate} with user response {commissioning_info.tc_user_response_to_simulate}." + ) + dev_ctrl.SetTCAcknowledgements(commissioning_info.tc_version_to_simulate, commissioning_info.tc_user_response_to_simulate) + + if commissioning_info.commissioning_method == "on-network": + try: + await dev_ctrl.CommissionOnNetwork( + nodeId=node_id, setupPinCode=info.passcode, filterType=info.filter_type, filter=info.filter_value + ) + return True + except ChipStackError as e: + logging.error("Commissioning failed: %s" % e) + return False + elif commissioning_info.commissioning_method == "ble-wifi": + try: + await dev_ctrl.CommissionWiFi( + info.filter_value, + info.passcode, + node_id, + commissioning_info.wifi_ssid, + commissioning_info.wifi_passphrase, + isShortDiscriminator=(info.filter_type == DiscoveryFilterType.SHORT_DISCRIMINATOR), + ) + return True + except ChipStackError as e: + logging.error("Commissioning failed: %s" % e) + return False + elif commissioning_info.commissioning_method == "ble-thread": + try: + await dev_ctrl.CommissionThread( + info.filter_value, + info.passcode, + node_id, + commissioning_info.thread_operational_dataset, + isShortDiscriminator=(info.filter_type == DiscoveryFilterType.SHORT_DISCRIMINATOR), + ) + return True + except ChipStackError as e: + logging.error("Commissioning failed: %s" % e) + return False + elif commissioning_info.commissioning_method == "on-network-ip": + try: + logging.warning("==== USING A DIRECT IP COMMISSIONING METHOD NOT SUPPORTED IN THE LONG TERM ====") + await dev_ctrl.CommissionIP( + ipaddr=commissioning_info.commissionee_ip_address_just_for_testing, + setupPinCode=info.passcode, + nodeid=node_id, + ) + return True + except ChipStackError as e: + logging.error("Commissioning failed: %s" % e) + return False + else: + raise ValueError("Invalid commissioning method %s!" % commissioning_info.commissioning_method) + + +async def commission_devices( + dev_ctrl: ChipDeviceCtrl.ChipDeviceController, + dut_node_ids: List[int], + setup_payloads: List[SetupPayloadInfo], + commissioning_info: CommissioningInfo, +) -> bool: + commissioned = [] + for node_id, setup_payload in zip(dut_node_ids, setup_payloads): + logging.info(f"Commissioning method: {commissioning_info.commissioning_method}") + commissioned.append(await commission_device(dev_ctrl, node_id, setup_payload, commissioning_info)) + + return all(commissioned) + + class SimpleEventCallback: def __init__(self, name: str, expected_cluster_id: int, expected_event_id: int, output_queue: queue.SimpleQueue): self._name = name @@ -629,6 +723,7 @@ class MatterTestConfig: app_pid: int = 0 commissioning_method: Optional[str] = None + in_test_commissioning_method: Optional[str] = None discriminators: List[int] = field(default_factory=list) setup_passcodes: List[int] = field(default_factory=list) commissionee_ip_address_just_for_testing: Optional[str] = None @@ -666,6 +761,10 @@ class MatterTestConfig: trace_to: List[str] = field(default_factory=list) + # Accepted Terms and Conditions if used + tc_version_to_simulate: int = None + tc_user_response_to_simulate: int = None + class ClusterMapper: """Describe clusters/attributes using schema names.""" @@ -826,10 +925,24 @@ def __str__(self): @dataclass -class SetupPayloadInfo: - filter_type: discovery.FilterType = discovery.FilterType.LONG_DISCRIMINATOR - filter_value: int = 0 - passcode: int = 0 +class SetupParameters: + passcode: int + vendor_id: int = 0xFFF1 + product_id: int = 0x8001 + discriminator: int = 3840 + custom_flow: int = 0 + capabilities: int = 0b0100 + version: int = 0 + + @property + def qr_code(self): + return SetupPayload().GenerateQrCode(self.passcode, self.vendor_id, self.product_id, self.discriminator, + self.custom_flow, self.capabilities, self.version) + + @property + def manual_code(self): + return SetupPayload().GenerateManualPairingCode(self.passcode, self.vendor_id, self.product_id, self.discriminator, + self.custom_flow, self.capabilities, self.version) class MatterStackState: @@ -1139,6 +1252,22 @@ def check_pics(self, pics_key: str) -> bool: def is_pics_sdk_ci_only(self) -> bool: return self.check_pics('PICS_SDK_CI_ONLY') + async def commission_devices(self) -> bool: + dev_ctrl: ChipDeviceCtrl.ChipDeviceController = self.default_controller + dut_node_ids: List[int] = self.matter_test_config.dut_node_ids + setup_payloads: List[SetupPayloadInfo] = self.get_setup_payload_info() + commissioning_info: CommissioningInfo = CommissioningInfo( + commissionee_ip_address_just_for_testing=self.matter_test_config.commissionee_ip_address_just_for_testing, + commissioning_method=self.matter_test_config.commissioning_method, + thread_operational_dataset=self.matter_test_config.thread_operational_dataset, + wifi_passphrase=self.matter_test_config.wifi_passphrase, + wifi_ssid=self.matter_test_config.wifi_ssid, + tc_version_to_simulate=self.matter_test_config.tc_version_to_simulate, + tc_user_response_to_simulate=self.matter_test_config.tc_user_response_to_simulate, + ) + + return await commission_devices(dev_ctrl, dut_node_ids, setup_payloads, commissioning_info) + async def openCommissioningWindow(self, dev_ctrl: ChipDeviceCtrl, node_id: int) -> CustomCommissioningParameters: rnd_discriminator = random.randint(0, 4095) try: @@ -1735,6 +1864,7 @@ def populate_commissioning_args(args: argparse.Namespace, config: MatterTestConf config.dut_node_ids = args.dut_node_ids config.commissioning_method = args.commissioning_method + config.in_test_commissioning_method = args.in_test_commissioning_method config.commission_only = args.commission_only config.qr_code_content.extend(args.qr_code) @@ -1837,6 +1967,9 @@ def convert_args_to_matter_config(args: argparse.Namespace) -> MatterTestConfig: config.controller_node_id = args.controller_node_id config.trace_to = args.trace_to + config.tc_version_to_simulate = args.tc_version_to_simulate + config.tc_user_response_to_simulate = args.tc_user_response_to_simulate + # Accumulate all command-line-passed named args all_global_args = [] argsets = [item for item in (args.int_arg, args.float_arg, args.string_arg, args.json_arg, @@ -1895,6 +2028,10 @@ def parse_matter_test_args(argv: Optional[List[str]] = None) -> MatterTestConfig metavar='METHOD_NAME', choices=["on-network", "ble-wifi", "ble-thread", "on-network-ip"], help='Name of commissioning method to use') + commission_group.add_argument('--in-test-commissioning-method', type=str, + metavar='METHOD_NAME', + choices=["on-network", "ble-wifi", "ble-thread", "on-network-ip"], + help='Name of commissioning method to use, for commissioning tests') commission_group.add_argument('-d', '--discriminator', type=int_decimal_or_hex, metavar='LONG_DISCRIMINATOR', dest='discriminators', @@ -1930,6 +2067,10 @@ def parse_matter_test_args(argv: Optional[List[str]] = None) -> MatterTestConfig commission_group.add_argument('--commission-only', action="store_true", default=False, help="If true, test exits after commissioning without running subsequent tests") + commission_group.add_argument('--tc-version-to-simulate', type=int, help="Terms and conditions version") + + commission_group.add_argument('--tc-user-response-to-simulate', type=int, help="Terms and conditions acknowledgements") + code_group = parser.add_mutually_exclusive_group(required=False) code_group.add_argument('-q', '--qr-code', type=str, @@ -2201,73 +2342,8 @@ def __init__(self, *args): self.is_commissioning = True def test_run_commissioning(self): - conf = self.matter_test_config - for commission_idx, node_id in enumerate(conf.dut_node_ids): - logging.info("Starting commissioning for root index %d, fabric ID 0x%016X, node ID 0x%016X" % - (conf.root_of_trust_index, conf.fabric_id, node_id)) - logging.info("Commissioning method: %s" % conf.commissioning_method) - - if not asyncio.run(self._commission_device(commission_idx)): - raise signals.TestAbortAll("Failed to commission node") - - async def _commission_device(self, i) -> bool: - dev_ctrl = self.default_controller - conf = self.matter_test_config - - info = self.get_setup_payload_info()[i] - - if conf.commissioning_method == "on-network": - try: - await dev_ctrl.CommissionOnNetwork( - nodeId=conf.dut_node_ids[i], - setupPinCode=info.passcode, - filterType=info.filter_type, - filter=info.filter_value - ) - return True - except ChipStackError as e: - logging.error("Commissioning failed: %s" % e) - return False - elif conf.commissioning_method == "ble-wifi": - try: - await dev_ctrl.CommissionWiFi( - info.filter_value, - info.passcode, - conf.dut_node_ids[i], - conf.wifi_ssid, - conf.wifi_passphrase, - isShortDiscriminator=(info.filter_type == DiscoveryFilterType.SHORT_DISCRIMINATOR) - ) - return True - except ChipStackError as e: - logging.error("Commissioning failed: %s" % e) - return False - elif conf.commissioning_method == "ble-thread": - try: - await dev_ctrl.CommissionThread( - info.filter_value, - info.passcode, - conf.dut_node_ids[i], - conf.thread_operational_dataset, - isShortDiscriminator=(info.filter_type == DiscoveryFilterType.SHORT_DISCRIMINATOR) - ) - return True - except ChipStackError as e: - logging.error("Commissioning failed: %s" % e) - return False - elif conf.commissioning_method == "on-network-ip": - try: - logging.warning("==== USING A DIRECT IP COMMISSIONING METHOD NOT SUPPORTED IN THE LONG TERM ====") - await dev_ctrl.CommissionIP( - ipaddr=conf.commissionee_ip_address_just_for_testing, - setupPinCode=info.passcode, nodeid=conf.dut_node_ids[i] - ) - return True - except ChipStackError as e: - logging.error("Commissioning failed: %s" % e) - return False - else: - raise ValueError("Invalid commissioning method %s!" % conf.commissioning_method) + if not asyncio.run(self.commission_devices()): + raise signals.TestAbortAll("Failed to commission node") def default_matter_test_main(): diff --git a/third_party/boringssl/repo/BUILD.gn b/third_party/boringssl/repo/BUILD.gn index a57807f52b4762..bb315cde989d60 100644 --- a/third_party/boringssl/repo/BUILD.gn +++ b/third_party/boringssl/repo/BUILD.gn @@ -41,6 +41,7 @@ all_sources = crypto_sources all_headers = crypto_headers +# Core BoringSSL library used by the SDK static_library("boringssl") { cflags = [ "-O2" ] @@ -55,3 +56,18 @@ static_library("boringssl") { # on boringssl, not just boringssl itself. configs += [ ":boringssl_config_disable_warnings" ] } + +# Extended version of BoringSSL with additional SSL sources (for optional tools) +static_library("boringssl_with_ssl_sources") { + cflags = [ "-O2" ] + + public = crypto_headers + ssl_headers + sources = crypto_sources + ssl_sources + + public_configs = [ ":boringssl_config" ] + + # The disable-warnings config should not be a public config, since + # that would make it apply to compilations of anything that depends + # on boringssl, not just boringssl itself. + configs += [ ":boringssl_config_disable_warnings" ] +} diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h index 791d9c0ca8cfcb..3c9976bf0e1dc9 100644 --- a/zzz_generated/app-common/app-common/zap-generated/callback.h +++ b/zzz_generated/app-common/app-common/zap-generated/callback.h @@ -5692,30 +5692,6 @@ bool emberAfOtaSoftwareUpdateProviderClusterNotifyUpdateAppliedCallback( bool emberAfOtaSoftwareUpdateRequestorClusterAnnounceOTAProviderCallback( chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, const chip::app::Clusters::OtaSoftwareUpdateRequestor::Commands::AnnounceOTAProvider::DecodableType & commandData); -/** - * @brief General Commissioning Cluster ArmFailSafe Command callback (from client) - */ -bool emberAfGeneralCommissioningClusterArmFailSafeCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::GeneralCommissioning::Commands::ArmFailSafe::DecodableType & commandData); -/** - * @brief General Commissioning Cluster SetRegulatoryConfig Command callback (from client) - */ -bool emberAfGeneralCommissioningClusterSetRegulatoryConfigCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::GeneralCommissioning::Commands::SetRegulatoryConfig::DecodableType & commandData); -/** - * @brief General Commissioning Cluster CommissioningComplete Command callback (from client) - */ -bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::GeneralCommissioning::Commands::CommissioningComplete::DecodableType & commandData); -/** - * @brief General Commissioning Cluster SetTCAcknowledgements Command callback (from client) - */ -bool emberAfGeneralCommissioningClusterSetTCAcknowledgementsCallback( - chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, - const chip::app::Clusters::GeneralCommissioning::Commands::SetTCAcknowledgements::DecodableType & commandData); /** * @brief Diagnostic Logs Cluster RetrieveLogsRequest Command callback (from client) */