Skip to content

Commit 56634bf

Browse files
[IM] Write Chunking Support (project-chip#13641)
* [IM] Implement write chunking with set+append for large lists * Python API update * Update LongListAttribute * Update TestSuite * Add more tests * Fix WriteClient construct call * Run Codegen * Address comments * Restyle * Fix test * Run Codegen * Fix dirty merge * Address comments * Update access-control-server * Fix merge * Updated code-gen * Address comments * Fix unclean merge * Fix GroupWrite Encoding * Fix merge and make access-control-server diff readable * Fix dirty merge * Update group-key-mgmt-server * Fix GroupWrite attribute path on the server side * Adapt WriteHandler for project-chip#14821 * Add ACL check cache * Resolve comments * Fix typo and fix merge * Fix * Comment out #if CONFIG_IM_BUILD_FOR_UNIT_TEST in WriteClient.h * Comment out #if CONFIG_IM_BUILD_FOR_UNIT_TEST in WriteClient.h Co-authored-by: Jerry Johns <johnsj@google.com>
1 parent 14eb3f5 commit 56634bf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+4917
-3372
lines changed

examples/all-clusters-app/all-clusters-common/all-clusters-app.matter

+1-1
Original file line numberDiff line numberDiff line change
@@ -2957,7 +2957,7 @@ server cluster TestCluster = 1295 {
29572957
attribute int8s rangeRestrictedInt8s = 39;
29582958
attribute int16u rangeRestrictedInt16u = 40;
29592959
attribute int16s rangeRestrictedInt16s = 41;
2960-
readonly attribute LONG_OCTET_STRING listLongOctetString[] = 42;
2960+
attribute LONG_OCTET_STRING listLongOctetString[] = 42;
29612961
readonly attribute TestFabricScoped listFabricScoped[] = 43;
29622962
attribute boolean timedWriteBoolean = 48;
29632963
attribute boolean generalErrorBoolean = 49;

examples/chip-tool/commands/clusters/WriteAttributeCommand.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class WriteAttribute : public ModelCommand, public chip::app::WriteClient::Callb
5858
}
5959

6060
/////////// WriteClient Callback Interface /////////
61-
void OnResponse(const chip::app::WriteClient * client, const chip::app::ConcreteAttributePath & path,
61+
void OnResponse(const chip::app::WriteClient * client, const chip::app::ConcreteDataAttributePath & path,
6262
chip::app::StatusIB status) override
6363
{
6464
CHIP_ERROR error = status.ToChipError();
@@ -98,7 +98,7 @@ class WriteAttribute : public ModelCommand, public chip::app::WriteClient::Callb
9898

9999
mWriteClient = std::make_unique<chip::app::WriteClient>(device->GetExchangeManager(), this, mTimedInteractionTimeoutMs);
100100

101-
ReturnErrorOnFailure(mWriteClient->EncodeAttributeWritePayload(attributePathParams, value));
101+
ReturnErrorOnFailure(mWriteClient->EncodeAttribute(attributePathParams, value));
102102
return mWriteClient->SendWriteRequest(device->GetSecureSession().Value());
103103
}
104104

src/app/AttributeAccessToken.h

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
*
3+
* Copyright (c) 2022 Project CHIP Authors
4+
* All rights reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <access/Privilege.h>
22+
#include <app/ConcreteAttributePath.h>
23+
24+
namespace chip {
25+
namespace app {
26+
27+
/**
28+
* AttributeAccessToken records the privilege granted for accessing the specified attribute. This struct is used in chunked write
29+
* to avoid losing privilege when updating ACL items.
30+
*/
31+
struct AttributeAccessToken
32+
{
33+
ConcreteAttributePath mPath;
34+
Access::Privilege mPrivilege;
35+
36+
bool operator==(const AttributeAccessToken & other) const { return mPath == other.mPath && mPrivilege == other.mPrivilege; }
37+
38+
bool Matches(const ConcreteAttributePath & aPath, const Access::Privilege & aPrivilege) const
39+
{
40+
return mPath == aPath && mPrivilege == aPrivilege;
41+
}
42+
};
43+
44+
} // namespace app
45+
} // namespace chip

src/app/BUILD.gn

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ static_library("app") {
4848
"CASEClientPool.h",
4949
"CASESessionManager.cpp",
5050
"CASESessionManager.h",
51+
"ChunkedWriteCallback.cpp",
52+
"ChunkedWriteCallback.h",
5153
"CommandHandler.cpp",
5254
"CommandResponseHelper.h",
5355
"CommandSender.cpp",

src/app/ChunkedWriteCallback.cpp

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
*
3+
* Copyright (c) 2022 Project CHIP Authors
4+
* All rights reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#include <app/ChunkedWriteCallback.h>
20+
21+
namespace chip {
22+
namespace app {
23+
24+
void ChunkedWriteCallback::OnResponse(const WriteClient * apWriteClient, const ConcreteDataAttributePath & aPath, StatusIB aStatus)
25+
{
26+
// We may send a chunked list. To make the behavior consistent whether a list is being chunked or not,
27+
// we merge the write responses for a chunked list here and provide our consumer with a single status response.
28+
if (mLastAttributePath.HasValue())
29+
{
30+
// This is not the first write response.
31+
if (IsAppendingToLastItem(aPath))
32+
{
33+
// This is a response on the same path as what we already have stored. Report the first
34+
// failure status we encountered, and ignore subsequent ones.
35+
if (mAttributeStatus.IsSuccess())
36+
{
37+
mAttributeStatus = aStatus;
38+
}
39+
return;
40+
}
41+
else
42+
{
43+
// This is a response to another attribute write. Report the final result of last attribute write.
44+
callback->OnResponse(apWriteClient, mLastAttributePath.Value(), mAttributeStatus);
45+
}
46+
}
47+
48+
// This is the first report for a new attribute. We assume it will never be a list item operation.
49+
if (aPath.IsListItemOperation())
50+
{
51+
aStatus = StatusIB(CHIP_ERROR_INCORRECT_STATE);
52+
}
53+
54+
mLastAttributePath.SetValue(aPath);
55+
mAttributeStatus = aStatus;
56+
// For the last status in the response, we will call the application callback in OnDone()
57+
}
58+
59+
void ChunkedWriteCallback::OnError(const WriteClient * apWriteClient, CHIP_ERROR aError)
60+
{
61+
callback->OnError(apWriteClient, aError);
62+
}
63+
64+
void ChunkedWriteCallback::OnDone(WriteClient * apWriteClient)
65+
{
66+
if (mLastAttributePath.HasValue())
67+
{
68+
// We have a cached status that has yet to be reported to the application so report it now.
69+
// If we failed to receive the response, or we received a malformed response, OnResponse won't be called,
70+
// mLastAttributePath will be Missing() in this case.
71+
callback->OnResponse(apWriteClient, mLastAttributePath.Value(), mAttributeStatus);
72+
}
73+
74+
callback->OnDone(apWriteClient);
75+
}
76+
77+
bool ChunkedWriteCallback::IsAppendingToLastItem(const ConcreteDataAttributePath & aPath)
78+
{
79+
if (!aPath.IsListItemOperation())
80+
{
81+
return false;
82+
}
83+
if (!mLastAttributePath.HasValue() || !(mLastAttributePath.Value() == aPath))
84+
{
85+
return false;
86+
}
87+
return aPath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem;
88+
}
89+
90+
} // namespace app
91+
} // namespace chip

src/app/ChunkedWriteCallback.h

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
*
3+
* Copyright (c) 2022 Project CHIP Authors
4+
* All rights reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <app/WriteClient.h>
22+
23+
namespace chip {
24+
namespace app {
25+
26+
/*
27+
* This is an adapter that intercepts calls that deliver status codes from the WriteClient and
28+
* selectively "merge"s the status codes for a chunked list write as follows:
29+
* - If the whole list was successfully written, callback->OnResponse will be called with success.
30+
* - If any element in the list was not successfully written, callback->OnResponse will be called with the first error received.
31+
* - callback->OnResponse will always have NotList as mListOp since we have merged the chunked responses.
32+
* The merge logic assumes all list operations are part of list chunking.
33+
*/
34+
class ChunkedWriteCallback : public WriteClient::Callback
35+
{
36+
public:
37+
ChunkedWriteCallback(WriteClient::Callback * apCallback) : callback(apCallback) {}
38+
39+
void OnResponse(const WriteClient * apWriteClient, const ConcreteDataAttributePath & aPath, StatusIB status) override;
40+
void OnError(const WriteClient * apWriteClient, CHIP_ERROR aError) override;
41+
void OnDone(WriteClient * apWriteClient) override;
42+
43+
private:
44+
bool IsAppendingToLastItem(const ConcreteDataAttributePath & aPath);
45+
46+
// We are using the casts between ConcreteAttributePath and ConcreteDataAttributePath, then all paths passed to upper
47+
// applications will always have NotList as mListOp.
48+
Optional<ConcreteAttributePath> mLastAttributePath;
49+
StatusIB mAttributeStatus;
50+
51+
WriteClient::Callback * callback;
52+
};
53+
54+
} // namespace app
55+
} // namespace chip

src/app/InteractionModelEngine.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,8 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr
337337
/**
338338
* TODO: Document.
339339
*/
340-
CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, ClusterInfo & aClusterInfo,
341-
TLV::TLVReader & aReader, WriteHandler * apWriteHandler);
340+
CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor,
341+
const ConcreteDataAttributePath & aAttributePath, TLV::TLVReader & aReader,
342+
WriteHandler * apWriteHandler);
342343
} // namespace app
343344
} // namespace chip

