Skip to content

Commit a669fc0

Browse files
Rollback() on data model builder should reset the error on the builder. (project-chip#26982)
* Rollback() on data model builder should reset the error on the builder. Without this, it's too easy to forget to ResetError() when rolling back. That's exactly what happened in ClusterStateCache:OnUpdateDataVersionFilterList, and that could lead to inability to ever re-subscribe after a subscription drop. We had the following Rollback() calls not immediately followed by ResetError(): * In SendFailureStatus in ember-compatibility-functions.cpp. This is never reached when there is a failure on aAttributeReports, so doing the ResetError here is not a problem. * AttributeValueEncoder::EncodeListItem. This was relying on the InteractionModelEngine to ResetError after the rollback. * ClusterStateCache:OnUpdateDataVersionFilterList. When CreateDataVersionFilter() failed, this could leave the DataVersionFilterIBs::Builder in an error state, which would lead to the subscribe request (incorrectly) failing completely. * WriteHandler::ProcessAttributeDataIBs. This codepath was apparently not getting hit in practice, since it would fail. It was also checkpointing/restoring on the wrong builder. * Engine::BuildSingleReportDataAttributeReportIBs. This was buggy: If we hit an error other than "out of memory" while encoding the value, we could end up with the builder in an error state and then fail to encode our status response. * ReadClient::SendReadRequest. This is never reached with an error on the builder. * ReadClient::SendSubscribeRequestImpl. This is never reached with an error on the builder. * Address review comment.
1 parent b5cd925 commit a669fc0

File tree

7 files changed

+95
-23
lines changed

7 files changed

+95
-23
lines changed

src/app/CommandHandler.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,6 @@ CHIP_ERROR CommandHandler::RollbackResponse()
559559
{
560560
VerifyOrReturnError(mState == State::Preparing || mState == State::AddingCommand, CHIP_ERROR_INCORRECT_STATE);
561561
mInvokeResponseBuilder.Rollback(mBackupWriter);
562-
mInvokeResponseBuilder.ResetError();
563562
// Note: We only support one command per request, so we reset the state to Idle here, need to review the states when adding
564563
// supports of having multiple requests in the same transaction.
565564
MoveToState(State::Idle);

src/app/MessageDef/Builder.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,13 @@ class Builder
8181
/**
8282
* Rollback the request state to the checkpointed TLVWriter
8383
*
84-
* @param[in] aPoint A that captured the state via Checkpoint() at some point in the past
84+
* @param[in] aPoint A writer that captured the state via Checkpoint() at some point in the past
8585
*/
86-
void Rollback(const chip::TLV::TLVWriter & aPoint) { *mpWriter = aPoint; }
86+
void Rollback(const chip::TLV::TLVWriter & aPoint)
87+
{
88+
*mpWriter = aPoint;
89+
ResetError();
90+
}
8791

8892
void EndOfContainer();
8993

src/app/WriteClient.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,6 @@ CHIP_ERROR WriteClient::PutSinglePreencodedAttributeWritePayload(const chip::app
240240
{
241241
// If it failed with no memory, then we create a new chunk for it.
242242
mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
243-
mWriteRequestBuilder.GetWriteRequests().ResetError();
244243
ReturnErrorOnFailure(StartNewMessage());
245244
err = TryPutSinglePreencodedAttributeWritePayload(attributePath, data);
246245
// Since we have created a new chunk for this element, the encode is expected to succeed.

src/app/WriteClient.h

-1
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,6 @@ class WriteClient : public Messaging::ExchangeDelegate
297297
{
298298
// If it failed with no memory, then we create a new chunk for it.
299299
mWriteRequestBuilder.GetWriteRequests().Rollback(backupWriter);
300-
mWriteRequestBuilder.GetWriteRequests().ResetError();
301300
ReturnErrorOnFailure(StartNewMessage());
302301
ReturnErrorOnFailure(TryEncodeSingleAttributeDataIB(attributePath, value));
303302
}

src/app/WriteHandler.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ CHIP_ERROR WriteHandler::ProcessAttributeDataIBs(TLV::TLVReader & aAttributeData
330330
MatterPreAttributeWriteCallback(dataAttributePath);
331331
TLV::TLVWriter backup;
332332
DataVersion version = 0;
333-
mWriteResponseBuilder.Checkpoint(backup);
333+
mWriteResponseBuilder.GetWriteResponses().Checkpoint(backup);
334334
err = element.GetDataVersion(&version);
335335
if (CHIP_NO_ERROR == err)
336336
{
@@ -344,7 +344,7 @@ CHIP_ERROR WriteHandler::ProcessAttributeDataIBs(TLV::TLVReader & aAttributeData
344344
err = WriteSingleClusterData(subjectDescriptor, dataAttributePath, dataReader, this);
345345
if (err != CHIP_NO_ERROR)
346346
{
347-
mWriteResponseBuilder.Rollback(backup);
347+
mWriteResponseBuilder.GetWriteResponses().Rollback(backup);
348348
err = AddStatus(dataAttributePath, StatusIB(err));
349349
}
350350
MatterPostAttributeWriteCallback(dataAttributePath);

src/app/reporting/Engine.cpp

+17-16
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ Engine::RetrieveClusterData(const SubjectDescriptor & aSubjectDescriptor, bool a
8484
return CHIP_NO_ERROR;
8585
}
8686

87+
static bool IsOutOfWriterSpaceError(CHIP_ERROR err)
88+
{
89+
return err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL;
90+
}
91+
8792
CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Builder & aReportDataBuilder,
8893
ReadHandler * apReadHandler, bool * apHasMoreChunks,
8994
bool * apHasEncodedData)
@@ -185,13 +190,17 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu
185190
"Error retrieving data from clusterId: " ChipLogFormatMEI ", err = %" CHIP_ERROR_FORMAT,
186191
ChipLogValueMEI(pathForRetrieval.mClusterId), err.Format());
187192

188-
// If error is not CHIP_ERROR_BUFFER_TOO_SMALL and is not CHIP_ERROR_NO_MEMORY, rollback and encode status.
193+
// If error is not an "out of writer space" error, rollback and encode status.
189194
// Otherwise, if partial data allowed, save the encode state.
190195
// Otherwise roll back. If we have already encoded some chunks, we are done; otherwise encode status.
191196

192-
if (encodeState.AllowPartialData() && ((err == CHIP_ERROR_BUFFER_TOO_SMALL) || (err == CHIP_ERROR_NO_MEMORY)))
197+
if (encodeState.AllowPartialData() && IsOutOfWriterSpaceError(err))
193198
{
194199
// Encoding is aborted but partial data is allowed, then we don't rollback and save the state for next chunk.
200+
// The expectation is that RetrieveClusterData has already reset attributeReportIBs to a good state (rolled
201+
// back any partially-written AttributeReportIB instances, reset its error status). Since AllowPartialData()
202+
// is true, we may not have encoded a complete attribute value, but we did, if we encoded anything, encode a
203+
// set of complete AttributeReportIB instances that represent part of the attribute value.
195204
apReadHandler->SetAttributeEncodeState(encodeState);
196205
}
197206
else
@@ -201,13 +210,14 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu
201210
attributeReportIBs.Rollback(attributeBackup);
202211
apReadHandler->SetAttributeEncodeState(AttributeValueEncoder::AttributeEncodeState());
203212

204-
if (err != CHIP_ERROR_NO_MEMORY && err != CHIP_ERROR_BUFFER_TOO_SMALL)
213+
if (!IsOutOfWriterSpaceError(err))
205214
{
206215
// Try to encode our error as a status response.
207216
err = attributeReportIBs.EncodeAttributeStatus(pathForRetrieval, StatusIB(err));
208217
if (err != CHIP_NO_ERROR)
209218
{
210-
// OK, just roll back again and give up.
219+
// OK, just roll back again and give up; if we still ran out of space we
220+
// will send this status response in the next chunk.
211221
attributeReportIBs.Rollback(attributeBackup);
212222
}
213223
}
@@ -241,15 +251,9 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu
241251
// These are are guaranteed to not fail since we've already reserved memory for the remaining 'close out' TLV operations in this
242252
// function and its callers.
243253
//
244-
if ((err == CHIP_ERROR_BUFFER_TOO_SMALL) || (err == CHIP_ERROR_NO_MEMORY))
254+
if (IsOutOfWriterSpaceError(err))
245255
{
246256
ChipLogDetail(DataManagement, "<RE:Run> We cannot put more chunks into this report. Enable chunking.");
247-
248-
//
249-
// Reset the error tracked within the builder. Otherwise, any further attempts to write
250-
// data through the builder will be blocked by that error.
251-
//
252-
attributeReportIBs.ResetError();
253257
err = CHIP_NO_ERROR;
254258
}
255259

@@ -276,7 +280,6 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu
276280
if (!attributeDataWritten && err == CHIP_NO_ERROR)
277281
{
278282
aReportDataBuilder.Rollback(backup);
279-
aReportDataBuilder.ResetError();
280283
}
281284

282285
// hasMoreChunks + no data encoded is a flag that we have encountered some trouble when processing the attribute.
@@ -379,7 +382,7 @@ CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder
379382
err = CHIP_NO_ERROR;
380383
hasMoreChunks = false;
381384
}
382-
else if ((err == CHIP_ERROR_BUFFER_TOO_SMALL) || (err == CHIP_ERROR_NO_MEMORY))
385+
else if (IsOutOfWriterSpaceError(err))
383386
{
384387
// when first cluster event is too big to fit in the packet, ignore that cluster event.
385388
// However, we may have encoded some attributes before, we don't skip it in that case.
@@ -423,11 +426,9 @@ CHIP_ERROR Engine::BuildSingleReportDataEventReports(ReportDataMessage::Builder
423426
}
424427

425428
// Maybe encoding the attributes has already used up all space.
426-
if ((err == CHIP_NO_ERROR || err == CHIP_ERROR_NO_MEMORY || err == CHIP_ERROR_BUFFER_TOO_SMALL) &&
427-
!(hasEncodedStatus || (eventCount != 0)))
429+
if ((err == CHIP_NO_ERROR || IsOutOfWriterSpaceError(err)) && !(hasEncodedStatus || (eventCount != 0)))
428430
{
429431
aReportDataBuilder.Rollback(backup);
430-
aReportDataBuilder.ResetError();
431432
err = CHIP_NO_ERROR;
432433
}
433434

src/app/tests/TestClusterStateCache.cpp

+70
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@
1919
#include "app-common/zap-generated/ids/Attributes.h"
2020
#include "app-common/zap-generated/ids/Clusters.h"
2121
#include "lib/core/TLVTags.h"
22+
#include "lib/core/TLVWriter.h"
2223
#include "protocols/interaction_model/Constants.h"
2324
#include "system/SystemPacketBuffer.h"
2425
#include "system/TLVPacketBufferBackingStore.h"
2526
#include <app-common/zap-generated/cluster-objects.h>
2627
#include <app/ClusterStateCache.h>
28+
#include <app/MessageDef/DataVersionFilterIBs.h>
2729
#include <app/data-model/DecodableList.h>
2830
#include <app/data-model/Decode.h>
2931
#include <app/tests/AppTestContext.h>
32+
#include <lib/support/ScopedBuffer.h>
3033
#include <lib/support/UnitTestContext.h>
3134
#include <lib/support/UnitTestRegistration.h>
3235
#include <nlunit-test.h>
@@ -540,8 +543,75 @@ void RunAndValidateSequence(AttributeInstructionListType list)
540543
ForwardedDataCallbackValidator dataCallbackValidator;
541544
CacheValidator client(list, dataCallbackValidator);
542545
ClusterStateCache cache(client);
546+
547+
// In order for the cache to track our data versions, we need to claim to it
548+
// that we are dealing with a wildcard path. And we need to do that before
549+
// it has seen any reports.
550+
AttributePathParams wildcardPath;
551+
const Span<AttributePathParams> pathSpan(&wildcardPath, 1);
552+
{
553+
// Just need a buffer big enough that we can start the list. We don't
554+
// care about the actual data versions here.
555+
uint8_t buf[20];
556+
TLV::TLVWriter writer;
557+
writer.Init(buf);
558+
DataVersionFilterIBs::Builder builder;
559+
CHIP_ERROR err = builder.Init(&writer);
560+
NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR);
561+
bool encodedDataVersionList = false;
562+
err = cache.GetBufferedCallback().OnUpdateDataVersionFilterList(builder, pathSpan, encodedDataVersionList);
563+
564+
// We had nothing to encode so far.
565+
NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR);
566+
NL_TEST_ASSERT(gSuite, !encodedDataVersionList);
567+
}
568+
543569
DataSeriesGenerator generator(&cache.GetBufferedCallback(), list);
544570
generator.Generate(dataCallbackValidator);
571+
572+
// Now verify that we would do the right thing when encoding our data
573+
// versions.
574+
575+
size_t bufferSize = 1;
576+
do
577+
{
578+
Platform::ScopedMemoryBuffer<uint8_t> buf;
579+
if (!buf.Calloc(bufferSize))
580+
{
581+
NL_TEST_ASSERT(gSuite, false);
582+
break;
583+
}
584+
585+
TLV::TLVWriter writer;
586+
writer.Init(buf.Get(), bufferSize);
587+
588+
DataVersionFilterIBs::Builder builder;
589+
CHIP_ERROR err = builder.Init(&writer);
590+
NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR || err == CHIP_ERROR_BUFFER_TOO_SMALL);
591+
if (err == CHIP_NO_ERROR)
592+
{
593+
// We had enough space to start the list. Now try encoding the data
594+
// version filters.
595+
bool encodedDataVersionList = false;
596+
err = cache.GetBufferedCallback().OnUpdateDataVersionFilterList(builder, pathSpan, encodedDataVersionList);
597+
598+
// We should be rolling back properly if we run out of space.
599+
NL_TEST_ASSERT(gSuite, err == CHIP_NO_ERROR);
600+
NL_TEST_ASSERT(gSuite, builder.GetError() == CHIP_NO_ERROR);
601+
602+
if (writer.GetRemainingFreeLength() > 40)
603+
{
604+
// We have lots of empty space left, so we did not end up
605+
// needing to roll back; no point testing larger buffer sizes.
606+
//
607+
// Note: we may still have encodedDataVersionList false here, if
608+
// there were no non-status attribute values cached.
609+
break;
610+
}
611+
}
612+
613+
++bufferSize;
614+
} while (true);
545615
}
546616

547617
/*

0 commit comments

Comments
 (0)