Skip to content

Commit 102faca

Browse files
ReadClient: Truncate data version list during encoding if necessary (project-chip#34111)
* ReadClient: Truncate data version list during encoding if necessary The existing code made the assumption that if a list of versions was able to fit into the request packet when generating the first subscribe request, then any resubscribe containing data versions for the same clusters would also fit. However the data version numbers themselves can be updated when we receive reports, and this can cause the list to no longer fit the request packet, leaving us in a state where every resubscribe attempt would fail. Note that this change means even an initial subscribe request with a data version list that is too long will no longer fail; ReadClient will simply truncate the list as needed in all cases. * Apply comment suggestions from code review Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Treat CHIP_ERROR_BUFFER_TOO_SMALL the same * Switch data_model tests to pw_unit_test * Add WillSendMessage to loopback delegate and make source addresses more plausible * Add test for ReadClient data version truncation * Make the linter happy --------- Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
1 parent 247f05d commit 102faca

File tree

8 files changed

+151
-19
lines changed

8 files changed

+151
-19
lines changed

src/app/ReadClient.cpp

+35-14
Original file line numberDiff line numberDiff line change
@@ -423,33 +423,54 @@ CHIP_ERROR ReadClient::BuildDataVersionFilterList(DataVersionFilterIBs::Builder
423423
continue;
424424
}
425425

426-
DataVersionFilterIB::Builder & filterIB = aDataVersionFilterIBsBuilder.CreateDataVersionFilter();
427-
ReturnErrorOnFailure(aDataVersionFilterIBsBuilder.GetError());
428-
ClusterPathIB::Builder & path = filterIB.CreatePath();
429-
ReturnErrorOnFailure(filterIB.GetError());
430-
ReturnErrorOnFailure(path.Endpoint(filter.mEndpointId).Cluster(filter.mClusterId).EndOfClusterPathIB());
431-
VerifyOrReturnError(filter.mDataVersion.HasValue(), CHIP_ERROR_INVALID_ARGUMENT);
432-
ReturnErrorOnFailure(filterIB.DataVersion(filter.mDataVersion.Value()).EndOfDataVersionFilterIB());
433-
aEncodedDataVersionList = true;
426+
TLV::TLVWriter backup;
427+
aDataVersionFilterIBsBuilder.Checkpoint(backup);
428+
CHIP_ERROR err = EncodeDataVersionFilter(aDataVersionFilterIBsBuilder, filter);
429+
if (err == CHIP_NO_ERROR)
430+
{
431+
aEncodedDataVersionList = true;
432+
}
433+
else if (err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL)
434+
{
435+
// Packet is full, ignore the rest of the list
436+
aDataVersionFilterIBsBuilder.Rollback(backup);
437+
return CHIP_NO_ERROR;
438+
}
439+
else
440+
{
441+
return err;
442+
}
434443
}
435444
return CHIP_NO_ERROR;
436445
}
437446

447+
CHIP_ERROR ReadClient::EncodeDataVersionFilter(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
448+
DataVersionFilter const & aFilter)
449+
{
450+
// Caller has checked aFilter.IsValidDataVersionFilter()
451+
DataVersionFilterIB::Builder & filterIB = aDataVersionFilterIBsBuilder.CreateDataVersionFilter();
452+
ReturnErrorOnFailure(aDataVersionFilterIBsBuilder.GetError());
453+
ClusterPathIB::Builder & path = filterIB.CreatePath();
454+
ReturnErrorOnFailure(filterIB.GetError());
455+
ReturnErrorOnFailure(path.Endpoint(aFilter.mEndpointId).Cluster(aFilter.mClusterId).EndOfClusterPathIB());
456+
ReturnErrorOnFailure(filterIB.DataVersion(aFilter.mDataVersion.Value()).EndOfDataVersionFilterIB());
457+
return CHIP_NO_ERROR;
458+
}
459+
438460
CHIP_ERROR ReadClient::GenerateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
439461
const Span<AttributePathParams> & aAttributePaths,
440462
const Span<DataVersionFilter> & aDataVersionFilters,
441463
bool & aEncodedDataVersionList)
442464
{
443-
if (!aDataVersionFilters.empty())
465+
// Give the callback a chance first, otherwise use the list we have, if any.
466+
ReturnErrorOnFailure(
467+
mpCallback.OnUpdateDataVersionFilterList(aDataVersionFilterIBsBuilder, aAttributePaths, aEncodedDataVersionList));
468+
469+
if (!aEncodedDataVersionList)
444470
{
445471
ReturnErrorOnFailure(BuildDataVersionFilterList(aDataVersionFilterIBsBuilder, aAttributePaths, aDataVersionFilters,
446472
aEncodedDataVersionList));
447473
}
448-
else
449-
{
450-
ReturnErrorOnFailure(
451-
mpCallback.OnUpdateDataVersionFilterList(aDataVersionFilterIBsBuilder, aAttributePaths, aEncodedDataVersionList));
452-
}
453474

454475
return CHIP_NO_ERROR;
455476
}

