From 35d685e472ff5883d3c0f0336ced95aa7d79844f Mon Sep 17 00:00:00 2001 From: James Swan <122404367+swan-amazon@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:30:26 +0000 Subject: [PATCH 01/10] Add initial feature logic for Terms and Conditions (TC) acknowledgements This commit introduces the initial logic for handling Terms and Conditions (TC) acknowledgements in the General Commissioning cluster. The logic includes support for setting and checking TC acknowledgements and versions during the commissioning process. Changes include: - Handling TC acknowledgements and TC acknowledgement version in the pairing command. - Logic to read TC attributes and check TC acceptance in the General Commissioning server. - Introduction of classes to manage TC acceptance logic. - Initialization and use of TC providers in the server setup. - Addition of a new commissioning stage for TC acknowledgements in the commissioning flow. The feature logic is currently disabled and will be enabled in an example in a subsequent commit. --- src/app/BUILD.gn | 18 +- src/app/FailSafeContext.cpp | 11 +- src/app/FailSafeContext.h | 15 +- .../general-commissioning-server.cpp | 157 ++++++++- src/app/common_flags.gni | 4 + src/app/server/BUILD.gn | 29 +- .../DefaultEnhancedSetupFlowProvider.cpp | 166 ++++++++++ .../server/DefaultEnhancedSetupFlowProvider.h | 67 ++++ .../DefaultTermsAndConditionsProvider.cpp | 215 ++++++++++++ .../DefaultTermsAndConditionsProvider.h | 73 +++++ src/app/server/EnhancedSetupFlowProvider.h | 89 +++++ src/app/server/Server.cpp | 10 +- src/app/server/Server.h | 40 ++- src/app/server/TermsAndConditionsProvider.h | 78 +++++ src/app/tests/BUILD.gn | 6 +- .../TestDefaultEnhancedSetupFlowProvider.cpp | 310 ++++++++++++++++++ .../TestDefaultTermsAndConditionsProvider.cpp | 231 +++++++++++++ src/controller/CommissioningDelegate.h | 2 + src/include/platform/CHIPDeviceEvent.h | 3 + src/lib/core/CHIPConfig.h | 54 +++ src/lib/support/DefaultStorageKeyAllocator.h | 6 +- 21 files changed, 1572 insertions(+), 12 deletions(-) create mode 100644 src/app/server/DefaultEnhancedSetupFlowProvider.cpp create mode 100644 src/app/server/DefaultEnhancedSetupFlowProvider.h create mode 100644 src/app/server/DefaultTermsAndConditionsProvider.cpp create mode 100644 src/app/server/DefaultTermsAndConditionsProvider.h create mode 100644 src/app/server/EnhancedSetupFlowProvider.h create mode 100644 src/app/server/TermsAndConditionsProvider.h create mode 100644 src/app/tests/TestDefaultEnhancedSetupFlowProvider.cpp create mode 100644 src/app/tests/TestDefaultTermsAndConditionsProvider.cpp diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 53dd3876c12410..79a20235132133 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -60,6 +60,19 @@ declare_args() { current_os != "android" } +config("enhanced_setup_flow_config") { + defines = [] + if (chip_config_tc_required) { + defines += [ + "CHIP_CONFIG_TC_REQUIRED=1", + "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS=${chip_config_tc_required_acknowledgements}", + "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION=${chip_config_tc_required_acknowledgements_version}", + ] + } else { + defines += [ "CHIP_CONFIG_TC_REQUIRED=0" ] + } +} + buildconfig_header("app_buildconfig") { header = "AppBuildConfig.h" header_dir = "app" @@ -519,5 +532,8 @@ static_library("app") { cflags = [ "-Wconversion" ] - public_configs = [ "${chip_root}/src:includes" ] + public_configs = [ + ":enhanced_setup_flow_config", + "${chip_root}/src:includes", + ] } diff --git a/src/app/FailSafeContext.cpp b/src/app/FailSafeContext.cpp index 95a5b267f2aa5e..9cb9fb411cc2e0 100644 --- a/src/app/FailSafeContext.cpp +++ b/src/app/FailSafeContext.cpp @@ -86,9 +86,14 @@ 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, +#if CHIP_CONFIG_TC_REQUIRED + .updateTermsAndConditionsHasBeenInvoked = mUpdateTermsAndConditionsHasBeenInvoked, +#endif + } }; 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..421774a521450a 100644 --- a/src/app/FailSafeContext.h +++ b/src/app/FailSafeContext.h @@ -56,7 +56,9 @@ class FailSafeContext void SetUpdateNocCommandInvoked() { mUpdateNocCommandHasBeenInvoked = true; } void SetAddTrustedRootCertInvoked() { mAddTrustedRootCertHasBeenInvoked = true; } void SetCsrRequestForUpdateNoc(bool isForUpdateNoc) { mIsCsrRequestForUpdateNoc = isForUpdateNoc; } - +#if CHIP_CONFIG_TC_REQUIRED + void SetUpdateTermsAndConditionsHasBeenInvoked() { mUpdateTermsAndConditionsHasBeenInvoked = true; } +#endif /** * @brief * Schedules a work to cleanup the FailSafe Context asynchronously after various cleanup work @@ -91,6 +93,9 @@ class FailSafeContext bool UpdateNocCommandHasBeenInvoked() const { return mUpdateNocCommandHasBeenInvoked; } bool AddTrustedRootCertHasBeenInvoked() const { return mAddTrustedRootCertHasBeenInvoked; } bool IsCsrRequestForUpdateNoc() const { return mIsCsrRequestForUpdateNoc; } +#if CHIP_CONFIG_TC_REQUIRED + bool UpdateTermsAndConditionsHasBeenInvoked() { return mUpdateTermsAndConditionsHasBeenInvoked; } +#endif FabricIndex GetFabricIndex() const { @@ -110,7 +115,10 @@ class FailSafeContext bool mAddTrustedRootCertHasBeenInvoked = false; // The fact of whether a CSR occurred at all is stored elsewhere. bool mIsCsrRequestForUpdateNoc = false; - FabricIndex mFabricIndex = kUndefinedFabricIndex; +#if CHIP_CONFIG_TC_REQUIRED + bool mUpdateTermsAndConditionsHasBeenInvoked = false; +#endif + FabricIndex mFabricIndex = kUndefinedFabricIndex; /** * @brief @@ -145,6 +153,9 @@ class FailSafeContext mAddTrustedRootCertHasBeenInvoked = false; mFailSafeBusy = false; mIsCsrRequestForUpdateNoc = false; +#if CHIP_CONFIG_TC_REQUIRED + mUpdateTermsAndConditionsHasBeenInvoked = false; +#endif } void FailSafeTimerExpired(); 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..e4d6ead862f26f 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. @@ -27,6 +27,9 @@ #include #include #include +#if CHIP_CONFIG_TC_REQUIRED +#include +#endif #include #include #include @@ -69,6 +72,9 @@ 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); + template + CHIP_ERROR ReadFromProvider(Provider * const aProvider, CHIP_ERROR (Provider::*const aConstGetter)(T &) const, + AttributeValueEncoder & aEncoder); }; GeneralCommissioningAttrAccess gAttrAccess; @@ -95,6 +101,28 @@ CHIP_ERROR GeneralCommissioningAttrAccess::Read(const ConcreteReadAttributePath case SupportsConcurrentConnection::Id: { return ReadSupportsConcurrentConnection(aEncoder); } +#if CHIP_CONFIG_TC_REQUIRED + case TCAcceptedVersion::Id: { + auto provider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + auto getter = &EnhancedSetupFlowProvider::GetTermsAndConditionsAcceptedAcknowledgementsVersion; + return ReadFromProvider(provider, getter, aEncoder); + } + case TCMinRequiredVersion::Id: { + auto provider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + auto getter = &EnhancedSetupFlowProvider::GetTermsAndConditionsRequiredAcknowledgementsVersion; + return ReadFromProvider(provider, getter, aEncoder); + } + case TCAcknowledgements::Id: { + auto provider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + auto getter = &EnhancedSetupFlowProvider::GetTermsAndConditionsAcceptedAcknowledgements; + return ReadFromProvider(provider, getter, aEncoder); + } + case TCAcknowledgementsRequired::Id: { + auto provider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + auto getter = &EnhancedSetupFlowProvider::IsTermsAndConditionsAcceptanceRequired; + return ReadFromProvider(provider, getter, aEncoder); + } +#endif default: { break; } @@ -144,6 +172,85 @@ CHIP_ERROR GeneralCommissioningAttrAccess::ReadSupportsConcurrentConnection(Attr return aEncoder.Encode(supportsConcurrentConnection); } +template +CHIP_ERROR GeneralCommissioningAttrAccess::ReadFromProvider(Provider * const aProvider, + CHIP_ERROR (Provider::*const aConstGetter)(T &) const, + AttributeValueEncoder & aEncoder) +{ + if (nullptr == aProvider) + { + return CHIP_ERROR_PERSISTED_STORAGE_FAILED; + } + + T value; + CHIP_ERROR err = (aProvider->*aConstGetter)(value); + if (err != CHIP_NO_ERROR) + { + return err; + } + + return aEncoder.Encode(value); +} + +#if CHIP_CONFIG_TC_REQUIRED +CHIP_ERROR checkTermsAndConditionsAcknowledgementsState(CommissioningErrorEnum & errorCode) +{ + EnhancedSetupFlowProvider * enhancedSetupFlowProvider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + + CHIP_ERROR err; + + uint16_t termsAndConditionsAcceptedAcknowledgements; + bool hasRequiredTermAccepted; + bool hasRequiredTermVersionAccepted; + + err = enhancedSetupFlowProvider->GetTermsAndConditionsAcceptedAcknowledgements(termsAndConditionsAcceptedAcknowledgements); + if (!::chip::ChipError::IsSuccess(err)) + { + ChipLogError(AppServer, "Failed to GetTermsAndConditionsAcceptedAcknowledgements"); + return err; + } + + err = enhancedSetupFlowProvider->HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasRequiredTermAccepted); + if (!::chip::ChipError::IsSuccess(err)) + { + ChipLogError(AppServer, "Failed to HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted"); + return err; + } + + err = + enhancedSetupFlowProvider->HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasRequiredTermVersionAccepted); + if (!::chip::ChipError::IsSuccess(err)) + { + ChipLogError(AppServer, "Failed to HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted"); + return err; + } + + if (!hasRequiredTermAccepted) + { + uint16_t requiredAcknowledgements = 0; + (void) enhancedSetupFlowProvider->GetTermsAndConditionsRequiredAcknowledgements(requiredAcknowledgements); + + ChipLogProgress(AppServer, "Required terms and conditions, 0x%04x,have not been accepted", requiredAcknowledgements); + errorCode = (0 == termsAndConditionsAcceptedAcknowledgements) ? CommissioningErrorEnum::kTCAcknowledgementsNotReceived + : CommissioningErrorEnum::kRequiredTCNotAccepted; + return CHIP_NO_ERROR; + } + + if (!hasRequiredTermVersionAccepted) + { + uint16_t requiredAcknowledgementsVersion = 0; + (void) enhancedSetupFlowProvider->GetTermsAndConditionsRequiredAcknowledgementsVersion(requiredAcknowledgementsVersion); + ChipLogProgress(AppServer, "Minimum terms and conditions version, 0x%04x, has not been accepted", + requiredAcknowledgementsVersion); + errorCode = CommissioningErrorEnum::kTCMinVersionNotMet; + return CHIP_NO_ERROR; + } + + errorCode = CommissioningErrorEnum::kOk; + return CHIP_NO_ERROR; +} +#endif + } // anonymous namespace bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler * commandObj, @@ -239,6 +346,10 @@ bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback( } else { +#if CHIP_CONFIG_TC_REQUIRED + CheckSuccess(checkTermsAndConditionsAcknowledgementsState(response.errorCode), Failure); +#endif + if (failSafe.NocCommandHasBeenInvoked()) { CHIP_ERROR err = fabricTable.CommitPendingFabricData(); @@ -328,6 +439,30 @@ bool emberAfGeneralCommissioningClusterSetRegulatoryConfigCallback(app::CommandH return true; } +bool emberAfGeneralCommissioningClusterSetTCAcknowledgementsCallback( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::GeneralCommissioning::Commands::SetTCAcknowledgements::DecodableType & commandData) +{ +#if !CHIP_CONFIG_TC_REQUIRED + return false; + +#else + auto & failSafeContext = Server::GetInstance().GetFailSafeContext(); + + MATTER_TRACE_SCOPE("SetTCAcknowledgements", "GeneralCommissioning"); + Commands::SetTCAcknowledgementsResponse::Type response; + EnhancedSetupFlowProvider * const enhancedSetupFlowProvider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + uint16_t acknowledgements = commandData.TCUserResponse; + uint16_t acknowledgementsVersion = commandData.TCVersion; + CheckSuccess(enhancedSetupFlowProvider->SetTermsAndConditionsAcceptance(acknowledgements, acknowledgementsVersion), Failure); + CheckSuccess(checkTermsAndConditionsAcknowledgementsState(response.errorCode), Failure); + failSafeContext.SetUpdateTermsAndConditionsHasBeenInvoked(); + commandObj->AddResponse(commandPath, response); + return true; + +#endif +} + namespace { void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) { @@ -335,6 +470,26 @@ void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t { // Spec says to reset Breadcrumb attribute to 0. Breadcrumb::Set(0, 0); + +#if CHIP_CONFIG_TC_REQUIRED + if (event->FailSafeTimerExpired.updateTermsAndConditionsHasBeenInvoked) + { + // Clear terms and conditions acceptance on failsafe timer expiration + Server::GetInstance().GetEnhancedSetupFlowProvider()->RevertTermsAndConditionsAcceptance(); + } +#endif + } + + if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete) + { +#if CHIP_CONFIG_TC_REQUIRED + auto & failSafeContext = Server::GetInstance().GetFailSafeContext(); + if (failSafeContext.UpdateTermsAndConditionsHasBeenInvoked()) + { + // Commit terms and conditions acceptance on commissioning complete + Server::GetInstance().GetEnhancedSetupFlowProvider()->CommitTermsAndConditionsAcceptance(); + } +#endif } } diff --git a/src/app/common_flags.gni b/src/app/common_flags.gni index be1149b2b67eb5..34e8549a35d1cd 100644 --- a/src/app/common_flags.gni +++ b/src/app/common_flags.gni @@ -18,6 +18,10 @@ declare_args() { chip_enable_read_client = true chip_build_controller_dynamic_server = false + chip_config_tc_required = false + chip_config_tc_required_acknowledgements = 0 + chip_config_tc_required_acknowledgements_version = 0 + # Flag that controls whether the time-to-wait from BUSY responses is # communicated to OperationalSessionSetup API consumers. chip_enable_busy_handling_for_operational_session_setup = true diff --git a/src/app/server/BUILD.gn b/src/app/server/BUILD.gn index 401356d7b753a4..3fb9bc5e405762 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. @@ -24,6 +24,28 @@ config("server_config") { } } +source_set("enhanced_setup_flow_interface") { + sources = [ + "DefaultEnhancedSetupFlowProvider.h", + "DefaultTermsAndConditionsProvider.h", + "EnhancedSetupFlowProvider.h", + "TermsAndConditionsProvider.h", + ] + + public_configs = [ "${chip_root}/src/app:enhanced_setup_flow_config" ] + + public_deps = [ "${chip_root}/src/lib/core" ] +} + +source_set("enhanced_setup_flow") { + sources = [ + "DefaultEnhancedSetupFlowProvider.cpp", + "DefaultTermsAndConditionsProvider.cpp", + ] + + deps = [ ":enhanced_setup_flow_interface" ] +} + static_library("server") { output_name = "libCHIPAppServer" @@ -51,6 +73,7 @@ static_library("server") { cflags = [ "-Wconversion" ] public_deps = [ + ":enhanced_setup_flow_interface", "${chip_root}/src/app", "${chip_root}/src/app:test-event-trigger", "${chip_root}/src/app/icd/server:check-in-back-off", @@ -79,4 +102,8 @@ static_library("server") { [ "${chip_root}/src/app/icd/server:default-check-in-back-off" ] } } + + if (chip_config_tc_required) { + public_deps += [ ":enhanced_setup_flow" ] + } } diff --git a/src/app/server/DefaultEnhancedSetupFlowProvider.cpp b/src/app/server/DefaultEnhancedSetupFlowProvider.cpp new file mode 100644 index 00000000000000..1ce01c398f1a04 --- /dev/null +++ b/src/app/server/DefaultEnhancedSetupFlowProvider.cpp @@ -0,0 +1,166 @@ +/* + * + * 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 "DefaultEnhancedSetupFlowProvider.h" + +#include +#include + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::Init(TermsAndConditionsProvider * const inTermsAndConditionsProvider) +{ + VerifyOrReturnError(nullptr != inTermsAndConditionsProvider, CHIP_ERROR_INVALID_ARGUMENT); + + mTermsAndConditionsProvider = inTermsAndConditionsProvider; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +chip::app::DefaultEnhancedSetupFlowProvider::HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(bool & outAccepted) const +{ + uint16_t requiredAcknowledgements; + uint16_t requiredAcknowledgementsVersion; + uint16_t acceptedAcknowledgements; + uint16_t acceptedAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetRequirements(requiredAcknowledgements, requiredAcknowledgementsVersion)); + + if (0 == requiredAcknowledgements) + { + outAccepted = true; + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetAcceptance(acceptedAcknowledgements, acceptedAcknowledgementsVersion)); + + outAccepted = ((requiredAcknowledgements & acceptedAcknowledgements) == requiredAcknowledgements); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted( + bool & outAccepted) const +{ + uint16_t requiredAcknowledgements; + uint16_t requiredAcknowledgementsVersion; + uint16_t acceptedAcknowledgements; + uint16_t acceptedAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetRequirements(requiredAcknowledgements, requiredAcknowledgementsVersion)); + + if (0 == requiredAcknowledgementsVersion) + { + outAccepted = true; + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetAcceptance(acceptedAcknowledgements, acceptedAcknowledgementsVersion)); + + outAccepted = (acceptedAcknowledgementsVersion >= requiredAcknowledgementsVersion); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::IsTermsAndConditionsAcceptanceRequired(bool & outValue) const +{ + // Default implementation requires terms and conditions check only if not previously accepted. Other implementations may skip + // requiring a terms and conditions check on secondary commissioning, in the case that the required terms and conditions may + // have changed. + return HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(outValue); +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::GetTermsAndConditionsRequiredAcknowledgements(uint16_t & outValue) const +{ + uint16_t requiredAcknowledgements; + uint16_t requiredAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetRequirements(requiredAcknowledgements, requiredAcknowledgementsVersion)); + + outValue = requiredAcknowledgements; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +chip::app::DefaultEnhancedSetupFlowProvider::GetTermsAndConditionsRequiredAcknowledgementsVersion(uint16_t & outValue) const +{ + uint16_t requiredAcknowledgements; + uint16_t requiredAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetRequirements(requiredAcknowledgements, requiredAcknowledgementsVersion)); + + outValue = requiredAcknowledgementsVersion; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::GetTermsAndConditionsAcceptedAcknowledgements(uint16_t & outValue) const +{ + uint16_t acceptedAcknowledgements; + uint16_t acceptedAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetAcceptance(acceptedAcknowledgements, acceptedAcknowledgementsVersion)); + + outValue = acceptedAcknowledgements; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +chip::app::DefaultEnhancedSetupFlowProvider::GetTermsAndConditionsAcceptedAcknowledgementsVersion(uint16_t & outValue) const +{ + uint16_t acceptedAcknowledgements; + uint16_t acceptedAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetAcceptance(acceptedAcknowledgements, acceptedAcknowledgementsVersion)); + + outValue = acceptedAcknowledgementsVersion; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::SetTermsAndConditionsAcceptance(uint16_t inTCAcknowledgementsValue, + uint16_t inTCAcknowledgementsVersionValue) +{ + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->SetAcceptance(inTCAcknowledgementsValue, inTCAcknowledgementsVersionValue)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::RevertTermsAndConditionsAcceptance() +{ + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->RevertAcceptance()); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::CommitTermsAndConditionsAcceptance() +{ + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->CommitAcceptance()); + + return CHIP_NO_ERROR; +} diff --git a/src/app/server/DefaultEnhancedSetupFlowProvider.h b/src/app/server/DefaultEnhancedSetupFlowProvider.h new file mode 100644 index 00000000000000..3da5266078f15e --- /dev/null +++ b/src/app/server/DefaultEnhancedSetupFlowProvider.h @@ -0,0 +1,67 @@ +/* + * + * 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 "EnhancedSetupFlowProvider.h" + +#include + +#include +#include + +#include "TermsAndConditionsProvider.h" + +namespace chip { +namespace app { +class DefaultEnhancedSetupFlowProvider : public EnhancedSetupFlowProvider +{ +public: + /** + * @brief Initializes the EnhancedSetupFlowProvider. + * + * @param[in] inTermsAndConditionsProvider The terms and conditions provide dependency. + */ + CHIP_ERROR Init(TermsAndConditionsProvider * const inTermsAndConditionsProvider); + + CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(bool & outAccepted) const override; + + CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(bool & outAccepted) const override; + + CHIP_ERROR IsTermsAndConditionsAcceptanceRequired(bool & outValue) const override; + + CHIP_ERROR GetTermsAndConditionsRequiredAcknowledgements(uint16_t & outValue) const override; + + CHIP_ERROR GetTermsAndConditionsRequiredAcknowledgementsVersion(uint16_t & outValue) const override; + + CHIP_ERROR GetTermsAndConditionsAcceptedAcknowledgements(uint16_t & outValue) const override; + + CHIP_ERROR GetTermsAndConditionsAcceptedAcknowledgementsVersion(uint16_t & outValue) const override; + + CHIP_ERROR SetTermsAndConditionsAcceptance(uint16_t aTCAcknowledgements, uint16_t inTCAcknowledgementsVersionValue) override; + + CHIP_ERROR CommitTermsAndConditionsAcceptance() override; + + CHIP_ERROR RevertTermsAndConditionsAcceptance() override; + +private: + TermsAndConditionsProvider * mTermsAndConditionsProvider; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/DefaultTermsAndConditionsProvider.cpp b/src/app/server/DefaultTermsAndConditionsProvider.cpp new file mode 100644 index 00000000000000..0d49f41c893ff9 --- /dev/null +++ b/src/app/server/DefaultTermsAndConditionsProvider.cpp @@ -0,0 +1,215 @@ +/* + * + * 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 +#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 kSerializationVersion = 1; + +constexpr size_t kEstimatedTlvBufferSize = chip::TLV::EstimateStructOverhead(sizeof(uint8_t), // SerializationVersion + sizeof(uint16_t), // AcceptedAcknowledgements + sizeof(uint16_t) // AcceptedAcknowledgementsVersion + ) * + 2; // Extra space for rollback compatibility +} // namespace + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::Init(chip::PersistentStorageDelegate * const inPersistentStorageDelegate, + uint16_t inRequiredAcknowledgementsValue, + uint16_t inRequiredAcknowledgementsVersionValue) +{ + VerifyOrReturnError(nullptr != inPersistentStorageDelegate, CHIP_ERROR_INVALID_ARGUMENT); + + mPersistentStorageDelegate = inPersistentStorageDelegate; + mRequiredAcknowledgementsValue = inRequiredAcknowledgementsValue; + mRequiredAcknowledgementsVersionValue = inRequiredAcknowledgementsVersionValue; + + uint16_t acknowledgementsAcceptanceValue = 0; + uint16_t acknowledgementsAcceptanceVersionValue = 0; + + if (CHIP_NO_ERROR == LoadAcceptance(acknowledgementsAcceptanceValue, acknowledgementsAcceptanceVersionValue)) + { + mLatchedAcknowledgementsAcceptanceValue.SetValue(acknowledgementsAcceptanceValue); + mLatchedAcknowledgementsAcceptanceVersionValue.SetValue(acknowledgementsAcceptanceVersionValue); + mTemporaryAcknowledgementsAcceptanceValue = mLatchedAcknowledgementsAcceptanceValue; + mTemporaryAcknowledgementsAcceptanceVersionValue = mLatchedAcknowledgementsAcceptanceVersionValue; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::RevertAcceptance() +{ + mTemporaryAcknowledgementsAcceptanceValue = mLatchedAcknowledgementsAcceptanceValue; + mTemporaryAcknowledgementsAcceptanceVersionValue = mLatchedAcknowledgementsAcceptanceVersionValue; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::CommitAcceptance() +{ + VerifyOrReturnError(mLatchedAcknowledgementsAcceptanceValue.HasValue() || mTemporaryAcknowledgementsAcceptanceValue.HasValue(), + CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mLatchedAcknowledgementsAcceptanceVersionValue.HasValue() || + mTemporaryAcknowledgementsAcceptanceVersionValue.HasValue(), + CHIP_ERROR_INCORRECT_STATE); + + uint16_t acknowledgementsAcceptanceValue = + mTemporaryAcknowledgementsAcceptanceValue.ValueOr(mLatchedAcknowledgementsAcceptanceValue.HasValue()); + uint16_t acknowledgementsAcceptanceVersionValue = + mTemporaryAcknowledgementsAcceptanceVersionValue.ValueOr(mLatchedAcknowledgementsAcceptanceVersionValue.HasValue()); + + CHIP_ERROR err = StoreAcceptance(acknowledgementsAcceptanceValue, acknowledgementsAcceptanceVersionValue); + + if (CHIP_NO_ERROR == err) + { + mLatchedAcknowledgementsAcceptanceValue.SetValue(acknowledgementsAcceptanceValue); + mLatchedAcknowledgementsAcceptanceVersionValue.SetValue(acknowledgementsAcceptanceVersionValue); + } + + return err; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetAcceptance(uint16_t & outAcknowledgementsValue, + uint16_t & outAcknowledgementsVersionValue) const +{ + VerifyOrReturnError(mTemporaryAcknowledgementsAcceptanceValue.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mTemporaryAcknowledgementsAcceptanceVersionValue.HasValue(), CHIP_ERROR_INCORRECT_STATE); + + outAcknowledgementsValue = mTemporaryAcknowledgementsAcceptanceValue.Value(); + outAcknowledgementsVersionValue = mTemporaryAcknowledgementsAcceptanceVersionValue.Value(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetRequirements(uint16_t & outAcknowledgementsValue, + uint16_t & outAcknowledgementsVersionValue) const +{ + outAcknowledgementsValue = mRequiredAcknowledgementsValue; + outAcknowledgementsVersionValue = mRequiredAcknowledgementsVersionValue; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::ResetAcceptance() +{ + VerifyOrReturnError(nullptr != mPersistentStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + const chip::StorageKeyName storageKey = DefaultStorageKeyAllocator::TermsAndConditionsAcceptance(); + + ReturnErrorOnFailure(mPersistentStorageDelegate->SyncDeleteKeyValue(storageKey.KeyName())); + mLatchedAcknowledgementsAcceptanceValue.ClearValue(); + mLatchedAcknowledgementsAcceptanceVersionValue.ClearValue(); + ReturnErrorOnFailure(RevertAcceptance()); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::SetAcceptance(uint16_t inAcceptedAcknowledgementsValue, + uint16_t inAcceptedAcknowledgementsVersionValue) +{ + mTemporaryAcknowledgementsAcceptanceValue.SetValue(inAcceptedAcknowledgementsValue); + mTemporaryAcknowledgementsAcceptanceVersionValue.SetValue(inAcceptedAcknowledgementsVersionValue); + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::LoadAcceptance(uint16_t & outAcknowledgementsValue, + uint16_t & outAcknowledgementsVersionValue) +{ + 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); + + VerifyOrReturnError(nullptr != mPersistentStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + const chip::StorageKeyName storageKey = DefaultStorageKeyAllocator::TermsAndConditionsAcceptance(); + CHIP_ERROR err = mPersistentStorageDelegate->SyncGetKeyValue(storageKey.KeyName(), &buffer, bufferSize); + if (CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == err) + { + outAcknowledgementsValue = acknowledgements; + outAcknowledgementsVersionValue = acknowledgementsVersion; + + return CHIP_NO_ERROR; + } + + VerifyOrReturnError(CHIP_NO_ERROR == err, err); + + 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)); + ReturnErrorOnFailure(tlvReader.Next(kAcceptedAcknowledgementsTag)); + ReturnErrorOnFailure(tlvReader.Get(acknowledgements)); + ReturnErrorOnFailure(tlvReader.Next(kAcceptedAcknowledgementsVersionTag)); + ReturnErrorOnFailure(tlvReader.Get(acknowledgementsVersion)); + ReturnErrorOnFailure(tlvReader.ExitContainer(tlvContainer)); + + if (kSerializationVersion != serializationVersion) + { + return CHIP_ERROR_VERSION_MISMATCH; + } + + outAcknowledgementsValue = acknowledgements; + outAcknowledgementsVersionValue = acknowledgementsVersion; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::StoreAcceptance(uint16_t inAcknowledgementsValue, + uint16_t inAcknowledgementsVersionValue) +{ + uint8_t buffer[kEstimatedTlvBufferSize] = { 0 }; + chip::TLV::TLVWriter tlvWriter; + chip::TLV::TLVType tlvContainer; + + VerifyOrReturnError(nullptr != mPersistentStorageDelegate, CHIP_ERROR_UNINITIALIZED); + VerifyOrReturnError(mTemporaryAcknowledgementsAcceptanceValue.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mTemporaryAcknowledgementsAcceptanceVersionValue.HasValue(), CHIP_ERROR_INCORRECT_STATE); + + tlvWriter.Init(buffer, sizeof(buffer)); + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, tlvContainer)); + ReturnErrorOnFailure(tlvWriter.Put(kSerializationVersionTag, kSerializationVersion)); + ReturnErrorOnFailure(tlvWriter.Put(kAcceptedAcknowledgementsTag, inAcknowledgementsValue)); + ReturnErrorOnFailure(tlvWriter.Put(kAcceptedAcknowledgementsVersionTag, inAcknowledgementsVersionValue)); + ReturnErrorOnFailure(tlvWriter.EndContainer(tlvContainer)); + ReturnErrorOnFailure(tlvWriter.Finalize()); + uint32_t lengthWritten = tlvWriter.GetLengthWritten(); + VerifyOrReturnError(CanCastTo(lengthWritten), CHIP_ERROR_BUFFER_TOO_SMALL); + + const chip::StorageKeyName storageKey = DefaultStorageKeyAllocator::TermsAndConditionsAcceptance(); + ReturnErrorOnFailure( + mPersistentStorageDelegate->SyncSetKeyValue(storageKey.KeyName(), buffer, static_cast(lengthWritten))); + + return CHIP_NO_ERROR; +} diff --git a/src/app/server/DefaultTermsAndConditionsProvider.h b/src/app/server/DefaultTermsAndConditionsProvider.h new file mode 100644 index 00000000000000..ee941216757976 --- /dev/null +++ b/src/app/server/DefaultTermsAndConditionsProvider.h @@ -0,0 +1,73 @@ +/* + * + * 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 { +class DefaultTermsAndConditionsProvider : public TermsAndConditionsProvider +{ +public: + /** + * @brief Initializes the TermsAndConditionsProvider. + * + * @param[in] inPersistentStorageDelegate Persistent storage delegate dependency. + * @param[in] inRequiredAcknowledgementsValue The bitmask of required acknowledgements. + * @param[in] inRequiredAcknowledgementsVersionValue The version of the required acknowledgements. + */ + CHIP_ERROR Init(PersistentStorageDelegate * const inPersistentStorageDelegate, uint16_t inRequiredAcknowledgementsValue, + uint16_t inRequiredAcknowledgementsVersionValue); + + CHIP_ERROR CommitAcceptance() override; + + CHIP_ERROR GetAcceptance(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const override; + + CHIP_ERROR GetRequirements(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const override; + + CHIP_ERROR ResetAcceptance() override; + + CHIP_ERROR RevertAcceptance() override; + + CHIP_ERROR SetAcceptance(uint16_t inAcknowledgementsValue, uint16_t inAcknowledgementsVersionValue) override; + +private: + CHIP_ERROR LoadAcceptance(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue); + + CHIP_ERROR StoreAcceptance(uint16_t inAcknowledgementsValue, uint16_t inAcknowledgementsVersionValue); + + PersistentStorageDelegate * mPersistentStorageDelegate; + uint16_t mRequiredAcknowledgementsValue; + uint16_t mRequiredAcknowledgementsVersionValue; + + Optional mLatchedAcknowledgementsAcceptanceValue; + Optional mLatchedAcknowledgementsAcceptanceVersionValue; + + Optional mTemporaryAcknowledgementsAcceptanceValue; + Optional mTemporaryAcknowledgementsAcceptanceVersionValue; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/EnhancedSetupFlowProvider.h b/src/app/server/EnhancedSetupFlowProvider.h new file mode 100644 index 00000000000000..af1d054e273b8d --- /dev/null +++ b/src/app/server/EnhancedSetupFlowProvider.h @@ -0,0 +1,89 @@ +/* + * + * 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 + +namespace chip { +namespace app { + +/** + * This class provides access to the state of the Enhanced Setup Flow feature. + */ +class EnhancedSetupFlowProvider +{ +public: + virtual ~EnhancedSetupFlowProvider() = default; + + /** + * @param[out] outAccepted true if the required acknowledgements have been accepted, false otherwise. + */ + virtual CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(bool & outAccepted) const = 0; + + /** + * @param[out] outAccepted true if the required acknowledgements version has been accepted, false otherwise. + */ + virtual CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(bool & outAccepted) const = 0; + + /** + * @param[out] outValue true if terms and conditions acceptance is required before commissioning complete may succeed, false + * otherwise. + */ + virtual CHIP_ERROR IsTermsAndConditionsAcceptanceRequired(bool & outValue) const = 0; + + /** + * @param[out] outValue The version of the required acknowledgements. + */ + virtual CHIP_ERROR GetTermsAndConditionsRequiredAcknowledgements(uint16_t & outValue) const = 0; + + /** + * @param[out] outValue The outValue of the required acknowledgements version. + */ + virtual CHIP_ERROR GetTermsAndConditionsRequiredAcknowledgementsVersion(uint16_t & outValue) const = 0; + + /** + * @param[out] outValue The outValue of the accepted acknowledgements. + */ + virtual CHIP_ERROR GetTermsAndConditionsAcceptedAcknowledgements(uint16_t & outValue) const = 0; + + /** + * @param[out] outValue The outValue of the accepted acknowledgements version. + */ + virtual CHIP_ERROR GetTermsAndConditionsAcceptedAcknowledgementsVersion(uint16_t & outValue) const = 0; + + /** + * @param[in] inTCAcknowledgements The acknowledgements to accept. + * @param[in] inTCAcknowledgementsVersionValue The version of the acknowledgements to accept. + */ + virtual CHIP_ERROR SetTermsAndConditionsAcceptance(uint16_t inTCAcknowledgementsValue, + uint16_t inTCAcknowledgementsVersionValue) = 0; + + /** + * Reset the terms and conditions acceptance. The terms and conditions should be cleared on factory reset or if failure occurs + * during a failsafe context. + */ + virtual CHIP_ERROR RevertTermsAndConditionsAcceptance() = 0; + + virtual CHIP_ERROR CommitTermsAndConditionsAcceptance() = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 22cd274ba87e39..25d03d1645b914 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/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. @@ -185,6 +185,11 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) mReportScheduler = initParams.reportScheduler; +#if CHIP_CONFIG_TC_REQUIRED + mTermsAndConditionsProvider = initParams.termsAndConditionsProvider; + mEnhancedSetupFlowProvider = initParams.enhancedSetupFlowProvider; +#endif + mTestEventTriggerDelegate = initParams.testEventTriggerDelegate; if (mTestEventTriggerDelegate == nullptr) { @@ -586,6 +591,9 @@ void Server::ScheduleFactoryReset() GetInstance().GetFabricTable().DeleteAllFabrics(); PlatformMgr().HandleServerShuttingDown(); ConfigurationMgr().InitiateFactoryReset(); +#if CHIP_CONFIG_TC_REQUIRED + // Clear accepted terms and conditions +#endif }); } diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 2f6126a4ace635..cb8ab12f40f547 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -1,6 +1,6 @@ /* * - * 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. @@ -33,6 +33,12 @@ #include #include #include +#if CHIP_CONFIG_TC_REQUIRED +#include +#include +#include +#include +#endif #include #include #include @@ -179,6 +185,12 @@ struct ServerInitParams // Optional. Support for the ICD Check-In BackOff strategy. Must be initialized before being provided. // If the ICD Check-In protocol use-case is supported and no strategy is provided, server will use the default strategy. app::ICDCheckInBackOffStrategy * icdCheckInBackOffStrategy = nullptr; +#if CHIP_CONFIG_TC_REQUIRED + // Optional. Enhanced setup flow provider to support terms and conditions acceptance check. + app::EnhancedSetupFlowProvider * enhancedSetupFlowProvider = nullptr; + // Optional. Terms and conditions provider to support enhanced setup flow feature. + app::TermsAndConditionsProvider * termsAndConditionsProvider = nullptr; +#endif }; /** @@ -300,6 +312,24 @@ struct CommonCaseDeviceServerInitParams : public ServerInitParams } #endif +#if CHIP_CONFIG_TC_REQUIRED + static app::DefaultEnhancedSetupFlowProvider sDefaultEnhancedSetupFlowProviderInstance; + static app::DefaultTermsAndConditionsProvider sDefaultTermsAndConditionsProviderInstance; + if (this->termsAndConditionsProvider == nullptr) + { + ReturnErrorOnFailure(sDefaultTermsAndConditionsProviderInstance.Init(this->persistentStorageDelegate, + CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS, + CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION)); + this->termsAndConditionsProvider = &sDefaultTermsAndConditionsProviderInstance; + } + + if (this->enhancedSetupFlowProvider == nullptr) + { + ReturnErrorOnFailure(sDefaultEnhancedSetupFlowProviderInstance.Init(this->termsAndConditionsProvider)); + this->enhancedSetupFlowProvider = &sDefaultEnhancedSetupFlowProviderInstance; + } +#endif + return CHIP_NO_ERROR; } @@ -402,6 +432,10 @@ class Server app::reporting::ReportScheduler * GetReportScheduler() { return mReportScheduler; } +#if CHIP_CONFIG_TC_REQUIRED + app::EnhancedSetupFlowProvider * GetEnhancedSetupFlowProvider() { return mEnhancedSetupFlowProvider; } +#endif + #if CHIP_CONFIG_ENABLE_ICD_SERVER app::ICDManager & GetICDManager() { return mICDManager; } @@ -675,6 +709,10 @@ class Server GroupDataProviderListener mListener; ServerFabricDelegate mFabricDelegate; app::reporting::ReportScheduler * mReportScheduler; +#if CHIP_CONFIG_TC_REQUIRED + app::EnhancedSetupFlowProvider * mEnhancedSetupFlowProvider; + app::TermsAndConditionsProvider * mTermsAndConditionsProvider; +#endif Access::AccessControl mAccessControl; app::AclStorage * mAclStorage; diff --git a/src/app/server/TermsAndConditionsProvider.h b/src/app/server/TermsAndConditionsProvider.h new file mode 100644 index 00000000000000..4fe0fe7769b47c --- /dev/null +++ b/src/app/server/TermsAndConditionsProvider.h @@ -0,0 +1,78 @@ +/* + * + * 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 Data access layer for the required terms and conditions and the store for the user acceptance. + */ +class TermsAndConditionsProvider +{ +public: + virtual ~TermsAndConditionsProvider() = default; + + /** + * @brief Clear the temporary acceptance status of the required terms and conditions. + */ + virtual CHIP_ERROR RevertAcceptance() = 0; + + /** + * @brief Commit the persistent acceptance status of the required terms and conditions. + */ + virtual CHIP_ERROR CommitAcceptance() = 0; + + /** + * @brief Retrieves the latest acceptance status of the required terms and conditions. + * + * @param[out] outAcknowledgementsValue The bitmask of acknowledgements accepted. + * @param[out] outAcknowledgementsVersionValue The version of the accepted acknowledgements. + */ + virtual CHIP_ERROR GetAcceptance(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const = 0; + + /** + * @brief Retrieves the requirements of the terms and conditions. + * + * @param[out] outAcknowledgementsValue The bitmask of required acknowledgements. + * @param[out] outAcknowledgementsVersionValue The version of the required acknowledgements. + */ + virtual CHIP_ERROR GetRequirements(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const = 0; + + /** + * @brief Reset the persisted acceptance status of the required terms and conditions. + */ + virtual CHIP_ERROR ResetAcceptance() = 0; + + /** + * @brief Sets the acceptance status of the required terms and conditions. + * + * @param[in] inAcknowledgementsValue The bitmask of acknowledgements that was accepted. + * @param[in] inAcknowledgementsVersionValue The version of the acknowledgements that was accepted. + */ + virtual CHIP_ERROR SetAcceptance(uint16_t inAcknowledgementsValue, uint16_t inAcknowledgementsVersionValue) = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 1bce08d55e0571..9da370d705d940 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. @@ -198,7 +198,9 @@ chip_test_suite("tests") { "TestCommandPathParams.cpp", "TestConcreteAttributePath.cpp", "TestDataModelSerialization.cpp", + "TestDefaultEnhancedSetupFlowProvider.cpp", "TestDefaultOTARequestorStorage.cpp", + "TestDefaultTermsAndConditionsProvider.cpp", "TestDefaultThreadNetworkDirectoryStorage.cpp", "TestEventLoggingNoUTCTime.cpp", "TestEventOverflow.cpp", @@ -236,6 +238,8 @@ chip_test_suite("tests") { "${chip_root}/src/app/codegen-data-model-provider:instance-header", "${chip_root}/src/app/common:cluster-objects", "${chip_root}/src/app/icd/client:manager", + "${chip_root}/src/app/server:enhanced_setup_flow", + "${chip_root}/src/app/server:server", "${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/TestDefaultEnhancedSetupFlowProvider.cpp b/src/app/tests/TestDefaultEnhancedSetupFlowProvider.cpp new file mode 100644 index 00000000000000..8ef94119ef54ee --- /dev/null +++ b/src/app/tests/TestDefaultEnhancedSetupFlowProvider.cpp @@ -0,0 +1,310 @@ +/* + * 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/DefaultEnhancedSetupFlowProvider.h" + +#include +#include +#include + +class FakeTermsAndConditionsProvider : public chip::app::TermsAndConditionsProvider +{ +public: + FakeTermsAndConditionsProvider(uint16_t inAcceptedAcknowledgements, uint16_t inAcceptedAcknowledgementsVersion, + uint16_t inRequiredAcknowledgements, uint16_t inRequiredAcknowledgementsVersion) : + mAcceptedAcknowledgements(inAcceptedAcknowledgements), + mAcceptedAcknowledgementsVersion(inAcceptedAcknowledgementsVersion), mRequiredAcknowledgements(inRequiredAcknowledgements), + mRequiredAcknowledgementsVersion(inRequiredAcknowledgementsVersion) + {} + + CHIP_ERROR RevertAcceptance() override + { + mAcceptedAcknowledgements = 0; + mAcceptedAcknowledgementsVersion = 0; + return CHIP_NO_ERROR; + } + + CHIP_ERROR CommitAcceptance() override { return CHIP_NO_ERROR; } + + CHIP_ERROR GetAcceptance(uint16_t & outAcknowledgements, uint16_t & outAcknowledgementsVersion) const override + { + outAcknowledgements = mAcceptedAcknowledgements; + outAcknowledgementsVersion = mAcceptedAcknowledgementsVersion; + return CHIP_NO_ERROR; + } + + CHIP_ERROR GetRequirements(uint16_t & outAcknowledgements, uint16_t & outAcknowledgementsVersion) const override + { + outAcknowledgements = mRequiredAcknowledgements; + outAcknowledgementsVersion = mRequiredAcknowledgementsVersion; + return CHIP_NO_ERROR; + } + + CHIP_ERROR SetAcceptance(uint16_t inAcknowledgements, uint16_t inAcknowledgementsVersion) override + { + mAcceptedAcknowledgements = inAcknowledgements; + mAcceptedAcknowledgementsVersion = inAcknowledgementsVersion; + return CHIP_NO_ERROR; + } + + CHIP_ERROR ResetAcceptance() override { return CHIP_NO_ERROR; } + +private: + uint16_t mAcceptedAcknowledgements; + uint16_t mAcceptedAcknowledgementsVersion; + uint16_t mRequiredAcknowledgements; + uint16_t mRequiredAcknowledgementsVersion; +}; + +TEST(DefaultEnhancedSetupFlowProvider, TestNoAcceptanceRequiredCheckAcknowledgementsAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 0, 0); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestNoAcceptanceRequiredCheckAcknowledgementsVersionAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsVersionBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 0, 0); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredNoTermsAcceptedCheckAcknowledgementsAcceptedFailure) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 1, 1); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(hasTermsBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, + TestAcceptanceRequiredTermsAcceptedTermsVersionOutdatedCheckAcknowledgementsVersionAcceptedFailure) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 1, 1); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.SetTermsAndConditionsAcceptance(1, 0); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredTermsAcceptedFutureVersionCheckAcknowledgementsAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + uint16_t acceptedTerms = 1; + uint16_t requiredTerms = 1; + uint16_t acceptedTermsVersion = 2; + uint16_t requiredTermsVersion = 1; + + FakeTermsAndConditionsProvider tncProvider(acceptedTerms, acceptedTermsVersion, requiredTerms, requiredTermsVersion); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredTermsAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 1, 1); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.SetTermsAndConditionsAcceptance(1, 1); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredTermsMissingFailure) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + uint16_t acceptedTerms = 0b0111'1111'1111'1111; + uint16_t requiredTerms = 0b1111'1111'1111'1111; + uint16_t acceptedTermsVersion = 1; + uint16_t requiredTermsVersion = 1; + + FakeTermsAndConditionsProvider tncProvider(acceptedTerms, acceptedTermsVersion, requiredTerms, requiredTermsVersion); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredAllTermsAcceptedCheckAcknowledgementsAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + uint16_t acceptedTerms = 0b1111'1111'1111'1111; + uint16_t requiredTerms = 0b1111'1111'1111'1111; + uint16_t acceptedTermsVersion = 1; + uint16_t requiredTermsVersion = 1; + + FakeTermsAndConditionsProvider tncProvider(acceptedTerms, acceptedTermsVersion, requiredTerms, requiredTermsVersion); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestRevertAcceptanceRetainsRequirements) +{ + CHIP_ERROR err; + + uint16_t initialAcceptedTermsAndConditions = 0; + uint16_t initialRequiredTermsAndConditions = 0b1111'1111'1111'1111; + uint16_t initialAcceptedTermsAndConditionsVersion = 0; + uint16_t initialRequiredTermsAndConditionsVersion = 1; + + uint16_t outAcceptedTermsAndConditions; + uint16_t outRequiredTermsAndConditions; + uint16_t outAcceptedTermsAndConditionsVersion; + uint16_t outRequiredTermsAndConditionsVersion; + + uint16_t updatedAcceptedTermsAndConditions = 0b1111'1111'1111'1111; + uint16_t updatedAcceptedTermsAndConditionsVersion = 1; + + FakeTermsAndConditionsProvider tncProvider(initialAcceptedTermsAndConditions, initialAcceptedTermsAndConditionsVersion, + initialRequiredTermsAndConditions, initialRequiredTermsAndConditionsVersion); + + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.SetTermsAndConditionsAcceptance(updatedAcceptedTermsAndConditions, updatedAcceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.GetTermsAndConditionsRequiredAcknowledgements(outRequiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outRequiredTermsAndConditions, initialRequiredTermsAndConditions); + + err = esfProvider.GetTermsAndConditionsRequiredAcknowledgementsVersion(outRequiredTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outRequiredTermsAndConditionsVersion, initialRequiredTermsAndConditionsVersion); + + err = esfProvider.GetTermsAndConditionsAcceptedAcknowledgements(outAcceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outAcceptedTermsAndConditions, updatedAcceptedTermsAndConditions); + + err = esfProvider.GetTermsAndConditionsAcceptedAcknowledgementsVersion(outAcceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outAcceptedTermsAndConditionsVersion, updatedAcceptedTermsAndConditionsVersion); + + err = esfProvider.RevertTermsAndConditionsAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.GetTermsAndConditionsRequiredAcknowledgements(outRequiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outRequiredTermsAndConditions, initialRequiredTermsAndConditions); + + err = esfProvider.GetTermsAndConditionsRequiredAcknowledgementsVersion(outRequiredTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outRequiredTermsAndConditionsVersion, initialRequiredTermsAndConditionsVersion); + + err = esfProvider.GetTermsAndConditionsAcceptedAcknowledgements(outAcceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outAcceptedTermsAndConditions, 0); + + err = esfProvider.GetTermsAndConditionsAcceptedAcknowledgementsVersion(outAcceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outAcceptedTermsAndConditionsVersion, 0); +} diff --git a/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp b/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp new file mode 100644 index 00000000000000..474dda7a6b2785 --- /dev/null +++ b/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp @@ -0,0 +1,231 @@ +/* + * + * 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 +#include +#include +#include + +TEST(DefaultTermsAndConditionsProvider, TestInitSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); +} + +TEST(DefaultTermsAndConditionsProvider, TestNoRequirementsGetRequirementsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 0; + uint16_t requiredAcknowledgementsVersion = 0; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(0, outAcceptance); + EXPECT_EQ(0, outAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestNeverAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 0b1111'1111'1111'1111; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(0, outAcceptance); + EXPECT_EQ(0, outAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestTermsAcceptedPersistsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + chip::app::DefaultTermsAndConditionsProvider anotherTncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t acceptedTermsAndConditions = 1; + uint16_t acceptedTermsAndConditionsVersion = 1; + err = tncProvider.SetAcceptance(acceptedTermsAndConditions, acceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); + + err = tncProvider.CommitAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); + + err = anotherTncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = anotherTncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestTermsRequiredGetRequirementsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t initialRequiredAcknowledgements = 1; + uint16_t initialRequiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, initialRequiredAcknowledgements, initialRequiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outRequiredAcknowledgements; + uint16_t outRequiredAcknowledgementsVersion; + err = tncProvider.GetRequirements(outRequiredAcknowledgements, outRequiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outRequiredAcknowledgements); + EXPECT_EQ(1, outRequiredAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestSetAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t acceptedTermsAndConditions = 1; + uint16_t acceptedTermsAndConditionsVersion = 1; + err = tncProvider.SetAcceptance(acceptedTermsAndConditions, acceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestRevertAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t acceptedTermsAndConditions = 1; + uint16_t acceptedTermsAndConditionsVersion = 1; + err = tncProvider.SetAcceptance(acceptedTermsAndConditions, acceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); + + err = tncProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance2; + uint16_t outAcknowledgementsVersion2; + err = tncProvider.GetAcceptance(outAcceptance2, outAcknowledgementsVersion2); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(0, outAcceptance2); + EXPECT_EQ(0, outAcknowledgementsVersion2); +} + +TEST(DefaultTermsAndConditionsProvider, TestAcceptanceRequiredTermsMissingFailure) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t acceptedTermsAndConditions = 1; + uint16_t acceptedTermsAndConditionsVersion = 1; + err = tncProvider.SetAcceptance(acceptedTermsAndConditions, acceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); + + err = tncProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outRequiredAcknowledgements; + uint16_t outRequiredAcknowledgementsVersion; + err = tncProvider.GetRequirements(outRequiredAcknowledgements, outRequiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outRequiredAcknowledgements); + EXPECT_EQ(1, outRequiredAcknowledgementsVersion); +} diff --git a/src/controller/CommissioningDelegate.h b/src/controller/CommissioningDelegate.h index 7a96939cf49434..749411ad0d33d1 100644 --- a/src/controller/CommissioningDelegate.h +++ b/src/controller/CommissioningDelegate.h @@ -82,6 +82,8 @@ enum CommissioningStage : uint8_t kPrimaryOperationalNetworkFailed, ///< Indicate that the primary operational network (on root endpoint) failed, should disable ///< the primary network interface later. kDisablePrimaryNetworkInterface, ///< Send InterfaceEnabled write request to the device to disable network interface. + kGetTCAcknowledgments, ///< Waiting for the higher layer to provide terms and conditions acknowledgements. + kConfigureTCAcknowledgments, ///< Send SetTCAcknowledgements (0x30:6) command to the device }; enum class ICDRegistrationStrategy : uint8_t diff --git a/src/include/platform/CHIPDeviceEvent.h b/src/include/platform/CHIPDeviceEvent.h index 09f4c46b652920..5ed80079d4e284 100644 --- a/src/include/platform/CHIPDeviceEvent.h +++ b/src/include/platform/CHIPDeviceEvent.h @@ -534,6 +534,9 @@ struct ChipDeviceEvent final FabricIndex fabricIndex; bool addNocCommandHasBeenInvoked; bool updateNocCommandHasBeenInvoked; +#if CHIP_CONFIG_TC_REQUIRED + bool updateTermsAndConditionsHasBeenInvoked; +#endif } FailSafeTimerExpired; struct diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index e9c317dfc79d5c..d739017e401179 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1785,6 +1785,60 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_MAX_BDX_LOG_TRANSFERS 5 #endif // CHIP_CONFIG_MAX_BDX_LOG_TRANSFERS +/** + * @file + * Configuration settings for Terms and Conditions (TC) acknowledgements during device commissioning. + */ + +/** + * @def CHIP_CONFIG_TC_REQUIRED + * + * @brief Indicates whether terms and conditions are required during commissioning. + * + * This macro defines whether the device commissioning process requires the user to acknowledge terms and conditions. + * - 1: Terms and conditions are required. + * - 0: Terms and conditions are not required. + * + * If this is set to 1, both CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS and + * CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION must be defined. + */ +#ifndef CHIP_CONFIG_TC_REQUIRED +#define CHIP_CONFIG_TC_REQUIRED (0) +#endif + +#if CHIP_CONFIG_TC_REQUIRED + +/** + * @def CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS + * + * @brief Configures the required terms and conditions acknowledgements bitmask. + * + * This macro defines the required terms and conditions acknowledgements bitmask. The bit-field is 16 bits long, so the possible + * value range is [0, 65535). This setting can be used to require that terms and conditions are presented to the user during + * commissioning. + */ +#ifndef CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS +#error "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS must be defined when CHIP_CONFIG_TC_REQUIRED is enabled." +#endif + +/** + * @def CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION + * + * @brief Configures the latest known version of the terms and conditions. + * + * This macro defines the version number of the latest terms and conditions. It allows the application to iterate on revisions of + * the terms and conditions. A value of 0 indicates that no specific version is required. This setting can be used to enforce + * version-specific terms and conditions acknowledgements in the application. When the set of terms and conditions needs to be + * changed, the version number should be monotonically increased. If the latest terms and conditions version is updated (most + * likely during an OTA), then this may signal to the Administrator that updated terms and conditions should be presented to the + * user. + */ +#ifndef CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION +#error "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION must be defined when CHIP_CONFIG_TC_REQUIRED is enabled." +#endif + +#endif // CHIP_CONFIG_TC_REQUIRED + /** * @} */ 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 From 56fc7ab5ea020e8a4f29abce3f54927c92629058 Mon Sep 17 00:00:00 2001 From: James Swan <122404367+swan-amazon@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:30:44 +0000 Subject: [PATCH 02/10] Enable Terms and Conditions (TC) feature for AutoCommissioner This commit enables the Terms and Conditions (TC) feature in the all-clusters-app example application. The changes ensure that the app can handle TC acknowledgements and enforce required terms and conditions during the commissioning process. Changes include: - Added TC attributes and commands in the General Commissioning cluster. - Enabled the TC feature in the application configuration. This enables the TC feature logic in the example app, building on the initial implementation. --- .../commands/pairing/PairingCommand.cpp | 16 ++++- .../commands/pairing/PairingCommand.h | 17 ++++- src/controller/AutoCommissioner.cpp | 6 +- src/controller/CHIPDeviceController.cpp | 71 ++++++++++++++++++- src/controller/CHIPDeviceController.h | 24 ++++++- src/controller/CommissioningDelegate.cpp | 6 ++ src/controller/CommissioningDelegate.h | 29 ++++++++ 7 files changed, 164 insertions(+), 5 deletions(-) diff --git a/examples/chip-tool/commands/pairing/PairingCommand.cpp b/examples/chip-tool/commands/pairing/PairingCommand.cpp index 245c9ed57ff82c..ab76c985339662 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"); @@ -129,6 +129,20 @@ CommissioningParameters PairingCommand::GetCommissioningParameters() params.SetCountryCode(CharSpan::fromCharString(mCountryCode.Value())); } + // Default requiring TCs to false, to preserve release 1.3 chip-tool behavior + params.SetRequireTermsAndConditionsAcknowledgement(mRequireTCAcknowledgements.ValueOr(false)); + + // 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. diff --git a/examples/chip-tool/commands/pairing/PairingCommand.h b/examples/chip-tool/commands/pairing/PairingCommand.h index 9965b663ec111c..b21f13067da4ed 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"); @@ -202,6 +202,18 @@ class PairingCommand : public CHIPCommand, AddArgument("dst-offset", &mComplex_DSTOffsets, "DSTOffset list to use when setting Time Synchronization cluster's DSTOffset attribute", Argument::kOptional); + + AddArgument("require-tc-acknowledgements", 0, 1, &mRequireTCAcknowledgements, + "Terms and Conditions acknowledgements is known to be required or not, (when required, the commissioner " + "will wait until they are provided)"); + + AddArgument("tc-acknowledgements", 0, UINT16_MAX, &mTCAcknowledgements, + "Terms and Conditions acknowledgements to use to set the General Commissioning cluster's TC " + "Acknowledgements bit-field"); + + AddArgument("tc-acknowledgements-version", 0, UINT16_MAX, &mTCAcknowledgementVersion, + "Terms and Conditions acknowledgement version to use to set the General Commissioning cluster's TC " + "Acknowledgement version"); } AddArgument("timeout", 0, UINT16_MAX, &mTimeout); @@ -259,6 +271,9 @@ class PairingCommand : public CHIPCommand, chip::Optional mICDMonitoredSubject; chip::Optional mICDClientType; chip::Optional mICDStayActiveDurationMsec; + chip::Optional mRequireTCAcknowledgements; + chip::Optional mTCAcknowledgements; + chip::Optional mTCAcknowledgementVersion; chip::app::DataModel::List mTimeZoneList; TypedComplexArgument> mComplex_TimeZones; diff --git a/src/controller/AutoCommissioner.cpp b/src/controller/AutoCommissioner.cpp index 0e83d7125cf175..cfdd5826919a8b 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,10 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(Commissio case CommissioningStage::kArmFailsafe: return CommissioningStage::kConfigRegulatory; case CommissioningStage::kConfigRegulatory: + return CommissioningStage::kGetTCAcknowledgments; + case CommissioningStage::kGetTCAcknowledgments: + 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 955e36bfb0d8a2..c1b788aca643bd 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. * @@ -2691,6 +2691,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) { @@ -2766,6 +2782,16 @@ CHIP_ERROR DeviceCommissioner::ICDRegistrationInfoReady() return CHIP_NO_ERROR; } +CHIP_ERROR DeviceCommissioner::TermsAndConditionsAcknowledgementsReady() +{ + ReturnErrorCodeIf(mCommissioningStage != CommissioningStage::kGetTCAcknowledgments, CHIP_ERROR_INCORRECT_STATE); + + // need to advance to next step + CommissioningStageComplete(CHIP_NO_ERROR); + + return CHIP_NO_ERROR; +} + void DeviceCommissioner::OnNetworkConfigResponse(void * context, const NetworkCommissioning::Commands::NetworkConfigResponse::DecodableType & data) { @@ -3163,6 +3189,49 @@ void DeviceCommissioner::PerformCommissioningStep(DeviceProxy * proxy, Commissio } } break; + case CommissioningStage::kGetTCAcknowledgments: { + ChipLogProgress(Controller, "Get Terms and Conditions Acknowledgments"); + + // If terms and conditions acknowledgements are not required, or have already been provided, then proceed + if (!params.GetRequireTermsAndConditionsAcknowledgement() || params.GetTermsAndConditionsAcknowledgement().HasValue()) + { + TermsAndConditionsAcknowledgementsReady(); + return; + } + + ChipLogProgress(Controller, "Waiting for Terms and Conditions"); + break; + } + case CommissioningStage::kConfigureTCAcknowledgments: { + ChipLogProgress(Controller, "Setting Terms and Conditions"); + + if (!params.GetRequireTermsAndConditionsAcknowledgement()) + { + CommissioningStageComplete(CHIP_NO_ERROR); + return; + } + + if (!params.GetTermsAndConditionsAcknowledgement().HasValue()) + { + ChipLogError(Controller, "No acknowledgements provided"); + CommissioningStageComplete(CHIP_ERROR_INCORRECT_STATE); + return; + } + + GeneralCommissioning::Commands::SetTCAcknowledgements::Type request; + TermsAndConditionsAcknowledgement termsAndConditionsAcknowledgement = params.GetTermsAndConditionsAcknowledgement().Value(); + request.TCUserResponse = termsAndConditionsAcknowledgement.acceptedTermsAndConditions; + request.TCVersion = termsAndConditionsAcknowledgement.acceptedTermsAndConditionsVersion; + 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 4b876156199735..1f1b381da96cbd 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. * @@ -696,6 +696,25 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, */ CHIP_ERROR ICDRegistrationInfoReady(); + /** + * @brief + * This function is called by the upper layer application to indicate that the required terms and conditions + * acknowledgements have been set. This function should be called after the terms and conditions bitmask and version + * have been defined using the appropriate configuration macros and the application has gathered the necessary + * acknowledgements from the user. + * + * The upper layer application should call this method once it has successfully presented and obtained acknowledgements + * for the required terms and conditions from the user. This indicates that the commissioning process can advance to the + * next stage. + * + * When the terms and conditions acknowledgements process is completed, this function will signal the readiness to proceed + * to the next step in the commissioning process. + * + * @return CHIP_ERROR The return status. Returns CHIP_ERROR_INCORRECT_STATE if the function is called when the device + * is not in the correct state to accept terms and conditions acknowledgements. + */ + CHIP_ERROR TermsAndConditionsAcknowledgementsReady(); + /** * @brief * This function returns the current CommissioningStage for this commissioner. @@ -947,6 +966,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 85ea5e86c5e3a6..ed7e558828db1b 100644 --- a/src/controller/CommissioningDelegate.cpp +++ b/src/controller/CommissioningDelegate.cpp @@ -46,6 +46,12 @@ const char * StageToString(CommissioningStage stage) case kConfigRegulatory: return "ConfigRegulatory"; + case kGetTCAcknowledgments: + return "GetTCAcknowledgments"; + + case kConfigureTCAcknowledgments: + return "ConfigureTCAcknowledgments"; + case kConfigureUTCTime: return "ConfigureUTCTime"; diff --git a/src/controller/CommissioningDelegate.h b/src/controller/CommissioningDelegate.h index 749411ad0d33d1..eac351aebe9e22 100644 --- a/src/controller/CommissioningDelegate.h +++ b/src/controller/CommissioningDelegate.h @@ -106,6 +106,12 @@ struct WiFiCredentials WiFiCredentials(ByteSpan newSsid, ByteSpan newCreds) : ssid(newSsid), credentials(newCreds) {} }; +struct TermsAndConditionsAcknowledgement +{ + uint16_t acceptedTermsAndConditions; + uint16_t acceptedTermsAndConditionsVersion; +}; + struct NOCChainGenerationParameters { ByteSpan nocsrElements; @@ -170,6 +176,13 @@ class CommissioningParameters // The country code to be used for the node, if set. Optional GetCountryCode() const { return mCountryCode; } + bool GetRequireTermsAndConditionsAcknowledgement() const { return mRequireTermsAndConditionsAcknowledgement; } + + 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 @@ -342,6 +355,19 @@ class CommissioningParameters return *this; } + CommissioningParameters & SetRequireTermsAndConditionsAcknowledgement(bool requireTermsAndConditionsAcknowledgement) + { + mRequireTermsAndConditionsAcknowledgement = requireTermsAndConditionsAcknowledgement; + 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) @@ -583,6 +609,7 @@ class CommissioningParameters mAttestationNonce.ClearValue(); mWiFiCreds.ClearValue(); mCountryCode.ClearValue(); + mTermsAndConditionsAcknowledgement.ClearValue(); mThreadOperationalDataset.ClearValue(); mNOCChainGenerationParameters.ClearValue(); mRootCert.ClearValue(); @@ -613,6 +640,7 @@ class CommissioningParameters Optional mAttestationNonce; Optional mWiFiCreds; Optional mCountryCode; + Optional mTermsAndConditionsAcknowledgement; Optional mThreadOperationalDataset; Optional mNOCChainGenerationParameters; Optional mRootCert; @@ -645,6 +673,7 @@ class CommissioningParameters Optional mICDStayActiveDurationMsec; ICDRegistrationStrategy mICDRegistrationStrategy = ICDRegistrationStrategy::kIgnore; bool mCheckForMatchingFabric = false; + bool mRequireTermsAndConditionsAcknowledgement = false; }; struct RequestedCertificate From f60fec1ad1b90fd9eaf279b671e46239404a86af Mon Sep 17 00:00:00 2001 From: James Swan Date: Wed, 14 Aug 2024 21:06:27 +0000 Subject: [PATCH 03/10] Enable Terms and Conditions (TC) feature in all-clusters-app example This commit enables the Terms and Conditions (TC) feature in the all-clusters-app example application. The changes ensure that the app can handle TC acknowledgements and enforce required terms and conditions during the commissioning process. Changes include: - Added TC attributes and commands in the General Commissioning cluster. - Enabled the TC feature in the application configuration. This enables the TC feature logic in the example app, building on the initial implementation. --- .../all-clusters-app.matter | 8 +- .../all-clusters-common/all-clusters-app.zap | 83 ++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index 46debb8a6cd363..02bd16f1424406 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -7852,11 +7852,15 @@ endpoint 0 { callback attribute regulatoryConfig; callback attribute locationCapability; callback attribute supportsConcurrentConnection; + ram attribute TCAcceptedVersion; + ram attribute TCMinRequiredVersion default = 1; + ram attribute TCAcknowledgements default = 0x0000; + ram attribute TCAcknowledgementsRequired default = true; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; callback attribute attributeList; - ram attribute featureMap default = 0; + ram attribute featureMap default = 1; ram attribute clusterRevision default = 1; handle command ArmFailSafe; @@ -7865,6 +7869,8 @@ endpoint 0 { handle command SetRegulatoryConfigResponse; handle command CommissioningComplete; handle command CommissioningCompleteResponse; + handle command SetTCAcknowledgements; + handle command SetTCAcknowledgementsResponse; } server cluster NetworkCommissioning { diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 9a736eea56fa30..a845343f9b85d8 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -2124,6 +2124,7 @@ "define": "GENERAL_COMMISSIONING_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "provisional", "commands": [ { "name": "ArmFailSafe", @@ -2172,6 +2173,22 @@ "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": [ @@ -2255,6 +2272,70 @@ "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": "1", + "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": "true", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "GeneratedCommandList", "code": 65528, @@ -2329,7 +2410,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, From 4686d5f7f417ccfea867e9957abc95572b4aa115 Mon Sep 17 00:00:00 2001 From: Chris Koos Date: Thu, 8 Aug 2024 22:54:10 -0700 Subject: [PATCH 04/10] Add GC 2_5 test script Add GC 2_5 test script --- .../ChipDeviceController-ScriptBinding.cpp | 26 +++ src/controller/python/chip/ChipDeviceCtrl.py | 31 +++ src/python_testing/TC_CGEN_2_5.py | 200 ++++++++++++++++++ src/python_testing/matter_testing_support.py | 18 ++ 4 files changed, 275 insertions(+) create mode 100644 src/python_testing/TC_CGEN_2_5.py diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index b31cb5b7b8a4e6..f60a94d12aa763 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -181,6 +181,9 @@ 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_SetRequireTermsAndConditionsAcknowledgement(bool tcRequired); +PyChipError pychip_DeviceController_SetTermsAcknowledgements(uint16_t tcVersion, uint16_t tcUserResponse); +PyChipError pychip_DeviceController_SetSkipCommissioningComplete(bool skipCommissioningComplete); // Pairing Delegate PyChipError @@ -570,6 +573,29 @@ PyChipError pychip_DeviceController_SetDefaultNtp(const char * defaultNTP) return ToPyChipError(CHIP_NO_ERROR); } + +PyChipError pychip_DeviceController_SetRequireTermsAndConditionsAcknowledgement(bool tcRequired) +{ + sCommissioningParameters.SetRequireTermsAndConditionsAcknowledgement(tcRequired); + 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 24fe59486f78d5..5a80d7e1f00eab 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -1938,6 +1938,16 @@ def _InitLib(self): self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters.restype = PyChipError self._dmLib.pychip_DeviceProxy_GetRemoteSessionParameters.argtypes = [c_void_p, c_char_p] + self._dmLib.pychip_DeviceController_SetSkipCommissioningComplete.restype = PyChipError + self._dmLib.pychip_DeviceController_SetSkipCommissioningComplete.argtypes = [c_bool] + + self._dmLib.pychip_DeviceController_SetRequireTermsAndConditionsAcknowledgement.restype = PyChipError + self._dmLib.pychip_DeviceController_SetRequireTermsAndConditionsAcknowledgement.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 @@ -2066,6 +2076,27 @@ def SetDSTOffset(self, offset: int, validStarting: int, validUntil: int): lambda: self._dmLib.pychip_DeviceController_SetDSTOffset(offset, validStarting, validUntil) ).raise_on_error() + def SetTCRequired(self, tcRequired: bool): + ''' Set whether TC Acknowledgements should be set during commissioning''' + self.CheckIsActive() + self._ChipStack.Call( + lambda: self._dmLib.pychip_DeviceController_SetRequireTermsAndConditionsAcknowledgement(tcRequired) + ).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/python_testing/TC_CGEN_2_5.py b/src/python_testing/TC_CGEN_2_5.py new file mode 100644 index 00000000000000..f35da7dfc5b2ce --- /dev/null +++ b/src/python_testing/TC_CGEN_2_5.py @@ -0,0 +1,200 @@ +# +# 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. +# + +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ALL_CLUSTERS_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto + +import logging +import random + +import chip.clusters as Clusters +from chip.exceptions import ChipStackError +from matter_testing_support import DiscoveryFilterType, MatterBaseTest, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_CGEN_2_5(MatterBaseTest): + + async def commission_device(self) -> bool: + dev_ctrl = self.default_controller + conf = self.matter_test_config + info = self.get_setup_payload_info()[0] + + if conf.commissioning_method == "on-network": + try: + await dev_ctrl.CommissionOnNetwork( + nodeId=self.dut_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 conf.commissioning_method == "ble-wifi": + try: + await dev_ctrl.CommissionWiFi( + info.filter_value, + info.passcode, + conf.self.dut_node_id, + 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.self.dut_node_id, + 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 + else: + raise ValueError("Invalid commissioning method %s!" % conf.commissioning_method) + + async def verify_tc_attributes(self, expected_tc_version: int, expected_tc_acknowledgements: int): + attr = Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion + tc_accepted_version = await self.read_single_attribute(dev_ctrl=self.th1, node_id=self.dut_node_id, endpoint=0, attribute=attr) + asserts.assert_equal(tc_accepted_version, expected_tc_version, + f"Expected TCAcceptedVersion to be {expected_tc_version}, but got {tc_accepted_version}") + + attr = Clusters.GeneralCommissioning.Attributes.TCAcknowledgements + tc_acknowedgements = await self.read_single_attribute(dev_ctrl=self.th1, node_id=self.dut_node_id, endpoint=0, attribute=attr) + asserts.assert_equal(tc_acknowedgements, expected_tc_acknowledgements, + f"Expected TCAcknowledgements to be {expected_tc_acknowledgements}, but got {tc_acknowedgements}") + + @async_test_body + async def test_TC_CGEN_2_5(self): + self.th1 = self.default_controller + self.discriminator = random.randint(0, 4095) + + logging.info('Step 1 - TH reads the TCAcceptedVersion attribute') + attr = Clusters.GeneralCommissioning.Attributes.TCAcceptedVersion + tc_accepted_version = await self.read_single_attribute(dev_ctrl=self.th1, node_id=self.dut_node_id, endpoint=0, attribute=attr) + + print(f"tc_accepted_version: {tc_accepted_version}") + + logging.info('Step 2 - TH reads the TCAcknowledgements attribute') + attr = Clusters.GeneralCommissioning.Attributes.TCAcknowledgements + tc_acknowedgements = await self.read_single_attribute(dev_ctrl=self.th1, node_id=self.dut_node_id, endpoint=0, attribute=attr) + print(f"tc_acknowedgements: {tc_acknowedgements}") + + logging.info('Step 3 - TH reads the TCMinRequiredVersion attribute') + attr = Clusters.GeneralCommissioning.Attributes.TCMinRequiredVersion + tc_min_requried_version = await self.read_single_attribute(dev_ctrl=self.th1, node_id=self.dut_node_id, endpoint=0, attribute=attr) + + logging.info('Step 4 - TH reads the TCAcknowledgementsRequired attribute') + attr = Clusters.GeneralCommissioning.Attributes.TCAcknowledgementsRequired + tc_required = await self.read_single_attribute(dev_ctrl=self.th1, node_id=self.dut_node_id, endpoint=0, attribute=attr) + + logging.info('Step 5 - TH sends SetTCAcknowledgements with greater values than current and verify set') + new_accepted_version = tc_accepted_version + 1 + new_acknowledgements = 65535 + + cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(new_accepted_version, new_acknowledgements) + resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, 'Incorrect error code') + self.verify_tc_attributes(new_accepted_version, new_acknowledgements) + + logging.info('Step 6 - TH sends SetTCAcknowledgements with no accepted terms at version 0') + cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(0, 0) + resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) + asserts.assert_equal( + resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kTCMinVersionNotMet, 'Incorrect error code' + ) + self.verify_tc_attributes(new_accepted_version, new_acknowledgements) + + logging.info('Step 7 - TH sends SetTCAcknowledgements with no accepted terms at version 1') + cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(1, 0) + resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) + asserts.assert_equal( + resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kRequiredTCNotAccepted, 'Incorrect error code' + ) + self.verify_tc_attributes(new_accepted_version, new_acknowledgements) + + logging.info('Step 8 - TH sends ArmFailSafe with ExpiryLengthSeconds set to 60') + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(60) + resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kNoError, + 'Incorrect error code') + + logging.info('Step 9 - TH sends SetTCAcknowledgements with incremented TCVersion') + new_accepted_version += 1 + cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(new_accepted_version, new_acknowledgements) + resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kNoError, + 'Incorrect error code') + + logging.info('Step 10 - TH sends ArmFailSafe with ExpiryLengthSeconds set to 0') + cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(0) + resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kNoError, + 'Incorrect error code') + + self.verify_tc_attributes(new_accepted_version, new_acknowledgements) + + logging.info('Step 11 - TH removes all fabrics from the device') + fabrics = await self.read_single_attribute(dev_ctrl=self.th1, node_id=self.dut_node_id, endpoint=0, attribute=Clusters.OperationalCredentials.Attributes.Fabrics) + for fabric in fabrics: + if fabric.fabricIndex == self.th1.fabricId: + continue + cmd = Clusters.OperationalCredentials.Commands.RemoveFabric(fabric.fabricIndex) + resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) + asserts.assert_equal(resp.statusCode, Clusters.OperationalCredentials.Enums.NodeOperationalCertStatusEnum.kOk) + + # Remove TH1 fabric last + cmd = Clusters.OperationalCredentials.Commands.RemoveFabric(self.th1.fabricId) + resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) + + # Give a prompt for the user to make the device in a commissionable state. + # Currently with the sample apps the device doesn't have an opening commissioning window after removing all fabrics. + print("Please return the device to a commissionable state if needed (reboot) and press any key to continue...") + input() + + logging.info('Step 12 - TH commissions the DUT without setting TCs') + self.th1.ResetTestCommissioner() + self.th1.ExpireSessions(self.dut_node_id) + + # Don't set TCs for the next commissioning and skip CommissioningComplete so we can check the error code + self.th1.SetTCRequired(False) + self.th1.SetTCAcknowledgements(0, 0) + self.th1.SetSkipCommissioningComplete(True) + + await self.commission_device() + + cmd = Clusters.GeneralCommissioning.Commands.CommissioningComplete() + resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kTCAcknowledgementsNotReceived, + 'Incorrect error code') + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index d44754f379d92e..3630d47192ab26 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -546,6 +546,10 @@ class MatterTestConfig: trace_to: List[str] = field(default_factory=list) + # Accepted Terms and Conditions if used + tc_version: int = None + tc_user_response: int = None + class ClusterMapper: """Describe clusters/attributes using schema names.""" @@ -1591,6 +1595,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 = args.tc_version + config.tc_user_response = args.tc_user_response + # 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, @@ -1684,6 +1691,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', type=int, help="Terms and conditions version") + + commission_group.add_argument('--tc-user-response', 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, @@ -1954,6 +1965,13 @@ async def _commission_device(self, i) -> bool: info = self.get_setup_payload_info()[i] + if conf.tc_version is not None and conf.tc_user_response is not None: + logging.debug(f"Setting TC Acknowledgements to version {conf.tc_version} with user response {conf.tc_user_response}.") + dev_ctrl.SetTCAcknowledgements(conf.tc_version, conf.tc_user_response) + dev_ctrl.SetTCRequired(True) + else: + dev_ctrl.SetTCRequired(False) + if conf.commissioning_method == "on-network": try: await dev_ctrl.CommissionOnNetwork( From 03c730dd67efe1d915721a5c64e9712790251f12 Mon Sep 17 00:00:00 2001 From: James Swan <122404367+swan-amazon@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:30:26 +0000 Subject: [PATCH 05/10] Add initial feature logic for Terms and Conditions (TC) acknowledgements This commit introduces the initial logic for handling Terms and Conditions (TC) acknowledgements in the General Commissioning cluster. The logic includes support for setting and checking TC acknowledgements and versions during the commissioning process. Changes include: - Handling TC acknowledgements and TC acknowledgement version in the pairing command. - Logic to read TC attributes and check TC acceptance in the General Commissioning server. - Introduction of classes to manage TC acceptance logic. - Initialization and use of TC providers in the server setup. - Addition of a new commissioning stage for TC acknowledgements in the commissioning flow. The feature logic is currently disabled and will be enabled in an example in a subsequent commit. --- src/app/BUILD.gn | 18 +- src/app/FailSafeContext.cpp | 11 +- src/app/FailSafeContext.h | 15 +- .../general-commissioning-server.cpp | 157 ++++++++- src/app/common_flags.gni | 4 + src/app/server/BUILD.gn | 29 +- .../DefaultEnhancedSetupFlowProvider.cpp | 166 ++++++++++ .../server/DefaultEnhancedSetupFlowProvider.h | 67 ++++ .../DefaultTermsAndConditionsProvider.cpp | 215 ++++++++++++ .../DefaultTermsAndConditionsProvider.h | 73 +++++ src/app/server/EnhancedSetupFlowProvider.h | 89 +++++ src/app/server/Server.cpp | 10 +- src/app/server/Server.h | 40 ++- src/app/server/TermsAndConditionsProvider.h | 78 +++++ src/app/tests/BUILD.gn | 6 +- .../TestDefaultEnhancedSetupFlowProvider.cpp | 310 ++++++++++++++++++ .../TestDefaultTermsAndConditionsProvider.cpp | 231 +++++++++++++ src/controller/CommissioningDelegate.h | 2 + src/include/platform/CHIPDeviceEvent.h | 3 + src/lib/core/CHIPConfig.h | 54 +++ src/lib/support/DefaultStorageKeyAllocator.h | 6 +- 21 files changed, 1572 insertions(+), 12 deletions(-) create mode 100644 src/app/server/DefaultEnhancedSetupFlowProvider.cpp create mode 100644 src/app/server/DefaultEnhancedSetupFlowProvider.h create mode 100644 src/app/server/DefaultTermsAndConditionsProvider.cpp create mode 100644 src/app/server/DefaultTermsAndConditionsProvider.h create mode 100644 src/app/server/EnhancedSetupFlowProvider.h create mode 100644 src/app/server/TermsAndConditionsProvider.h create mode 100644 src/app/tests/TestDefaultEnhancedSetupFlowProvider.cpp create mode 100644 src/app/tests/TestDefaultTermsAndConditionsProvider.cpp diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 53dd3876c12410..79a20235132133 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -60,6 +60,19 @@ declare_args() { current_os != "android" } +config("enhanced_setup_flow_config") { + defines = [] + if (chip_config_tc_required) { + defines += [ + "CHIP_CONFIG_TC_REQUIRED=1", + "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS=${chip_config_tc_required_acknowledgements}", + "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION=${chip_config_tc_required_acknowledgements_version}", + ] + } else { + defines += [ "CHIP_CONFIG_TC_REQUIRED=0" ] + } +} + buildconfig_header("app_buildconfig") { header = "AppBuildConfig.h" header_dir = "app" @@ -519,5 +532,8 @@ static_library("app") { cflags = [ "-Wconversion" ] - public_configs = [ "${chip_root}/src:includes" ] + public_configs = [ + ":enhanced_setup_flow_config", + "${chip_root}/src:includes", + ] } diff --git a/src/app/FailSafeContext.cpp b/src/app/FailSafeContext.cpp index 95a5b267f2aa5e..9cb9fb411cc2e0 100644 --- a/src/app/FailSafeContext.cpp +++ b/src/app/FailSafeContext.cpp @@ -86,9 +86,14 @@ 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, +#if CHIP_CONFIG_TC_REQUIRED + .updateTermsAndConditionsHasBeenInvoked = mUpdateTermsAndConditionsHasBeenInvoked, +#endif + } }; 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..421774a521450a 100644 --- a/src/app/FailSafeContext.h +++ b/src/app/FailSafeContext.h @@ -56,7 +56,9 @@ class FailSafeContext void SetUpdateNocCommandInvoked() { mUpdateNocCommandHasBeenInvoked = true; } void SetAddTrustedRootCertInvoked() { mAddTrustedRootCertHasBeenInvoked = true; } void SetCsrRequestForUpdateNoc(bool isForUpdateNoc) { mIsCsrRequestForUpdateNoc = isForUpdateNoc; } - +#if CHIP_CONFIG_TC_REQUIRED + void SetUpdateTermsAndConditionsHasBeenInvoked() { mUpdateTermsAndConditionsHasBeenInvoked = true; } +#endif /** * @brief * Schedules a work to cleanup the FailSafe Context asynchronously after various cleanup work @@ -91,6 +93,9 @@ class FailSafeContext bool UpdateNocCommandHasBeenInvoked() const { return mUpdateNocCommandHasBeenInvoked; } bool AddTrustedRootCertHasBeenInvoked() const { return mAddTrustedRootCertHasBeenInvoked; } bool IsCsrRequestForUpdateNoc() const { return mIsCsrRequestForUpdateNoc; } +#if CHIP_CONFIG_TC_REQUIRED + bool UpdateTermsAndConditionsHasBeenInvoked() { return mUpdateTermsAndConditionsHasBeenInvoked; } +#endif FabricIndex GetFabricIndex() const { @@ -110,7 +115,10 @@ class FailSafeContext bool mAddTrustedRootCertHasBeenInvoked = false; // The fact of whether a CSR occurred at all is stored elsewhere. bool mIsCsrRequestForUpdateNoc = false; - FabricIndex mFabricIndex = kUndefinedFabricIndex; +#if CHIP_CONFIG_TC_REQUIRED + bool mUpdateTermsAndConditionsHasBeenInvoked = false; +#endif + FabricIndex mFabricIndex = kUndefinedFabricIndex; /** * @brief @@ -145,6 +153,9 @@ class FailSafeContext mAddTrustedRootCertHasBeenInvoked = false; mFailSafeBusy = false; mIsCsrRequestForUpdateNoc = false; +#if CHIP_CONFIG_TC_REQUIRED + mUpdateTermsAndConditionsHasBeenInvoked = false; +#endif } void FailSafeTimerExpired(); 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..16b62f1fab9207 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. @@ -27,6 +27,9 @@ #include #include #include +#if CHIP_CONFIG_TC_REQUIRED +#include +#endif #include #include #include @@ -69,6 +72,9 @@ 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); + template + CHIP_ERROR ReadFromProvider(Provider * const aProvider, CHIP_ERROR (Provider::*const aConstGetter)(T &) const, + AttributeValueEncoder & aEncoder); }; GeneralCommissioningAttrAccess gAttrAccess; @@ -95,6 +101,28 @@ CHIP_ERROR GeneralCommissioningAttrAccess::Read(const ConcreteReadAttributePath case SupportsConcurrentConnection::Id: { return ReadSupportsConcurrentConnection(aEncoder); } +#if CHIP_CONFIG_TC_REQUIRED + case TCAcceptedVersion::Id: { + auto provider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + auto getter = &EnhancedSetupFlowProvider::GetTermsAndConditionsAcceptedAcknowledgementsVersion; + return ReadFromProvider(provider, getter, aEncoder); + } + case TCMinRequiredVersion::Id: { + auto provider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + auto getter = &EnhancedSetupFlowProvider::GetTermsAndConditionsRequiredAcknowledgementsVersion; + return ReadFromProvider(provider, getter, aEncoder); + } + case TCAcknowledgements::Id: { + auto provider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + auto getter = &EnhancedSetupFlowProvider::GetTermsAndConditionsAcceptedAcknowledgements; + return ReadFromProvider(provider, getter, aEncoder); + } + case TCAcknowledgementsRequired::Id: { + auto provider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + auto getter = &EnhancedSetupFlowProvider::IsTermsAndConditionsAcceptanceRequired; + return ReadFromProvider(provider, getter, aEncoder); + } +#endif default: { break; } @@ -144,6 +172,85 @@ CHIP_ERROR GeneralCommissioningAttrAccess::ReadSupportsConcurrentConnection(Attr return aEncoder.Encode(supportsConcurrentConnection); } +template +CHIP_ERROR GeneralCommissioningAttrAccess::ReadFromProvider(Provider * const aProvider, + CHIP_ERROR (Provider::*const aConstGetter)(T &) const, + AttributeValueEncoder & aEncoder) +{ + if (nullptr == aProvider) + { + return CHIP_ERROR_PERSISTED_STORAGE_FAILED; + } + + T value; + CHIP_ERROR err = (aProvider->*aConstGetter)(value); + if (err != CHIP_NO_ERROR) + { + return err; + } + + return aEncoder.Encode(value); +} + +#if CHIP_CONFIG_TC_REQUIRED +CHIP_ERROR checkTermsAndConditionsAcknowledgementsState(CommissioningErrorEnum & errorCode) +{ + EnhancedSetupFlowProvider * enhancedSetupFlowProvider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + + CHIP_ERROR err; + + uint16_t termsAndConditionsAcceptedAcknowledgements; + bool hasRequiredTermAccepted; + bool hasRequiredTermVersionAccepted; + + err = enhancedSetupFlowProvider->GetTermsAndConditionsAcceptedAcknowledgements(termsAndConditionsAcceptedAcknowledgements); + if (!::chip::ChipError::IsSuccess(err)) + { + ChipLogError(AppServer, "Failed to GetTermsAndConditionsAcceptedAcknowledgements"); + return err; + } + + err = enhancedSetupFlowProvider->HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasRequiredTermAccepted); + if (!::chip::ChipError::IsSuccess(err)) + { + ChipLogError(AppServer, "Failed to HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted"); + return err; + } + + err = + enhancedSetupFlowProvider->HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasRequiredTermVersionAccepted); + if (!::chip::ChipError::IsSuccess(err)) + { + ChipLogError(AppServer, "Failed to HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted"); + return err; + } + + if (!hasRequiredTermVersionAccepted) + { + uint16_t requiredAcknowledgementsVersion = 0; + (void) enhancedSetupFlowProvider->GetTermsAndConditionsRequiredAcknowledgementsVersion(requiredAcknowledgementsVersion); + ChipLogProgress(AppServer, "Minimum terms and conditions version, 0x%04x, has not been accepted", + requiredAcknowledgementsVersion); + errorCode = CommissioningErrorEnum::kTCMinVersionNotMet; + return CHIP_NO_ERROR; + } + + if (!hasRequiredTermAccepted) + { + uint16_t requiredAcknowledgements = 0; + (void) enhancedSetupFlowProvider->GetTermsAndConditionsRequiredAcknowledgements(requiredAcknowledgements); + + ChipLogProgress(AppServer, "Required terms and conditions, 0x%04x,have not been accepted", requiredAcknowledgements); + errorCode = (0 == termsAndConditionsAcceptedAcknowledgements) ? CommissioningErrorEnum::kTCAcknowledgementsNotReceived + : CommissioningErrorEnum::kRequiredTCNotAccepted; + return CHIP_NO_ERROR; + } + + errorCode = CommissioningErrorEnum::kOk; + return CHIP_NO_ERROR; +} +#endif + } // anonymous namespace bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler * commandObj, @@ -239,6 +346,10 @@ bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback( } else { +#if CHIP_CONFIG_TC_REQUIRED + CheckSuccess(checkTermsAndConditionsAcknowledgementsState(response.errorCode), Failure); +#endif + if (failSafe.NocCommandHasBeenInvoked()) { CHIP_ERROR err = fabricTable.CommitPendingFabricData(); @@ -328,6 +439,30 @@ bool emberAfGeneralCommissioningClusterSetRegulatoryConfigCallback(app::CommandH return true; } +bool emberAfGeneralCommissioningClusterSetTCAcknowledgementsCallback( + chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, + const chip::app::Clusters::GeneralCommissioning::Commands::SetTCAcknowledgements::DecodableType & commandData) +{ +#if !CHIP_CONFIG_TC_REQUIRED + return false; + +#else + auto & failSafeContext = Server::GetInstance().GetFailSafeContext(); + + MATTER_TRACE_SCOPE("SetTCAcknowledgements", "GeneralCommissioning"); + Commands::SetTCAcknowledgementsResponse::Type response; + EnhancedSetupFlowProvider * const enhancedSetupFlowProvider = Server::GetInstance().GetEnhancedSetupFlowProvider(); + uint16_t acknowledgements = commandData.TCUserResponse; + uint16_t acknowledgementsVersion = commandData.TCVersion; + CheckSuccess(enhancedSetupFlowProvider->SetTermsAndConditionsAcceptance(acknowledgements, acknowledgementsVersion), Failure); + CheckSuccess(checkTermsAndConditionsAcknowledgementsState(response.errorCode), Failure); + failSafeContext.SetUpdateTermsAndConditionsHasBeenInvoked(); + commandObj->AddResponse(commandPath, response); + return true; + +#endif +} + namespace { void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) { @@ -335,6 +470,26 @@ void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t { // Spec says to reset Breadcrumb attribute to 0. Breadcrumb::Set(0, 0); + +#if CHIP_CONFIG_TC_REQUIRED + if (event->FailSafeTimerExpired.updateTermsAndConditionsHasBeenInvoked) + { + // Clear terms and conditions acceptance on failsafe timer expiration + Server::GetInstance().GetEnhancedSetupFlowProvider()->RevertTermsAndConditionsAcceptance(); + } +#endif + } + + if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete) + { +#if CHIP_CONFIG_TC_REQUIRED + auto & failSafeContext = Server::GetInstance().GetFailSafeContext(); + if (failSafeContext.UpdateTermsAndConditionsHasBeenInvoked()) + { + // Commit terms and conditions acceptance on commissioning complete + Server::GetInstance().GetEnhancedSetupFlowProvider()->CommitTermsAndConditionsAcceptance(); + } +#endif } } diff --git a/src/app/common_flags.gni b/src/app/common_flags.gni index be1149b2b67eb5..34e8549a35d1cd 100644 --- a/src/app/common_flags.gni +++ b/src/app/common_flags.gni @@ -18,6 +18,10 @@ declare_args() { chip_enable_read_client = true chip_build_controller_dynamic_server = false + chip_config_tc_required = false + chip_config_tc_required_acknowledgements = 0 + chip_config_tc_required_acknowledgements_version = 0 + # Flag that controls whether the time-to-wait from BUSY responses is # communicated to OperationalSessionSetup API consumers. chip_enable_busy_handling_for_operational_session_setup = true diff --git a/src/app/server/BUILD.gn b/src/app/server/BUILD.gn index 401356d7b753a4..3fb9bc5e405762 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. @@ -24,6 +24,28 @@ config("server_config") { } } +source_set("enhanced_setup_flow_interface") { + sources = [ + "DefaultEnhancedSetupFlowProvider.h", + "DefaultTermsAndConditionsProvider.h", + "EnhancedSetupFlowProvider.h", + "TermsAndConditionsProvider.h", + ] + + public_configs = [ "${chip_root}/src/app:enhanced_setup_flow_config" ] + + public_deps = [ "${chip_root}/src/lib/core" ] +} + +source_set("enhanced_setup_flow") { + sources = [ + "DefaultEnhancedSetupFlowProvider.cpp", + "DefaultTermsAndConditionsProvider.cpp", + ] + + deps = [ ":enhanced_setup_flow_interface" ] +} + static_library("server") { output_name = "libCHIPAppServer" @@ -51,6 +73,7 @@ static_library("server") { cflags = [ "-Wconversion" ] public_deps = [ + ":enhanced_setup_flow_interface", "${chip_root}/src/app", "${chip_root}/src/app:test-event-trigger", "${chip_root}/src/app/icd/server:check-in-back-off", @@ -79,4 +102,8 @@ static_library("server") { [ "${chip_root}/src/app/icd/server:default-check-in-back-off" ] } } + + if (chip_config_tc_required) { + public_deps += [ ":enhanced_setup_flow" ] + } } diff --git a/src/app/server/DefaultEnhancedSetupFlowProvider.cpp b/src/app/server/DefaultEnhancedSetupFlowProvider.cpp new file mode 100644 index 00000000000000..1ce01c398f1a04 --- /dev/null +++ b/src/app/server/DefaultEnhancedSetupFlowProvider.cpp @@ -0,0 +1,166 @@ +/* + * + * 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 "DefaultEnhancedSetupFlowProvider.h" + +#include +#include + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::Init(TermsAndConditionsProvider * const inTermsAndConditionsProvider) +{ + VerifyOrReturnError(nullptr != inTermsAndConditionsProvider, CHIP_ERROR_INVALID_ARGUMENT); + + mTermsAndConditionsProvider = inTermsAndConditionsProvider; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +chip::app::DefaultEnhancedSetupFlowProvider::HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(bool & outAccepted) const +{ + uint16_t requiredAcknowledgements; + uint16_t requiredAcknowledgementsVersion; + uint16_t acceptedAcknowledgements; + uint16_t acceptedAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetRequirements(requiredAcknowledgements, requiredAcknowledgementsVersion)); + + if (0 == requiredAcknowledgements) + { + outAccepted = true; + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetAcceptance(acceptedAcknowledgements, acceptedAcknowledgementsVersion)); + + outAccepted = ((requiredAcknowledgements & acceptedAcknowledgements) == requiredAcknowledgements); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted( + bool & outAccepted) const +{ + uint16_t requiredAcknowledgements; + uint16_t requiredAcknowledgementsVersion; + uint16_t acceptedAcknowledgements; + uint16_t acceptedAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetRequirements(requiredAcknowledgements, requiredAcknowledgementsVersion)); + + if (0 == requiredAcknowledgementsVersion) + { + outAccepted = true; + return CHIP_NO_ERROR; + } + + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetAcceptance(acceptedAcknowledgements, acceptedAcknowledgementsVersion)); + + outAccepted = (acceptedAcknowledgementsVersion >= requiredAcknowledgementsVersion); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::IsTermsAndConditionsAcceptanceRequired(bool & outValue) const +{ + // Default implementation requires terms and conditions check only if not previously accepted. Other implementations may skip + // requiring a terms and conditions check on secondary commissioning, in the case that the required terms and conditions may + // have changed. + return HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(outValue); +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::GetTermsAndConditionsRequiredAcknowledgements(uint16_t & outValue) const +{ + uint16_t requiredAcknowledgements; + uint16_t requiredAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetRequirements(requiredAcknowledgements, requiredAcknowledgementsVersion)); + + outValue = requiredAcknowledgements; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +chip::app::DefaultEnhancedSetupFlowProvider::GetTermsAndConditionsRequiredAcknowledgementsVersion(uint16_t & outValue) const +{ + uint16_t requiredAcknowledgements; + uint16_t requiredAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetRequirements(requiredAcknowledgements, requiredAcknowledgementsVersion)); + + outValue = requiredAcknowledgementsVersion; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::GetTermsAndConditionsAcceptedAcknowledgements(uint16_t & outValue) const +{ + uint16_t acceptedAcknowledgements; + uint16_t acceptedAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetAcceptance(acceptedAcknowledgements, acceptedAcknowledgementsVersion)); + + outValue = acceptedAcknowledgements; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +chip::app::DefaultEnhancedSetupFlowProvider::GetTermsAndConditionsAcceptedAcknowledgementsVersion(uint16_t & outValue) const +{ + uint16_t acceptedAcknowledgements; + uint16_t acceptedAcknowledgementsVersion; + + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->GetAcceptance(acceptedAcknowledgements, acceptedAcknowledgementsVersion)); + + outValue = acceptedAcknowledgementsVersion; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::SetTermsAndConditionsAcceptance(uint16_t inTCAcknowledgementsValue, + uint16_t inTCAcknowledgementsVersionValue) +{ + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->SetAcceptance(inTCAcknowledgementsValue, inTCAcknowledgementsVersionValue)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::RevertTermsAndConditionsAcceptance() +{ + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->RevertAcceptance()); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::CommitTermsAndConditionsAcceptance() +{ + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->CommitAcceptance()); + + return CHIP_NO_ERROR; +} diff --git a/src/app/server/DefaultEnhancedSetupFlowProvider.h b/src/app/server/DefaultEnhancedSetupFlowProvider.h new file mode 100644 index 00000000000000..3da5266078f15e --- /dev/null +++ b/src/app/server/DefaultEnhancedSetupFlowProvider.h @@ -0,0 +1,67 @@ +/* + * + * 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 "EnhancedSetupFlowProvider.h" + +#include + +#include +#include + +#include "TermsAndConditionsProvider.h" + +namespace chip { +namespace app { +class DefaultEnhancedSetupFlowProvider : public EnhancedSetupFlowProvider +{ +public: + /** + * @brief Initializes the EnhancedSetupFlowProvider. + * + * @param[in] inTermsAndConditionsProvider The terms and conditions provide dependency. + */ + CHIP_ERROR Init(TermsAndConditionsProvider * const inTermsAndConditionsProvider); + + CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(bool & outAccepted) const override; + + CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(bool & outAccepted) const override; + + CHIP_ERROR IsTermsAndConditionsAcceptanceRequired(bool & outValue) const override; + + CHIP_ERROR GetTermsAndConditionsRequiredAcknowledgements(uint16_t & outValue) const override; + + CHIP_ERROR GetTermsAndConditionsRequiredAcknowledgementsVersion(uint16_t & outValue) const override; + + CHIP_ERROR GetTermsAndConditionsAcceptedAcknowledgements(uint16_t & outValue) const override; + + CHIP_ERROR GetTermsAndConditionsAcceptedAcknowledgementsVersion(uint16_t & outValue) const override; + + CHIP_ERROR SetTermsAndConditionsAcceptance(uint16_t aTCAcknowledgements, uint16_t inTCAcknowledgementsVersionValue) override; + + CHIP_ERROR CommitTermsAndConditionsAcceptance() override; + + CHIP_ERROR RevertTermsAndConditionsAcceptance() override; + +private: + TermsAndConditionsProvider * mTermsAndConditionsProvider; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/DefaultTermsAndConditionsProvider.cpp b/src/app/server/DefaultTermsAndConditionsProvider.cpp new file mode 100644 index 00000000000000..0d49f41c893ff9 --- /dev/null +++ b/src/app/server/DefaultTermsAndConditionsProvider.cpp @@ -0,0 +1,215 @@ +/* + * + * 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 +#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 kSerializationVersion = 1; + +constexpr size_t kEstimatedTlvBufferSize = chip::TLV::EstimateStructOverhead(sizeof(uint8_t), // SerializationVersion + sizeof(uint16_t), // AcceptedAcknowledgements + sizeof(uint16_t) // AcceptedAcknowledgementsVersion + ) * + 2; // Extra space for rollback compatibility +} // namespace + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::Init(chip::PersistentStorageDelegate * const inPersistentStorageDelegate, + uint16_t inRequiredAcknowledgementsValue, + uint16_t inRequiredAcknowledgementsVersionValue) +{ + VerifyOrReturnError(nullptr != inPersistentStorageDelegate, CHIP_ERROR_INVALID_ARGUMENT); + + mPersistentStorageDelegate = inPersistentStorageDelegate; + mRequiredAcknowledgementsValue = inRequiredAcknowledgementsValue; + mRequiredAcknowledgementsVersionValue = inRequiredAcknowledgementsVersionValue; + + uint16_t acknowledgementsAcceptanceValue = 0; + uint16_t acknowledgementsAcceptanceVersionValue = 0; + + if (CHIP_NO_ERROR == LoadAcceptance(acknowledgementsAcceptanceValue, acknowledgementsAcceptanceVersionValue)) + { + mLatchedAcknowledgementsAcceptanceValue.SetValue(acknowledgementsAcceptanceValue); + mLatchedAcknowledgementsAcceptanceVersionValue.SetValue(acknowledgementsAcceptanceVersionValue); + mTemporaryAcknowledgementsAcceptanceValue = mLatchedAcknowledgementsAcceptanceValue; + mTemporaryAcknowledgementsAcceptanceVersionValue = mLatchedAcknowledgementsAcceptanceVersionValue; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::RevertAcceptance() +{ + mTemporaryAcknowledgementsAcceptanceValue = mLatchedAcknowledgementsAcceptanceValue; + mTemporaryAcknowledgementsAcceptanceVersionValue = mLatchedAcknowledgementsAcceptanceVersionValue; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::CommitAcceptance() +{ + VerifyOrReturnError(mLatchedAcknowledgementsAcceptanceValue.HasValue() || mTemporaryAcknowledgementsAcceptanceValue.HasValue(), + CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mLatchedAcknowledgementsAcceptanceVersionValue.HasValue() || + mTemporaryAcknowledgementsAcceptanceVersionValue.HasValue(), + CHIP_ERROR_INCORRECT_STATE); + + uint16_t acknowledgementsAcceptanceValue = + mTemporaryAcknowledgementsAcceptanceValue.ValueOr(mLatchedAcknowledgementsAcceptanceValue.HasValue()); + uint16_t acknowledgementsAcceptanceVersionValue = + mTemporaryAcknowledgementsAcceptanceVersionValue.ValueOr(mLatchedAcknowledgementsAcceptanceVersionValue.HasValue()); + + CHIP_ERROR err = StoreAcceptance(acknowledgementsAcceptanceValue, acknowledgementsAcceptanceVersionValue); + + if (CHIP_NO_ERROR == err) + { + mLatchedAcknowledgementsAcceptanceValue.SetValue(acknowledgementsAcceptanceValue); + mLatchedAcknowledgementsAcceptanceVersionValue.SetValue(acknowledgementsAcceptanceVersionValue); + } + + return err; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetAcceptance(uint16_t & outAcknowledgementsValue, + uint16_t & outAcknowledgementsVersionValue) const +{ + VerifyOrReturnError(mTemporaryAcknowledgementsAcceptanceValue.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mTemporaryAcknowledgementsAcceptanceVersionValue.HasValue(), CHIP_ERROR_INCORRECT_STATE); + + outAcknowledgementsValue = mTemporaryAcknowledgementsAcceptanceValue.Value(); + outAcknowledgementsVersionValue = mTemporaryAcknowledgementsAcceptanceVersionValue.Value(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetRequirements(uint16_t & outAcknowledgementsValue, + uint16_t & outAcknowledgementsVersionValue) const +{ + outAcknowledgementsValue = mRequiredAcknowledgementsValue; + outAcknowledgementsVersionValue = mRequiredAcknowledgementsVersionValue; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::ResetAcceptance() +{ + VerifyOrReturnError(nullptr != mPersistentStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + const chip::StorageKeyName storageKey = DefaultStorageKeyAllocator::TermsAndConditionsAcceptance(); + + ReturnErrorOnFailure(mPersistentStorageDelegate->SyncDeleteKeyValue(storageKey.KeyName())); + mLatchedAcknowledgementsAcceptanceValue.ClearValue(); + mLatchedAcknowledgementsAcceptanceVersionValue.ClearValue(); + ReturnErrorOnFailure(RevertAcceptance()); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::SetAcceptance(uint16_t inAcceptedAcknowledgementsValue, + uint16_t inAcceptedAcknowledgementsVersionValue) +{ + mTemporaryAcknowledgementsAcceptanceValue.SetValue(inAcceptedAcknowledgementsValue); + mTemporaryAcknowledgementsAcceptanceVersionValue.SetValue(inAcceptedAcknowledgementsVersionValue); + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::LoadAcceptance(uint16_t & outAcknowledgementsValue, + uint16_t & outAcknowledgementsVersionValue) +{ + 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); + + VerifyOrReturnError(nullptr != mPersistentStorageDelegate, CHIP_ERROR_UNINITIALIZED); + + const chip::StorageKeyName storageKey = DefaultStorageKeyAllocator::TermsAndConditionsAcceptance(); + CHIP_ERROR err = mPersistentStorageDelegate->SyncGetKeyValue(storageKey.KeyName(), &buffer, bufferSize); + if (CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND == err) + { + outAcknowledgementsValue = acknowledgements; + outAcknowledgementsVersionValue = acknowledgementsVersion; + + return CHIP_NO_ERROR; + } + + VerifyOrReturnError(CHIP_NO_ERROR == err, err); + + 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)); + ReturnErrorOnFailure(tlvReader.Next(kAcceptedAcknowledgementsTag)); + ReturnErrorOnFailure(tlvReader.Get(acknowledgements)); + ReturnErrorOnFailure(tlvReader.Next(kAcceptedAcknowledgementsVersionTag)); + ReturnErrorOnFailure(tlvReader.Get(acknowledgementsVersion)); + ReturnErrorOnFailure(tlvReader.ExitContainer(tlvContainer)); + + if (kSerializationVersion != serializationVersion) + { + return CHIP_ERROR_VERSION_MISMATCH; + } + + outAcknowledgementsValue = acknowledgements; + outAcknowledgementsVersionValue = acknowledgementsVersion; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::StoreAcceptance(uint16_t inAcknowledgementsValue, + uint16_t inAcknowledgementsVersionValue) +{ + uint8_t buffer[kEstimatedTlvBufferSize] = { 0 }; + chip::TLV::TLVWriter tlvWriter; + chip::TLV::TLVType tlvContainer; + + VerifyOrReturnError(nullptr != mPersistentStorageDelegate, CHIP_ERROR_UNINITIALIZED); + VerifyOrReturnError(mTemporaryAcknowledgementsAcceptanceValue.HasValue(), CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mTemporaryAcknowledgementsAcceptanceVersionValue.HasValue(), CHIP_ERROR_INCORRECT_STATE); + + tlvWriter.Init(buffer, sizeof(buffer)); + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, tlvContainer)); + ReturnErrorOnFailure(tlvWriter.Put(kSerializationVersionTag, kSerializationVersion)); + ReturnErrorOnFailure(tlvWriter.Put(kAcceptedAcknowledgementsTag, inAcknowledgementsValue)); + ReturnErrorOnFailure(tlvWriter.Put(kAcceptedAcknowledgementsVersionTag, inAcknowledgementsVersionValue)); + ReturnErrorOnFailure(tlvWriter.EndContainer(tlvContainer)); + ReturnErrorOnFailure(tlvWriter.Finalize()); + uint32_t lengthWritten = tlvWriter.GetLengthWritten(); + VerifyOrReturnError(CanCastTo(lengthWritten), CHIP_ERROR_BUFFER_TOO_SMALL); + + const chip::StorageKeyName storageKey = DefaultStorageKeyAllocator::TermsAndConditionsAcceptance(); + ReturnErrorOnFailure( + mPersistentStorageDelegate->SyncSetKeyValue(storageKey.KeyName(), buffer, static_cast(lengthWritten))); + + return CHIP_NO_ERROR; +} diff --git a/src/app/server/DefaultTermsAndConditionsProvider.h b/src/app/server/DefaultTermsAndConditionsProvider.h new file mode 100644 index 00000000000000..ee941216757976 --- /dev/null +++ b/src/app/server/DefaultTermsAndConditionsProvider.h @@ -0,0 +1,73 @@ +/* + * + * 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 { +class DefaultTermsAndConditionsProvider : public TermsAndConditionsProvider +{ +public: + /** + * @brief Initializes the TermsAndConditionsProvider. + * + * @param[in] inPersistentStorageDelegate Persistent storage delegate dependency. + * @param[in] inRequiredAcknowledgementsValue The bitmask of required acknowledgements. + * @param[in] inRequiredAcknowledgementsVersionValue The version of the required acknowledgements. + */ + CHIP_ERROR Init(PersistentStorageDelegate * const inPersistentStorageDelegate, uint16_t inRequiredAcknowledgementsValue, + uint16_t inRequiredAcknowledgementsVersionValue); + + CHIP_ERROR CommitAcceptance() override; + + CHIP_ERROR GetAcceptance(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const override; + + CHIP_ERROR GetRequirements(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const override; + + CHIP_ERROR ResetAcceptance() override; + + CHIP_ERROR RevertAcceptance() override; + + CHIP_ERROR SetAcceptance(uint16_t inAcknowledgementsValue, uint16_t inAcknowledgementsVersionValue) override; + +private: + CHIP_ERROR LoadAcceptance(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue); + + CHIP_ERROR StoreAcceptance(uint16_t inAcknowledgementsValue, uint16_t inAcknowledgementsVersionValue); + + PersistentStorageDelegate * mPersistentStorageDelegate; + uint16_t mRequiredAcknowledgementsValue; + uint16_t mRequiredAcknowledgementsVersionValue; + + Optional mLatchedAcknowledgementsAcceptanceValue; + Optional mLatchedAcknowledgementsAcceptanceVersionValue; + + Optional mTemporaryAcknowledgementsAcceptanceValue; + Optional mTemporaryAcknowledgementsAcceptanceVersionValue; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/EnhancedSetupFlowProvider.h b/src/app/server/EnhancedSetupFlowProvider.h new file mode 100644 index 00000000000000..af1d054e273b8d --- /dev/null +++ b/src/app/server/EnhancedSetupFlowProvider.h @@ -0,0 +1,89 @@ +/* + * + * 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 + +namespace chip { +namespace app { + +/** + * This class provides access to the state of the Enhanced Setup Flow feature. + */ +class EnhancedSetupFlowProvider +{ +public: + virtual ~EnhancedSetupFlowProvider() = default; + + /** + * @param[out] outAccepted true if the required acknowledgements have been accepted, false otherwise. + */ + virtual CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(bool & outAccepted) const = 0; + + /** + * @param[out] outAccepted true if the required acknowledgements version has been accepted, false otherwise. + */ + virtual CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(bool & outAccepted) const = 0; + + /** + * @param[out] outValue true if terms and conditions acceptance is required before commissioning complete may succeed, false + * otherwise. + */ + virtual CHIP_ERROR IsTermsAndConditionsAcceptanceRequired(bool & outValue) const = 0; + + /** + * @param[out] outValue The version of the required acknowledgements. + */ + virtual CHIP_ERROR GetTermsAndConditionsRequiredAcknowledgements(uint16_t & outValue) const = 0; + + /** + * @param[out] outValue The outValue of the required acknowledgements version. + */ + virtual CHIP_ERROR GetTermsAndConditionsRequiredAcknowledgementsVersion(uint16_t & outValue) const = 0; + + /** + * @param[out] outValue The outValue of the accepted acknowledgements. + */ + virtual CHIP_ERROR GetTermsAndConditionsAcceptedAcknowledgements(uint16_t & outValue) const = 0; + + /** + * @param[out] outValue The outValue of the accepted acknowledgements version. + */ + virtual CHIP_ERROR GetTermsAndConditionsAcceptedAcknowledgementsVersion(uint16_t & outValue) const = 0; + + /** + * @param[in] inTCAcknowledgements The acknowledgements to accept. + * @param[in] inTCAcknowledgementsVersionValue The version of the acknowledgements to accept. + */ + virtual CHIP_ERROR SetTermsAndConditionsAcceptance(uint16_t inTCAcknowledgementsValue, + uint16_t inTCAcknowledgementsVersionValue) = 0; + + /** + * Reset the terms and conditions acceptance. The terms and conditions should be cleared on factory reset or if failure occurs + * during a failsafe context. + */ + virtual CHIP_ERROR RevertTermsAndConditionsAcceptance() = 0; + + virtual CHIP_ERROR CommitTermsAndConditionsAcceptance() = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 22cd274ba87e39..25d03d1645b914 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/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. @@ -185,6 +185,11 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) mReportScheduler = initParams.reportScheduler; +#if CHIP_CONFIG_TC_REQUIRED + mTermsAndConditionsProvider = initParams.termsAndConditionsProvider; + mEnhancedSetupFlowProvider = initParams.enhancedSetupFlowProvider; +#endif + mTestEventTriggerDelegate = initParams.testEventTriggerDelegate; if (mTestEventTriggerDelegate == nullptr) { @@ -586,6 +591,9 @@ void Server::ScheduleFactoryReset() GetInstance().GetFabricTable().DeleteAllFabrics(); PlatformMgr().HandleServerShuttingDown(); ConfigurationMgr().InitiateFactoryReset(); +#if CHIP_CONFIG_TC_REQUIRED + // Clear accepted terms and conditions +#endif }); } diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 2f6126a4ace635..cb8ab12f40f547 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -1,6 +1,6 @@ /* * - * 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. @@ -33,6 +33,12 @@ #include #include #include +#if CHIP_CONFIG_TC_REQUIRED +#include +#include +#include +#include +#endif #include #include #include @@ -179,6 +185,12 @@ struct ServerInitParams // Optional. Support for the ICD Check-In BackOff strategy. Must be initialized before being provided. // If the ICD Check-In protocol use-case is supported and no strategy is provided, server will use the default strategy. app::ICDCheckInBackOffStrategy * icdCheckInBackOffStrategy = nullptr; +#if CHIP_CONFIG_TC_REQUIRED + // Optional. Enhanced setup flow provider to support terms and conditions acceptance check. + app::EnhancedSetupFlowProvider * enhancedSetupFlowProvider = nullptr; + // Optional. Terms and conditions provider to support enhanced setup flow feature. + app::TermsAndConditionsProvider * termsAndConditionsProvider = nullptr; +#endif }; /** @@ -300,6 +312,24 @@ struct CommonCaseDeviceServerInitParams : public ServerInitParams } #endif +#if CHIP_CONFIG_TC_REQUIRED + static app::DefaultEnhancedSetupFlowProvider sDefaultEnhancedSetupFlowProviderInstance; + static app::DefaultTermsAndConditionsProvider sDefaultTermsAndConditionsProviderInstance; + if (this->termsAndConditionsProvider == nullptr) + { + ReturnErrorOnFailure(sDefaultTermsAndConditionsProviderInstance.Init(this->persistentStorageDelegate, + CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS, + CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION)); + this->termsAndConditionsProvider = &sDefaultTermsAndConditionsProviderInstance; + } + + if (this->enhancedSetupFlowProvider == nullptr) + { + ReturnErrorOnFailure(sDefaultEnhancedSetupFlowProviderInstance.Init(this->termsAndConditionsProvider)); + this->enhancedSetupFlowProvider = &sDefaultEnhancedSetupFlowProviderInstance; + } +#endif + return CHIP_NO_ERROR; } @@ -402,6 +432,10 @@ class Server app::reporting::ReportScheduler * GetReportScheduler() { return mReportScheduler; } +#if CHIP_CONFIG_TC_REQUIRED + app::EnhancedSetupFlowProvider * GetEnhancedSetupFlowProvider() { return mEnhancedSetupFlowProvider; } +#endif + #if CHIP_CONFIG_ENABLE_ICD_SERVER app::ICDManager & GetICDManager() { return mICDManager; } @@ -675,6 +709,10 @@ class Server GroupDataProviderListener mListener; ServerFabricDelegate mFabricDelegate; app::reporting::ReportScheduler * mReportScheduler; +#if CHIP_CONFIG_TC_REQUIRED + app::EnhancedSetupFlowProvider * mEnhancedSetupFlowProvider; + app::TermsAndConditionsProvider * mTermsAndConditionsProvider; +#endif Access::AccessControl mAccessControl; app::AclStorage * mAclStorage; diff --git a/src/app/server/TermsAndConditionsProvider.h b/src/app/server/TermsAndConditionsProvider.h new file mode 100644 index 00000000000000..4fe0fe7769b47c --- /dev/null +++ b/src/app/server/TermsAndConditionsProvider.h @@ -0,0 +1,78 @@ +/* + * + * 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 Data access layer for the required terms and conditions and the store for the user acceptance. + */ +class TermsAndConditionsProvider +{ +public: + virtual ~TermsAndConditionsProvider() = default; + + /** + * @brief Clear the temporary acceptance status of the required terms and conditions. + */ + virtual CHIP_ERROR RevertAcceptance() = 0; + + /** + * @brief Commit the persistent acceptance status of the required terms and conditions. + */ + virtual CHIP_ERROR CommitAcceptance() = 0; + + /** + * @brief Retrieves the latest acceptance status of the required terms and conditions. + * + * @param[out] outAcknowledgementsValue The bitmask of acknowledgements accepted. + * @param[out] outAcknowledgementsVersionValue The version of the accepted acknowledgements. + */ + virtual CHIP_ERROR GetAcceptance(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const = 0; + + /** + * @brief Retrieves the requirements of the terms and conditions. + * + * @param[out] outAcknowledgementsValue The bitmask of required acknowledgements. + * @param[out] outAcknowledgementsVersionValue The version of the required acknowledgements. + */ + virtual CHIP_ERROR GetRequirements(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const = 0; + + /** + * @brief Reset the persisted acceptance status of the required terms and conditions. + */ + virtual CHIP_ERROR ResetAcceptance() = 0; + + /** + * @brief Sets the acceptance status of the required terms and conditions. + * + * @param[in] inAcknowledgementsValue The bitmask of acknowledgements that was accepted. + * @param[in] inAcknowledgementsVersionValue The version of the acknowledgements that was accepted. + */ + virtual CHIP_ERROR SetAcceptance(uint16_t inAcknowledgementsValue, uint16_t inAcknowledgementsVersionValue) = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 1bce08d55e0571..9da370d705d940 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. @@ -198,7 +198,9 @@ chip_test_suite("tests") { "TestCommandPathParams.cpp", "TestConcreteAttributePath.cpp", "TestDataModelSerialization.cpp", + "TestDefaultEnhancedSetupFlowProvider.cpp", "TestDefaultOTARequestorStorage.cpp", + "TestDefaultTermsAndConditionsProvider.cpp", "TestDefaultThreadNetworkDirectoryStorage.cpp", "TestEventLoggingNoUTCTime.cpp", "TestEventOverflow.cpp", @@ -236,6 +238,8 @@ chip_test_suite("tests") { "${chip_root}/src/app/codegen-data-model-provider:instance-header", "${chip_root}/src/app/common:cluster-objects", "${chip_root}/src/app/icd/client:manager", + "${chip_root}/src/app/server:enhanced_setup_flow", + "${chip_root}/src/app/server:server", "${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/TestDefaultEnhancedSetupFlowProvider.cpp b/src/app/tests/TestDefaultEnhancedSetupFlowProvider.cpp new file mode 100644 index 00000000000000..8ef94119ef54ee --- /dev/null +++ b/src/app/tests/TestDefaultEnhancedSetupFlowProvider.cpp @@ -0,0 +1,310 @@ +/* + * 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/DefaultEnhancedSetupFlowProvider.h" + +#include +#include +#include + +class FakeTermsAndConditionsProvider : public chip::app::TermsAndConditionsProvider +{ +public: + FakeTermsAndConditionsProvider(uint16_t inAcceptedAcknowledgements, uint16_t inAcceptedAcknowledgementsVersion, + uint16_t inRequiredAcknowledgements, uint16_t inRequiredAcknowledgementsVersion) : + mAcceptedAcknowledgements(inAcceptedAcknowledgements), + mAcceptedAcknowledgementsVersion(inAcceptedAcknowledgementsVersion), mRequiredAcknowledgements(inRequiredAcknowledgements), + mRequiredAcknowledgementsVersion(inRequiredAcknowledgementsVersion) + {} + + CHIP_ERROR RevertAcceptance() override + { + mAcceptedAcknowledgements = 0; + mAcceptedAcknowledgementsVersion = 0; + return CHIP_NO_ERROR; + } + + CHIP_ERROR CommitAcceptance() override { return CHIP_NO_ERROR; } + + CHIP_ERROR GetAcceptance(uint16_t & outAcknowledgements, uint16_t & outAcknowledgementsVersion) const override + { + outAcknowledgements = mAcceptedAcknowledgements; + outAcknowledgementsVersion = mAcceptedAcknowledgementsVersion; + return CHIP_NO_ERROR; + } + + CHIP_ERROR GetRequirements(uint16_t & outAcknowledgements, uint16_t & outAcknowledgementsVersion) const override + { + outAcknowledgements = mRequiredAcknowledgements; + outAcknowledgementsVersion = mRequiredAcknowledgementsVersion; + return CHIP_NO_ERROR; + } + + CHIP_ERROR SetAcceptance(uint16_t inAcknowledgements, uint16_t inAcknowledgementsVersion) override + { + mAcceptedAcknowledgements = inAcknowledgements; + mAcceptedAcknowledgementsVersion = inAcknowledgementsVersion; + return CHIP_NO_ERROR; + } + + CHIP_ERROR ResetAcceptance() override { return CHIP_NO_ERROR; } + +private: + uint16_t mAcceptedAcknowledgements; + uint16_t mAcceptedAcknowledgementsVersion; + uint16_t mRequiredAcknowledgements; + uint16_t mRequiredAcknowledgementsVersion; +}; + +TEST(DefaultEnhancedSetupFlowProvider, TestNoAcceptanceRequiredCheckAcknowledgementsAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 0, 0); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestNoAcceptanceRequiredCheckAcknowledgementsVersionAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsVersionBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 0, 0); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredNoTermsAcceptedCheckAcknowledgementsAcceptedFailure) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 1, 1); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(hasTermsBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, + TestAcceptanceRequiredTermsAcceptedTermsVersionOutdatedCheckAcknowledgementsVersionAcceptedFailure) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 1, 1); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.SetTermsAndConditionsAcceptance(1, 0); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredTermsAcceptedFutureVersionCheckAcknowledgementsAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + uint16_t acceptedTerms = 1; + uint16_t requiredTerms = 1; + uint16_t acceptedTermsVersion = 2; + uint16_t requiredTermsVersion = 1; + + FakeTermsAndConditionsProvider tncProvider(acceptedTerms, acceptedTermsVersion, requiredTerms, requiredTermsVersion); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredTermsAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + FakeTermsAndConditionsProvider tncProvider(0, 0, 1, 1); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.SetTermsAndConditionsAcceptance(1, 1); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredTermsMissingFailure) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + uint16_t acceptedTerms = 0b0111'1111'1111'1111; + uint16_t requiredTerms = 0b1111'1111'1111'1111; + uint16_t acceptedTermsVersion = 1; + uint16_t requiredTermsVersion = 1; + + FakeTermsAndConditionsProvider tncProvider(acceptedTerms, acceptedTermsVersion, requiredTerms, requiredTermsVersion); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestAcceptanceRequiredAllTermsAcceptedCheckAcknowledgementsAcceptedSuccess) +{ + CHIP_ERROR err; + bool hasTermsBeenAccepted; + bool hasTermsVersionBeenAccepted; + + uint16_t acceptedTerms = 0b1111'1111'1111'1111; + uint16_t requiredTerms = 0b1111'1111'1111'1111; + uint16_t acceptedTermsVersion = 1; + uint16_t requiredTermsVersion = 1; + + FakeTermsAndConditionsProvider tncProvider(acceptedTerms, acceptedTermsVersion, requiredTerms, requiredTermsVersion); + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(hasTermsBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsBeenAccepted); + + err = esfProvider.HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(hasTermsVersionBeenAccepted); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_TRUE(hasTermsVersionBeenAccepted); +} + +TEST(DefaultEnhancedSetupFlowProvider, TestRevertAcceptanceRetainsRequirements) +{ + CHIP_ERROR err; + + uint16_t initialAcceptedTermsAndConditions = 0; + uint16_t initialRequiredTermsAndConditions = 0b1111'1111'1111'1111; + uint16_t initialAcceptedTermsAndConditionsVersion = 0; + uint16_t initialRequiredTermsAndConditionsVersion = 1; + + uint16_t outAcceptedTermsAndConditions; + uint16_t outRequiredTermsAndConditions; + uint16_t outAcceptedTermsAndConditionsVersion; + uint16_t outRequiredTermsAndConditionsVersion; + + uint16_t updatedAcceptedTermsAndConditions = 0b1111'1111'1111'1111; + uint16_t updatedAcceptedTermsAndConditionsVersion = 1; + + FakeTermsAndConditionsProvider tncProvider(initialAcceptedTermsAndConditions, initialAcceptedTermsAndConditionsVersion, + initialRequiredTermsAndConditions, initialRequiredTermsAndConditionsVersion); + + chip::app::DefaultEnhancedSetupFlowProvider esfProvider; + + err = esfProvider.Init(&tncProvider); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.SetTermsAndConditionsAcceptance(updatedAcceptedTermsAndConditions, updatedAcceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.GetTermsAndConditionsRequiredAcknowledgements(outRequiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outRequiredTermsAndConditions, initialRequiredTermsAndConditions); + + err = esfProvider.GetTermsAndConditionsRequiredAcknowledgementsVersion(outRequiredTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outRequiredTermsAndConditionsVersion, initialRequiredTermsAndConditionsVersion); + + err = esfProvider.GetTermsAndConditionsAcceptedAcknowledgements(outAcceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outAcceptedTermsAndConditions, updatedAcceptedTermsAndConditions); + + err = esfProvider.GetTermsAndConditionsAcceptedAcknowledgementsVersion(outAcceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outAcceptedTermsAndConditionsVersion, updatedAcceptedTermsAndConditionsVersion); + + err = esfProvider.RevertTermsAndConditionsAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = esfProvider.GetTermsAndConditionsRequiredAcknowledgements(outRequiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outRequiredTermsAndConditions, initialRequiredTermsAndConditions); + + err = esfProvider.GetTermsAndConditionsRequiredAcknowledgementsVersion(outRequiredTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outRequiredTermsAndConditionsVersion, initialRequiredTermsAndConditionsVersion); + + err = esfProvider.GetTermsAndConditionsAcceptedAcknowledgements(outAcceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outAcceptedTermsAndConditions, 0); + + err = esfProvider.GetTermsAndConditionsAcceptedAcknowledgementsVersion(outAcceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(outAcceptedTermsAndConditionsVersion, 0); +} diff --git a/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp b/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp new file mode 100644 index 00000000000000..474dda7a6b2785 --- /dev/null +++ b/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp @@ -0,0 +1,231 @@ +/* + * + * 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 +#include +#include +#include + +TEST(DefaultTermsAndConditionsProvider, TestInitSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); +} + +TEST(DefaultTermsAndConditionsProvider, TestNoRequirementsGetRequirementsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 0; + uint16_t requiredAcknowledgementsVersion = 0; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(0, outAcceptance); + EXPECT_EQ(0, outAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestNeverAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 0b1111'1111'1111'1111; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(0, outAcceptance); + EXPECT_EQ(0, outAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestTermsAcceptedPersistsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + chip::app::DefaultTermsAndConditionsProvider anotherTncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t acceptedTermsAndConditions = 1; + uint16_t acceptedTermsAndConditionsVersion = 1; + err = tncProvider.SetAcceptance(acceptedTermsAndConditions, acceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); + + err = tncProvider.CommitAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); + + err = anotherTncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = anotherTncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestTermsRequiredGetRequirementsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t initialRequiredAcknowledgements = 1; + uint16_t initialRequiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, initialRequiredAcknowledgements, initialRequiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outRequiredAcknowledgements; + uint16_t outRequiredAcknowledgementsVersion; + err = tncProvider.GetRequirements(outRequiredAcknowledgements, outRequiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outRequiredAcknowledgements); + EXPECT_EQ(1, outRequiredAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestSetAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t acceptedTermsAndConditions = 1; + uint16_t acceptedTermsAndConditionsVersion = 1; + err = tncProvider.SetAcceptance(acceptedTermsAndConditions, acceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); +} + +TEST(DefaultTermsAndConditionsProvider, TestRevertAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t acceptedTermsAndConditions = 1; + uint16_t acceptedTermsAndConditionsVersion = 1; + err = tncProvider.SetAcceptance(acceptedTermsAndConditions, acceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); + + err = tncProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance2; + uint16_t outAcknowledgementsVersion2; + err = tncProvider.GetAcceptance(outAcceptance2, outAcknowledgementsVersion2); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(0, outAcceptance2); + EXPECT_EQ(0, outAcknowledgementsVersion2); +} + +TEST(DefaultTermsAndConditionsProvider, TestAcceptanceRequiredTermsMissingFailure) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + uint16_t requiredAcknowledgements = 1; + uint16_t requiredAcknowledgementsVersion = 1; + err = tncProvider.Init(&storageDelegate, requiredAcknowledgements, requiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t acceptedTermsAndConditions = 1; + uint16_t acceptedTermsAndConditionsVersion = 1; + err = tncProvider.SetAcceptance(acceptedTermsAndConditions, acceptedTermsAndConditionsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outAcceptance; + uint16_t outAcknowledgementsVersion; + err = tncProvider.GetAcceptance(outAcceptance, outAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcceptance); + EXPECT_EQ(1, outAcknowledgementsVersion); + + err = tncProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + uint16_t outRequiredAcknowledgements; + uint16_t outRequiredAcknowledgementsVersion; + err = tncProvider.GetRequirements(outRequiredAcknowledgements, outRequiredAcknowledgementsVersion); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outRequiredAcknowledgements); + EXPECT_EQ(1, outRequiredAcknowledgementsVersion); +} diff --git a/src/controller/CommissioningDelegate.h b/src/controller/CommissioningDelegate.h index 7a96939cf49434..749411ad0d33d1 100644 --- a/src/controller/CommissioningDelegate.h +++ b/src/controller/CommissioningDelegate.h @@ -82,6 +82,8 @@ enum CommissioningStage : uint8_t kPrimaryOperationalNetworkFailed, ///< Indicate that the primary operational network (on root endpoint) failed, should disable ///< the primary network interface later. kDisablePrimaryNetworkInterface, ///< Send InterfaceEnabled write request to the device to disable network interface. + kGetTCAcknowledgments, ///< Waiting for the higher layer to provide terms and conditions acknowledgements. + kConfigureTCAcknowledgments, ///< Send SetTCAcknowledgements (0x30:6) command to the device }; enum class ICDRegistrationStrategy : uint8_t diff --git a/src/include/platform/CHIPDeviceEvent.h b/src/include/platform/CHIPDeviceEvent.h index 09f4c46b652920..5ed80079d4e284 100644 --- a/src/include/platform/CHIPDeviceEvent.h +++ b/src/include/platform/CHIPDeviceEvent.h @@ -534,6 +534,9 @@ struct ChipDeviceEvent final FabricIndex fabricIndex; bool addNocCommandHasBeenInvoked; bool updateNocCommandHasBeenInvoked; +#if CHIP_CONFIG_TC_REQUIRED + bool updateTermsAndConditionsHasBeenInvoked; +#endif } FailSafeTimerExpired; struct diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index e9c317dfc79d5c..d739017e401179 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1785,6 +1785,60 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_MAX_BDX_LOG_TRANSFERS 5 #endif // CHIP_CONFIG_MAX_BDX_LOG_TRANSFERS +/** + * @file + * Configuration settings for Terms and Conditions (TC) acknowledgements during device commissioning. + */ + +/** + * @def CHIP_CONFIG_TC_REQUIRED + * + * @brief Indicates whether terms and conditions are required during commissioning. + * + * This macro defines whether the device commissioning process requires the user to acknowledge terms and conditions. + * - 1: Terms and conditions are required. + * - 0: Terms and conditions are not required. + * + * If this is set to 1, both CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS and + * CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION must be defined. + */ +#ifndef CHIP_CONFIG_TC_REQUIRED +#define CHIP_CONFIG_TC_REQUIRED (0) +#endif + +#if CHIP_CONFIG_TC_REQUIRED + +/** + * @def CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS + * + * @brief Configures the required terms and conditions acknowledgements bitmask. + * + * This macro defines the required terms and conditions acknowledgements bitmask. The bit-field is 16 bits long, so the possible + * value range is [0, 65535). This setting can be used to require that terms and conditions are presented to the user during + * commissioning. + */ +#ifndef CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS +#error "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS must be defined when CHIP_CONFIG_TC_REQUIRED is enabled." +#endif + +/** + * @def CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION + * + * @brief Configures the latest known version of the terms and conditions. + * + * This macro defines the version number of the latest terms and conditions. It allows the application to iterate on revisions of + * the terms and conditions. A value of 0 indicates that no specific version is required. This setting can be used to enforce + * version-specific terms and conditions acknowledgements in the application. When the set of terms and conditions needs to be + * changed, the version number should be monotonically increased. If the latest terms and conditions version is updated (most + * likely during an OTA), then this may signal to the Administrator that updated terms and conditions should be presented to the + * user. + */ +#ifndef CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION +#error "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION must be defined when CHIP_CONFIG_TC_REQUIRED is enabled." +#endif + +#endif // CHIP_CONFIG_TC_REQUIRED + /** * @} */ 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 From 5051120a71192b10c0363fc4e403bafca70a0a6a Mon Sep 17 00:00:00 2001 From: James Swan <122404367+swan-amazon@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:30:44 +0000 Subject: [PATCH 06/10] Enable Terms and Conditions (TC) feature for AutoCommissioner This commit enables the Terms and Conditions (TC) feature in the all-clusters-app example application. The changes ensure that the app can handle TC acknowledgements and enforce required terms and conditions during the commissioning process. Changes include: - Added TC attributes and commands in the General Commissioning cluster. - Enabled the TC feature in the application configuration. This enables the TC feature logic in the example app, building on the initial implementation. --- .../commands/pairing/PairingCommand.cpp | 16 ++++- .../commands/pairing/PairingCommand.h | 17 ++++- src/controller/AutoCommissioner.cpp | 6 +- src/controller/CHIPDeviceController.cpp | 71 ++++++++++++++++++- src/controller/CHIPDeviceController.h | 24 ++++++- src/controller/CommissioningDelegate.cpp | 6 ++ src/controller/CommissioningDelegate.h | 29 ++++++++ 7 files changed, 164 insertions(+), 5 deletions(-) diff --git a/examples/chip-tool/commands/pairing/PairingCommand.cpp b/examples/chip-tool/commands/pairing/PairingCommand.cpp index 245c9ed57ff82c..ab76c985339662 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"); @@ -129,6 +129,20 @@ CommissioningParameters PairingCommand::GetCommissioningParameters() params.SetCountryCode(CharSpan::fromCharString(mCountryCode.Value())); } + // Default requiring TCs to false, to preserve release 1.3 chip-tool behavior + params.SetRequireTermsAndConditionsAcknowledgement(mRequireTCAcknowledgements.ValueOr(false)); + + // 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. diff --git a/examples/chip-tool/commands/pairing/PairingCommand.h b/examples/chip-tool/commands/pairing/PairingCommand.h index 9965b663ec111c..b21f13067da4ed 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"); @@ -202,6 +202,18 @@ class PairingCommand : public CHIPCommand, AddArgument("dst-offset", &mComplex_DSTOffsets, "DSTOffset list to use when setting Time Synchronization cluster's DSTOffset attribute", Argument::kOptional); + + AddArgument("require-tc-acknowledgements", 0, 1, &mRequireTCAcknowledgements, + "Terms and Conditions acknowledgements is known to be required or not, (when required, the commissioner " + "will wait until they are provided)"); + + AddArgument("tc-acknowledgements", 0, UINT16_MAX, &mTCAcknowledgements, + "Terms and Conditions acknowledgements to use to set the General Commissioning cluster's TC " + "Acknowledgements bit-field"); + + AddArgument("tc-acknowledgements-version", 0, UINT16_MAX, &mTCAcknowledgementVersion, + "Terms and Conditions acknowledgement version to use to set the General Commissioning cluster's TC " + "Acknowledgement version"); } AddArgument("timeout", 0, UINT16_MAX, &mTimeout); @@ -259,6 +271,9 @@ class PairingCommand : public CHIPCommand, chip::Optional mICDMonitoredSubject; chip::Optional mICDClientType; chip::Optional mICDStayActiveDurationMsec; + chip::Optional mRequireTCAcknowledgements; + chip::Optional mTCAcknowledgements; + chip::Optional mTCAcknowledgementVersion; chip::app::DataModel::List mTimeZoneList; TypedComplexArgument> mComplex_TimeZones; diff --git a/src/controller/AutoCommissioner.cpp b/src/controller/AutoCommissioner.cpp index 0e83d7125cf175..cfdd5826919a8b 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,10 @@ CommissioningStage AutoCommissioner::GetNextCommissioningStageInternal(Commissio case CommissioningStage::kArmFailsafe: return CommissioningStage::kConfigRegulatory; case CommissioningStage::kConfigRegulatory: + return CommissioningStage::kGetTCAcknowledgments; + case CommissioningStage::kGetTCAcknowledgments: + 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 955e36bfb0d8a2..c1b788aca643bd 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. * @@ -2691,6 +2691,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) { @@ -2766,6 +2782,16 @@ CHIP_ERROR DeviceCommissioner::ICDRegistrationInfoReady() return CHIP_NO_ERROR; } +CHIP_ERROR DeviceCommissioner::TermsAndConditionsAcknowledgementsReady() +{ + ReturnErrorCodeIf(mCommissioningStage != CommissioningStage::kGetTCAcknowledgments, CHIP_ERROR_INCORRECT_STATE); + + // need to advance to next step + CommissioningStageComplete(CHIP_NO_ERROR); + + return CHIP_NO_ERROR; +} + void DeviceCommissioner::OnNetworkConfigResponse(void * context, const NetworkCommissioning::Commands::NetworkConfigResponse::DecodableType & data) { @@ -3163,6 +3189,49 @@ void DeviceCommissioner::PerformCommissioningStep(DeviceProxy * proxy, Commissio } } break; + case CommissioningStage::kGetTCAcknowledgments: { + ChipLogProgress(Controller, "Get Terms and Conditions Acknowledgments"); + + // If terms and conditions acknowledgements are not required, or have already been provided, then proceed + if (!params.GetRequireTermsAndConditionsAcknowledgement() || params.GetTermsAndConditionsAcknowledgement().HasValue()) + { + TermsAndConditionsAcknowledgementsReady(); + return; + } + + ChipLogProgress(Controller, "Waiting for Terms and Conditions"); + break; + } + case CommissioningStage::kConfigureTCAcknowledgments: { + ChipLogProgress(Controller, "Setting Terms and Conditions"); + + if (!params.GetRequireTermsAndConditionsAcknowledgement()) + { + CommissioningStageComplete(CHIP_NO_ERROR); + return; + } + + if (!params.GetTermsAndConditionsAcknowledgement().HasValue()) + { + ChipLogError(Controller, "No acknowledgements provided"); + CommissioningStageComplete(CHIP_ERROR_INCORRECT_STATE); + return; + } + + GeneralCommissioning::Commands::SetTCAcknowledgements::Type request; + TermsAndConditionsAcknowledgement termsAndConditionsAcknowledgement = params.GetTermsAndConditionsAcknowledgement().Value(); + request.TCUserResponse = termsAndConditionsAcknowledgement.acceptedTermsAndConditions; + request.TCVersion = termsAndConditionsAcknowledgement.acceptedTermsAndConditionsVersion; + 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 4b876156199735..1f1b381da96cbd 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. * @@ -696,6 +696,25 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, */ CHIP_ERROR ICDRegistrationInfoReady(); + /** + * @brief + * This function is called by the upper layer application to indicate that the required terms and conditions + * acknowledgements have been set. This function should be called after the terms and conditions bitmask and version + * have been defined using the appropriate configuration macros and the application has gathered the necessary + * acknowledgements from the user. + * + * The upper layer application should call this method once it has successfully presented and obtained acknowledgements + * for the required terms and conditions from the user. This indicates that the commissioning process can advance to the + * next stage. + * + * When the terms and conditions acknowledgements process is completed, this function will signal the readiness to proceed + * to the next step in the commissioning process. + * + * @return CHIP_ERROR The return status. Returns CHIP_ERROR_INCORRECT_STATE if the function is called when the device + * is not in the correct state to accept terms and conditions acknowledgements. + */ + CHIP_ERROR TermsAndConditionsAcknowledgementsReady(); + /** * @brief * This function returns the current CommissioningStage for this commissioner. @@ -947,6 +966,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 85ea5e86c5e3a6..ed7e558828db1b 100644 --- a/src/controller/CommissioningDelegate.cpp +++ b/src/controller/CommissioningDelegate.cpp @@ -46,6 +46,12 @@ const char * StageToString(CommissioningStage stage) case kConfigRegulatory: return "ConfigRegulatory"; + case kGetTCAcknowledgments: + return "GetTCAcknowledgments"; + + case kConfigureTCAcknowledgments: + return "ConfigureTCAcknowledgments"; + case kConfigureUTCTime: return "ConfigureUTCTime"; diff --git a/src/controller/CommissioningDelegate.h b/src/controller/CommissioningDelegate.h index 749411ad0d33d1..eac351aebe9e22 100644 --- a/src/controller/CommissioningDelegate.h +++ b/src/controller/CommissioningDelegate.h @@ -106,6 +106,12 @@ struct WiFiCredentials WiFiCredentials(ByteSpan newSsid, ByteSpan newCreds) : ssid(newSsid), credentials(newCreds) {} }; +struct TermsAndConditionsAcknowledgement +{ + uint16_t acceptedTermsAndConditions; + uint16_t acceptedTermsAndConditionsVersion; +}; + struct NOCChainGenerationParameters { ByteSpan nocsrElements; @@ -170,6 +176,13 @@ class CommissioningParameters // The country code to be used for the node, if set. Optional GetCountryCode() const { return mCountryCode; } + bool GetRequireTermsAndConditionsAcknowledgement() const { return mRequireTermsAndConditionsAcknowledgement; } + + 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 @@ -342,6 +355,19 @@ class CommissioningParameters return *this; } + CommissioningParameters & SetRequireTermsAndConditionsAcknowledgement(bool requireTermsAndConditionsAcknowledgement) + { + mRequireTermsAndConditionsAcknowledgement = requireTermsAndConditionsAcknowledgement; + 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) @@ -583,6 +609,7 @@ class CommissioningParameters mAttestationNonce.ClearValue(); mWiFiCreds.ClearValue(); mCountryCode.ClearValue(); + mTermsAndConditionsAcknowledgement.ClearValue(); mThreadOperationalDataset.ClearValue(); mNOCChainGenerationParameters.ClearValue(); mRootCert.ClearValue(); @@ -613,6 +640,7 @@ class CommissioningParameters Optional mAttestationNonce; Optional mWiFiCreds; Optional mCountryCode; + Optional mTermsAndConditionsAcknowledgement; Optional mThreadOperationalDataset; Optional mNOCChainGenerationParameters; Optional mRootCert; @@ -645,6 +673,7 @@ class CommissioningParameters Optional mICDStayActiveDurationMsec; ICDRegistrationStrategy mICDRegistrationStrategy = ICDRegistrationStrategy::kIgnore; bool mCheckForMatchingFabric = false; + bool mRequireTermsAndConditionsAcknowledgement = false; }; struct RequestedCertificate From d989abae03c4898550ecbace7b221a67d819b08d Mon Sep 17 00:00:00 2001 From: James Swan Date: Wed, 14 Aug 2024 21:06:27 +0000 Subject: [PATCH 07/10] Enable Terms and Conditions (TC) feature in all-clusters-app example This commit enables the Terms and Conditions (TC) feature in the all-clusters-app example application. The changes ensure that the app can handle TC acknowledgements and enforce required terms and conditions during the commissioning process. Changes include: - Added TC attributes and commands in the General Commissioning cluster. - Enabled the TC feature in the application configuration. This enables the TC feature logic in the example app, building on the initial implementation. --- .../all-clusters-app.matter | 8 +- .../all-clusters-common/all-clusters-app.zap | 83 ++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index 46debb8a6cd363..02bd16f1424406 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -7852,11 +7852,15 @@ endpoint 0 { callback attribute regulatoryConfig; callback attribute locationCapability; callback attribute supportsConcurrentConnection; + ram attribute TCAcceptedVersion; + ram attribute TCMinRequiredVersion default = 1; + ram attribute TCAcknowledgements default = 0x0000; + ram attribute TCAcknowledgementsRequired default = true; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; callback attribute attributeList; - ram attribute featureMap default = 0; + ram attribute featureMap default = 1; ram attribute clusterRevision default = 1; handle command ArmFailSafe; @@ -7865,6 +7869,8 @@ endpoint 0 { handle command SetRegulatoryConfigResponse; handle command CommissioningComplete; handle command CommissioningCompleteResponse; + handle command SetTCAcknowledgements; + handle command SetTCAcknowledgementsResponse; } server cluster NetworkCommissioning { diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 9a736eea56fa30..a845343f9b85d8 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -2124,6 +2124,7 @@ "define": "GENERAL_COMMISSIONING_CLUSTER", "side": "server", "enabled": 1, + "apiMaturity": "provisional", "commands": [ { "name": "ArmFailSafe", @@ -2172,6 +2173,22 @@ "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": [ @@ -2255,6 +2272,70 @@ "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": "1", + "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": "true", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "GeneratedCommandList", "code": 65528, @@ -2329,7 +2410,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, From 5a9e75de5e8628cff08e5a718272b4a9717a4e44 Mon Sep 17 00:00:00 2001 From: James Swan <122404367+swan-amazon@users.noreply.github.com> Date: Sat, 17 Aug 2024 00:10:35 +0000 Subject: [PATCH 08/10] Terms and Conditions kTCAcknowledgementsNotReceived fix --- .../general-commissioning-server.cpp | 9 +++++++++ src/app/server/DefaultEnhancedSetupFlowProvider.cpp | 7 +++++++ src/app/server/DefaultEnhancedSetupFlowProvider.h | 2 ++ src/app/server/DefaultTermsAndConditionsProvider.cpp | 8 ++++++++ src/app/server/DefaultTermsAndConditionsProvider.h | 2 ++ src/app/server/EnhancedSetupFlowProvider.h | 2 ++ src/app/server/TermsAndConditionsProvider.h | 7 +++++++ src/controller/CommissioningDelegate.h | 4 ++-- 8 files changed, 39 insertions(+), 2 deletions(-) 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 16b62f1fab9207..6c1465873de433 100644 --- a/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp +++ b/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp @@ -200,9 +200,18 @@ CHIP_ERROR checkTermsAndConditionsAcknowledgementsState(CommissioningErrorEnum & CHIP_ERROR err; uint16_t termsAndConditionsAcceptedAcknowledgements; + bool hasAnyAcknowledgements; bool hasRequiredTermAccepted; bool hasRequiredTermVersionAccepted; + err = enhancedSetupFlowProvider->HasReceivedTermsAndConditionscknowledgements(hasAnyAcknowledgements); + if (!::chip::ChipError::IsSuccess(err)) + { + ChipLogError(AppServer, "Failed to HasReceivedTermsAndConditionscknowledgements"); + errorCode = CommissioningErrorEnum::kTCAcknowledgementsNotReceived; + return err; + } + err = enhancedSetupFlowProvider->GetTermsAndConditionsAcceptedAcknowledgements(termsAndConditionsAcceptedAcknowledgements); if (!::chip::ChipError::IsSuccess(err)) { diff --git a/src/app/server/DefaultEnhancedSetupFlowProvider.cpp b/src/app/server/DefaultEnhancedSetupFlowProvider.cpp index 1ce01c398f1a04..9bcd0e4dc1acbc 100644 --- a/src/app/server/DefaultEnhancedSetupFlowProvider.cpp +++ b/src/app/server/DefaultEnhancedSetupFlowProvider.cpp @@ -30,6 +30,13 @@ CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::Init(TermsAndConditionsP return CHIP_NO_ERROR; } +CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::HasReceivedTermsAndConditionscknowledgements(bool & outReceived) const +{ + VerifyOrReturnError(nullptr != mTermsAndConditionsProvider, CHIP_ERROR_UNINITIALIZED); + ReturnErrorOnFailure(mTermsAndConditionsProvider->HasAcceptance(outReceived)); + return CHIP_NO_ERROR; +} + CHIP_ERROR chip::app::DefaultEnhancedSetupFlowProvider::HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(bool & outAccepted) const { diff --git a/src/app/server/DefaultEnhancedSetupFlowProvider.h b/src/app/server/DefaultEnhancedSetupFlowProvider.h index 3da5266078f15e..01a04269723b79 100644 --- a/src/app/server/DefaultEnhancedSetupFlowProvider.h +++ b/src/app/server/DefaultEnhancedSetupFlowProvider.h @@ -39,6 +39,8 @@ class DefaultEnhancedSetupFlowProvider : public EnhancedSetupFlowProvider */ CHIP_ERROR Init(TermsAndConditionsProvider * const inTermsAndConditionsProvider); + CHIP_ERROR HasReceivedTermsAndConditionscknowledgements(bool & outReceived) const override; + CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsBeenAccepted(bool & outAccepted) const override; CHIP_ERROR HasTermsAndConditionsRequiredAcknowledgementsVersionBeenAccepted(bool & outAccepted) const override; diff --git a/src/app/server/DefaultTermsAndConditionsProvider.cpp b/src/app/server/DefaultTermsAndConditionsProvider.cpp index 0d49f41c893ff9..b32a507b3c52fa 100644 --- a/src/app/server/DefaultTermsAndConditionsProvider.cpp +++ b/src/app/server/DefaultTermsAndConditionsProvider.cpp @@ -115,6 +115,14 @@ CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetRequirements(uint16_ return CHIP_NO_ERROR; } +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::HasAcceptance(bool & outHasAcceptance) const +{ + outHasAcceptance = + mTemporaryAcknowledgementsAcceptanceValue.HasValue() && mTemporaryAcknowledgementsAcceptanceVersionValue.HasValue(); + + return CHIP_NO_ERROR; +} + CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::ResetAcceptance() { VerifyOrReturnError(nullptr != mPersistentStorageDelegate, CHIP_ERROR_UNINITIALIZED); diff --git a/src/app/server/DefaultTermsAndConditionsProvider.h b/src/app/server/DefaultTermsAndConditionsProvider.h index ee941216757976..c68293d5143642 100644 --- a/src/app/server/DefaultTermsAndConditionsProvider.h +++ b/src/app/server/DefaultTermsAndConditionsProvider.h @@ -47,6 +47,8 @@ class DefaultTermsAndConditionsProvider : public TermsAndConditionsProvider CHIP_ERROR GetRequirements(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const override; + CHIP_ERROR HasAcceptance(bool & outHasAcceptance) const override; + CHIP_ERROR ResetAcceptance() override; CHIP_ERROR RevertAcceptance() override; diff --git a/src/app/server/EnhancedSetupFlowProvider.h b/src/app/server/EnhancedSetupFlowProvider.h index af1d054e273b8d..fd9862fd69ef2c 100644 --- a/src/app/server/EnhancedSetupFlowProvider.h +++ b/src/app/server/EnhancedSetupFlowProvider.h @@ -33,6 +33,8 @@ class EnhancedSetupFlowProvider public: virtual ~EnhancedSetupFlowProvider() = default; + virtual CHIP_ERROR HasReceivedTermsAndConditionscknowledgements(bool & outReceived) const = 0; + /** * @param[out] outAccepted true if the required acknowledgements have been accepted, false otherwise. */ diff --git a/src/app/server/TermsAndConditionsProvider.h b/src/app/server/TermsAndConditionsProvider.h index 4fe0fe7769b47c..45268d78baf23f 100644 --- a/src/app/server/TermsAndConditionsProvider.h +++ b/src/app/server/TermsAndConditionsProvider.h @@ -60,6 +60,13 @@ class TermsAndConditionsProvider */ virtual CHIP_ERROR GetRequirements(uint16_t & outAcknowledgementsValue, uint16_t & outAcknowledgementsVersionValue) const = 0; + /** + * @brief Retrieve if any terms and conditions has been set. + * + * @param[out] outHasAcceptance True if any acceptance has been set. + */ + virtual CHIP_ERROR HasAcceptance(bool & outHasAcceptance) const = 0; + /** * @brief Reset the persisted acceptance status of the required terms and conditions. */ diff --git a/src/controller/CommissioningDelegate.h b/src/controller/CommissioningDelegate.h index eac351aebe9e22..a707b05f873385 100644 --- a/src/controller/CommissioningDelegate.h +++ b/src/controller/CommissioningDelegate.h @@ -82,8 +82,8 @@ enum CommissioningStage : uint8_t kPrimaryOperationalNetworkFailed, ///< Indicate that the primary operational network (on root endpoint) failed, should disable ///< the primary network interface later. kDisablePrimaryNetworkInterface, ///< Send InterfaceEnabled write request to the device to disable network interface. - kGetTCAcknowledgments, ///< Waiting for the higher layer to provide terms and conditions acknowledgements. - kConfigureTCAcknowledgments, ///< Send SetTCAcknowledgements (0x30:6) command to the device + kGetTCAcknowledgments, ///< Waiting for the higher layer to provide terms and conditions acknowledgements. + kConfigureTCAcknowledgments, ///< Send SetTCAcknowledgements (0x30:6) command to the device }; enum class ICDRegistrationStrategy : uint8_t From 0f0dc6734139dae701c817ab22b9e474302e29e5 Mon Sep 17 00:00:00 2001 From: koosc Date: Sun, 18 Aug 2024 10:38:19 -0700 Subject: [PATCH 09/10] Update TC_CGEN_2_5.py --- src/python_testing/TC_CGEN_2_5.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_5.py b/src/python_testing/TC_CGEN_2_5.py index f35da7dfc5b2ce..c40f9fa10d28a5 100644 --- a/src/python_testing/TC_CGEN_2_5.py +++ b/src/python_testing/TC_CGEN_2_5.py @@ -143,20 +143,20 @@ async def test_TC_CGEN_2_5(self): logging.info('Step 8 - TH sends ArmFailSafe with ExpiryLengthSeconds set to 60') cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(60) resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) - asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kNoError, + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, 'Incorrect error code') logging.info('Step 9 - TH sends SetTCAcknowledgements with incremented TCVersion') new_accepted_version += 1 cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(new_accepted_version, new_acknowledgements) resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) - asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kNoError, + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, 'Incorrect error code') logging.info('Step 10 - TH sends ArmFailSafe with ExpiryLengthSeconds set to 0') cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(0) resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) - asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kNoError, + asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, 'Incorrect error code') self.verify_tc_attributes(new_accepted_version, new_acknowledgements) @@ -183,7 +183,7 @@ async def test_TC_CGEN_2_5(self): self.th1.ResetTestCommissioner() self.th1.ExpireSessions(self.dut_node_id) - # Don't set TCs for the next commissioning and skip CommissioningComplete so we can check the error code + # Don't set TCs for the next commissioning and skip CommissioningComplete so we can manually call CommissioningComplete in order to check the response error code self.th1.SetTCRequired(False) self.th1.SetTCAcknowledgements(0, 0) self.th1.SetSkipCommissioningComplete(True) From 128d151812697818e43d53e95d54d06db19e7ece Mon Sep 17 00:00:00 2001 From: Chris Koos Date: Mon, 19 Aug 2024 20:21:47 +0000 Subject: [PATCH 10/10] Await on attribute verification --- src/python_testing/TC_CGEN_2_5.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/python_testing/TC_CGEN_2_5.py b/src/python_testing/TC_CGEN_2_5.py index c40f9fa10d28a5..08a1d8a5870c1d 100644 --- a/src/python_testing/TC_CGEN_2_5.py +++ b/src/python_testing/TC_CGEN_2_5.py @@ -122,7 +122,7 @@ async def test_TC_CGEN_2_5(self): cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(new_accepted_version, new_acknowledgements) resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, 'Incorrect error code') - self.verify_tc_attributes(new_accepted_version, new_acknowledgements) + await self.verify_tc_attributes(new_accepted_version, new_acknowledgements) logging.info('Step 6 - TH sends SetTCAcknowledgements with no accepted terms at version 0') cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(0, 0) @@ -130,7 +130,7 @@ async def test_TC_CGEN_2_5(self): asserts.assert_equal( resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kTCMinVersionNotMet, 'Incorrect error code' ) - self.verify_tc_attributes(new_accepted_version, new_acknowledgements) + await self.verify_tc_attributes(new_accepted_version, new_acknowledgements) logging.info('Step 7 - TH sends SetTCAcknowledgements with no accepted terms at version 1') cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(1, 0) @@ -138,7 +138,7 @@ async def test_TC_CGEN_2_5(self): asserts.assert_equal( resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kRequiredTCNotAccepted, 'Incorrect error code' ) - self.verify_tc_attributes(new_accepted_version, new_acknowledgements) + await self.verify_tc_attributes(new_accepted_version, new_acknowledgements) logging.info('Step 8 - TH sends ArmFailSafe with ExpiryLengthSeconds set to 60') cmd = Clusters.GeneralCommissioning.Commands.ArmFailSafe(60) @@ -147,8 +147,7 @@ async def test_TC_CGEN_2_5(self): 'Incorrect error code') logging.info('Step 9 - TH sends SetTCAcknowledgements with incremented TCVersion') - new_accepted_version += 1 - cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(new_accepted_version, new_acknowledgements) + cmd = Clusters.GeneralCommissioning.Commands.SetTCAcknowledgements(new_accepted_version + 1, new_acknowledgements) resp = await self.th1.SendCommand(nodeid=self.dut_node_id, endpoint=0, payload=cmd, timedRequestTimeoutMs=6000) asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, 'Incorrect error code') @@ -159,7 +158,8 @@ async def test_TC_CGEN_2_5(self): asserts.assert_equal(resp.errorCode, Clusters.GeneralCommissioning.Enums.CommissioningErrorEnum.kOk, 'Incorrect error code') - self.verify_tc_attributes(new_accepted_version, new_acknowledgements) + # Verify that the TC attributes have been reset to the pre-failsafe state + await self.verify_tc_attributes(new_accepted_version, new_acknowledgements) logging.info('Step 11 - TH removes all fabrics from the device') fabrics = await self.read_single_attribute(dev_ctrl=self.th1, node_id=self.dut_node_id, endpoint=0, attribute=Clusters.OperationalCredentials.Attributes.Fabrics) @@ -185,9 +185,11 @@ async def test_TC_CGEN_2_5(self): # Don't set TCs for the next commissioning and skip CommissioningComplete so we can manually call CommissioningComplete in order to check the response error code self.th1.SetTCRequired(False) - self.th1.SetTCAcknowledgements(0, 0) self.th1.SetSkipCommissioningComplete(True) + # Verify that the TC attributes have been reset to the default values of 0 + await self.verify_tc_attributes(0, 0) + await self.commission_device() cmd = Clusters.GeneralCommissioning.Commands.CommissioningComplete()