src/app/MessageDef/AttributePathIB.cpp

+51
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,33 @@ CHIP_ERROR AttributePathIB::Parser::GetListIndex(DataModel::Nullable<ListIndex>
198198
return GetNullableUnsignedInteger(to_underlying(Tag::kListIndex), apListIndex);
199199
}
200200

201+
CHIP_ERROR AttributePathIB::Parser::GetListIndex(ConcreteDataAttributePath & aAttributePath) const
202+
{
203+
CHIP_ERROR err = CHIP_NO_ERROR;
204+
DataModel::Nullable<ListIndex> listIndex;
205+
err = GetListIndex(&(listIndex));
206+
if (err == CHIP_NO_ERROR)
207+
{
208+
if (listIndex.IsNull())
209+
{
210+
aAttributePath.mListOp = ConcreteDataAttributePath::ListOperation::AppendItem;
211+
}
212+
else
213+
{
214+
// TODO: Add ListOperation::ReplaceItem support. (Attribute path with valid list index)
215+
err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH;
216+
}
217+
}
218+
else if (CHIP_END_OF_TLV == err)
219+
{
220+
// We do not have the context for the actual data type here. We always set the list operation to not list and the users
221+
// should interpret it as ReplaceAll when the attribute type is a list.
222+
aAttributePath.mListOp = ConcreteDataAttributePath::ListOperation::NotList;
223+
err = CHIP_NO_ERROR;
224+
}
225+
return err;
226+
}
227+
201228
AttributePathIB::Builder & AttributePathIB::Builder::EnableTagCompression(const bool aEnableTagCompression)
202229
{
203230
// skip if error has already been set
@@ -300,5 +327,29 @@ CHIP_ERROR AttributePathIB::Builder::Encode(const AttributePathParams & aAttribu
300327
return GetError();
301328
}
302329

330+
CHIP_ERROR AttributePathIB::Builder::Encode(const ConcreteDataAttributePath & aAttributePath)
331+
{
332+
Endpoint(aAttributePath.mEndpointId);
333+
Cluster(aAttributePath.mClusterId);
334+
Attribute(aAttributePath.mAttributeId);
335+
336+
if (!aAttributePath.IsListOperation() || aAttributePath.mListOp == ConcreteDataAttributePath::ListOperation::ReplaceAll)
337+
{
338+
/* noop */
339+
}
340+
else if (aAttributePath.mListOp == ConcreteDataAttributePath::ListOperation::AppendItem)
341+
{
342+
ListIndex(DataModel::NullNullable);
343+
}
344+
else
345+
{
346+
// TODO: Add ListOperation::ReplaceItem support. (Attribute path with valid list index)
347+
return CHIP_ERROR_INVALID_ARGUMENT;
348+
}
349+
350+
EndOfAttributePathIB();
351+
return GetError();
352+
}
353+
303354
} // namespace app
304355
} // namespace chip

