Line data Source code
1 : /*
2 : * Copyright (c) 2024 Project CHIP Authors
3 : * All rights reserved.
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 : #include <data-model-providers/codegen/CodegenDataModelProvider.h>
18 :
19 : #include <access/AccessControl.h>
20 : #include <app-common/zap-generated/attribute-type.h>
21 : #include <app/AttributeAccessInterface.h>
22 : #include <app/AttributeAccessInterfaceRegistry.h>
23 : #include <app/GlobalAttributes.h>
24 : #include <app/RequiredPrivilege.h>
25 : #include <app/data-model/FabricScoped.h>
26 : #include <app/reporting/reporting.h>
27 : #include <app/util/af-types.h>
28 : #include <app/util/attribute-metadata.h>
29 : #include <app/util/attribute-storage-detail.h>
30 : #include <app/util/attribute-storage-null-handling.h>
31 : #include <app/util/attribute-storage.h>
32 : #include <app/util/attribute-table-detail.h>
33 : #include <app/util/attribute-table.h>
34 : #include <app/util/ember-io-storage.h>
35 : #include <app/util/ember-strings.h>
36 : #include <app/util/odd-sized-integers.h>
37 : #include <data-model-providers/codegen/EmberAttributeDataBuffer.h>
38 : #include <data-model-providers/codegen/EmberMetadata.h>
39 : #include <lib/core/CHIPError.h>
40 : #include <lib/support/CodeUtils.h>
41 :
42 : #include <zap-generated/endpoint_config.h>
43 :
44 : namespace chip {
45 : namespace app {
46 : namespace {
47 :
48 : using namespace chip::app::Compatibility::Internal;
49 : using Protocols::InteractionModel::Status;
50 :
51 : class ContextAttributesChangeListener : public AttributesChangedListener
52 : {
53 : public:
54 2577 : ContextAttributesChangeListener(const DataModel::InteractionModelContext & context) : mListener(context.dataModelChangeListener)
55 2577 : {}
56 2544 : void MarkDirty(const AttributePathParams & path) override { mListener->MarkDirty(path); }
57 :
58 : private:
59 : DataModel::ProviderChangeListener * mListener;
60 : };
61 :
62 : /// Attempts to write via an attribute access interface (AAI)
63 : ///
64 : /// If it returns a CHIP_ERROR, then this is a FINAL result (i.e. either failure or success)
65 : ///
66 : /// If it returns std::nullopt, then there is no AAI to handle the given path
67 : /// and processing should figure out the value otherwise (generally from other ember data)
68 2577 : std::optional<CHIP_ERROR> TryWriteViaAccessInterface(const ConcreteDataAttributePath & path, AttributeAccessInterface * aai,
69 : AttributeValueDecoder & decoder)
70 : {
71 : // Processing can happen only if an attribute access interface actually exists..
72 2577 : if (aai == nullptr)
73 : {
74 137 : return std::nullopt;
75 : }
76 :
77 2440 : CHIP_ERROR err = aai->Write(path, decoder);
78 :
79 2440 : if (err != CHIP_NO_ERROR)
80 : {
81 6 : return std::make_optional(err);
82 : }
83 :
84 : // If the decoder tried to decode, then a value should have been read for processing.
85 : // - if decoding was done, assume DONE (i.e. final CHIP_NO_ERROR)
86 : // - otherwise, if no decoding done, return that processing must continue via nullopt
87 4868 : return decoder.TriedDecode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt;
88 : }
89 :
90 : } // namespace
91 :
92 2582 : DataModel::ActionReturnStatus CodegenDataModelProvider::WriteAttribute(const DataModel::WriteAttributeRequest & request,
93 : AttributeValueDecoder & decoder)
94 : {
95 2582 : auto metadata = Ember::FindAttributeMetadata(request.path);
96 :
97 : // Explicit failure in finding a suitable metadata
98 2582 : if (const Status * status = std::get_if<Status>(&metadata))
99 : {
100 4 : VerifyOrDie((*status == Status::UnsupportedEndpoint) || //
101 : (*status == Status::UnsupportedCluster) || //
102 : (*status == Status::UnsupportedAttribute));
103 :
104 : // Check if this is an attribute that ember does not know about but is valid after all and
105 : // adjust the return code. All these global attributes are `read only` hence the return
106 : // of unsupported write.
107 : //
108 : // If the cluster or endpoint does not exist, though, keep that return code.
109 6 : if ((*status == Protocols::InteractionModel::Status::UnsupportedAttribute) &&
110 2 : IsSupportedGlobalAttributeNotInMetadata(request.path.mAttributeId))
111 : {
112 1 : return Status::UnsupportedWrite;
113 : }
114 :
115 3 : return *status;
116 : }
117 :
118 2578 : const EmberAfAttributeMetadata ** attributeMetadata = std::get_if<const EmberAfAttributeMetadata *>(&metadata);
119 2578 : VerifyOrDie(*attributeMetadata != nullptr);
120 :
121 : // Extra check: internal requests can bypass the read only check, however global attributes
122 : // have no underlying storage, so write still cannot be done
123 : //
124 : // I.e. if we get a `EmberAfCluster*` value from finding metadata, we fail here.
125 2578 : VerifyOrReturnError(attributeMetadata != nullptr, Status::UnsupportedWrite);
126 :
127 2578 : if (request.path.mDataVersion.HasValue())
128 : {
129 2 : DataVersion * versionPtr = emberAfDataVersionStorage(request.path);
130 :
131 2 : if (versionPtr == nullptr)
132 : {
133 0 : ChipLogError(DataManagement, "Unable to get cluster info for Endpoint 0x%x, Cluster " ChipLogFormatMEI,
134 : request.path.mEndpointId, ChipLogValueMEI(request.path.mClusterId));
135 0 : return Status::DataVersionMismatch;
136 : }
137 :
138 2 : if (request.path.mDataVersion.Value() != *versionPtr)
139 : {
140 1 : ChipLogError(DataManagement, "Write Version mismatch for Endpoint 0x%x, Cluster " ChipLogFormatMEI,
141 : request.path.mEndpointId, ChipLogValueMEI(request.path.mClusterId));
142 1 : return Status::DataVersionMismatch;
143 : }
144 : }
145 :
146 2577 : ContextAttributesChangeListener change_listener(CurrentContext());
147 :
148 : AttributeAccessInterface * aai =
149 2577 : AttributeAccessInterfaceRegistry::Instance().Get(request.path.mEndpointId, request.path.mClusterId);
150 2577 : std::optional<CHIP_ERROR> aai_result = TryWriteViaAccessInterface(request.path, aai, decoder);
151 2577 : if (aai_result.has_value())
152 : {
153 2436 : if (*aai_result == CHIP_NO_ERROR)
154 : {
155 : // TODO: this is awkward since it provides AAI no control over this, specifically
156 : // AAI may not want to increase versions for some attributes that are Q
157 2430 : emberAfAttributeChanged(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId, &change_listener);
158 : }
159 2436 : return *aai_result;
160 : }
161 :
162 141 : MutableByteSpan dataBuffer = gEmberAttributeIOBufferSpan;
163 : {
164 141 : Ember::EmberAttributeDataBuffer emberData(*attributeMetadata, dataBuffer);
165 141 : ReturnErrorOnFailure(decoder.Decode(emberData));
166 : }
167 :
168 : Protocols::InteractionModel::Status status;
169 :
170 117 : if (dataBuffer.size() > (*attributeMetadata)->size)
171 : {
172 1 : ChipLogDetail(Zcl, "Data to write exceeds the attribute size claimed.");
173 1 : return Status::InvalidValue;
174 : }
175 :
176 116 : EmberAfWriteDataInput dataInput(dataBuffer.data(), (*attributeMetadata)->attributeType);
177 :
178 116 : dataInput.SetChangeListener(&change_listener);
179 : // TODO: dataInput.SetMarkDirty() should be according to `ChangesOmmited`
180 :
181 116 : if (request.operationFlags.Has(DataModel::OperationFlags::kInternal))
182 : {
183 : // Internal requests use the non-External interface that has less enforcement
184 : // than the external version (e.g. does not check/enforce writable settings, does not
185 : // validate attribute types) - see attribute-table.h documentation for details.
186 0 : status = emberAfWriteAttribute(request.path, dataInput);
187 : }
188 : else
189 : {
190 116 : status = emAfWriteAttributeExternal(request.path, dataInput);
191 : }
192 :
193 116 : if (status != Protocols::InteractionModel::Status::Success)
194 : {
195 2 : return status;
196 : }
197 :
198 114 : return CHIP_NO_ERROR;
199 2577 : }
200 :
201 0 : void CodegenDataModelProvider::Temporary_ReportAttributeChanged(const AttributePathParams & path)
202 : {
203 0 : ContextAttributesChangeListener change_listener(CurrentContext());
204 0 : if (path.mClusterId != kInvalidClusterId)
205 : {
206 0 : emberAfAttributeChanged(path.mEndpointId, path.mClusterId, path.mAttributeId, &change_listener);
207 : }
208 : else
209 : {
210 : // When the path has wildcard cluster Id, call the emberAfEndpointChanged to mark attributes on the given endpoint
211 : // as having changing, but do NOT increase/alter any cluster data versions, as this happens when a bridged endpoint is
212 : // added or removed from a bridge and the cluster data is not changed during the process.
213 0 : emberAfEndpointChanged(path.mEndpointId, &change_listener);
214 : }
215 0 : }
216 :
217 : } // namespace app
218 : } // namespace chip
|