src/app/ReadClient.h

+5
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,9 @@ class ReadClient : public Messaging::ExchangeDelegate
339339
* This will send either a Read Request or a Subscribe Request depending on
340340
* the InteractionType this read client was initialized with.
341341
*
342+
* If the params contain more data version filters than can fit in the request packet
343+
* the list will be truncated as needed, i.e. filter inclusion is on a best effort basis.
344+
*
342345
* @retval #others fail to send read request
343346
* @retval #CHIP_NO_ERROR On success.
344347
*/
@@ -559,6 +562,8 @@ class ReadClient : public Messaging::ExchangeDelegate
559562
CHIP_ERROR BuildDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
560563
const Span<AttributePathParams> & aAttributePaths,
561564
const Span<DataVersionFilter> & aDataVersionFilters, bool & aEncodedDataVersionList);
565+
CHIP_ERROR EncodeDataVersionFilter(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
566+
DataVersionFilter const & aFilter);
562567
CHIP_ERROR ReadICDOperatingModeFromAttributeDataIB(TLV::TLVReader && aReader, PeerType & aType);
563568
CHIP_ERROR ProcessAttributeReportIBs(TLV::TLVReader & aAttributeDataIBsReader);
564569
CHIP_ERROR ProcessEventReportIBs(TLV::TLVReader & aEventReportIBsReader);

src/controller/tests/data_model/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ chip_test_suite("data_model") {
3535
"${chip_root}/src/app/tests:helpers",
3636
"${chip_root}/src/app/util/mock:mock_ember",
3737
"${chip_root}/src/controller",
38+
"${chip_root}/src/lib/core:string-builder-adapters",
3839
"${chip_root}/src/messaging/tests:helpers",
3940
"${chip_root}/src/transport/raw/tests:helpers",
4041
]

src/controller/tests/data_model/TestCommands.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
*
2323
*/
2424

25-
#include <gtest/gtest.h>
25+
#include <lib/core/StringBuilderAdapters.h>
26+
#include <pw_unit_test/framework.h>
2627

2728
#include "app/data-model/NullObject.h"
2829
#include <app-common/zap-generated/cluster-objects.h>

src/controller/tests/data_model/TestRead.cpp

+83-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
* limitations under the License.
1717
*/
1818

19-
#include <gtest/gtest.h>
19+
#include <lib/core/StringBuilderAdapters.h>
20+
#include <pw_unit_test/framework.h>
2021

2122
#include "system/SystemClock.h"
2223
#include "transport/SecureSession.h"
@@ -25,6 +26,7 @@
2526
#include <app/ConcreteAttributePath.h>
2627
#include <app/ConcreteEventPath.h>
2728
#include <app/InteractionModelEngine.h>
29+
#include <app/ReadClient.h>
2830
#include <app/tests/AppTestContext.h>
2931
#include <app/util/mock/Constants.h>
3032
#include <app/util/mock/Functions.h>
@@ -3093,6 +3095,86 @@ TEST_F(TestRead, TestReadHandler_MultipleSubscriptionsWithDataVersionFilter)
30933095
EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
30943096
}
30953097