src/app/MessageDef/AttributePathIB.h

+13
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,18 @@ class Parser : public ListParser
139139
* #CHIP_END_OF_TLV if there is no such element
140140
*/
141141
CHIP_ERROR GetListIndex(DataModel::Nullable<ListIndex> * const apListIndex) const;
142+
143+
/**
144+
* @brief Get the ListIndex, and set the mListIndex and mListOp fields in the ConcreteDataAttributePath accordingly. It will set
145+
* ListOp to NotList when the list index is missing, users should interpret it as ReplaceAll according to the context.
146+
*
147+
* @param [in] aAttributePath The attribute path object for setting list index and list op.
148+
*
149+
* @return #CHIP_NO_ERROR on success
150+
*/
151+
CHIP_ERROR GetListIndex(ConcreteDataAttributePath & aAttributePath) const;
152+
153+
// TODO(#14934) Add a function to get ConcreteDataAttributePath from AttributePathIB::Parser directly.
142154
};
143155

144156
class Builder : public ListBuilder
@@ -209,6 +221,7 @@ class Builder : public ListBuilder
209221
AttributePathIB::Builder & EndOfAttributePathIB();
210222

211223
CHIP_ERROR Encode(const AttributePathParams & aAttributePathParams);
224+
CHIP_ERROR Encode(const ConcreteDataAttributePath & aAttributePathParams);
212225
};
213226
} // namespace AttributePathIB
214227
} // namespace app

