Skip to content

Commit bb5859f

Browse files
andy31415andreilitvinbzbarsky-apple
authored andcommitted
Move ::WriteAttribute validation inside Interaction Model Write Handling (project-chip#37322)
* Move write validity inside the write handler rather than requesting the datamodel provider to perform the checks * Restyle * A bit of code cleanup and reuse, to minimize flash impact * Restyle * Drop the mPreviousSuccessPath, making objects smaller and saving yet another small amount of flash * Cleanup some includes * Restyle * Remove obsolete tests from codgen, as we do not do more validation now * Test write interaction passes * Test write passes * Slight clarity of code update * Fix comment * Manual check for last written saves 16 bytes of flash on QPG. * Status success is 2 bytes smaller in code generation than CHIP_NO_ERROR * Not using endpoint finder saves flash * Clean up code. We now save flash * More flash savings by reusing the server cluster finder * Update src/app/data-model-provider/MetadataLookup.cpp Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Update src/app/data-model-provider/MetadataLookup.h Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Update src/app/data-model-provider/Provider.h Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Update src/app/data-model-provider/MetadataLookup.h Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Updated comment * Hoist "writing" log up in processing, to preserve similar functionality * Code clarity updates * Fix includes and update the code AGAIN because it does seem we use more flash * Fix unsupported write on global attributes * Fix includes * Update src/app/WriteHandler.cpp Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Post merge cleanup * Fix merge error --------- Co-authored-by: Andrei Litvin <andreilitvin@google.com> Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
1 parent bb10019 commit bb5859f

13 files changed

+254
-238
lines changed

src/app/InteractionModelEngine.cpp

+2-22
Original file line numberDiff line numberDiff line change
@@ -1801,28 +1801,8 @@ Protocols::InteractionModel::Status InteractionModelEngine::CheckCommandExistenc
18011801
}
18021802
}
18031803

1804-
{
1805-
DataModel::ServerClusterFinder finder(provider);
1806-
if (finder.Find(aCommandPath).has_value())
1807-
{
1808-
// cluster exists, so command is invalid
1809-
return Protocols::InteractionModel::Status::UnsupportedCommand;
1810-
}
1811-
}
1812-
1813-
// At this point either cluster or endpoint does not exist. If we find the endpoint, then the cluster
1814-
// is invalid
1815-
{
1816-
DataModel::EndpointFinder finder(provider);
1817-
if (finder.Find(aCommandPath.mEndpointId))
1818-
{
1819-
// endpoint exists, so cluster is invalid
1820-
return Protocols::InteractionModel::Status::UnsupportedCluster;
1821-
}
1822-
}
1823-
1824-
// endpoint not found
1825-
return Protocols::InteractionModel::Status::UnsupportedEndpoint;
1804+
// invalid command, return the right failure status
1805+
return DataModel::ValidateClusterPath(provider, aCommandPath, Protocols::InteractionModel::Status::UnsupportedCommand);
18261806
}
18271807

18281808
DataModel::Provider * InteractionModelEngine::SetDataModelProvider(DataModel::Provider * model)

src/app/WriteHandler.cpp

