Skip to content

Commit

Permalink
Reduce the amount of per-instantiation logic in EncodeListItem. (#37093)
Browse files Browse the repository at this point in the history
The parts that are not dependent on ItemType can be separate functions.
  • Loading branch information
bzbarsky-apple authored Jan 17, 2025
1 parent ac7f062 commit 9b8fffe
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 23 deletions.
29 changes: 29 additions & 0 deletions src/app/AttributeValueEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,34 @@ void AttributeValueEncoder::EnsureListEnded()
}
}

bool AttributeValueEncoder::ShouldEncodeListItem(TLV::TLVWriter & aCheckpoint)
{
// EncodeListItem (our caller) must be called after EnsureListStarted(),
// thus mCurrentEncodingListIndex and mEncodeState.mCurrentEncodingListIndex
// are not invalid values.
if (mCurrentEncodingListIndex < mEncodeState.CurrentEncodingListIndex())
{
// We have encoded this element in previous chunks, skip it.
mCurrentEncodingListIndex++;
return false;
}

mAttributeReportIBsBuilder.Checkpoint(aCheckpoint);
return true;
}

void AttributeValueEncoder::PostEncodeListItem(CHIP_ERROR aEncodeStatus, const TLV::TLVWriter & aCheckpoint)
{
if (aEncodeStatus != CHIP_NO_ERROR)
{
mAttributeReportIBsBuilder.Rollback(aCheckpoint);
return;
}

mCurrentEncodingListIndex++;
mEncodeState.SetCurrentEncodingListIndex(mCurrentEncodingListIndex);
mEncodedAtLeastOneListItem = true;
}

} // namespace app
} // namespace chip
42 changes: 19 additions & 23 deletions src/app/AttributeValueEncoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,22 @@ class AttributeValueEncoder
VerifyOrReturnError(!mAttributeValueEncoder.mIsFabricFiltered ||
aArg.GetFabricIndex() == mAttributeValueEncoder.AccessingFabricIndex(),
CHIP_NO_ERROR);
return mAttributeValueEncoder.EncodeListItem(aArg, mAttributeValueEncoder.AccessingFabricIndex());
return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg, mAttributeValueEncoder.AccessingFabricIndex());
}

template <typename T, std::enable_if_t<!DataModel::IsFabricScoped<T>::value, bool> = true>
CHIP_ERROR Encode(const T & aArg) const
{
return mAttributeValueEncoder.EncodeListItem(aArg);
return mAttributeValueEncoder.EncodeListItem(mCheckpoint, aArg);
}

private:
AttributeValueEncoder & mAttributeValueEncoder;
// Avoid calling the TLVWriter constructor for every instantiation of
// EncodeListItem. We treat encoding as a const operation, so either
// have to put this on the stack (in which case it's per-instantiation),
// or have it as mutable state.
mutable TLV::TLVWriter mCheckpoint;
};

AttributeValueEncoder(AttributeReportIBs::Builder & aAttributeReportIBsBuilder, Access::SubjectDescriptor subjectDescriptor,
Expand Down Expand Up @@ -163,25 +168,26 @@ class AttributeValueEncoder
friend class ListEncodeHelper;
friend class TestOnlyAttributeValueEncoderAccessor;

// Returns true if the list item should be encoded. If it should, the
// passed-in TLVWriter will be used to checkpoint the current state of our
// attribute report list builder.
bool ShouldEncodeListItem(TLV::TLVWriter & aCheckpoint);

// Does any cleanup work needed after attempting to encode a list item.
void PostEncodeListItem(CHIP_ERROR aEncodeStatus, const TLV::TLVWriter & aCheckpoint);

// EncodeListItem may be given an extra FabricIndex argument as a second
// arg, or not. Represent that via a parameter pack (which might be
// empty). In practice, for any given ItemType the extra arg is either there
// or not, so we don't get more template explosion due to aExtraArgs.
template <typename ItemType, typename... ExtraArgTypes>
CHIP_ERROR EncodeListItem(const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
CHIP_ERROR EncodeListItem(TLV::TLVWriter & aCheckpoint, const ItemType & aItem, ExtraArgTypes &&... aExtraArgs)
{
// EncodeListItem must be called after EnsureListStarted(), thus mCurrentEncodingListIndex and
// mEncodeState.mCurrentEncodingListIndex are not invalid values.
if (mCurrentEncodingListIndex < mEncodeState.CurrentEncodingListIndex())
if (!ShouldEncodeListItem(aCheckpoint))
{
// We have encoded this element in previous chunks, skip it.
mCurrentEncodingListIndex++;
return CHIP_NO_ERROR;
}

TLV::TLVWriter backup;
mAttributeReportIBsBuilder.Checkpoint(backup);

CHIP_ERROR err;
if (mEncodingInitialList)
{
Expand All @@ -194,19 +200,9 @@ class AttributeValueEncoder
{
err = EncodeAttributeReportIB(aItem, std::forward<ExtraArgTypes>(aExtraArgs)...);
}
if (err != CHIP_NO_ERROR)
{
// For list chunking, ReportEngine should not rollback the buffer when CHIP_ERROR_NO_MEMORY or similar error occurred.
// However, the error might be raised in the middle of encoding procedure, then the buffer may contain partial data,
// unclosed containers etc. This line clears all possible partial data and makes EncodeListItem is atomic.
mAttributeReportIBsBuilder.Rollback(backup);
return err;
}

mCurrentEncodingListIndex++;
mEncodeState.SetCurrentEncodingListIndex(mCurrentEncodingListIndex);
mEncodedAtLeastOneListItem = true;
return CHIP_NO_ERROR;
PostEncodeListItem(err, aCheckpoint);
return err;
}

/**
Expand Down

0 comments on commit 9b8fffe

Please sign in to comment.