3098+
TEST_F(TestRead, TestReadHandler_DataVersionFiltersTruncated)
3099+
{
3100+
struct : public chip::Test::LoopbackTransportDelegate
3101+
{
3102+
size_t requestSize = 0;
3103+
void WillSendMessage(const Transport::PeerAddress & peer, const System::PacketBufferHandle & message) override
3104+
{
3105+
// We only care about the messages we (Alice) send to Bob, not the responses.
3106+
// Assume the first message we see in an iteration is the request.
3107+
if (peer == mpContext->GetBobAddress() && requestSize == 0)
3108+
{
3109+
requestSize = message->TotalLength();
3110+
}
3111+
}
3112+
} loopbackDelegate;
3113+
mpContext->GetLoopback().SetLoopbackTransportDelegate(&loopbackDelegate);
3114+
3115+
// Note that on the server side, wildcard expansion does not actually work for kTestEndpointId due
3116+
// to lack of meta-data, but we don't care about the reports we get back in this test.
3117+
AttributePathParams wildcardPath(kTestEndpointId, kInvalidClusterId, kInvalidAttributeId);
3118+
constexpr size_t maxDataVersionFilterCount = 100;
3119+
DataVersionFilter dataVersionFilters[maxDataVersionFilterCount];
3120+
ClusterId nextClusterId = 0;
3121+
for (auto & dv : dataVersionFilters)
3122+
{
3123+
dv.mEndpointId = wildcardPath.mEndpointId;
3124+
dv.mClusterId = nextClusterId++;
3125+
dv.mDataVersion = MakeOptional(0x01000000u);
3126+
}
3127+
3128+
// Keep increasing the number of data version filters until we see truncation kick in.
3129+
size_t lastRequestSize;
3130+
for (size_t count = 1; count <= maxDataVersionFilterCount; count++)
3131+
{
3132+
lastRequestSize = loopbackDelegate.requestSize;
3133+
loopbackDelegate.requestSize = 0; // reset
3134+
3135+
ReadPrepareParams read(mpContext->GetSessionAliceToBob());
3136+
read.mpAttributePathParamsList = &wildcardPath;
3137+
read.mAttributePathParamsListSize = 1;
3138+
read.mpDataVersionFilterList = dataVersionFilters;
3139+
read.mDataVersionFilterListSize = count;
3140+
3141+
struct : public ReadClient::Callback
3142+
{
3143+
CHIP_ERROR error = CHIP_NO_ERROR;
3144+
bool done = false;
3145+
void OnError(CHIP_ERROR aError) override { error = aError; }
3146+
void OnDone(ReadClient * apReadClient) override { done = true; };
3147+
3148+
} readCallback;
3149+
3150+
ReadClient readClient(app::InteractionModelEngine::GetInstance(), &mpContext->GetExchangeManager(), readCallback,
3151+
ReadClient::InteractionType::Read);
3152+
3153+
EXPECT_EQ(readClient.SendRequest(read), CHIP_NO_ERROR);
3154+
3155+
mpContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return readCallback.done; });
3156+
EXPECT_EQ(readCallback.error, CHIP_NO_ERROR);
3157+
EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
3158+
3159+
EXPECT_NE(loopbackDelegate.requestSize, 0u);
3160+
EXPECT_GE(loopbackDelegate.requestSize, lastRequestSize);
3161+
if (loopbackDelegate.requestSize == lastRequestSize)
3162+
{
3163+
ChipLogProgress(DataManagement, "Data Version truncation detected after %llu elements",
3164+
static_cast<unsigned long long>(count - 1));
3165+
// With the parameters used in this test and current encoding rules we can fit 68 data versions
3166+
// into a packet. If we're seeing substantially less then something is likely gone wrong.
3167+
EXPECT_GE(count, 60u);
3168+
ExitNow();
3169+
}
3170+
}
3171+
ChipLogProgress(DataManagement, "Unable to detect Data Version truncation, maxDataVersionFilterCount too small?");
3172+
ADD_FAILURE();
3173+
3174+
exit:
3175+
mpContext->GetLoopback().SetLoopbackTransportDelegate(nullptr);
3176+
}
3177+
30963178
TEST_F(TestRead, TestReadHandlerResourceExhaustion_MultipleReads)
30973179
{
30983180
auto sessionHandle = mpContext->GetSessionBobToAlice();

src/controller/tests/data_model/TestWrite.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
* limitations under the License.
1717
*/
1818

19-
#include <gtest/gtest.h>
19+
#include <lib/core/StringBuilderAdapters.h>
20+
#include <pw_unit_test/framework.h>
2021

2122
#include "app-common/zap-generated/ids/Clusters.h"
2223
#include <app-common/zap-generated/cluster-objects.h>

src/messaging/tests/MessagingContext.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class MessagingContext : public PlatformMemoryUser
9494

9595
MessagingContext() :
9696
mInitialized(false), mAliceAddress(Transport::PeerAddress::UDP(GetAddress(), CHIP_PORT + 1)),
97-
mBobAddress(Transport::PeerAddress::UDP(GetAddress(), CHIP_PORT))
97+
mBobAddress(LoopbackTransport::LoopbackPeer(mAliceAddress))
9898
{}
9999
// TODO Replace VerifyOrDie with Pigweed assert after transition app/tests to Pigweed.
100100
// TODO Currently src/app/icd/server/tests is using MessagingConetext as dependency.

src/transport/raw/tests/NetworkTestHelpers.h

+22-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ class LoopbackTransportDelegate
6464
public:
6565
virtual ~LoopbackTransportDelegate() {}
6666

67+
// Called by the loopback transport when a message is requested to be sent.
68+
// This is called even if the message is subsequently rejected or dropped.
69+
virtual void WillSendMessage(const Transport::PeerAddress & peer, const System::PacketBufferHandle & message) {}
70+
6771
// Called by the loopback transport when it drops one of a configurable number of messages (mDroppedMessageCount) after a
6872
// configurable allowed number of messages (mNumMessagesToAllowBeforeDropping)
6973
virtual void OnMessageDropped() {}
@@ -72,6 +76,18 @@ class LoopbackTransportDelegate
7276
class LoopbackTransport : public Transport::Base
7377
{
7478
public:
79+
// In test scenarios using the loopback transport, we're only ever given
80+
// the address we're sending to, but we don't have any information about
81+
// what our local address is. Assume our fake addresses come in pairs of
82+
// even and odd port numbers, so we can calculate one from the other by
83+
// flipping the LSB of the port number.
84+
static Transport::PeerAddress LoopbackPeer(const Transport::PeerAddress & address)
85+
{
86+
Transport::PeerAddress other(address);
87+
other.SetPort(address.GetPort() ^ 1);
88+
return other;
89+
}
90+
7591
void InitLoopbackTransport(System::Layer * systemLayer)
7692
{
7793
Reset();
@@ -100,14 +116,19 @@ class LoopbackTransport : public Transport::Base
100116
{
101117
auto item = std::move(_this->mPendingMessageQueue.front());
102118
_this->mPendingMessageQueue.pop();
103-
_this->HandleMessageReceived(item.mDestinationAddress, std::move(item.mPendingMessage));
119+
_this->HandleMessageReceived(LoopbackPeer(item.mDestinationAddress), std::move(item.mPendingMessage));
104120
}
105121
}
106122

107123
static constexpr uint32_t kUnlimitedMessageCount = std::numeric_limits<uint32_t>::max();
108124

109125
CHIP_ERROR SendMessage(const Transport::PeerAddress & address, System::PacketBufferHandle && msgBuf) override
110126
{
127+
if (mDelegate != nullptr)
128+
{
129+
mDelegate->WillSendMessage(address, msgBuf);
130+
}
131+
111132
if (mNumMessagesToAllowBeforeError == 0)
112133
{
113134
ReturnErrorOnFailure(mMessageSendError);

0 commit comments

Comments
 (0)