+87-7
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@
1919
#include <app/AppConfig.h>
2020
#include <app/AttributeAccessInterfaceRegistry.h>
2121
#include <app/AttributeValueDecoder.h>
22+
#include <app/ConcreteAttributePath.h>
23+
#include <app/GlobalAttributes.h>
2224
#include <app/InteractionModelEngine.h>
2325
#include <app/MessageDef/EventPathIB.h>
2426
#include <app/MessageDef/StatusIB.h>
27+
#include <app/RequiredPrivilege.h>
2528
#include <app/StatusResponse.h>
2629
#include <app/WriteHandler.h>
30+
#include <app/data-model-provider/ActionReturnStatus.h>
2731
#include <app/data-model-provider/MetadataLookup.h>
2832
#include <app/data-model-provider/MetadataTypes.h>
2933
#include <app/data-model-provider/OperationTypes.h>
@@ -44,6 +48,8 @@ namespace app {
4448

4549
namespace {
4650

51+
using Protocols::InteractionModel::Status;
52+
4753
/// Wraps a EndpointIterator and ensures that `::Release()` is called
4854
/// for the iterator (assuming it is non-null)
4955
class AutoReleaseGroupEndpointIterator
@@ -754,23 +760,97 @@ void WriteHandler::MoveToState(const State aTargetState)
754760
ChipLogDetail(DataManagement, "IM WH moving to [%s]", GetStateStr());
755761
}
756762

763+
DataModel::ActionReturnStatus WriteHandler::CheckWriteAllowed(const Access::SubjectDescriptor & aSubject,
764+
const ConcreteAttributePath & aPath)
765+
{
766+
// TODO: ordering is to check writability/existence BEFORE ACL and this seems wrong, however
767+
// existing unit tests (TC_AcessChecker.py) validate that we get UnsupportedWrite instead of UnsupportedAccess
768+
//
769+
// This should likely be fixed in spec (probably already fixed by
770+
// https://github.com/CHIP-Specifications/connectedhomeip-spec/pull/9024)
771+
// and tests and implementation
772+
//
773+
// Open issue that needs fixing: https://github.com/project-chip/connectedhomeip/issues/33735
774+
775+
std::optional<DataModel::AttributeEntry> attributeEntry;
776+
DataModel::AttributeFinder finder(mDataModelProvider);
777+
778+
attributeEntry = finder.Find(aPath);
779+
780+
// if path is not valid, return a spec-compliant return code.
781+
if (!attributeEntry.has_value())
782+
{
783+
// Global lists are not in metadata and not writable. Return the correct error code according to the spec
784+
Status attributeErrorStatus =
785+
IsSupportedGlobalAttributeNotInMetadata(aPath.mAttributeId) ? Status::UnsupportedWrite : Status::UnsupportedAttribute;
786+
787+
return DataModel::ValidateClusterPath(mDataModelProvider, aPath, attributeErrorStatus);
788+
}
789+
790+
// Allow writes on writable attributes only
791+
VerifyOrReturnValue(attributeEntry->writePrivilege.has_value(), Status::UnsupportedWrite);
792+
793+
bool checkAcl = true;
794+
if (mLastSuccessfullyWrittenPath.has_value())
795+
{
796+
// only validate ACL if path has changed
797+
//
798+
// Note that this is NOT operator==: we could do `checkAcl == (aPath != *mLastSuccessfullyWrittenPath)`
799+
// however that seems to use more flash.
800+
if ((aPath.mEndpointId == mLastSuccessfullyWrittenPath->mEndpointId) &&
801+
(aPath.mClusterId == mLastSuccessfullyWrittenPath->mClusterId) &&
802+
(aPath.mAttributeId == mLastSuccessfullyWrittenPath->mAttributeId))
803+
{
804+
checkAcl = false;
805+
}
806+
}
807+
808+
if (checkAcl)
809+
{
810+
Access::RequestPath requestPath{ .cluster = aPath.mClusterId,
811+
.endpoint = aPath.mEndpointId,
812+
.requestType = Access::RequestType::kAttributeWriteRequest,
813+
.entityId = aPath.mAttributeId };
814+
CHIP_ERROR err = Access::GetAccessControl().Check(aSubject, requestPath, RequiredPrivilege::ForWriteAttribute(aPath));
815+
816+
if (err != CHIP_NO_ERROR)
817+
{
818+
VerifyOrReturnValue(err != CHIP_ERROR_ACCESS_DENIED, Status::UnsupportedAccess);
819+
VerifyOrReturnValue(err != CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL, Status::AccessRestricted);
820+
821+
return err;
822+
}
823+
}
824+
825+
// validate that timed write is enforced
826+
VerifyOrReturnValue(IsTimedWrite() || !attributeEntry->flags.Has(DataModel::AttributeQualityFlags::kTimed),
827+
Status::NeedsTimedInteraction);
828+
829+
return Status::Success;
830+
}
831+
757832
CHIP_ERROR WriteHandler::WriteClusterData(const Access::SubjectDescriptor & aSubject, const ConcreteDataAttributePath & aPath,
758833
TLV::TLVReader & aData)
759834
{
760835
// Writes do not have a checked-path. If data model interface is enabled (both checked and only version)
761836
// the write is done via the DataModel interface
762837
VerifyOrReturnError(mDataModelProvider != nullptr, CHIP_ERROR_INCORRECT_STATE);
763838

764-
DataModel::WriteAttributeRequest request;
839+
ChipLogDetail(DataManagement, "Writing attribute: Cluster=" ChipLogFormatMEI " Endpoint=0x%x AttributeId=" ChipLogFormatMEI,
840+
ChipLogValueMEI(aPath.mClusterId), aPath.mEndpointId, ChipLogValueMEI(aPath.mAttributeId));
765841

766-
request.path = aPath;
767-
request.subjectDescriptor = &aSubject;
768-
request.previousSuccessPath = mLastSuccessfullyWrittenPath;
769-
request.writeFlags.Set(DataModel::WriteFlags::kTimed, IsTimedWrite());
842+
DataModel::ActionReturnStatus status = CheckWriteAllowed(aSubject, aPath);
843+
if (status.IsSuccess())
844+
{
845+
DataModel::WriteAttributeRequest request;
770846

771-
AttributeValueDecoder decoder(aData, aSubject);
847+
request.path = aPath;
848+
request.subjectDescriptor = &aSubject;
849+
request.writeFlags.Set(DataModel::WriteFlags::kTimed, IsTimedWrite());
772850

773-
DataModel::ActionReturnStatus status = mDataModelProvider->WriteAttribute(request, decoder);
851+
AttributeValueDecoder decoder(aData, aSubject);
852+
status = mDataModelProvider->WriteAttribute(request, decoder);
853+
}
774854

775855
mLastSuccessfullyWrittenPath = status.IsSuccess() ? std::make_optional(aPath) : std::nullopt;
776856

src/app/WriteHandler.h

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <app/ConcreteAttributePath.h>
2525
#include <app/InteractionModelDelegatePointers.h>
2626
#include <app/MessageDef/WriteResponseMessage.h>
27+
#include <app/data-model-provider/ActionReturnStatus.h>
2728
#include <app/data-model-provider/Provider.h>
2829
#include <lib/core/CHIPCore.h>
2930
#include <lib/core/TLVDebug.h>
@@ -185,6 +186,14 @@ class WriteHandler : public Messaging::ExchangeDelegate
185186
System::PacketBufferHandle && aPayload) override;
186187
void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;
187188

189+
/// Validate that a write is acceptable on the given path.
190+
///
191+
/// Validates that ACL, writability and Timed interaction settings are ok.
192+
///
193+
/// Returns a success status if all is ok, failure otherwise.
194+
DataModel::ActionReturnStatus CheckWriteAllowed(const Access::SubjectDescriptor & aSubject,
195+
const ConcreteAttributePath & aPath);
196+
188197
// Write the given data to the given path
189198
CHIP_ERROR WriteClusterData(const Access::SubjectDescriptor & aSubject, const ConcreteDataAttributePath & aPath,
190199
TLV::TLVReader & aData);

src/app/data-model-provider/MetadataLookup.cpp

+3-19
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ namespace chip {
2121
namespace app {
2222
namespace DataModel {
2323

24+
using Protocols::InteractionModel::Status;
25+
2426
std::optional<ServerClusterEntry> ServerClusterFinder::Find(const ConcreteClusterPath & path)
2527
{
2628
VerifyOrReturnValue(mProvider != nullptr, std::nullopt);
@@ -63,25 +65,6 @@ std::optional<AttributeEntry> AttributeFinder::Find(const ConcreteAttributePath
6365
return std::nullopt;
6466
}
6567

66-
EndpointFinder::EndpointFinder(ProviderMetadataTree * provider) : mProvider(provider)
67-
{
68-
VerifyOrReturn(mProvider != nullptr);
69-
mEndpoints = mProvider->EndpointsIgnoreError();
70-
}
71-
72-
std::optional<EndpointEntry> EndpointFinder::Find(EndpointId endpointId)
73-
{
74-
for (auto & endpointEntry : mEndpoints)
75-
{
76-
if (endpointEntry.id == endpointId)
77-
{
78-
return endpointEntry;
79-
}
80-
}
81-
82-
return std::nullopt;
83-
}
84-
8568
Protocols::InteractionModel::Status ValidateClusterPath(ProviderMetadataTree * provider, const ConcreteClusterPath & path,
8669
Protocols::InteractionModel::Status successStatus)
8770
{
@@ -90,6 +73,7 @@ Protocols::InteractionModel::Status ValidateClusterPath(ProviderMetadataTree * p
9073
return successStatus;
9174
}
9275

76+
// If we get here, the cluster identified by the path does not exist.
9377
auto endpoints = provider->EndpointsIgnoreError();
9478
for (auto & endpointEntry : endpoints)
9579
{

src/app/data-model-provider/MetadataLookup.h

+8-13
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <app/data-model/MetadataList.h>
2525
#include <lib/core/DataModelTypes.h>
2626
#include <lib/support/CodeUtils.h>
27+
#include <protocols/interaction_model/StatusCode.h>
2728

2829
#include <optional>
2930

@@ -65,20 +66,14 @@ class AttributeFinder
6566
ReadOnlyBuffer<AttributeEntry> mAttributes;
6667
};
6768

68-
/// Helps search for a specific server endpoint in the given
69-
/// metadata provider.
69+
/// Validates that the cluster identified by `path` exists within the given provider.
7070
///
71-
/// Facilitates the operation of "find an endpoint with a given ID".
72-
class EndpointFinder
73-
{
74-
public:
75-
EndpointFinder(ProviderMetadataTree * provider);
76-
std::optional<EndpointEntry> Find(EndpointId endpointId);
77-
78-
private:
79-
ProviderMetadataTree * mProvider;
80-
ReadOnlyBuffer<EndpointEntry> mEndpoints;
81-
};
71+
/// If the endpoint identified by the path does not exist, will return Status::UnsupportedEndpoint.
72+
/// If the endpoint exists but does not have the cluster identified by the path, will return Status::UnsupportedCluster.
73+
///
74+
/// otherwise, it will return successStatus.
75+
Protocols::InteractionModel::Status ValidateClusterPath(ProviderMetadataTree * provider, const ConcreteClusterPath & path,
76+
Protocols::InteractionModel::Status successStatus);
8277

8378
/// Validates that the cluster identified by `path` exists within the given provider.
8479
/// If the endpoint does not exist, will return Status::UnsupportedEndpoint.

src/app/data-model-provider/OperationTypes.h

-11
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,6 @@ struct WriteAttributeRequest : OperationRequest
8888
{
8989
ConcreteDataAttributePath path; // NOTE: this also contains LIST operation options (i.e. "data" path type)
9090
BitFlags<WriteFlags> writeFlags;
91-
92-
// The path of the previous successful write in the same write transaction, if any.
93-
//
94-
// In particular this means that a write to this path has succeeded before (i.e. it passed required ACL checks).
95-
// The intent for this is to allow short-cutting ACL checks when ACL is in progress of being updated:
96-
// - During write chunking, list writes can be of the form "reset list" followed by "append item by item"
97-
// - When ACL is updating, a reset to empty would result in the entire ACL being deny and the "append"
98-
// would fail.
99-
// callers are expected to keep track of a `previousSuccessPath` whenever a write succeeds (otherwise ACL
100-
// checks may fail)
101-
std::optional<ConcreteAttributePath> previousSuccessPath;
10291
};
10392

10493
enum class InvokeFlags : uint32_t

src/app/data-model-provider/Provider.h

+1-6
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,7 @@ class Provider : public ProviderMetadataTree
7676
///
7777
/// When this is invoked, caller is expected to have already done some validations:
7878
/// - cluster `data version` has been checked for the incoming request if applicable
79-
///
80-
/// TEMPORARY/TRANSITIONAL requirement for transitioning from ember-specific code
81-
/// WriteAttribute is REQUIRED to perform:
82-
/// - ACL validation (see notes on OperationFlags::kInternal)
83-
/// - Validation of readability/writability (also controlled by OperationFlags::kInternal)
84-
/// - Validation of timed interaction required (also controlled by OperationFlags::kInternal)
79+
/// - validation of ACL/timed interaction flags/writability, if those checks are desired.
8580
virtual ActionReturnStatus WriteAttribute(const WriteAttributeRequest & request, AttributeValueDecoder & decoder) = 0;
8681

8782
/// `handler` is used to send back the reply.

src/app/data-model-provider/tests/WriteTesting.h

-6
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,6 @@ class WriteOperation
6060
return *this;
6161
}
6262

63-
WriteOperation & SetPreviousSuccessPath(std::optional<ConcreteAttributePath> path)
64-
{
65-
mRequest.previousSuccessPath = path;
66-
return *this;
67-
}
68-
6963
WriteOperation & SetDataVersion(Optional<DataVersion> version)
7064
{
7165
mRequest.path.mDataVersion = version;

src/app/reporting/Engine.cpp

+3-15
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,6 @@ namespace {
5252

5353
using Protocols::InteractionModel::Status;
5454

55-
Status EventPathValid(DataModel::Provider * model, const ConcreteEventPath & eventPath)
56-
{
57-
{
58-
DataModel::ServerClusterFinder serverClusterFinder(model);
59-
if (serverClusterFinder.Find(eventPath).has_value())
60-
{
61-
return Status::Success;
62-
}
63-
}
64-
65-
DataModel::EndpointFinder endpointFinder(model);
66-
return endpointFinder.Find(eventPath.mEndpointId).has_value() ? Status::UnsupportedCluster : Status::UnsupportedEndpoint;
67-
}
68-
6955
/// Returns the status of ACL validation.
7056
/// If the return value has a status set, that means the ACL check failed,
7157
/// the read must not be performed, and the returned status (which may
@@ -530,7 +516,9 @@ CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool &
530516
}
531517

532518
ConcreteEventPath path(current->mValue.mEndpointId, current->mValue.mClusterId, current->mValue.mEventId);
533-
Status status = EventPathValid(mpImEngine->GetDataModelProvider(), path);
519+
520+
// A event path is valid only if the cluster is valid
521+
Status status = DataModel::ValidateClusterPath(mpImEngine->GetDataModelProvider(), path, Status::Success);
534522

535523
if (status != Status::Success)
536524
{

0 commit comments

Comments
 (0)