From a51e3ef76cabdb8219c35ef421f902b009f1b499 Mon Sep 17 00:00:00 2001 From: James Swan <122404367+swan-amazon@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:31:22 +0000 Subject: [PATCH] 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 | 21 +- src/app/FailSafeContext.cpp | 11 +- src/app/FailSafeContext.h | 12 + .../general-commissioning-server.cpp | 180 ++++++++++++- src/app/common_flags.gni | 23 ++ src/app/server/BUILD.gn | 24 +- .../DefaultTermsAndConditionsProvider.cpp | 219 +++++++++++++++ .../DefaultTermsAndConditionsProvider.h | 70 +++++ src/app/server/Server.cpp | 9 +- src/app/server/Server.h | 32 ++- src/app/server/TermsAndConditionsProvider.h | 89 +++++++ src/app/tests/BUILD.gn | 5 +- .../TestDefaultTermsAndConditionsProvider.cpp | 250 ++++++++++++++++++ src/include/platform/CHIPDeviceEvent.h | 3 + src/lib/core/CHIPConfig.h | 20 +- src/lib/support/DefaultStorageKeyAllocator.h | 6 +- 16 files changed, 955 insertions(+), 19 deletions(-) create mode 100644 src/app/server/DefaultTermsAndConditionsProvider.cpp create mode 100644 src/app/server/DefaultTermsAndConditionsProvider.h create mode 100644 src/app/server/TermsAndConditionsProvider.h create mode 100644 src/app/tests/TestDefaultTermsAndConditionsProvider.cpp diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 8327bef57008aa..e713f3d1a38a5f 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -60,6 +60,15 @@ declare_args() { current_os != "android" } +config("enhanced_setup_flow_config") { + defines = [] + if (chip_config_tc_required) { + defines += [ "CHIP_CONFIG_TC_REQUIRED=1" ] + } else { + defines += [ "CHIP_CONFIG_TC_REQUIRED=0" ] + } +} + buildconfig_header("app_buildconfig") { header = "AppBuildConfig.h" header_dir = "app" @@ -81,6 +90,13 @@ buildconfig_header("app_buildconfig") { "CHIP_CONFIG_DATA_MODEL_CHECK_DIE_ON_FAILURE=${chip_data_model_check_die_on_failure}", ] + if (chip_config_tc_required) { + defines += [ + "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS=${chip_config_tc_required_acknowledgements}", + "CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION=${chip_config_tc_required_acknowledgements_version}", + ] + } + if (chip_use_data_model_interface == "disabled") { defines += [ "CHIP_CONFIG_USE_DATA_MODEL_INTERFACE=0", @@ -520,5 +536,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..db5f163c29c904 100644 --- a/src/app/FailSafeContext.h +++ b/src/app/FailSafeContext.h @@ -57,6 +57,9 @@ class FailSafeContext 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 +94,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 { @@ -111,6 +117,9 @@ class FailSafeContext // 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 /** * @brief @@ -145,6 +154,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..3e7948e3eb8976 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,8 +27,12 @@ #include #include #include +#if CHIP_CONFIG_TC_REQUIRED +#include +#endif #include #include +#include #include #include #include @@ -95,6 +99,28 @@ CHIP_ERROR GeneralCommissioningAttrAccess::Read(const ConcreteReadAttributePath case SupportsConcurrentConnection::Id: { return ReadSupportsConcurrentConnection(aEncoder); } +#if CHIP_CONFIG_TC_REQUIRED + case TCAcceptedVersion::Id: { + Optional outTermsAndConditions; + Server::GetInstance().GetTermsAndConditionsProvider()->GetAcceptance(outTermsAndConditions); + return !outTermsAndConditions.HasValue() ? aEncoder.Encode(0) : aEncoder.Encode(outTermsAndConditions.Value().version); + } + case TCMinRequiredVersion::Id: { + Optional outTermsAndConditions; + Server::GetInstance().GetTermsAndConditionsProvider()->GetRequirements(outTermsAndConditions); + return !outTermsAndConditions.HasValue() ? aEncoder.Encode(0) : aEncoder.Encode(outTermsAndConditions.Value().version); + } + case TCAcknowledgements::Id: { + Optional outTermsAndConditions; + Server::GetInstance().GetTermsAndConditionsProvider()->GetAcceptance(outTermsAndConditions); + return !outTermsAndConditions.HasValue() ? aEncoder.Encode(0) : aEncoder.Encode(outTermsAndConditions.Value().value); + } + case TCAcknowledgementsRequired::Id: { + Optional outTermsAndConditions; + Server::GetInstance().GetTermsAndConditionsProvider()->GetRequirements(outTermsAndConditions); + return aEncoder.Encode(outTermsAndConditions.HasValue()); + } +#endif default: { break; } @@ -144,6 +170,33 @@ CHIP_ERROR GeneralCommissioningAttrAccess::ReadSupportsConcurrentConnection(Attr return aEncoder.Encode(supportsConcurrentConnection); } +#if CHIP_CONFIG_TC_REQUIRED +CommissioningErrorEnum CheckTermsAndConditionsAcknowledgementsState(TermsAndConditionsProvider * const termsAndConditionsProvider, + const Optional & acceptedTermsAndConditions) +{ + TermsAndConditionsState termsAndConditionsState; + + CHIP_ERROR err = termsAndConditionsProvider->CheckAcceptance(acceptedTermsAndConditions, termsAndConditionsState); + if (CHIP_NO_ERROR != err) + { + return chip::app::Clusters::GeneralCommissioning::CommissioningErrorEnum::kUnknownEnumValue; + } + + switch (termsAndConditionsState) + { + case OK: + return chip::app::Clusters::GeneralCommissioning::CommissioningErrorEnum::kOk; + case TC_ACKNOWLEDGEMENTS_NOT_RECEIVED: + return chip::app::Clusters::GeneralCommissioning::CommissioningErrorEnum::kTCAcknowledgementsNotReceived; + case TC_MIN_VERSION_NOT_MET: + return chip::app::Clusters::GeneralCommissioning::CommissioningErrorEnum::kTCMinVersionNotMet; + case REQUIRED_TC_NOT_ACCEPTED: + return chip::app::Clusters::GeneralCommissioning::CommissioningErrorEnum::kRequiredTCNotAccepted; + } + + return chip::app::Clusters::GeneralCommissioning::CommissioningErrorEnum::kOk; +} +#endif // CHIP_CONFIG_TC_REQUIRED } // anonymous namespace bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler * commandObj, @@ -218,6 +271,8 @@ bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback( auto & failSafe = Server::GetInstance().GetFailSafeContext(); auto & fabricTable = Server::GetInstance().GetFabricTable(); + CHIP_ERROR err; + ChipLogProgress(FailSafe, "GeneralCommissioning: Received CommissioningComplete"); Commands::CommissioningCompleteResponse::Type response; @@ -239,9 +294,45 @@ bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback( } else { +#if CHIP_CONFIG_TC_REQUIRED + TermsAndConditionsProvider * const termsAndConditionsProvider = Server::GetInstance().GetTermsAndConditionsProvider(); + Optional acceptedTermsAndConditions; + + err = termsAndConditionsProvider->GetAcceptance(acceptedTermsAndConditions); + if (CHIP_NO_ERROR != err) + { + // + } + + response.errorCode = + CheckTermsAndConditionsAcknowledgementsState(termsAndConditionsProvider, acceptedTermsAndConditions); + if (CommissioningErrorEnum::kOk != response.errorCode) + { + commandObj->AddResponse(commandPath, response); + return true; + } + + if (failSafe.UpdateTermsAndConditionsHasBeenInvoked()) + { + // Commit terms and conditions acceptance on commissioning complete + err = Server::GetInstance().GetTermsAndConditionsProvider()->CommitAcceptance(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(FailSafe, "GeneralCommissioning: Failed to commit terms and conditions: %" CHIP_ERROR_FORMAT, + err.Format()); + fabricTable.RevertPendingFabricData(); + } + else + { + ChipLogProgress(FailSafe, "GeneralCommissioning: Successfully commited terms and conditions"); + } + CheckSuccess(err, Failure); + } +#endif + if (failSafe.NocCommandHasBeenInvoked()) { - CHIP_ERROR err = fabricTable.CommitPendingFabricData(); + err = fabricTable.CommitPendingFabricData(); if (err != CHIP_NO_ERROR) { // No need to revert on error: CommitPendingFabricData always reverts if not fully successful. @@ -328,6 +419,47 @@ 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 + MATTER_TRACE_SCOPE("SetTCAcknowledgements", "GeneralCommissioning"); + + auto & failSafeContext = Server::GetInstance().GetFailSafeContext(); + TermsAndConditionsProvider * const termsAndConditionsProvider = Server::GetInstance().GetTermsAndConditionsProvider(); + + Optional acceptedTermsAndConditions = Optional({ + .value = commandData.TCUserResponse, + .version = commandData.TCVersion, + }); + + Commands::SetTCAcknowledgementsResponse::Type response; + response.errorCode = CheckTermsAndConditionsAcknowledgementsState(termsAndConditionsProvider, acceptedTermsAndConditions); + + if (CommissioningErrorEnum::kOk == response.errorCode) + { + CheckSuccess(termsAndConditionsProvider->SetAcceptance(acceptedTermsAndConditions), Failure); + + if (failSafeContext.IsFailSafeArmed()) + { + failSafeContext.SetUpdateTermsAndConditionsHasBeenInvoked(); + } + else + { + CheckSuccess(termsAndConditionsProvider->CommitAcceptance(), Failure); + } + } + + commandObj->AddResponse(commandPath, response); + return true; + +#endif +} + namespace { void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) { @@ -335,18 +467,19 @@ 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().GetTermsAndConditionsProvider()->RevertAcceptance(); + } +#endif } } } // anonymous namespace -void MatterGeneralCommissioningPluginServerInitCallback() -{ - Breadcrumb::Set(0, 0); - AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); - DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler); -} - namespace chip { namespace app { namespace Clusters { @@ -359,3 +492,32 @@ void SetBreadcrumb(Attributes::Breadcrumb::TypeInfo::Type breadcrumb) } // namespace Clusters } // namespace app } // namespace chip + +class GeneralCommissioningFabricTableDelegate : public chip::FabricTable::Delegate +{ +public: + // Gets called when a fabric is deleted + void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override + { + // If the FabricIndex matches the last remaining entry in the Fabrics list, then the device SHALL delete all Matter + // related data on the node which was created since it was commissioned. + if (Server::GetInstance().GetFabricTable().FabricCount() == 0) + { + ChipLogProgress(Zcl, "general-commissioning-server: Last Fabric index 0x%x was removed", + static_cast(fabricIndex)); +#if CHIP_CONFIG_TC_REQUIRED + Server::GetInstance().GetTermsAndConditionsProvider()->ResetAcceptance(); +#endif // CHIP_CONFIG_TC_REQUIRED + } + } +}; + +void MatterGeneralCommissioningPluginServerInitCallback() +{ + Breadcrumb::Set(0, 0); + AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); + DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler); + + static GeneralCommissioningFabricTableDelegate generalCommissioningFabricTableDelegate; + Server::GetInstance().GetFabricTable().AddFabricDelegate(&generalCommissioningFabricTableDelegate); +} diff --git a/src/app/common_flags.gni b/src/app/common_flags.gni index 953afc23a20650..5e62c0554856a8 100644 --- a/src/app/common_flags.gni +++ b/src/app/common_flags.gni @@ -18,6 +18,29 @@ declare_args() { chip_enable_read_client = true chip_build_controller_dynamic_server = false + # Configures whether terms and conditions acknowledgements are required. + # + # When set to true, the application will enforce the requirement of + # terms and conditions acknowledgements during commissioning. + chip_config_tc_required = false + + # Configures the required terms and conditions acknowledgements bitmask. + # + # This setting 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. + chip_config_tc_required_acknowledgements = 0 + + # Configures the latest known version of the terms and conditions. + # + # This setting 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. + 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 58524c3a648aab..326db2f44fc6aa 100644 --- a/src/app/server/BUILD.gn +++ b/src/app/server/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Project CHIP Authors +# Copyright (c) 2020-2024 Project CHIP Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +25,23 @@ config("server_config") { } } +source_set("enhanced_setup_flow_interface") { + sources = [ + "DefaultTermsAndConditionsProvider.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 = [ "DefaultTermsAndConditionsProvider.cpp" ] + + deps = [ ":enhanced_setup_flow_interface" ] +} + static_library("server") { output_name = "libCHIPAppServer" @@ -52,6 +69,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", @@ -80,4 +98,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/DefaultTermsAndConditionsProvider.cpp b/src/app/server/DefaultTermsAndConditionsProvider.cpp new file mode 100644 index 00000000000000..d00c16d14492cb --- /dev/null +++ b/src/app/server/DefaultTermsAndConditionsProvider.cpp @@ -0,0 +1,219 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DefaultTermsAndConditionsProvider.h" +#include "TermsAndConditionsProvider.h" + +#include +#include +#include +#include +#include +#include + +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, + const chip::Optional & inmRequiredAcknowledgements) +{ + VerifyOrReturnError(nullptr != inPersistentStorageDelegate, CHIP_ERROR_INVALID_ARGUMENT); + + mPersistentStorageDelegate = inPersistentStorageDelegate; + mRequiredAcknowledgements = inmRequiredAcknowledgements; + + if (CHIP_NO_ERROR == LoadAcceptance(mLatchedAcceptance)) + { + mTemporalAcceptance.SetValue(mLatchedAcceptance.Value()); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::CheckAcceptance(const Optional & inTermsAndConditions, + TermsAndConditionsState & outState) const +{ + // No validation checks required if no required terms and conditions + if (!mRequiredAcknowledgements.HasValue()) + { + outState = TermsAndConditionsState::OK; + return CHIP_NO_ERROR; + } + + // Validate if we have received any terms and conditions acceptance + if (!inTermsAndConditions.HasValue()) + { + ChipLogError(AppServer, "No terms and conditions have been accepted"); + outState = TermsAndConditionsState::TC_ACKNOWLEDGEMENTS_NOT_RECEIVED; + return CHIP_NO_ERROR; + } + + // Validate the accepted version first... + if (mRequiredAcknowledgements.Value().version > inTermsAndConditions.Value().version) + { + ChipLogProgress(AppServer, "Minimum terms and conditions version, 0x%04x, has not been accepted", + mRequiredAcknowledgements.Value().version); + outState = TermsAndConditionsState::TC_MIN_VERSION_NOT_MET; + return CHIP_NO_ERROR; + } + + // Validate the accepted bits second... + if (mRequiredAcknowledgements.Value().value != (mRequiredAcknowledgements.Value().value & inTermsAndConditions.Value().value)) + { + ChipLogProgress(AppServer, "Required terms and conditions, 0x%04x,have not been accepted", + mRequiredAcknowledgements.Value().value); + outState = TermsAndConditionsState::REQUIRED_TC_NOT_ACCEPTED; + return CHIP_NO_ERROR; + } + + // All validation check succeeded... + outState = TermsAndConditionsState::OK; + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::CommitAcceptance() +{ + if (!mTemporalAcceptance.HasValue()) + { + return CHIP_NO_ERROR; + } + + mLatchedAcceptance.SetValue(mTemporalAcceptance.Value()); + return StoreAcceptance(mLatchedAcceptance); +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetAcceptance(Optional & outTermsAndConditions) const +{ + outTermsAndConditions = mTemporalAcceptance; + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::GetRequirements(Optional & outTermsAndConditions) const +{ + outTermsAndConditions = mRequiredAcknowledgements; + 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())); + mLatchedAcceptance.ClearValue(); + mTemporalAcceptance.ClearValue(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::RevertAcceptance() +{ + mTemporalAcceptance = mLatchedAcceptance; + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::SetAcceptance(const Optional & inTermsAndConditions) +{ + mTemporalAcceptance.SetValue(inTermsAndConditions.Value()); + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::LoadAcceptance(Optional & outTermsAndConditions) +{ + 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) + { + return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + + 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; + } + + outTermsAndConditions.SetValue({ + .value = acknowledgements, + .version = acknowledgementsVersion, + }); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR chip::app::DefaultTermsAndConditionsProvider::StoreAcceptance(const Optional & inTermsAndConditions) +{ + uint8_t buffer[kEstimatedTlvBufferSize] = { 0 }; + chip::TLV::TLVWriter tlvWriter; + chip::TLV::TLVType tlvContainer; + + VerifyOrReturnError(nullptr != mPersistentStorageDelegate, CHIP_ERROR_UNINITIALIZED); + VerifyOrReturnError(inTermsAndConditions.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, inTermsAndConditions.Value().value)); + ReturnErrorOnFailure(tlvWriter.Put(kAcceptedAcknowledgementsVersionTag, inTermsAndConditions.Value().version)); + 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..1fbde5a7e51418 --- /dev/null +++ b/src/app/server/DefaultTermsAndConditionsProvider.h @@ -0,0 +1,70 @@ +/* + * + * 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. + */ + CHIP_ERROR Init(PersistentStorageDelegate * const inPersistentStorageDelegate, + const chip::Optional & inRequiredTermsAndConditions); + + CHIP_ERROR CheckAcceptance(const Optional & inTermsAndConditions, + TermsAndConditionsState & outState) const override; + + CHIP_ERROR CommitAcceptance() override; + + CHIP_ERROR GetAcceptance(Optional & outTermsAndConditions) const override; + + CHIP_ERROR GetRequirements(Optional & outTermsAndConditions) const override; + + CHIP_ERROR ResetAcceptance() override; + + CHIP_ERROR RevertAcceptance() override; + + CHIP_ERROR SetAcceptance(const Optional & inTermsAndConditions) override; + +private: + CHIP_ERROR LoadAcceptance(Optional & outTermsAndConditions); + + CHIP_ERROR StoreAcceptance(const Optional & inTermsAndConditions); + + PersistentStorageDelegate * mPersistentStorageDelegate; + + Optional mLatchedAcceptance; + Optional mTemporalAcceptance; + Optional mRequiredAcknowledgements; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 426e3c686572c0..f34754d97a86ef 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. @@ -192,6 +192,10 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) mReportScheduler = initParams.reportScheduler; +#if CHIP_CONFIG_TC_REQUIRED + mTermsAndConditionsProvider = initParams.termsAndConditionsProvider; +#endif + mTestEventTriggerDelegate = initParams.testEventTriggerDelegate; if (mTestEventTriggerDelegate == nullptr) { @@ -593,6 +597,9 @@ void Server::ScheduleFactoryReset() GetInstance().GetFabricTable().DeleteAllFabrics(); PlatformMgr().HandleServerShuttingDown(); ConfigurationMgr().InitiateFactoryReset(); +#if CHIP_CONFIG_TC_REQUIRED + GetInstance().GetTermsAndConditionsProvider()->ResetAcceptance(); +#endif }); } diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 27c850563f6e98..ea9d33f8b0b355 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,9 @@ #include #include #include +#if CHIP_CONFIG_TC_REQUIRED +#include +#endif #include #include #include @@ -186,6 +189,10 @@ 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. Terms and conditions provider to support enhanced setup flow feature. + app::TermsAndConditionsProvider * termsAndConditionsProvider = nullptr; +#endif }; /** @@ -307,6 +314,22 @@ struct CommonCaseDeviceServerInitParams : public ServerInitParams } #endif +#if CHIP_CONFIG_TC_REQUIRED + static app::DefaultTermsAndConditionsProvider sDefaultTermsAndConditionsProviderInstance; + + if (this->termsAndConditionsProvider == nullptr) + { + Optional termsAndConditions = Optional({ + .value = CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS, + .version = CHIP_CONFIG_TC_REQUIRED_ACKNOWLEDGEMENTS_VERSION, + }); + + ReturnErrorOnFailure( + sDefaultTermsAndConditionsProviderInstance.Init(this->persistentStorageDelegate, termsAndConditions)); + this->termsAndConditionsProvider = &sDefaultTermsAndConditionsProviderInstance; + } +#endif + return CHIP_NO_ERROR; } @@ -409,6 +432,10 @@ class Server app::reporting::ReportScheduler * GetReportScheduler() { return mReportScheduler; } +#if CHIP_CONFIG_TC_REQUIRED + app::TermsAndConditionsProvider * GetTermsAndConditionsProvider() { return mTermsAndConditionsProvider; } +#endif + #if CHIP_CONFIG_ENABLE_ICD_SERVER app::ICDManager & GetICDManager() { return mICDManager; } @@ -682,6 +709,9 @@ class Server GroupDataProviderListener mListener; ServerFabricDelegate mFabricDelegate; app::reporting::ReportScheduler * mReportScheduler; +#if CHIP_CONFIG_TC_REQUIRED + 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..95b73d70229583 --- /dev/null +++ b/src/app/server/TermsAndConditionsProvider.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 +#include + +namespace chip { +namespace app { + +typedef struct sTermsAndConditions +{ + uint16_t value; + uint16_t version; +} TermsAndConditions; + +typedef enum eTermsAndConditionsState +{ + OK = 0, + TC_ACKNOWLEDGEMENTS_NOT_RECEIVED = 1, + TC_MIN_VERSION_NOT_MET = 2, + REQUIRED_TC_NOT_ACCEPTED = 3, +} TermsAndConditionsState; + +/** + * @brief Data access layer for the required terms and conditions and the store for the user acceptance. + */ +class TermsAndConditionsProvider +{ +public: + virtual ~TermsAndConditionsProvider() = default; + + /** + * @brief Checks the acceptance status without latching the value, nor temporal storage. + */ + virtual CHIP_ERROR CheckAcceptance(const Optional & inTermsAndConditions, + TermsAndConditionsState & outState) const = 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. + */ + virtual CHIP_ERROR GetAcceptance(Optional & outTermsAndConditions) const = 0; + + /** + * @brief Retrieves the requirements of the terms and conditions. + */ + virtual CHIP_ERROR GetRequirements(Optional & outTermsAndConditions) const = 0; + + /** + * @brief Reset the persisted acceptance status of the required terms and conditions. + */ + virtual CHIP_ERROR ResetAcceptance() = 0; + + /** + * @brief Clear the temporary acceptance status of the required terms and conditions. + */ + virtual CHIP_ERROR RevertAcceptance() = 0; + + /** + * @brief Sets the acceptance status of the required terms and conditions. + */ + virtual CHIP_ERROR SetAcceptance(const Optional & inTermsAndConditions) = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 104a57a2fc019a..4d3a9138f6d5ee 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. @@ -200,6 +200,7 @@ chip_test_suite("tests") { "TestConcreteAttributePath.cpp", "TestDataModelSerialization.cpp", "TestDefaultOTARequestorStorage.cpp", + "TestDefaultTermsAndConditionsProvider.cpp", "TestDefaultThreadNetworkDirectoryStorage.cpp", "TestEventLoggingNoUTCTime.cpp", "TestEventOverflow.cpp", @@ -237,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:enhanced_setup_flow_interface", "${chip_root}/src/app/tests:helpers", "${chip_root}/src/app/util/mock:mock_codegen_data_model", "${chip_root}/src/app/util/mock:mock_ember", diff --git a/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp b/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp new file mode 100644 index 00000000000000..bb1fff24806b2a --- /dev/null +++ b/src/app/tests/TestDefaultTermsAndConditionsProvider.cpp @@ -0,0 +1,250 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "app/server/DefaultTermsAndConditionsProvider.h" +#include "app/server/TermsAndConditionsProvider.h" + +#include +#include +#include +#include + +TEST(DefaultTermsAndConditionsProvider, TestInitSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + chip::Optional termsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + + err = tncProvider.Init(&storageDelegate, termsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); +} + +TEST(DefaultTermsAndConditionsProvider, TestNoRequirementsGetRequirementsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + chip::Optional termsAndConditions = chip::Optional(); + + err = tncProvider.Init(&storageDelegate, termsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = tncProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(outTermsAndConditions.HasValue()); +} + +TEST(DefaultTermsAndConditionsProvider, TestNeverAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + chip::Optional termsAndConditions = chip::Optional({ + .value = 0b1111'1111'1111'1111, + .version = 1, + }); + + err = tncProvider.Init(&storageDelegate, termsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = tncProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(outTermsAndConditions.HasValue()); +} + +TEST(DefaultTermsAndConditionsProvider, TestTermsAcceptedPersistsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + chip::app::DefaultTermsAndConditionsProvider anotherTncProvider; + + chip::Optional termsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + + err = tncProvider.Init(&storageDelegate, termsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional newTermsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + + err = tncProvider.SetAcceptance(newTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = tncProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().value); + EXPECT_EQ(1, outTermsAndConditions.Value().version); + + err = tncProvider.CommitAcceptance(); + err = tncProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().value); + EXPECT_EQ(1, outTermsAndConditions.Value().version); + + err = anotherTncProvider.Init(&storageDelegate, termsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + err = anotherTncProvider.GetAcceptance(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().value); + EXPECT_EQ(1, outTermsAndConditions.Value().version); +} + +TEST(DefaultTermsAndConditionsProvider, TestTermsRequiredGetRequirementsSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + chip::Optional termsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + + err = tncProvider.Init(&storageDelegate, termsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = tncProvider.GetRequirements(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().value); + EXPECT_EQ(1, outTermsAndConditions.Value().version); +} + +TEST(DefaultTermsAndConditionsProvider, TestSetAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + chip::Optional termsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + + err = tncProvider.Init(&storageDelegate, termsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional acceptedTermsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + err = tncProvider.SetAcceptance(acceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = tncProvider.GetRequirements(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().value); + EXPECT_EQ(1, outTermsAndConditions.Value().version); +} + +TEST(DefaultTermsAndConditionsProvider, TestRevertAcceptanceGetAcceptanceSuccess) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + chip::Optional termsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + + err = tncProvider.Init(&storageDelegate, termsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional acceptedTermsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + err = tncProvider.SetAcceptance(acceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outTermsAndConditions; + err = tncProvider.GetRequirements(outTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outTermsAndConditions.Value().value); + EXPECT_EQ(1, outTermsAndConditions.Value().version); + + err = tncProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outAcceptance2; + err = tncProvider.GetAcceptance(outAcceptance2); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_FALSE(outAcceptance2.HasValue()); +} + +TEST(DefaultTermsAndConditionsProvider, TestAcceptanceRequiredTermsMissingFailure) +{ + CHIP_ERROR err; + + chip::TestPersistentStorageDelegate storageDelegate; + chip::app::DefaultTermsAndConditionsProvider tncProvider; + + chip::Optional requiredTermsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + err = tncProvider.Init(&storageDelegate, requiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional acceptedTermsAndConditions = chip::Optional({ + .value = 1, + .version = 1, + }); + err = tncProvider.SetAcceptance(acceptedTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outAcknowledgementTermsAndConditions; + err = tncProvider.GetAcceptance(outAcknowledgementTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outAcknowledgementTermsAndConditions.Value().value); + EXPECT_EQ(1, outAcknowledgementTermsAndConditions.Value().version); + + err = tncProvider.RevertAcceptance(); + EXPECT_EQ(CHIP_NO_ERROR, err); + + chip::Optional outRequiredTermsAndConditions; + err = tncProvider.GetRequirements(outRequiredTermsAndConditions); + EXPECT_EQ(CHIP_NO_ERROR, err); + EXPECT_EQ(1, outRequiredTermsAndConditions.Value().value); + EXPECT_EQ(1, outRequiredTermsAndConditions.Value().version); +} 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 8936cc19c4f76d..87127f99cf6bc4 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2020-2024 Project CHIP Authors * Copyright (c) 2019 Google LLC. * Copyright (c) 2013-2018 Nest Labs, Inc. * @@ -1828,6 +1828,24 @@ 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. + */ +#ifndef CHIP_CONFIG_TC_REQUIRED +#define CHIP_CONFIG_TC_REQUIRED 0 +#endif + /** * @} */ 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