Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 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 | |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 19 | #include <memory> |
| 20 | #include <utility> |
| 21 | |
Arkadiusz Bokowy | 111f62f | 2024-07-16 16:22:38 +0200 | [diff] [blame] | 22 | #include <pw_unit_test/framework.h> |
| 23 | |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 24 | #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 GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 29 | #include <app/AttributeAccessInterface.h> |
Andrei Litvin | 07f4b35 | 2024-04-12 14:12:24 -0400 | [diff] [blame] | 30 | #include <app/AttributeAccessInterfaceRegistry.h> |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 31 | #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 Zbarsky | 9dee693 | 2023-09-28 22:49:28 -0400 | [diff] [blame] | 39 | #include <lib/core/ErrorStr.h> |
Arkadiusz Bokowy | 111f62f | 2024-07-16 16:22:38 +0200 | [diff] [blame] | 40 | #include <lib/core/StringBuilderAdapters.h> |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 41 | #include <lib/support/logging/CHIPLogging.h> |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 42 | |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 43 | using namespace chip; |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 44 | using namespace chip::app; |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 45 | using namespace chip::app::Clusters; |
| 46 | |
| 47 | namespace { |
| 48 | |
| 49 | uint32_t gIterationCount = 0; |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 50 | |
| 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 GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 56 | constexpr EndpointId kTestEndpointId = 2; |
| 57 | constexpr AttributeId kTestListAttribute = 6; |
| 58 | constexpr AttributeId kTestListAttribute2 = 7; |
| 59 | constexpr uint32_t kTestListLength = 5; |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 60 | |
| 61 | // We don't really care about the content, we just need a buffer. |
| 62 | uint8_t sByteSpanData[app::kMaxSecureSduLengthBytes]; |
| 63 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 64 | class TestWriteChunking : public Test::AppContext |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 65 | { |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 66 | private: |
| 67 | using PathStatus = std::pair<app::ConcreteAttributePath, bool>; |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 68 | |
| 69 | protected: |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 70 | enum class Operations : uint8_t |
| 71 | { |
| 72 | kNoop, |
| 73 | kShutdownWriteClient, |
| 74 | }; |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 75 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 76 | enum class ListData : uint8_t |
| 77 | { |
| 78 | kNull, |
| 79 | kList, |
| 80 | kBadValue, |
| 81 | }; |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 82 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 83 | 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 GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 96 | }; |
| 97 | |
| 98 | //clang-format off |
| 99 | DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrsOnEndpoint) |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 100 | DECLARE_DYNAMIC_ATTRIBUTE(kTestListAttribute, ARRAY, 1, ATTRIBUTE_MASK_WRITABLE), |
| 101 | DECLARE_DYNAMIC_ATTRIBUTE(kTestListAttribute2, ARRAY, 1, ATTRIBUTE_MASK_WRITABLE), DECLARE_DYNAMIC_ATTRIBUTE_LIST_END(); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 102 | |
| 103 | DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(testEndpointClusters) |
Kamil Kasperczyk | d389403 | 2023-12-13 08:57:45 +0100 | [diff] [blame] | 104 | DECLARE_DYNAMIC_CLUSTER(Clusters::UnitTesting::Id, testClusterAttrsOnEndpoint, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr), |
| 105 | DECLARE_DYNAMIC_CLUSTER_LIST_END; |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 106 | |
| 107 | DECLARE_DYNAMIC_ENDPOINT(testEndpoint, testEndpointClusters); |
| 108 | |
| 109 | DataVersion dataVersionStorage[ArraySize(testEndpointClusters)]; |
| 110 | |
| 111 | //clang-format on |
| 112 | |
| 113 | class TestWriteCallback : public app::WriteClient::Callback |
| 114 | { |
| 115 | public: |
| 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 GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 125 | mLastErrorReason = status; |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 126 | mErrorCount++; |
| 127 | } |
| 128 | } |
| 129 | |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 130 | void OnError(const app::WriteClient * apWriteClient, CHIP_ERROR aError) override |
| 131 | { |
| 132 | mLastErrorReason = app::StatusIB(aError); |
| 133 | mErrorCount++; |
| 134 | } |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 135 | |
| 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 GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 141 | app::StatusIB mLastErrorReason; |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 142 | }; |
| 143 | |
| 144 | class TestAttrAccess : public app::AttributeAccessInterface |
| 145 | { |
| 146 | public: |
| 147 | // Register for the Test Cluster cluster on all endpoints. |
Andrei Litvin | cf32330 | 2022-11-15 15:00:44 +0100 | [diff] [blame] | 148 | TestAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), Clusters::UnitTesting::Id) {} |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 149 | |
| 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 GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 152 | |
| 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 GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 171 | } testServer; |
| 172 | |
| 173 | CHIP_ERROR TestAttrAccess::Read(const app::ConcreteReadAttributePath & aPath, app::AttributeValueEncoder & aEncoder) |
| 174 | { |
| 175 | return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; |
| 176 | } |
| 177 | |
| 178 | CHIP_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 GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 183 | app::DataModel::Nullable<app::DataModel::DecodableList<ByteSpan>> list; |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 184 | CHIP_ERROR err = aDecoder.Decode(list); |
| 185 | ChipLogError(Zcl, "Decode result: %s", err.AsString()); |
| 186 | return err; |
| 187 | } |
Andrei Litvin | 1866f46 | 2022-04-08 05:21:04 -1000 | [diff] [blame] | 188 | if (aPath.mListOp == app::ConcreteDataAttributePath::ListOperation::AppendItem) |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 189 | { |
| 190 | ByteSpan listItem; |
| 191 | CHIP_ERROR err = aDecoder.Decode(listItem); |
| 192 | ChipLogError(Zcl, "Decode result: %s", err.AsString()); |
| 193 | return err; |
| 194 | } |
Andrei Litvin | 1866f46 | 2022-04-08 05:21:04 -1000 | [diff] [blame] | 195 | |
| 196 | return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 197 | } |
| 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 | */ |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 209 | TEST_F(TestWriteChunking, TestListChunking) |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 210 | { |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 211 | auto sessionHandle = GetSessionBobToAlice(); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 212 | |
| 213 | // Initialize the ember side server logic |
Boris Zbarsky | 0edb83a | 2023-02-17 12:35:46 -0500 | [diff] [blame] | 214 | InitDataModelHandler(); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 215 | |
| 216 | // Register our fake dynamic endpoint. |
Jerry Johns | 40b08b9 | 2022-04-06 18:31:35 -0700 | [diff] [blame] | 217 | emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage)); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 218 | |
| 219 | // Register our fake attribute access interface. |
Andrei Litvin | b85f076 | 2024-08-07 20:26:04 -0400 | [diff] [blame] | 220 | AttributeAccessInterfaceRegistry::Instance().Register(&testServer); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 221 | |
Andrei Litvin | cf32330 | 2022-11-15 15:00:44 +0100 | [diff] [blame] | 222 | app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 223 | // |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 224 | // We've empirically determined that by reserving all but 75 bytes in the packet buffer, we can fit 2 |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 225 | // 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 Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 228 | constexpr size_t minReservationSize = kMaxSecureSduLengthBytes - 75 - 100; |
| 229 | for (uint32_t i = 100; i > 0; i--) |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 230 | { |
| 231 | CHIP_ERROR err = CHIP_NO_ERROR; |
| 232 | TestWriteCallback writeCallback; |
| 233 | |
| 234 | ChipLogDetail(DataManagement, "Running iteration %d\n", i); |
| 235 | |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 236 | gIterationCount = i; |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 237 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 238 | app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing(), |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 239 | static_cast<uint16_t>(minReservationSize + i) /* reserved buffer size */); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 240 | |
| 241 | ByteSpan list[kTestListLength]; |
| 242 | |
| 243 | err = writeClient.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength)); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 244 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 245 | |
| 246 | err = writeClient.SendWriteRequest(sessionHandle); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 247 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 248 | |
| 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 | { |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 256 | DrainAndServiceIO(); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 257 | } |
| 258 | |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 259 | 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 GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 262 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 263 | EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 264 | |
| 265 | // |
| 266 | // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. |
| 267 | // |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 268 | if (HasFailure()) |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 269 | { |
| 270 | break; |
| 271 | } |
| 272 | } |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 273 | emberAfClearDynamicEndpoint(0); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 274 | } |
| 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. |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 279 | TEST_F(TestWriteChunking, TestBadChunking) |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 280 | { |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 281 | auto sessionHandle = GetSessionBobToAlice(); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 282 | |
| 283 | bool atLeastOneRequestSent = false; |
| 284 | bool atLeastOneRequestFailed = false; |
| 285 | |
| 286 | // Initialize the ember side server logic |
Boris Zbarsky | 0edb83a | 2023-02-17 12:35:46 -0500 | [diff] [blame] | 287 | InitDataModelHandler(); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 288 | |
| 289 | // Register our fake dynamic endpoint. |
Jerry Johns | 40b08b9 | 2022-04-06 18:31:35 -0700 | [diff] [blame] | 290 | emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage)); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 291 | |
| 292 | // Register our fake attribute access interface. |
Andrei Litvin | b85f076 | 2024-08-07 20:26:04 -0400 | [diff] [blame] | 293 | AttributeAccessInterfaceRegistry::Instance().Register(&testServer); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 294 | |
Andrei Litvin | cf32330 | 2022-11-15 15:00:44 +0100 | [diff] [blame] | 295 | app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 296 | |
| 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 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 306 | app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing()); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 307 | |
| 308 | ByteSpan list[kTestListLength]; |
Andrei Litvin | 72f0471 | 2022-11-03 20:10:39 -0400 | [diff] [blame] | 309 | for (auto & item : list) |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 310 | { |
Andrei Litvin | 72f0471 | 2022-11-03 20:10:39 -0400 | [diff] [blame] | 311 | item = ByteSpan(sByteSpanData, static_cast<uint32_t>(i)); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 312 | } |
| 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); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 326 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 327 | |
| 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 | { |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 335 | DrainAndServiceIO(); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 336 | } |
| 337 | |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 338 | 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 GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 341 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 342 | EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 343 | |
| 344 | // |
| 345 | // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. |
| 346 | // |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 347 | if (HasFailure()) |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 348 | { |
| 349 | break; |
| 350 | } |
| 351 | } |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 352 | EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 353 | EXPECT_TRUE(atLeastOneRequestSent && atLeastOneRequestFailed); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 354 | 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 | */ |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 361 | TEST_F(TestWriteChunking, TestConflictWrite) |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 362 | { |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 363 | auto sessionHandle = GetSessionBobToAlice(); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 364 | |
| 365 | // Initialize the ember side server logic |
Boris Zbarsky | 0edb83a | 2023-02-17 12:35:46 -0500 | [diff] [blame] | 366 | InitDataModelHandler(); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 367 | |
| 368 | // Register our fake dynamic endpoint. |
Jerry Johns | 40b08b9 | 2022-04-06 18:31:35 -0700 | [diff] [blame] | 369 | emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage)); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 370 | |
| 371 | // Register our fake attribute access interface. |
Andrei Litvin | b85f076 | 2024-08-07 20:26:04 -0400 | [diff] [blame] | 372 | AttributeAccessInterfaceRegistry::Instance().Register(&testServer); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 373 | |
Andrei Litvin | cf32330 | 2022-11-15 15:00:44 +0100 | [diff] [blame] | 374 | app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 375 | |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 376 | /* 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 GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 379 | TestWriteCallback writeCallback1; |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 380 | app::WriteClient writeClient1(&GetExchangeManager(), &writeCallback1, Optional<uint16_t>::Missing(), |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 381 | static_cast<uint16_t>(kReserveSize)); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 382 | |
| 383 | TestWriteCallback writeCallback2; |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 384 | app::WriteClient writeClient2(&GetExchangeManager(), &writeCallback2, Optional<uint16_t>::Missing(), |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 385 | static_cast<uint16_t>(kReserveSize)); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 386 | |
| 387 | ByteSpan list[kTestListLength]; |
| 388 | |
| 389 | CHIP_ERROR err = CHIP_NO_ERROR; |
| 390 | |
| 391 | err = writeClient1.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength)); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 392 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 393 | err = writeClient2.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength)); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 394 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 395 | |
| 396 | err = writeClient1.SendWriteRequest(sessionHandle); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 397 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 398 | |
| 399 | err = writeClient2.SendWriteRequest(sessionHandle); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 400 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 401 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 402 | DrainAndServiceIO(); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 403 | |
| 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 | |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 416 | 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 GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 421 | |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 422 | EXPECT_EQ(writeCallbackRef1->mOnDoneCount, 1u); |
| 423 | EXPECT_EQ(writeCallbackRef2->mOnDoneCount, 1u); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 424 | } |
| 425 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 426 | EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 427 | |
| 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 | */ |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 435 | TEST_F(TestWriteChunking, TestNonConflictWrite) |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 436 | { |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 437 | auto sessionHandle = GetSessionBobToAlice(); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 438 | |
| 439 | // Initialize the ember side server logic |
Boris Zbarsky | 0edb83a | 2023-02-17 12:35:46 -0500 | [diff] [blame] | 440 | InitDataModelHandler(); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 441 | |
| 442 | // Register our fake dynamic endpoint. |
Jerry Johns | 40b08b9 | 2022-04-06 18:31:35 -0700 | [diff] [blame] | 443 | emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage)); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 444 | |
| 445 | // Register our fake attribute access interface. |
Andrei Litvin | b85f076 | 2024-08-07 20:26:04 -0400 | [diff] [blame] | 446 | AttributeAccessInterfaceRegistry::Instance().Register(&testServer); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 447 | |
Andrei Litvin | cf32330 | 2022-11-15 15:00:44 +0100 | [diff] [blame] | 448 | app::AttributePathParams attributePath1(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute); |
| 449 | app::AttributePathParams attributePath2(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListAttribute2); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 450 | |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 451 | /* 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 GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 454 | TestWriteCallback writeCallback1; |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 455 | app::WriteClient writeClient1(&GetExchangeManager(), &writeCallback1, Optional<uint16_t>::Missing(), |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 456 | static_cast<uint16_t>(kReserveSize)); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 457 | |
| 458 | TestWriteCallback writeCallback2; |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 459 | app::WriteClient writeClient2(&GetExchangeManager(), &writeCallback2, Optional<uint16_t>::Missing(), |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 460 | static_cast<uint16_t>(kReserveSize)); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 461 | |
| 462 | ByteSpan list[kTestListLength]; |
| 463 | |
| 464 | CHIP_ERROR err = CHIP_NO_ERROR; |
| 465 | |
| 466 | err = writeClient1.EncodeAttribute(attributePath1, app::DataModel::List<ByteSpan>(list, kTestListLength)); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 467 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 468 | err = writeClient2.EncodeAttribute(attributePath2, app::DataModel::List<ByteSpan>(list, kTestListLength)); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 469 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 470 | |
| 471 | err = writeClient1.SendWriteRequest(sessionHandle); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 472 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 473 | |
| 474 | err = writeClient2.SendWriteRequest(sessionHandle); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 475 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 476 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 477 | DrainAndServiceIO(); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 478 | |
| 479 | { |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 480 | 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 GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 484 | |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 485 | EXPECT_EQ(writeCallback1.mOnDoneCount, 1u); |
| 486 | EXPECT_EQ(writeCallback2.mOnDoneCount, 1u); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 487 | } |
| 488 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 489 | EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
Song GUO | 1020e8c | 2022-03-01 21:54:04 +0800 | [diff] [blame] | 490 | |
| 491 | emberAfClearDynamicEndpoint(0); |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 492 | } |
| 493 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 494 | void TestWriteChunking::RunTest(Instructions instructions) |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 495 | { |
| 496 | CHIP_ERROR err = CHIP_NO_ERROR; |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 497 | auto sessionHandle = GetSessionBobToAlice(); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 498 | |
| 499 | TestWriteCallback writeCallback; |
| 500 | std::unique_ptr<WriteClient> writeClient = std::make_unique<WriteClient>( |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 501 | &GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing(), |
Boris Zbarsky | 40f39f7 | 2023-08-08 11:33:21 -0400 | [diff] [blame] | 502 | static_cast<uint16_t>(kMaxSecureSduLengthBytes - |
| 503 | 128) /* use a smaller chunk so we only need a few attributes in the write request. */); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 504 | |
| 505 | ConcreteAttributePath onGoingPath = ConcreteAttributePath(); |
| 506 | std::vector<PathStatus> status; |
| 507 | |
| 508 | testServer.mOnListWriteBegin = [&](const ConcreteAttributePath & aPath) { |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 509 | EXPECT_EQ(onGoingPath, ConcreteAttributePath()); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 510 | onGoingPath = aPath; |
andrei-menzopol | 57471cb | 2022-04-20 23:31:17 +0300 | [diff] [blame] | 511 | ChipLogProgress(Zcl, "OnListWriteBegin endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI, |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 512 | 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) { |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 526 | EXPECT_EQ(onGoingPath, aPath); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 527 | status.push_back(PathStatus(aPath, aWasSuccessful)); |
| 528 | onGoingPath = ConcreteAttributePath(); |
andrei-menzopol | 57471cb | 2022-04-20 23:31:17 +0300 | [diff] [blame] | 529 | ChipLogProgress(Zcl, "OnListWriteEnd endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI, |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 530 | 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 | } |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 540 | EXPECT_EQ(instructions.paths.size(), instructions.data.size()); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 541 | |
| 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 | } |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 563 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 564 | } |
| 565 | |
| 566 | err = writeClient->SendWriteRequest(sessionHandle); |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 567 | EXPECT_EQ(err, CHIP_NO_ERROR); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 568 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 569 | GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) + |
| 570 | System::Clock::Seconds16(1), |
| 571 | [&]() { return GetExchangeManager().GetNumActiveExchanges() == 0; }); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 572 | |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 573 | EXPECT_EQ(onGoingPath, app::ConcreteAttributePath()); |
| 574 | EXPECT_EQ(status.size(), instructions.expectedStatus.size()); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 575 | |
| 576 | for (size_t i = 0; i < status.size(); i++) |
| 577 | { |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 578 | EXPECT_EQ(status[i], PathStatus(instructions.paths[i], instructions.expectedStatus[i])); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 579 | } |
| 580 | |
| 581 | testServer.mOnListWriteBegin = nullptr; |
| 582 | testServer.mOnListWriteEnd = nullptr; |
| 583 | } |
| 584 | |
feasel | 0180ee6 | 2024-05-13 11:09:16 -0400 | [diff] [blame] | 585 | TEST_F(TestWriteChunking, TestTransactionalList) |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 586 | { |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 587 | // Initialize the ember side server logic |
Boris Zbarsky | 0edb83a | 2023-02-17 12:35:46 -0500 | [diff] [blame] | 588 | InitDataModelHandler(); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 589 | |
| 590 | // Register our fake dynamic endpoint. |
| 591 | emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage)); |
| 592 | |
| 593 | // Register our fake attribute access interface. |
Andrei Litvin | b85f076 | 2024-08-07 20:26:04 -0400 | [diff] [blame] | 594 | AttributeAccessInterfaceRegistry::Instance().Register(&testServer); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 595 | |
| 596 | // Test 1: we should receive transaction notifications |
| 597 | ChipLogProgress(Zcl, "Test 1: we should receive transaction notifications"); |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 598 | RunTest(Instructions{ |
| 599 | .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, |
| 600 | .expectedStatus = { true }, |
| 601 | }); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 602 | |
| 603 | ChipLogProgress(Zcl, "Test 2: we should receive transaction notifications for incomplete list operations"); |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 604 | RunTest(Instructions{ |
| 605 | .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, |
| 606 | .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; }, |
| 607 | .expectedStatus = { false }, |
| 608 | }); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 609 | |
| 610 | ChipLogProgress(Zcl, "Test 3: we should receive transaction notifications for every list in the transaction"); |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 611 | RunTest(Instructions{ |
| 612 | .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), |
| 613 | ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, |
| 614 | .expectedStatus = { true, true }, |
| 615 | }); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 616 | |
| 617 | ChipLogProgress(Zcl, "Test 4: we should receive transaction notifications with the status of each list"); |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 618 | 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 GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 631 | |
| 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"); |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 635 | 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 GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 641 | |
| 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"); |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 645 | 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 GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 651 | |
| 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"); |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 655 | 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 GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 662 | |
| 663 | ChipLogProgress(Zcl, "Test 8: transactional list callbacks will be called for nullable lists"); |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 664 | RunTest(Instructions{ |
| 665 | .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, |
| 666 | .data = { ListData::kNull }, |
| 667 | .expectedStatus = { true }, |
| 668 | }); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 669 | |
| 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"); |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 673 | RunTest(Instructions{ |
| 674 | .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, |
| 675 | .data = { ListData::kBadValue }, |
| 676 | .expectedStatus = { false }, |
| 677 | }); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 678 | |
feasel | 74fa3f3 | 2024-07-09 14:16:07 -0400 | [diff] [blame] | 679 | EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); |
Song GUO | 01b5137 | 2022-04-08 16:57:04 +0800 | [diff] [blame] | 680 | |
| 681 | emberAfClearDynamicEndpoint(0); |
| 682 | } |
| 683 | |
Song GUO | 56634bf | 2022-02-10 09:08:23 +0800 | [diff] [blame] | 684 | } // namespace |