forked from project-chip/connectedhomeip
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAttributeValueEncoder.h
293 lines (262 loc) · 13.2 KB
/
AttributeValueEncoder.h
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
/*
* Copyright (c) 2021-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 <access/SubjectDescriptor.h>
#include <app/AttributeEncodeState.h>
#include <app/AttributeReportBuilder.h>
#include <app/ConcreteAttributePath.h>
#include <app/MessageDef/AttributeReportIBs.h>
#include <app/data-model/FabricScoped.h>
#include <app/data-model/List.h>
#include <type_traits>
namespace chip {
namespace app {
/**
* The AttributeValueEncoder is a helper class for filling report payloads into AttributeReportIBs.
* The attribute value encoder can be initialized with a AttributeEncodeState for saving and recovering its state between encode
* sessions (chunkings).
*
* When Encode returns recoverable errors (e.g. CHIP_ERROR_NO_MEMORY) the state can be used to initialize the AttributeValueEncoder
* for future use on the same attribute path.
*/
class AttributeValueEncoder
{
public:
class ListEncodeHelper
{
public:
ListEncodeHelper(AttributeValueEncoder & encoder) : mAttributeValueEncoder(encoder) {}
template <typename T, std::enable_if_t<DataModel::IsFabricScoped<T>::value, bool> = true>
CHIP_ERROR Encode(const T & aArg) const
{
VerifyOrReturnError(aArg.GetFabricIndex() != kUndefinedFabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX);
// If we are encoding for a fabric filtered attribute read and the fabric index does not match that present in the
// request, skip encoding this list item.
VerifyOrReturnError(!mAttributeValueEncoder.mIsFabricFiltered ||
aArg.GetFabricIndex() == mAttributeValueEncoder.AccessingFabricIndex(),
CHIP_NO_ERROR);
return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg, mAttributeValueEncoder.AccessingFabricIndex());
}
template <typename T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, bool> = true>
CHIP_ERROR Encode(const T & aArg) const
{
return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg);
}
// overrides that save flash: no need to care about the extra const
// Without this, we have a usage of:
// chip::ChipError chip::app::AttributeValueEncoder::EncodeListItem<unsigned long const&>
// Overall we tend to have very similar code expand (from nm):
// chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned char>(unsigned char&&)
// chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned char&>(unsigned char&)
// chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned char const&>(unsigned char const&)
// chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned short const&>(unsigned short const&)
// chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned long&>(unsigned long&)
// chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned short&>(unsigned short&)
// chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned long long&>(unsigned long long&)
// chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned short>(unsigned short&&)
// chip::ChipError chip::app::AttributeValueEncoder::Encode<unsigned long long>(unsigned long long&&)
// that we try to reduce
//
// TODO:
// - we should figure where the extra const override is used
// - we should try to avoid having such footguns. This list template-explosion seems
// dangerous for flash.
//
// This relies on TLV numbers always being encoded as 64-bit value
inline CHIP_ERROR Encode(uint32_t const & aArg) const { return Encode<uint64_t>(aArg); }
inline CHIP_ERROR Encode(uint32_t & aArg) const { return Encode<uint64_t>(aArg); }
inline CHIP_ERROR Encode(uint16_t const & aArg) const { return Encode<uint64_t>(aArg); }
inline CHIP_ERROR Encode(uint16_t & aArg) const { return Encode<uint64_t>(aArg); }
inline CHIP_ERROR Encode(uint8_t const & aArg) const { return Encode<uint64_t>(aArg); }
inline CHIP_ERROR Encode(uint8_t & aArg) const { return Encode<uint64_t>(aArg); }
private:
AttributeValueEncoder & mAttributeValueEncoder;
// Avoid calling the TLVWriter constructor for every instantiation of
// EncodeListItem. We treat encoding as a const operation, so either
// have to put this on the stack (in which case it's per-instantiation),
// or have it as mutable state.
mutable TLV::TLVWriter mCheckpoint;
};
AttributeValueEncoder(AttributeReportIBs::Builder & aAttributeReportIBsBuilder, Access::SubjectDescriptor subjectDescriptor,
const ConcreteAttributePath & aPath, DataVersion aDataVersion, bool aIsFabricFiltered = false,
const AttributeEncodeState & aState = AttributeEncodeState()) :
mAttributeReportIBsBuilder(aAttributeReportIBsBuilder),
mSubjectDescriptor(subjectDescriptor), mPath(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
mDataVersion(aDataVersion), mIsFabricFiltered(aIsFabricFiltered), mEncodeState(aState)
{}
/**
* Encode a single value. This value will not be chunked; it will either be
* entirely encoded or fail to be encoded. Consumers are allowed to make
* either one call to Encode or one call to EncodeList to handle a read.
*/
template <typename... Ts>
CHIP_ERROR Encode(Ts &&... aArgs)
{
mTriedEncode = true;
return EncodeAttributeReportIB(std::forward<Ts>(aArgs)...);
}
/**
* Encode an explicit null value.
*/
CHIP_ERROR EncodeNull()
{
// Doesn't matter what type Nullable we use here.
return Encode(DataModel::Nullable<uint8_t>());
}
/**
* Encode an explicit empty list.
*/
CHIP_ERROR EncodeEmptyList()
{
// Doesn't matter what type List we use here.
return Encode(DataModel::List<uint8_t>());
}
/**
* aCallback is expected to take a const auto & argument and Encode() on it as many times as needed to encode all the list
* elements one by one. If any of those Encode() calls returns failure, aCallback must stop encoding and return failure. When
* all items are encoded aCallback is expected to return success.
*
* aCallback may not be called. Consumers must not assume it will be called.
*
* When EncodeList returns an error, the consumers must abort the encoding, and return the exact error to the caller.
*
* TODO: Can we hold a error state in the AttributeValueEncoder itself?
*
* Consumers are allowed to make either one call to EncodeList or one call to Encode to handle a read.
*
*/
template <typename ListGenerator>
CHIP_ERROR EncodeList(ListGenerator aCallback)
{
mTriedEncode = true;
// Spec 10.5.4.3.1, 10.5.4.6 (Replace a list w/ Multiple IBs)
// EmptyList acts as the beginning of the whole array type attribute report.
// An empty list is encoded iff both mCurrentEncodingListIndex and mEncodeState.mCurrentEncodingListIndex are invalid
// values. After encoding the empty list, mEncodeState.mCurrentEncodingListIndex and mCurrentEncodingListIndex are set to 0.
ReturnErrorOnFailure(EnsureListStarted());
CHIP_ERROR err = aCallback(ListEncodeHelper(*this));
// Even if encoding list items failed, make sure we EnsureListEnded().
// Since we encode list items atomically, in the case when we just
// didn't fit the next item we want to make sure our list is properly
// ended before the reporting engine starts chunking.
EnsureListEnded();
if (err == CHIP_NO_ERROR)
{
// The Encode procedure finished without any error, clear the state.
mEncodeState.Reset();
}
return err;
}
bool TriedEncode() const { return mTriedEncode; }
const Access::SubjectDescriptor & GetSubjectDescriptor() const { return mSubjectDescriptor; }
/**
* The accessing fabric index for this read or subscribe interaction.
*/
FabricIndex AccessingFabricIndex() const { return GetSubjectDescriptor().fabricIndex; }
/**
* AttributeValueEncoder is a short lived object, and the state is persisted by mEncodeState and restored by constructor.
*/
const AttributeEncodeState & GetState() const { return mEncodeState; }
private:
// We made EncodeListItem() private, and ListEncoderHelper will expose it by Encode()
friend class ListEncodeHelper;
friend class TestOnlyAttributeValueEncoderAccessor;
// Returns true if the list item should be encoded. If it should, the
// passed-in TLVWriter will be used to checkpoint the current state of our
// attribute report list builder.
bool ShouldEncodeListItem(TLV::TLVWriter & aCheckpoint);
// Does any cleanup work needed after attempting to encode a list item.
void PostEncodeListItem(CHIP_ERROR aEncodeStatus, const TLV::TLVWriter & aCheckpoint);
// EncodeListItem may be given an extra FabricIndex argument as a second
// arg, or not. Represent that via a parameter pack (which might be
// empty). In practice, for any given ItemType the extra arg is either there
// or not, so we don't get more template explosion due to aExtraArgs.
template <typename ItemType, typename... ExtraArgTypes>
CHIP_ERROR EncodeListItem(TLV::TLVWriter & aCheckpoint, const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
{
if (!ShouldEncodeListItem(aCheckpoint))
{
return CHIP_NO_ERROR;
}
CHIP_ERROR err;
if (mEncodingInitialList)
{
// Just encode a single item, with an anonymous tag.
AttributeReportBuilder builder;
err = builder.EncodeValue(mAttributeReportIBsBuilder, TLV::AnonymousTag(), aItem,
std::forward<ExtraArgTypes>(aExtraArgs)...);
}
else
{
err = EncodeAttributeReportIB(aItem, std::forward<ExtraArgTypes>(aExtraArgs)...);
}
PostEncodeListItem(err, aCheckpoint);
return err;
}
/**
* Builds a single AttributeReportIB in AttributeReportIBs. The caller is
* responsible for setting up mPath correctly.
*
* In particular, when we are encoding a single element in the list, mPath
* must indicate a null list index to represent an "append" operation.
* operation.
*
* EncodeAttributeReportIB may be given an extra FabricIndex argument as a second
* arg, or not. Represent that via a parameter pack (which might be
* empty). In practice, for any given ItemType the extra arg is either
* there or not, so we don't get more template explosion due to aExtraArgs.
*/
template <typename ItemType, typename... ExtraArgTypes>
CHIP_ERROR EncodeAttributeReportIB(const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
{
AttributeReportBuilder builder;
ReturnErrorOnFailure(builder.PrepareAttribute(mAttributeReportIBsBuilder, mPath, mDataVersion));
ReturnErrorOnFailure(builder.EncodeValue(mAttributeReportIBsBuilder, TLV::ContextTag(AttributeDataIB::Tag::kData), aItem,
std::forward<ExtraArgTypes>(aExtraArgs)...));
return builder.FinishAttribute(mAttributeReportIBsBuilder);
}
/**
* EnsureListStarted sets our mCurrentEncodingListIndex to 0, and:
*
* * If we are just starting the list, gets us ready to encode list items.
*
* * If we are continuing a chunked list, guarantees that mPath.mListOp is
* AppendItem after it returns.
*/
CHIP_ERROR EnsureListStarted();
/**
* EnsureListEnded writes out the end of the list and our attribute data IB,
* if we were encoding our initial list
*/
void EnsureListEnded();
AttributeReportIBs::Builder & mAttributeReportIBsBuilder;
const Access::SubjectDescriptor mSubjectDescriptor;
ConcreteDataAttributePath mPath;
DataVersion mDataVersion;
bool mTriedEncode = false;
bool mIsFabricFiltered = false;
// mEncodingInitialList is true if we're encoding a list and we have not
// started chunking it yet, so we're encoding a single attribute report IB
// for the whole list, not one per item.
bool mEncodingInitialList = false;
// mEncodedAtLeastOneListItem becomes true once we successfully encode a list item.
bool mEncodedAtLeastOneListItem = false;
ListIndex mCurrentEncodingListIndex = kInvalidListIndex;
AttributeEncodeState mEncodeState;
};
} // namespace app
} // namespace chip