src/app/MessageDef/WriteRequestMessage.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,7 @@ CHIP_ERROR WriteRequestMessage::Parser::CheckSchemaValidity() const
125125

126126
if (CHIP_END_OF_TLV == err)
127127
{
128-
const int RequiredFields = (1 << to_underlying(Tag::kIsFabricFiltered)) | (1 << to_underlying(Tag::kTimedRequest)) |
129-
(1 << to_underlying(Tag::kWriteRequests));
128+
const int RequiredFields = ((1 << to_underlying(Tag::kTimedRequest)) | (1 << to_underlying(Tag::kWriteRequests)));
130129

131130
if ((tagPresenceMask & RequiredFields) == RequiredFields)
132131
{

src/app/ReadClient.cpp

+1-16
Original file line numberDiff line numberDiff line change
@@ -533,22 +533,7 @@ CHIP_ERROR ReadClient::ProcessAttributePath(AttributePathIB::Parser & aAttribute
533533
VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
534534
err = aAttributePathParser.GetAttribute(&(aAttributePath.mAttributeId));
535535
VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
536-
537-
DataModel::Nullable<ListIndex> listIndex;
538-
err = aAttributePathParser.GetListIndex(&(listIndex));
539-
if (CHIP_END_OF_TLV == err)
540-
{
541-
err = CHIP_NO_ERROR;
542-
}
543-
else if (listIndex.IsNull())
544-
{
545-
aAttributePath.mListOp = ConcreteDataAttributePath::ListOperation::AppendItem;
546-
}
547-
else
548-
{
549-
// TODO: Add ListOperation::ReplaceItem support. (Attribute path with valid list index)
550-
err = CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH;
551-
}
536+
err = aAttributePathParser.GetListIndex(aAttributePath);
552537
VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH);
553538
return CHIP_NO_ERROR;
554539
}

0 commit comments

Comments
 (0)