Skip to content

Commit

Permalink
Add initial feature logic for Terms and Conditions (TC) acknowledgements
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
swan-amazon committed Oct 7, 2024
1 parent 4ec1716 commit 158a29b
Show file tree
Hide file tree
Showing 16 changed files with 965 additions and 19 deletions.
21 changes: 20 additions & 1 deletion src/app/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,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"
Expand All @@ -78,6 +87,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",
Expand Down Expand Up @@ -521,5 +537,8 @@ static_library("app") {

cflags = [ "-Wconversion" ]

public_configs = [ "${chip_root}/src:includes" ]
public_configs = [
":enhanced_setup_flow_config",
"${chip_root}/src:includes",
]
}
11 changes: 8 additions & 3 deletions src/app/FailSafeContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions src/app/FailSafeContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand All @@ -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
Expand Down Expand Up @@ -145,6 +154,9 @@ class FailSafeContext
mAddTrustedRootCertHasBeenInvoked = false;
mFailSafeBusy = false;
mIsCsrRequestForUpdateNoc = false;
#if CHIP_CONFIG_TC_REQUIRED
mUpdateTermsAndConditionsHasBeenInvoked = false;
#endif
}

void FailSafeTimerExpired();
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -27,8 +27,12 @@
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/server/CommissioningWindowManager.h>
#if CHIP_CONFIG_TC_REQUIRED
#include <app/server/TermsAndConditionsProvider.h>
#endif
#include <app/server/Server.h>
#include <app/util/attribute-storage.h>
#include <credentials/FabricTable.h>
#include <lib/support/Span.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/CHIPDeviceConfig.h>
Expand Down Expand Up @@ -95,6 +99,37 @@ CHIP_ERROR GeneralCommissioningAttrAccess::Read(const ConcreteReadAttributePath
case SupportsConcurrentConnection::Id: {
return ReadSupportsConcurrentConnection(aEncoder);
}
#if CHIP_CONFIG_TC_REQUIRED
case TCAcceptedVersion::Id: {
Optional<TermsAndConditions> outTermsAndConditions;
Server::GetInstance().GetTermsAndConditionsProvider()->GetAcceptance(outTermsAndConditions);
return !outTermsAndConditions.HasValue() ? aEncoder.Encode(static_cast<uint16_t>(0))
: aEncoder.Encode(outTermsAndConditions.Value().version);
}
case TCMinRequiredVersion::Id: {
Optional<TermsAndConditions> outTermsAndConditions;
Server::GetInstance().GetTermsAndConditionsProvider()->GetRequirements(outTermsAndConditions);
return !outTermsAndConditions.HasValue() ? aEncoder.Encode(static_cast<uint16_t>(0))
: aEncoder.Encode(outTermsAndConditions.Value().version);
}
case TCAcknowledgements::Id: {
Optional<TermsAndConditions> outTermsAndConditions;
Server::GetInstance().GetTermsAndConditionsProvider()->GetAcceptance(outTermsAndConditions);
return !outTermsAndConditions.HasValue() ? aEncoder.Encode(static_cast<uint16_t>(0))
: aEncoder.Encode(outTermsAndConditions.Value().value);
}
case TCAcknowledgementsRequired::Id: {
Optional<TermsAndConditions> outTermsAndConditions;
Server::GetInstance().GetTermsAndConditionsProvider()->GetAcceptance(outTermsAndConditions);

TermsAndConditionsState termsAndConditionsState;
Server::GetInstance().GetTermsAndConditionsProvider()->CheckAcceptance(outTermsAndConditions, termsAndConditionsState);

bool setTermsAndConditionsCallRequiredBeforeCommissioningCompleteSuccess =
termsAndConditionsState != TermsAndConditionsState::OK;
return aEncoder.Encode(setTermsAndConditionsCallRequiredBeforeCommissioningCompleteSuccess);
}
#endif
default: {
break;
}
Expand Down Expand Up @@ -144,6 +179,33 @@ CHIP_ERROR GeneralCommissioningAttrAccess::ReadSupportsConcurrentConnection(Attr
return aEncoder.Encode(supportsConcurrentConnection);
}

#if CHIP_CONFIG_TC_REQUIRED
CommissioningErrorEnum CheckTermsAndConditionsAcknowledgementsState(TermsAndConditionsProvider * const termsAndConditionsProvider,
const Optional<TermsAndConditions> & 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,
Expand Down Expand Up @@ -218,6 +280,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;
Expand All @@ -239,9 +303,45 @@ bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback(
}
else
{
#if CHIP_CONFIG_TC_REQUIRED
TermsAndConditionsProvider * const termsAndConditionsProvider = Server::GetInstance().GetTermsAndConditionsProvider();
Optional<TermsAndConditions> 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.
Expand Down Expand Up @@ -328,25 +428,67 @@ 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<TermsAndConditions> acceptedTermsAndConditions = Optional<TermsAndConditions>({
.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)
{
if (event->Type == DeviceLayer::DeviceEventType::kFailSafeTimerExpired)
{
// 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 {
Expand All @@ -359,3 +501,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<unsigned>(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);
}
23 changes: 23 additions & 0 deletions src/app/common_flags.gni
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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
Expand Down
Loading

0 comments on commit 158a29b

Please sign in to comment.