blob: 5bb7ef95214ce92680702778aca08d18e7ee0017 [file] [log] [blame]
Song GUO56634bf2022-02-10 09:08:23 +08001/*
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
feasel0180ee62024-05-13 11:09:16 -040019#include <memory>
20#include <utility>
21
Arkadiusz Bokowy111f62f2024-07-16 16:22:38 +020022#include <pw_unit_test/framework.h>
23
Song GUO56634bf2022-02-10 09:08:23 +080024#include "app-common/zap-generated/ids/Attributes.h"
25#include "app-common/zap-generated/ids/Clusters.h"
26#include "app/ConcreteAttributePath.h"
27#include "protocols/interaction_model/Constants.h"
28#include <app-common/zap-generated/cluster-objects.h>
Song GUO56634bf2022-02-10 09:08:23 +080029#include <app/AttributeAccessInterface.h>
Andrei Litvin07f4b352024-04-12 14:12:24 -040030#include <app/AttributeAccessInterfaceRegistry.h>
Song GUO56634bf2022-02-10 09:08:23 +080031#include <app/CommandHandlerInterface.h>
32#include <app/InteractionModelEngine.h>
33#include <app/WriteClient.h>
34#include <app/data-model/Decode.h>
35#include <app/tests/AppTestContext.h>
36#include <app/util/DataModelHandler.h>
37#include <app/util/attribute-storage.h>
38#include <controller/InvokeInteraction.h>
Boris Zbarsky9dee6932023-09-28 22:49:28 -040039#include <lib/core/ErrorStr.h>
Arkadiusz Bokowy111f62f2024-07-16 16:22:38 +020040#include <lib/core/StringBuilderAdapters.h>
Song GUO56634bf2022-02-10 09:08:23 +080041#include <lib/support/logging/CHIPLogging.h>
Song GUO01b51372022-04-08 16:57:04 +080042
Song GUO56634bf2022-02-10 09:08:23 +080043using namespace chip;
Song GUO01b51372022-04-08 16:57:04 +080044using namespace chip::app;
Song GUO56634bf2022-02-10 09:08:23 +080045using namespace chip::app::Clusters;
46
47namespace {
48
49uint32_t gIterationCount = 0;
Song GUO56634bf2022-02-10 09:08:23 +080050
51//
52// The generated endpoint_config for the controller app has Endpoint 1
53// already used in the fixed endpoint set of size 1. Consequently, let's use the next
54// number higher than that for our dynamic test endpoint.
55//
Song GUO1020e8c2022-03-01 21:54:04 +080056constexpr EndpointId kTestEndpointId = 2;
57constexpr AttributeId kTestListAttribute = 6;
58constexpr AttributeId kTestListAttribute2 = 7;
59constexpr uint32_t kTestListLength = 5;
Song GUO56634bf2022-02-10 09:08:23 +080060
61// We don't really care about the content, we just need a buffer.
62uint8_t sByteSpanData[app::kMaxSecureSduLengthBytes];
63
feasel74fa3f32024-07-09 14:16:07 -040064class TestWriteChunking : public Test::AppContext
Song GUO56634bf2022-02-10 09:08:23 +080065{
feasel74fa3f32024-07-09 14:16:07 -040066private:
67 using PathStatus = std::pair<app::ConcreteAttributePath, bool>;
feasel0180ee62024-05-13 11:09:16 -040068
69protected:
feasel74fa3f32024-07-09 14:16:07 -040070 enum class Operations : uint8_t
71 {
72 kNoop,
73 kShutdownWriteClient,
74 };
feasel0180ee62024-05-13 11:09:16 -040075
feasel74fa3f32024-07-09 14:16:07 -040076 enum class ListData : uint8_t
77 {
78 kNull,
79 kList,
80 kBadValue,
81 };
feasel0180ee62024-05-13 11:09:16 -040082
feasel74fa3f32024-07-09 14:16:07 -040083 struct Instructions
84 {
85 // The paths used in write request
86 std::vector<ConcreteAttributePath> paths;
87 // The type of content of the list, it should be an empty vector or its size should equals to the list of paths.
88 std::vector<ListData> data;
89 // operations on OnListWriteBegin and OnListWriteEnd on the server side.
90 std::function<Operations(const app::ConcreteAttributePath & path)> onListWriteBeginActions;
91 // The expected status when OnListWriteEnd is called. In the same order as paths
92 std::vector<bool> expectedStatus;
93 };
94
95 void RunTest(Instructions instructions);
Song GUO56634bf2022-02-10 09:08:23 +080096};
97
98//clang-format off
99DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrsOnEndpoint)
Song GUO1020e8c2022-03-01 21:54:04 +0800100DECLARE_DYNAMIC_ATTRIBUTE(kTestListAttribute, ARRAY, 1, ATTRIBUTE_MASK_WRITABLE),
101 DECLARE_DYNAMIC_ATTRIBUTE(kTestListAttribute2, ARRAY, 1, ATTRIBUTE_MASK_WRITABLE), DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
Song GUO56634bf2022-02-10 09:08:23 +0800102
103DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(testEndpointClusters)
Kamil Kasperczykd3894032023-12-13 08:57:45 +0100104DECLARE_DYNAMIC_CLUSTER(Clusters::UnitTesting::Id, testClusterAttrsOnEndpoint, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
105 DECLARE_DYNAMIC_CLUSTER_LIST_END;
Song GUO56634bf2022-02-10 09:08:23 +0800106
107DECLARE_DYNAMIC_ENDPOINT(testEndpoint, testEndpointClusters);
108
109DataVersion dataVersionStorage[ArraySize(testEndpointClusters)];
110
111//clang-format on
112
113class TestWriteCallback : public app::WriteClient::Callback
114{
115public:
116 void OnResponse(const app::WriteClient * apWriteClient, const app::ConcreteDataAttributePath & aPath,
117 app::StatusIB status) override
118 {
119 if (status.mStatus == Protocols::InteractionModel::Status::Success)
120 {
121 mSuccessCount++;
122 }
123 else
124 {
Song GUO1020e8c2022-03-01 21:54:04 +0800125 mLastErrorReason = status;
Song GUO56634bf2022-02-10 09:08:23 +0800126 mErrorCount++;
127 }
128 }
129
Song GUO1020e8c2022-03-01 21:54:04 +0800130 void OnError(const app::WriteClient * apWriteClient, CHIP_ERROR aError) override
131 {
132 mLastErrorReason = app::StatusIB(aError);
133 mErrorCount++;
134 }
Song GUO56634bf2022-02-10 09:08:23 +0800135
136 void OnDone(app::WriteClient * apWriteClient) override { mOnDoneCount++; }
137
138 uint32_t mSuccessCount = 0;
139 uint32_t mErrorCount = 0;
140 uint32_t mOnDoneCount = 0;
Song GUO1020e8c2022-03-01 21:54:04 +0800141 app::StatusIB mLastErrorReason;
Song GUO56634bf2022-02-10 09:08:23 +0800142};
143
144class TestAttrAccess : public app::AttributeAccessInterface
145{
146public:
147 // Register for the Test Cluster cluster on all endpoints.
Andrei Litvincf323302022-11-15 15:00:44 +0100148 TestAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), Clusters::UnitTesting::Id) {}
Song GUO56634bf2022-02-10 09:08:23 +0800149
150 CHIP_ERROR Read(const app::ConcreteReadAttributePath & aPath, app::AttributeValueEncoder & aEncoder) override;
151 CHIP_ERROR Write(const app::ConcreteDataAttributePath & aPath, app::AttributeValueDecoder & aDecoder) override;
Song GUO01b51372022-04-08 16:57:04 +0800152
153 void OnListWriteBegin(const app::ConcreteAttributePath & aPath) override
154 {
155 if (mOnListWriteBegin)
156 {
157 mOnListWriteBegin(aPath);
158 }
159 }
160
161 void OnListWriteEnd(const app::ConcreteAttributePath & aPath, bool aWriteWasSuccessful) override
162 {
163 if (mOnListWriteEnd)
164 {
165 mOnListWriteEnd(aPath, aWriteWasSuccessful);
166 }
167 }
168
169 std::function<void(const app::ConcreteAttributePath & path)> mOnListWriteBegin;
170 std::function<void(const app::ConcreteAttributePath & path, bool wasSuccessful)> mOnListWriteEnd;
Song GUO56634bf2022-02-10 09:08:23 +0800171} testServer;
172
173CHIP_ERROR TestAttrAccess::Read(const app::ConcreteReadAttributePath & aPath, app::AttributeValueEncoder & aEncoder)
174{
175 return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
176}
177
178CHIP_ERROR TestAttrAccess::Write(const app::ConcreteDataAttributePath & aPath, app::AttributeValueDecoder & aDecoder)
179{
180 // We only care about the number of attribute data.
181 if (!aPath.IsListItemOperation())
182 {
Song GUO01b51372022-04-08 16:57:04 +0800183 app::DataModel::Nullable<app::DataModel::DecodableList<ByteSpan>> list;
Song GUO56634bf2022-02-10 09:08:23 +0800184 CHIP_ERROR err = aDecoder.Decode(list);
185 ChipLogError(Zcl, "Decode result: %s", err.AsString());
186 return err;
187 }
Andrei Litvin1866f462022-04-08 05:21:04 -1000188 if (aPath.mListOp == app::ConcreteDataAttributePath::ListOperation::AppendItem)
Song GUO56634bf2022-02-10 09:08:23 +0800189 {
190 ByteSpan listItem;
191 CHIP_ERROR err = aDecoder.Decode(listItem);
192 ChipLogError(Zcl, "Decode result: %s", err.AsString());
193 return err;
194 }
Andrei Litvin1866f462022-04-08 05:21:04 -1000195
196 return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
Song GUO56634bf2022-02-10 09:08:23 +0800197}
198
199/*
200 * This validates all the various corner cases encountered during chunking by artificially reducing the size of a packet buffer used
201 * to encode attribute data to force chunking to happen over multiple packets even with a small number of attributes and then slowly
202 * increasing the available size by 1 byte in each test iteration and re-running the write request generation logic. This 1-byte
203 * incremental approach sweeps through from a base scenario of N attributes fitting in a write request chunk, to eventually
204 * resulting in N+1 attributes fitting in a write request chunk.
205 *
206 * This will cause all the various corner cases encountered of closing out the various containers within the write request and
207 * thoroughly and definitely validate those edge cases.
208 */
feasel0180ee62024-05-13 11:09:16 -0400209TEST_F(TestWriteChunking, TestListChunking)
Song GUO56634bf2022-02-10 09:08:23 +0800210{
feasel74fa3f32024-07-09 14:16:07 -0400211 auto sessionHandle = GetSessionBobToAlice();
Song GUO56634bf2022-02-10 09:08:23 +0800212
213 // Initialize the ember side server logic
Boris Zbarsky0edb83a2023-02-17 12:35:46 -0500214 InitDataModelHandler();
Song GUO56634bf2022-02-10 09:08:23 +0800215
216 // Register our fake dynamic endpoint.
Jerry Johns40b08b92022-04-06 18:31:35 -0700217 emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage));
Song GUO56634bf2022-02-10 09:08:23 +0800218
219 // Register our fake attribute access interface.
Andrei Litvinb85f0762024-08-07 20:26:04 -0400220 AttributeAccessInterfaceRegistry::Instance().Register(&testServer);
Song GUO56634bf2022-02-10 09:08:23 +0800221
Andrei Litvincf323302022-11-15 15:00:44 +0100222 app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute);
Song GUO56634bf2022-02-10 09:08:23 +0800223 //
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400224 // We've empirically determined that by reserving all but 75 bytes in the packet buffer, we can fit 2
Song GUO56634bf2022-02-10 09:08:23 +0800225 // AttributeDataIBs into the packet. ~30-40 bytes covers a single write chunk, but let's 2-3x that
226 // to ensure we'll sweep from fitting 2 chunks to 3-4 chunks.
227 //
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400228 constexpr size_t minReservationSize = kMaxSecureSduLengthBytes - 75 - 100;
229 for (uint32_t i = 100; i > 0; i--)
Song GUO56634bf2022-02-10 09:08:23 +0800230 {
231 CHIP_ERROR err = CHIP_NO_ERROR;
232 TestWriteCallback writeCallback;
233
234 ChipLogDetail(DataManagement, "Running iteration %d\n", i);
235
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400236 gIterationCount = i;
Song GUO56634bf2022-02-10 09:08:23 +0800237
feasel74fa3f32024-07-09 14:16:07 -0400238 app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing(),
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400239 static_cast<uint16_t>(minReservationSize + i) /* reserved buffer size */);
Song GUO56634bf2022-02-10 09:08:23 +0800240
241 ByteSpan list[kTestListLength];
242
243 err = writeClient.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength));
feasel0180ee62024-05-13 11:09:16 -0400244 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO56634bf2022-02-10 09:08:23 +0800245
246 err = writeClient.SendWriteRequest(sessionHandle);
feasel0180ee62024-05-13 11:09:16 -0400247 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO56634bf2022-02-10 09:08:23 +0800248
249 //
250 // Service the IO + Engine till we get a ReportEnd callback on the client.
251 // Since bugs can happen, we don't want this test to never stop, so create a ceiling for how many
252 // times this can run without seeing expected results.
253 //
254 for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++)
255 {
feasel74fa3f32024-07-09 14:16:07 -0400256 DrainAndServiceIO();
Song GUO56634bf2022-02-10 09:08:23 +0800257 }
258
feasel0180ee62024-05-13 11:09:16 -0400259 EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */);
260 EXPECT_EQ(writeCallback.mErrorCount, 0u);
261 EXPECT_EQ(writeCallback.mOnDoneCount, 1u);
Song GUO56634bf2022-02-10 09:08:23 +0800262
feasel74fa3f32024-07-09 14:16:07 -0400263 EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
Song GUO56634bf2022-02-10 09:08:23 +0800264
265 //
266 // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
267 //
feasel0180ee62024-05-13 11:09:16 -0400268 if (HasFailure())
Song GUO56634bf2022-02-10 09:08:23 +0800269 {
270 break;
271 }
272 }
Song GUO1020e8c2022-03-01 21:54:04 +0800273 emberAfClearDynamicEndpoint(0);
Song GUO56634bf2022-02-10 09:08:23 +0800274}
275
276// We encode a pretty large write payload to test the corner cases related to message layer and secure session overheads.
277// The test should gurantee that if encode returns no error, the send should also success.
278// As the actual overhead may change, we will test over a few possible payload lengths, from 850 to MTU used in write clients.
feasel0180ee62024-05-13 11:09:16 -0400279TEST_F(TestWriteChunking, TestBadChunking)
Song GUO56634bf2022-02-10 09:08:23 +0800280{
feasel74fa3f32024-07-09 14:16:07 -0400281 auto sessionHandle = GetSessionBobToAlice();
Song GUO56634bf2022-02-10 09:08:23 +0800282
283 bool atLeastOneRequestSent = false;
284 bool atLeastOneRequestFailed = false;
285
286 // Initialize the ember side server logic
Boris Zbarsky0edb83a2023-02-17 12:35:46 -0500287 InitDataModelHandler();
Song GUO56634bf2022-02-10 09:08:23 +0800288
289 // Register our fake dynamic endpoint.
Jerry Johns40b08b92022-04-06 18:31:35 -0700290 emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage));
Song GUO56634bf2022-02-10 09:08:23 +0800291
292 // Register our fake attribute access interface.
Andrei Litvinb85f0762024-08-07 20:26:04 -0400293 AttributeAccessInterfaceRegistry::Instance().Register(&testServer);
Song GUO56634bf2022-02-10 09:08:23 +0800294
Andrei Litvincf323302022-11-15 15:00:44 +0100295 app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute);
Song GUO56634bf2022-02-10 09:08:23 +0800296
297 for (int i = 850; i < static_cast<int>(chip::app::kMaxSecureSduLengthBytes); i++)
298 {
299 CHIP_ERROR err = CHIP_NO_ERROR;
300 TestWriteCallback writeCallback;
301
302 ChipLogDetail(DataManagement, "Running iteration with OCTET_STRING length = %d\n", i);
303
304 gIterationCount = (uint32_t) i;
305
feasel74fa3f32024-07-09 14:16:07 -0400306 app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing());
Song GUO56634bf2022-02-10 09:08:23 +0800307
308 ByteSpan list[kTestListLength];
Andrei Litvin72f04712022-11-03 20:10:39 -0400309 for (auto & item : list)
Song GUO56634bf2022-02-10 09:08:23 +0800310 {
Andrei Litvin72f04712022-11-03 20:10:39 -0400311 item = ByteSpan(sByteSpanData, static_cast<uint32_t>(i));
Song GUO56634bf2022-02-10 09:08:23 +0800312 }
313
314 err = writeClient.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength));
315 if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
316 {
317 // This kind of error is expected.
318 atLeastOneRequestFailed = true;
319 continue;
320 }
321
322 atLeastOneRequestSent = true;
323
324 // If we successfully encoded the attribute, then we must be able to send the message.
325 err = writeClient.SendWriteRequest(sessionHandle);
feasel0180ee62024-05-13 11:09:16 -0400326 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO56634bf2022-02-10 09:08:23 +0800327
328 //
329 // Service the IO + Engine till we get a ReportEnd callback on the client.
330 // Since bugs can happen, we don't want this test to never stop, so create a ceiling for how many
331 // times this can run without seeing expected results.
332 //
333 for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++)
334 {
feasel74fa3f32024-07-09 14:16:07 -0400335 DrainAndServiceIO();
Song GUO56634bf2022-02-10 09:08:23 +0800336 }
337
feasel0180ee62024-05-13 11:09:16 -0400338 EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */);
339 EXPECT_EQ(writeCallback.mErrorCount, 0u);
340 EXPECT_EQ(writeCallback.mOnDoneCount, 1u);
Song GUO56634bf2022-02-10 09:08:23 +0800341
feasel74fa3f32024-07-09 14:16:07 -0400342 EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
Song GUO56634bf2022-02-10 09:08:23 +0800343
344 //
345 // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
346 //
feasel0180ee62024-05-13 11:09:16 -0400347 if (HasFailure())
Song GUO56634bf2022-02-10 09:08:23 +0800348 {
349 break;
350 }
351 }
feasel74fa3f32024-07-09 14:16:07 -0400352 EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
feasel0180ee62024-05-13 11:09:16 -0400353 EXPECT_TRUE(atLeastOneRequestSent && atLeastOneRequestFailed);
Song GUO1020e8c2022-03-01 21:54:04 +0800354 emberAfClearDynamicEndpoint(0);
355}
356
357/*
358 * When chunked write is enabled, it is dangerious to handle multiple write requests at the same time. In this case, we will reject
359 * the latter write requests to the same attribute.
360 */
feasel0180ee62024-05-13 11:09:16 -0400361TEST_F(TestWriteChunking, TestConflictWrite)
Song GUO1020e8c2022-03-01 21:54:04 +0800362{
feasel74fa3f32024-07-09 14:16:07 -0400363 auto sessionHandle = GetSessionBobToAlice();
Song GUO1020e8c2022-03-01 21:54:04 +0800364
365 // Initialize the ember side server logic
Boris Zbarsky0edb83a2023-02-17 12:35:46 -0500366 InitDataModelHandler();
Song GUO1020e8c2022-03-01 21:54:04 +0800367
368 // Register our fake dynamic endpoint.
Jerry Johns40b08b92022-04-06 18:31:35 -0700369 emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage));
Song GUO1020e8c2022-03-01 21:54:04 +0800370
371 // Register our fake attribute access interface.
Andrei Litvinb85f0762024-08-07 20:26:04 -0400372 AttributeAccessInterfaceRegistry::Instance().Register(&testServer);
Song GUO1020e8c2022-03-01 21:54:04 +0800373
Andrei Litvincf323302022-11-15 15:00:44 +0100374 app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute);
Song GUO1020e8c2022-03-01 21:54:04 +0800375
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400376 /* use a smaller chunk (128 bytes) so we only need a few attributes in the write request. */
377 constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128;
378
Song GUO1020e8c2022-03-01 21:54:04 +0800379 TestWriteCallback writeCallback1;
feasel74fa3f32024-07-09 14:16:07 -0400380 app::WriteClient writeClient1(&GetExchangeManager(), &writeCallback1, Optional<uint16_t>::Missing(),
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400381 static_cast<uint16_t>(kReserveSize));
Song GUO1020e8c2022-03-01 21:54:04 +0800382
383 TestWriteCallback writeCallback2;
feasel74fa3f32024-07-09 14:16:07 -0400384 app::WriteClient writeClient2(&GetExchangeManager(), &writeCallback2, Optional<uint16_t>::Missing(),
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400385 static_cast<uint16_t>(kReserveSize));
Song GUO1020e8c2022-03-01 21:54:04 +0800386
387 ByteSpan list[kTestListLength];
388
389 CHIP_ERROR err = CHIP_NO_ERROR;
390
391 err = writeClient1.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength));
feasel0180ee62024-05-13 11:09:16 -0400392 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO1020e8c2022-03-01 21:54:04 +0800393 err = writeClient2.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength));
feasel0180ee62024-05-13 11:09:16 -0400394 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO1020e8c2022-03-01 21:54:04 +0800395
396 err = writeClient1.SendWriteRequest(sessionHandle);
feasel0180ee62024-05-13 11:09:16 -0400397 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO1020e8c2022-03-01 21:54:04 +0800398
399 err = writeClient2.SendWriteRequest(sessionHandle);
feasel0180ee62024-05-13 11:09:16 -0400400 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO1020e8c2022-03-01 21:54:04 +0800401
feasel74fa3f32024-07-09 14:16:07 -0400402 DrainAndServiceIO();
Song GUO1020e8c2022-03-01 21:54:04 +0800403
404 {
405 const TestWriteCallback * writeCallbackRef1 = &writeCallback1;
406 const TestWriteCallback * writeCallbackRef2 = &writeCallback2;
407
408 // Exactly one of WriteClient1 and WriteClient2 should success, not both.
409
410 if (writeCallback1.mSuccessCount == 0)
411 {
412 writeCallbackRef2 = &writeCallback1;
413 writeCallbackRef1 = &writeCallback2;
414 }
415
feasel0180ee62024-05-13 11:09:16 -0400416 EXPECT_EQ(writeCallbackRef1->mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */);
417 EXPECT_EQ(writeCallbackRef1->mErrorCount, 0u);
418 EXPECT_EQ(writeCallbackRef2->mSuccessCount, 0u);
419 EXPECT_EQ(writeCallbackRef2->mErrorCount, kTestListLength + 1);
420 EXPECT_EQ(writeCallbackRef2->mLastErrorReason.mStatus, Protocols::InteractionModel::Status::Busy);
Song GUO1020e8c2022-03-01 21:54:04 +0800421
feasel0180ee62024-05-13 11:09:16 -0400422 EXPECT_EQ(writeCallbackRef1->mOnDoneCount, 1u);
423 EXPECT_EQ(writeCallbackRef2->mOnDoneCount, 1u);
Song GUO1020e8c2022-03-01 21:54:04 +0800424 }
425
feasel74fa3f32024-07-09 14:16:07 -0400426 EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
Song GUO1020e8c2022-03-01 21:54:04 +0800427
428 emberAfClearDynamicEndpoint(0);
429}
430
431/*
432 * When chunked write is enabled, it is dangerious to handle multiple write requests at the same time. However, we will allow such
433 * change when writing to different attributes in parallel.
434 */
feasel0180ee62024-05-13 11:09:16 -0400435TEST_F(TestWriteChunking, TestNonConflictWrite)
Song GUO1020e8c2022-03-01 21:54:04 +0800436{
feasel74fa3f32024-07-09 14:16:07 -0400437 auto sessionHandle = GetSessionBobToAlice();
Song GUO1020e8c2022-03-01 21:54:04 +0800438
439 // Initialize the ember side server logic
Boris Zbarsky0edb83a2023-02-17 12:35:46 -0500440 InitDataModelHandler();
Song GUO1020e8c2022-03-01 21:54:04 +0800441
442 // Register our fake dynamic endpoint.
Jerry Johns40b08b92022-04-06 18:31:35 -0700443 emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage));
Song GUO1020e8c2022-03-01 21:54:04 +0800444
445 // Register our fake attribute access interface.
Andrei Litvinb85f0762024-08-07 20:26:04 -0400446 AttributeAccessInterfaceRegistry::Instance().Register(&testServer);
Song GUO1020e8c2022-03-01 21:54:04 +0800447
Andrei Litvincf323302022-11-15 15:00:44 +0100448 app::AttributePathParams attributePath1(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute);
449 app::AttributePathParams attributePath2(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute2);
Song GUO1020e8c2022-03-01 21:54:04 +0800450
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400451 /* use a smaller chunk (128 bytes) so we only need a few attributes in the write request. */
452 constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128;
453
Song GUO1020e8c2022-03-01 21:54:04 +0800454 TestWriteCallback writeCallback1;
feasel74fa3f32024-07-09 14:16:07 -0400455 app::WriteClient writeClient1(&GetExchangeManager(), &writeCallback1, Optional<uint16_t>::Missing(),
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400456 static_cast<uint16_t>(kReserveSize));
Song GUO1020e8c2022-03-01 21:54:04 +0800457
458 TestWriteCallback writeCallback2;
feasel74fa3f32024-07-09 14:16:07 -0400459 app::WriteClient writeClient2(&GetExchangeManager(), &writeCallback2, Optional<uint16_t>::Missing(),
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400460 static_cast<uint16_t>(kReserveSize));
Song GUO1020e8c2022-03-01 21:54:04 +0800461
462 ByteSpan list[kTestListLength];
463
464 CHIP_ERROR err = CHIP_NO_ERROR;
465
466 err = writeClient1.EncodeAttribute(attributePath1, app::DataModel::List<ByteSpan>(list, kTestListLength));
feasel0180ee62024-05-13 11:09:16 -0400467 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO1020e8c2022-03-01 21:54:04 +0800468 err = writeClient2.EncodeAttribute(attributePath2, app::DataModel::List<ByteSpan>(list, kTestListLength));
feasel0180ee62024-05-13 11:09:16 -0400469 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO1020e8c2022-03-01 21:54:04 +0800470
471 err = writeClient1.SendWriteRequest(sessionHandle);
feasel0180ee62024-05-13 11:09:16 -0400472 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO1020e8c2022-03-01 21:54:04 +0800473
474 err = writeClient2.SendWriteRequest(sessionHandle);
feasel0180ee62024-05-13 11:09:16 -0400475 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO1020e8c2022-03-01 21:54:04 +0800476
feasel74fa3f32024-07-09 14:16:07 -0400477 DrainAndServiceIO();
Song GUO1020e8c2022-03-01 21:54:04 +0800478
479 {
feasel0180ee62024-05-13 11:09:16 -0400480 EXPECT_EQ(writeCallback1.mErrorCount, 0u);
481 EXPECT_EQ(writeCallback1.mSuccessCount, kTestListLength + 1);
482 EXPECT_EQ(writeCallback2.mErrorCount, 0u);
483 EXPECT_EQ(writeCallback2.mSuccessCount, kTestListLength + 1);
Song GUO1020e8c2022-03-01 21:54:04 +0800484
feasel0180ee62024-05-13 11:09:16 -0400485 EXPECT_EQ(writeCallback1.mOnDoneCount, 1u);
486 EXPECT_EQ(writeCallback2.mOnDoneCount, 1u);
Song GUO1020e8c2022-03-01 21:54:04 +0800487 }
488
feasel74fa3f32024-07-09 14:16:07 -0400489 EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
Song GUO1020e8c2022-03-01 21:54:04 +0800490
491 emberAfClearDynamicEndpoint(0);
Song GUO56634bf2022-02-10 09:08:23 +0800492}
493
feasel74fa3f32024-07-09 14:16:07 -0400494void TestWriteChunking::RunTest(Instructions instructions)
Song GUO01b51372022-04-08 16:57:04 +0800495{
496 CHIP_ERROR err = CHIP_NO_ERROR;
feasel74fa3f32024-07-09 14:16:07 -0400497 auto sessionHandle = GetSessionBobToAlice();
Song GUO01b51372022-04-08 16:57:04 +0800498
499 TestWriteCallback writeCallback;
500 std::unique_ptr<WriteClient> writeClient = std::make_unique<WriteClient>(
feasel74fa3f32024-07-09 14:16:07 -0400501 &GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing(),
Boris Zbarsky40f39f72023-08-08 11:33:21 -0400502 static_cast<uint16_t>(kMaxSecureSduLengthBytes -
503 128) /* use a smaller chunk so we only need a few attributes in the write request. */);
Song GUO01b51372022-04-08 16:57:04 +0800504
505 ConcreteAttributePath onGoingPath = ConcreteAttributePath();
506 std::vector<PathStatus> status;
507
508 testServer.mOnListWriteBegin = [&](const ConcreteAttributePath & aPath) {
feasel0180ee62024-05-13 11:09:16 -0400509 EXPECT_EQ(onGoingPath, ConcreteAttributePath());
Song GUO01b51372022-04-08 16:57:04 +0800510 onGoingPath = aPath;
andrei-menzopol57471cb2022-04-20 23:31:17 +0300511 ChipLogProgress(Zcl, "OnListWriteBegin endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI,
Song GUO01b51372022-04-08 16:57:04 +0800512 aPath.mEndpointId, ChipLogValueMEI(aPath.mClusterId), ChipLogValueMEI(aPath.mAttributeId));
513 if (instructions.onListWriteBeginActions)
514 {
515 switch (instructions.onListWriteBeginActions(aPath))
516 {
517 case Operations::kNoop:
518 break;
519 case Operations::kShutdownWriteClient:
520 // By setting writeClient to nullptr, we actually shutdown the write interaction to simulate a timeout.
521 writeClient = nullptr;
522 }
523 }
524 };
525 testServer.mOnListWriteEnd = [&](const ConcreteAttributePath & aPath, bool aWasSuccessful) {
feasel0180ee62024-05-13 11:09:16 -0400526 EXPECT_EQ(onGoingPath, aPath);
Song GUO01b51372022-04-08 16:57:04 +0800527 status.push_back(PathStatus(aPath, aWasSuccessful));
528 onGoingPath = ConcreteAttributePath();
andrei-menzopol57471cb2022-04-20 23:31:17 +0300529 ChipLogProgress(Zcl, "OnListWriteEnd endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI,
Song GUO01b51372022-04-08 16:57:04 +0800530 aPath.mEndpointId, ChipLogValueMEI(aPath.mClusterId), ChipLogValueMEI(aPath.mAttributeId));
531 };
532
533 ByteSpan list[kTestListLength];
534 uint8_t badList[kTestListLength];
535
536 if (instructions.data.size() == 0)
537 {
538 instructions.data = std::vector<ListData>(instructions.paths.size(), ListData::kList);
539 }
feasel0180ee62024-05-13 11:09:16 -0400540 EXPECT_EQ(instructions.paths.size(), instructions.data.size());
Song GUO01b51372022-04-08 16:57:04 +0800541
542 for (size_t i = 0; i < instructions.paths.size(); i++)
543 {
544 const auto & p = instructions.paths[i];
545 switch (instructions.data[i])
546 {
547 case ListData::kNull: {
548 DataModel::Nullable<uint8_t> null; // The actual type is not important since we will only put a null value.
549 err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId), null);
550 break;
551 }
552 case ListData::kList: {
553 err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
554 DataModel::List<ByteSpan>(list, kTestListLength));
555 break;
556 }
557 case ListData::kBadValue: {
558 err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
559 DataModel::List<uint8_t>(badList, kTestListLength));
560 break;
561 }
562 }
feasel0180ee62024-05-13 11:09:16 -0400563 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO01b51372022-04-08 16:57:04 +0800564 }
565
566 err = writeClient->SendWriteRequest(sessionHandle);
feasel0180ee62024-05-13 11:09:16 -0400567 EXPECT_EQ(err, CHIP_NO_ERROR);
Song GUO01b51372022-04-08 16:57:04 +0800568
feasel74fa3f32024-07-09 14:16:07 -0400569 GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) +
570 System::Clock::Seconds16(1),
571 [&]() { return GetExchangeManager().GetNumActiveExchanges() == 0; });
Song GUO01b51372022-04-08 16:57:04 +0800572
feasel0180ee62024-05-13 11:09:16 -0400573 EXPECT_EQ(onGoingPath, app::ConcreteAttributePath());
574 EXPECT_EQ(status.size(), instructions.expectedStatus.size());
Song GUO01b51372022-04-08 16:57:04 +0800575
576 for (size_t i = 0; i < status.size(); i++)
577 {
feasel0180ee62024-05-13 11:09:16 -0400578 EXPECT_EQ(status[i], PathStatus(instructions.paths[i], instructions.expectedStatus[i]));
Song GUO01b51372022-04-08 16:57:04 +0800579 }
580
581 testServer.mOnListWriteBegin = nullptr;
582 testServer.mOnListWriteEnd = nullptr;
583}
584
feasel0180ee62024-05-13 11:09:16 -0400585TEST_F(TestWriteChunking, TestTransactionalList)
Song GUO01b51372022-04-08 16:57:04 +0800586{
Song GUO01b51372022-04-08 16:57:04 +0800587 // Initialize the ember side server logic
Boris Zbarsky0edb83a2023-02-17 12:35:46 -0500588 InitDataModelHandler();
Song GUO01b51372022-04-08 16:57:04 +0800589
590 // Register our fake dynamic endpoint.
591 emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage));
592
593 // Register our fake attribute access interface.
Andrei Litvinb85f0762024-08-07 20:26:04 -0400594 AttributeAccessInterfaceRegistry::Instance().Register(&testServer);
Song GUO01b51372022-04-08 16:57:04 +0800595
596 // Test 1: we should receive transaction notifications
597 ChipLogProgress(Zcl, "Test 1: we should receive transaction notifications");
feasel74fa3f32024-07-09 14:16:07 -0400598 RunTest(Instructions{
599 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
600 .expectedStatus = { true },
601 });
Song GUO01b51372022-04-08 16:57:04 +0800602
603 ChipLogProgress(Zcl, "Test 2: we should receive transaction notifications for incomplete list operations");
feasel74fa3f32024-07-09 14:16:07 -0400604 RunTest(Instructions{
605 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
606 .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; },
607 .expectedStatus = { false },
608 });
Song GUO01b51372022-04-08 16:57:04 +0800609
610 ChipLogProgress(Zcl, "Test 3: we should receive transaction notifications for every list in the transaction");
feasel74fa3f32024-07-09 14:16:07 -0400611 RunTest(Instructions{
612 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
613 ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) },
614 .expectedStatus = { true, true },
615 });
Song GUO01b51372022-04-08 16:57:04 +0800616
617 ChipLogProgress(Zcl, "Test 4: we should receive transaction notifications with the status of each list");
feasel74fa3f32024-07-09 14:16:07 -0400618 RunTest(Instructions{
619 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
620 ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) },
621 .onListWriteBeginActions =
622 [&](const app::ConcreteAttributePath & aPath) {
623 if (aPath.mAttributeId == kTestListAttribute2)
624 {
625 return Operations::kShutdownWriteClient;
626 }
627 return Operations::kNoop;
628 },
629 .expectedStatus = { true, false },
630 });
Song GUO01b51372022-04-08 16:57:04 +0800631
632 ChipLogProgress(Zcl,
633 "Test 5: transactional list callbacks will be called for nullable lists, test if it is handled correctly for "
634 "null value before non null values");
feasel74fa3f32024-07-09 14:16:07 -0400635 RunTest(Instructions{
636 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
637 ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
638 .data = { ListData::kNull, ListData::kList },
639 .expectedStatus = { true },
640 });
Song GUO01b51372022-04-08 16:57:04 +0800641
642 ChipLogProgress(Zcl,
643 "Test 6: transactional list callbacks will be called for nullable lists, test if it is handled correctly for "
644 "null value after non null values");
feasel74fa3f32024-07-09 14:16:07 -0400645 RunTest(Instructions{
646 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
647 ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
648 .data = { ListData::kList, ListData::kNull },
649 .expectedStatus = { true },
650 });
Song GUO01b51372022-04-08 16:57:04 +0800651
652 ChipLogProgress(Zcl,
653 "Test 7: transactional list callbacks will be called for nullable lists, test if it is handled correctly for "
654 "null value between non null values");
feasel74fa3f32024-07-09 14:16:07 -0400655 RunTest(Instructions{
656 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
657 ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
658 ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
659 .data = { ListData::kList, ListData::kNull, ListData::kList },
660 .expectedStatus = { true },
661 });
Song GUO01b51372022-04-08 16:57:04 +0800662
663 ChipLogProgress(Zcl, "Test 8: transactional list callbacks will be called for nullable lists");
feasel74fa3f32024-07-09 14:16:07 -0400664 RunTest(Instructions{
665 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
666 .data = { ListData::kNull },
667 .expectedStatus = { true },
668 });
Song GUO01b51372022-04-08 16:57:04 +0800669
670 ChipLogProgress(Zcl,
671 "Test 9: for nullable lists, we should receive notifications for unsuccessful writes when non-fatal occurred "
672 "during processing the requests");
feasel74fa3f32024-07-09 14:16:07 -0400673 RunTest(Instructions{
674 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
675 .data = { ListData::kBadValue },
676 .expectedStatus = { false },
677 });
Song GUO01b51372022-04-08 16:57:04 +0800678
feasel74fa3f32024-07-09 14:16:07 -0400679 EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
Song GUO01b51372022-04-08 16:57:04 +0800680
681 emberAfClearDynamicEndpoint(0);
682}
683
Song GUO56634bf2022-02-10 09:08:23 +0800684} // namespace