forked from project-chip/connectedhomeip
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCodegenDataModel_Read.cpp
337 lines (297 loc) · 13.9 KB
/
CodegenDataModel_Read.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/*
* 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/codegen-data-model/CodegenDataModel.h>
#include <optional>
#include <variant>
#include <access/AccessControl.h>
#include <access/Privilege.h>
#include <access/RequestPath.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app/AttributeAccessInterface.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/AttributeValueEncoder.h>
#include <app/GlobalAttributes.h>
#include <app/RequiredPrivilege.h>
#include <app/codegen-data-model/EmberMetadata.h>
#include <app/data-model/FabricScoped.h>
#include <app/util/af-types.h>
#include <app/util/attribute-metadata.h>
#include <app/util/attribute-storage-detail.h>
#include <app/util/attribute-storage-null-handling.h>
#include <app/util/attribute-storage.h>
#include <app/util/ember-global-attribute-access-interface.h>
#include <app/util/ember-io-storage.h>
#include <app/util/endpoint-config-api.h>
#include <app/util/odd-sized-integers.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CodeUtils.h>
#include <zap-generated/endpoint_config.h>
namespace chip {
namespace app {
namespace {
using namespace chip::app::Compatibility::Internal;
/// Attempts to read via an attribute access interface (AAI)
///
/// If it returns a CHIP_ERROR, then this is a FINAL result (i.e. either failure or success).
///
/// If it returns std::nullopt, then there is no AAI to handle the given path
/// and processing should figure out the value otherwise (generally from other ember data)
std::optional<CHIP_ERROR> TryReadViaAccessInterface(const ConcreteAttributePath & path, AttributeAccessInterface * aai,
AttributeValueEncoder & encoder)
{
// Processing can happen only if an attribute access interface actually exists..
if (aai == nullptr)
{
return std::nullopt;
}
CHIP_ERROR err = aai->Read(path, encoder);
if (err != CHIP_NO_ERROR)
{
// Implementation of 8.4.3.2 of the spec for path expansion
if (path.mExpanded && (err == CHIP_IM_GLOBAL_STATUS(UnsupportedRead)))
{
return CHIP_NO_ERROR;
}
return err;
}
// If the encoder tried to encode, then a value should have been written.
// - if encode, assume DONE (i.e. FINAL CHIP_NO_ERROR)
// - if no encode, say that processing must continue
return encoder.TriedEncode() ? std::make_optional(CHIP_NO_ERROR) : std::nullopt;
}
/// Metadata of what a ember/pascal short string means (prepended by a u8 length)
struct ShortPascalString
{
using LengthType = uint8_t;
static constexpr LengthType kNullLength = 0xFF;
static LengthType GetLength(const uint8_t * buffer)
{
// NOTE: we do NOT use emberAfLongStringLength because that will result in 0 length
// for null strings
return *buffer;
}
};
/// Metadata of what a ember/pascal LONG string means (prepended by a u16 length)
struct LongPascalString
{
using LengthType = uint16_t;
static constexpr LengthType kNullLength = 0xFFFF;
static LengthType GetLength(const uint8_t * buffer)
{
// NOTE: we do NOT use emberAfLongStringLength because that will result in 0 length
// for null strings
return Encoding::LittleEndian::Read16(buffer);
}
};
// ember assumptions ... should just work
static_assert(sizeof(ShortPascalString::LengthType) == 1);
static_assert(sizeof(LongPascalString::LengthType) == 2);
/// Given a ByteSpan containing data from ember, interpret it
/// as a span of type OUT (i.e. ByteSpan or CharSpan) given a ENCODING
/// where ENCODING is Short or Long pascal strings.
template <class OUT, class ENCODING>
std::optional<OUT> ExtractEmberString(ByteSpan data)
{
VerifyOrDie(sizeof(typename ENCODING::LengthType) <= data.size());
auto len = ENCODING::GetLength(data.data());
if (len == ENCODING::kNullLength)
{
return std::nullopt;
}
VerifyOrDie(static_cast<size_t>(len + sizeof(len)) <= data.size());
return std::make_optional<OUT>(reinterpret_cast<typename OUT::pointer>(data.data() + sizeof(len)), len);
}
/// Encode a value inside `encoder`
///
/// The value encoded will be of type T (e.g. CharSpan or ByteSpan) and it will be decoded
/// via the given ENCODING (i.e. ShortPascalString or LongPascalString)
///
/// isNullable defines if the value of NULL is allowed to be encoded.
template <typename T, class ENCODING>
CHIP_ERROR EncodeStringLike(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder)
{
std::optional<T> value = ExtractEmberString<T, ENCODING>(data);
if (!value.has_value())
{
if (isNullable)
{
return encoder.EncodeNull();
}
return CHIP_ERROR_INCORRECT_STATE;
}
// encode value as-is
return encoder.Encode(*value);
}
/// Encodes a numeric data value of type T from the given ember-encoded buffer `data`.
///
/// isNullable defines if the value of NULL is allowed to be encoded.
template <typename T>
CHIP_ERROR EncodeFromSpan(ByteSpan data, bool isNullable, AttributeValueEncoder & encoder)
{
typename NumericAttributeTraits<T>::StorageType value;
VerifyOrReturnError(data.size() >= sizeof(value), CHIP_ERROR_INVALID_ARGUMENT);
memcpy(&value, data.data(), sizeof(value));
if (isNullable && NumericAttributeTraits<T>::IsNullValue(value))
{
return encoder.EncodeNull();
}
if (!NumericAttributeTraits<T>::CanRepresentValue(isNullable, value))
{
return CHIP_ERROR_INCORRECT_STATE;
}
return encoder.Encode(NumericAttributeTraits<T>::StorageToWorking(value));
}
/// Converts raw ember data from `data` into the encoder
///
/// Uses the attribute `metadata` to determine how the data is encoded into `data` and
/// write a suitable value into `encoder`.
CHIP_ERROR EncodeEmberValue(ByteSpan data, const EmberAfAttributeMetadata * metadata, AttributeValueEncoder & encoder)
{
VerifyOrReturnError(metadata != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
const bool isNullable = metadata->IsNullable();
switch (AttributeBaseType(metadata->attributeType))
{
case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data
return encoder.EncodeNull();
case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean
return EncodeFromSpan<bool>(data, isNullable, encoder);
case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer
return EncodeFromSpan<uint8_t>(data, isNullable, encoder);
case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer
return EncodeFromSpan<uint16_t>(data, isNullable, encoder);
case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer
return EncodeFromSpan<OddSizedInteger<3, false>>(data, isNullable, encoder);
case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer
return EncodeFromSpan<uint32_t>(data, isNullable, encoder);
case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer
return EncodeFromSpan<OddSizedInteger<5, false>>(data, isNullable, encoder);
case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer
return EncodeFromSpan<OddSizedInteger<6, false>>(data, isNullable, encoder);
case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer
return EncodeFromSpan<OddSizedInteger<7, false>>(data, isNullable, encoder);
case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer
return EncodeFromSpan<uint64_t>(data, isNullable, encoder);
case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer
return EncodeFromSpan<int8_t>(data, isNullable, encoder);
case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer
return EncodeFromSpan<int16_t>(data, isNullable, encoder);
case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer
return EncodeFromSpan<OddSizedInteger<3, true>>(data, isNullable, encoder);
case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer
return EncodeFromSpan<int32_t>(data, isNullable, encoder);
case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer
return EncodeFromSpan<OddSizedInteger<5, true>>(data, isNullable, encoder);
case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer
return EncodeFromSpan<OddSizedInteger<6, true>>(data, isNullable, encoder);
case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer
return EncodeFromSpan<OddSizedInteger<7, true>>(data, isNullable, encoder);
case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer
return EncodeFromSpan<int64_t>(data, isNullable, encoder);
case ZCL_SINGLE_ATTRIBUTE_TYPE: // 32-bit float
return EncodeFromSpan<float>(data, isNullable, encoder);
case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float
return EncodeFromSpan<double>(data, isNullable, encoder);
case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string
return EncodeStringLike<CharSpan, ShortPascalString>(data, isNullable, encoder);
case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE:
return EncodeStringLike<CharSpan, LongPascalString>(data, isNullable, encoder);
case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string
return EncodeStringLike<ByteSpan, ShortPascalString>(data, isNullable, encoder);
case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE:
return EncodeStringLike<ByteSpan, LongPascalString>(data, isNullable, encoder);
default:
ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast<int>(metadata->attributeType));
return CHIP_IM_GLOBAL_STATUS(Failure);
}
}
} // namespace
/// separated-out ReadAttribute implementation (given existing complexity)
///
/// Generally will:
/// - validate ACL (only for non-internal requests)
/// - Try to read attribute via the AttributeAccessInterface
/// - Try to read the value from ember RAM storage
CHIP_ERROR CodegenDataModel::ReadAttribute(const InteractionModel::ReadAttributeRequest & request, AttributeValueEncoder & encoder)
{
ChipLogDetail(DataManagement,
"Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=0x%x AttributeId=" ChipLogFormatMEI " (expanded=%d)",
ChipLogValueMEI(request.path.mClusterId), request.path.mEndpointId, ChipLogValueMEI(request.path.mAttributeId),
request.path.mExpanded);
// ACL check for non-internal requests
if (!request.operationFlags.Has(InteractionModel::OperationFlags::kInternal))
{
ReturnErrorCodeIf(!request.subjectDescriptor.has_value(), CHIP_ERROR_INVALID_ARGUMENT);
Access::RequestPath requestPath{ .cluster = request.path.mClusterId, .endpoint = request.path.mEndpointId };
CHIP_ERROR err = Access::GetAccessControl().Check(*request.subjectDescriptor, requestPath,
RequiredPrivilege::ForReadAttribute(request.path));
if (err != CHIP_NO_ERROR)
{
ReturnErrorCodeIf(err != CHIP_ERROR_ACCESS_DENIED, err);
// Implementation of 8.4.3.2 of the spec for path expansion
if (request.path.mExpanded)
{
return CHIP_NO_ERROR;
}
// access denied has a specific code for IM
return CHIP_IM_GLOBAL_STATUS(UnsupportedAccess);
}
}
auto metadata = Ember::FindAttributeMetadata(request.path);
// Explicit failure in finding a suitable metadata
if (const CHIP_ERROR * err = std::get_if<CHIP_ERROR>(&metadata))
{
VerifyOrDie((*err == CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)) || //
(*err == CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)) || //
(*err == CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)));
return *err;
}
// Read via AAI
std::optional<CHIP_ERROR> aai_result;
if (const EmberAfCluster ** cluster = std::get_if<const EmberAfCluster *>(&metadata))
{
Compatibility::GlobalAttributeReader aai(*cluster);
aai_result = TryReadViaAccessInterface(request.path, &aai, encoder);
}
else
{
aai_result = TryReadViaAccessInterface(
request.path, GetAttributeAccessOverride(request.path.mEndpointId, request.path.mClusterId), encoder);
}
ReturnErrorCodeIf(aai_result.has_value(), *aai_result);
if (!std::holds_alternative<const EmberAfAttributeMetadata *>(metadata))
{
// if we only got a cluster, this was for a global attribute. We cannot read ember attributes
// at this point, so give up (although GlobalAttributeReader should have returned something here).
chipDie();
}
const EmberAfAttributeMetadata * attributeMetadata = std::get<const EmberAfAttributeMetadata *>(metadata);
// At this point, we have to use ember directly to read the data.
EmberAfAttributeSearchRecord record;
record.endpoint = request.path.mEndpointId;
record.clusterId = request.path.mClusterId;
record.attributeId = request.path.mAttributeId;
Protocols::InteractionModel::Status status = emAfReadOrWriteAttribute(
&record, &attributeMetadata, gEmberAttributeIOBufferSpan.data(), static_cast<uint16_t>(gEmberAttributeIOBufferSpan.size()),
/* write = */ false);
if (status != Protocols::InteractionModel::Status::Success)
{
return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status);
}
return EncodeEmberValue(gEmberAttributeIOBufferSpan, attributeMetadata, encoder);
}
} // namespace app
} // namespace chip