Add DataVersionFilter for read/subscribe (#14711)

* Add DataVersionFilter support for read/subscribe
diff --git a/src/app/AttributeCache.cpp b/src/app/AttributeCache.cpp
index 35a9b70..349a856 100644
--- a/src/app/AttributeCache.cpp
+++ b/src/app/AttributeCache.cpp
@@ -100,8 +100,7 @@
     mCallback.OnReportEnd();
 }
 
-void AttributeCache::OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
-                                     const StatusIB & aStatus)
+void AttributeCache::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus)
 {
     //
     // Since the cache itself is a ReadClient::Callback, it may be incorrectly passed in directly when registering with the
@@ -121,7 +120,7 @@
     //
     // Forward the call through.
     //
-    mCallback.OnAttributeData(aPath, aVersion, apData, aStatus);
+    mCallback.OnAttributeData(aPath, apData, aStatus);
 }
 
 CHIP_ERROR AttributeCache::Get(const ConcreteAttributePath & path, TLV::TLVReader & reader)
diff --git a/src/app/AttributeCache.h b/src/app/AttributeCache.h
index 1e682d2..907dbed 100644
--- a/src/app/AttributeCache.h
+++ b/src/app/AttributeCache.h
@@ -342,8 +342,7 @@
     //
     void OnReportBegin() override;
     void OnReportEnd() override;
-    void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
-                         const StatusIB & aStatus) override;
+    void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override;
     void OnError(CHIP_ERROR aError) override { return mCallback.OnError(aError); }
 
     void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override
diff --git a/src/app/BufferedReadCallback.cpp b/src/app/BufferedReadCallback.cpp
index b268691..e1188a4 100644
--- a/src/app/BufferedReadCallback.cpp
+++ b/src/app/BufferedReadCallback.cpp
@@ -215,7 +215,7 @@
     //
     ReturnErrorOnFailure(reader.Next());
 
-    mCallback.OnAttributeData(mBufferedPath, mDataVersion, &reader, statusIB);
+    mCallback.OnAttributeData(mBufferedPath, &reader, statusIB);
 
     //
     // Clear out our buffered contents to free up allocated buffers, and reset the buffered path.
@@ -225,7 +225,7 @@
     return CHIP_NO_ERROR;
 }
 
-void BufferedReadCallback::OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
+void BufferedReadCallback::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
                                            const StatusIB & aStatus)
 {
     CHIP_ERROR err;
@@ -246,14 +246,13 @@
     }
     else
     {
-        mCallback.OnAttributeData(aPath, aVersion, apData, aStatus);
+        mCallback.OnAttributeData(aPath, apData, aStatus);
     }
 
     //
     // Update our latched buffered path.
     //
     mBufferedPath = aPath;
-    mDataVersion  = aVersion;
 
 exit:
     if (err != CHIP_NO_ERROR)
diff --git a/src/app/BufferedReadCallback.h b/src/app/BufferedReadCallback.h
index ce9d959..5f88ebd 100644
--- a/src/app/BufferedReadCallback.h
+++ b/src/app/BufferedReadCallback.h
@@ -69,8 +69,7 @@
     //
     void OnReportBegin() override;
     void OnReportEnd() override;
-    void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
-                         const StatusIB & aStatus) override;
+    void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override;
     void OnError(CHIP_ERROR aError) override { return mCallback.OnError(aError); }
     void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override
     {
@@ -94,7 +93,6 @@
      *
      */
     CHIP_ERROR BufferListItem(TLV::TLVReader & reader);
-    DataVersion mDataVersion;
     ConcreteDataAttributePath mBufferedPath;
     std::vector<System::PacketBufferHandle> mBufferedList;
     Callback & mCallback;
diff --git a/src/app/ClusterInfo.h b/src/app/ClusterInfo.h
index 55e7d7e..e8fdc95 100644
--- a/src/app/ClusterInfo.h
+++ b/src/app/ClusterInfo.h
@@ -79,6 +79,8 @@
     // For event, an event id can only be interpreted if the cluster id is known.
     bool IsValidEventPath() const { return !(HasWildcardClusterId() && !HasWildcardEventId()); }
 
+    bool IsValidDataVersionFilter() const { return !HasWildcardEndpointId() && !HasWildcardClusterId() && mDataVersion.HasValue(); }
+
     inline bool HasWildcardNodeId() const { return mNodeId == kUndefinedNodeId; }
     inline bool HasWildcardEndpointId() const { return mEndpointId == kInvalidEndpointId; }
     inline bool HasWildcardClusterId() const { return mClusterId == kInvalidClusterId; }
@@ -100,6 +102,7 @@
     EventId mEventId         = kInvalidEventId;     // uint32
     ListIndex mListIndex     = kInvalidListIndex;   // uint16
     EndpointId mEndpointId   = kInvalidEndpointId;  // uint16
+    Optional<DataVersion> mDataVersion;             // uint32
 };
 } // namespace app
 } // namespace chip
diff --git a/src/app/ConcreteAttributePath.h b/src/app/ConcreteAttributePath.h
index 97e29bc..486c5dc 100644
--- a/src/app/ConcreteAttributePath.h
+++ b/src/app/ConcreteAttributePath.h
@@ -111,6 +111,10 @@
         ConcreteAttributePath(aEndpointId, aClusterId, aAttributeId)
     {}
 
+    ConcreteDataAttributePath(EndpointId aEndpointId, ClusterId aClusterId, AttributeId aAttributeId, DataVersion aDataVersion) :
+        ConcreteAttributePath(aEndpointId, aClusterId, aAttributeId), mDataVersion(aDataVersion)
+    {}
+
     ConcreteDataAttributePath(EndpointId aEndpointId, ClusterId aClusterId, AttributeId aAttributeId, ListOperation aListOp,
                               uint16_t aListIndex) :
         ConcreteAttributePath(aEndpointId, aClusterId, aAttributeId)
@@ -126,8 +130,9 @@
     // This index is only valid if `mListOp` is set to a list item operation, i.e
     // ReplaceItem, DeleteItem or AppendItem. Otherwise, it is to be ignored.
     //
-    uint16_t mListIndex   = 0;
-    ListOperation mListOp = ListOperation::NotList;
+    uint16_t mListIndex      = 0;
+    ListOperation mListOp    = ListOperation::NotList;
+    DataVersion mDataVersion = 0;
 };
 
 } // namespace app
diff --git a/src/app/DataVersionFilter.h b/src/app/DataVersionFilter.h
new file mode 100644
index 0000000..ba93de6
--- /dev/null
+++ b/src/app/DataVersionFilter.h
@@ -0,0 +1,45 @@
+/*
+ *
+ *    Copyright (c) 2022 Project CHIP Authors
+ *    All rights reserved.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#pragma once
+
+#include <app/util/basic-types.h>
+
+#include <app/ClusterInfo.h>
+
+namespace chip {
+namespace app {
+struct DataVersionFilter
+{
+    DataVersionFilter(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aDataVersion) :
+        mEndpointId(aEndpointId), mClusterId(aClusterId), mDataVersion(aDataVersion)
+    {}
+
+    DataVersionFilter() {}
+
+    bool IsValidDataVersionFilter()
+    {
+        return (mEndpointId != kInvalidEndpointId) && (mClusterId != kInvalidClusterId) && (mDataVersion.HasValue());
+    }
+
+    EndpointId mEndpointId = kInvalidEndpointId;
+    ClusterId mClusterId   = kInvalidClusterId;
+    Optional<DataVersion> mDataVersion;
+};
+} // namespace app
+} // namespace chip
diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h
index 78b936b..6a7aa18 100644
--- a/src/app/InteractionModelEngine.h
+++ b/src/app/InteractionModelEngine.h
@@ -340,5 +340,10 @@
 CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor,
                                   const ConcreteDataAttributePath & aAttributePath, TLV::TLVReader & aReader,
                                   WriteHandler * apWriteHandler);
+
+/**
+ * Check if the given cluster has the given DataVersion.
+ */
+bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion);
 } // namespace app
 } // namespace chip
diff --git a/src/app/MessageDef/ReadRequestMessage.cpp b/src/app/MessageDef/ReadRequestMessage.cpp
index 9747882..97d2a2a 100644
--- a/src/app/MessageDef/ReadRequestMessage.cpp
+++ b/src/app/MessageDef/ReadRequestMessage.cpp
@@ -59,7 +59,7 @@
             break;
         case to_underlying(Tag::kDataVersionFilters):
             // check if this tag has appeared before
-            VerifyOrReturnError(!(tagPresenceMask & (1 << to_underlying(Tag::kEventFilters))), CHIP_ERROR_INVALID_TLV_TAG);
+            VerifyOrReturnError(!(tagPresenceMask & (1 << to_underlying(Tag::kDataVersionFilters))), CHIP_ERROR_INVALID_TLV_TAG);
             tagPresenceMask |= (1 << to_underlying(Tag::kDataVersionFilters));
             {
                 DataVersionFilterIBs::Parser dataVersionFilters;
diff --git a/src/app/ReadClient.cpp b/src/app/ReadClient.cpp
index 7c0047b..e88aa7c 100644
--- a/src/app/ReadClient.cpp
+++ b/src/app/ReadClient.cpp
@@ -222,6 +222,14 @@
             ReturnErrorOnFailure(err = request.GetError());
             ReturnErrorOnFailure(GenerateAttributePathList(attributePathListBuilder, aReadPrepareParams.mpAttributePathParamsList,
                                                            aReadPrepareParams.mAttributePathParamsListSize));
+            if (aReadPrepareParams.mDataVersionFilterListSize != 0 && aReadPrepareParams.mpDataVersionFilterList != nullptr)
+            {
+                DataVersionFilterIBs::Builder & dataVersionFilterListBuilder = request.CreateDataVersionFilters();
+                ReturnErrorOnFailure(request.GetError());
+                ReturnErrorOnFailure(GenerateDataVersionFilterList(dataVersionFilterListBuilder,
+                                                                   aReadPrepareParams.mpDataVersionFilterList,
+                                                                   aReadPrepareParams.mDataVersionFilterListSize));
+            }
         }
 
         if (aReadPrepareParams.mEventPathParamsListSize != 0 && aReadPrepareParams.mpEventPathParamsList != nullptr)
@@ -296,6 +304,28 @@
     return aAttributePathIBsBuilder.GetError();
 }
 
+CHIP_ERROR ReadClient::GenerateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
+                                                     DataVersionFilter * apDataVersionFilterList, size_t aDataVersionFilterListSize)
+{
+    for (size_t index = 0; index < aDataVersionFilterListSize; index++)
+    {
+        VerifyOrReturnError(apDataVersionFilterList[index].IsValidDataVersionFilter(), CHIP_ERROR_INVALID_ARGUMENT);
+        DataVersionFilterIB::Builder & filter = aDataVersionFilterIBsBuilder.CreateDataVersionFilter();
+        ReturnErrorOnFailure(aDataVersionFilterIBsBuilder.GetError());
+        ClusterPathIB::Builder & path = filter.CreatePath();
+        ReturnErrorOnFailure(filter.GetError());
+        ReturnErrorOnFailure(path.Endpoint(apDataVersionFilterList[index].mEndpointId)
+                                 .Cluster(apDataVersionFilterList[index].mClusterId)
+                                 .EndOfClusterPathIB()
+                                 .GetError());
+        VerifyOrReturnError(apDataVersionFilterList[index].mDataVersion.HasValue(), CHIP_ERROR_INVALID_ARGUMENT);
+        ReturnErrorOnFailure(
+            filter.DataVersion(apDataVersionFilterList[index].mDataVersion.Value()).EndOfDataVersionFilterIB().GetError());
+    }
+
+    return aDataVersionFilterIBsBuilder.EndOfDataVersionFilterIBs().GetError();
+}
+
 CHIP_ERROR ReadClient::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
                                          System::PacketBufferHandle && aPayload)
 {
@@ -554,8 +584,7 @@
         TLV::TLVReader reader = aAttributeReportIBsReader;
         ReturnErrorOnFailure(report.Init(reader));
 
-        DataVersion version = kUndefinedDataVersion;
-        err                 = report.GetAttributeStatus(&status);
+        err = report.GetAttributeStatus(&status);
         if (CHIP_NO_ERROR == err)
         {
             StatusIB::Parser errorStatus;
@@ -563,14 +592,19 @@
             ReturnErrorOnFailure(ProcessAttributePath(path, attributePath));
             ReturnErrorOnFailure(status.GetErrorStatus(&errorStatus));
             ReturnErrorOnFailure(errorStatus.DecodeStatusIB(statusIB));
-            mpCallback.OnAttributeData(attributePath, version, nullptr, statusIB);
+            mpCallback.OnAttributeData(attributePath, nullptr, statusIB);
         }
         else if (CHIP_END_OF_TLV == err)
         {
             ReturnErrorOnFailure(report.GetAttributeData(&data));
             ReturnErrorOnFailure(data.GetPath(&path));
             ReturnErrorOnFailure(ProcessAttributePath(path, attributePath));
-            ReturnErrorOnFailure(data.GetDataVersion(&version));
+            ReturnErrorOnFailure(data.GetDataVersion(&attributePath.mDataVersion));
+            if (mReadPrepareParams.mResubscribePolicy != nullptr)
+            {
+                UpdateDataVersionFilters(attributePath);
+            }
+
             ReturnErrorOnFailure(data.GetData(&dataReader));
 
             // The element in an array may be another array -- so we should only set the list operation when we are handling the
@@ -580,7 +614,7 @@
                 attributePath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll;
             }
 
-            mpCallback.OnAttributeData(attributePath, version, &dataReader, statusIB);
+            mpCallback.OnAttributeData(attributePath, &dataReader, statusIB);
         }
     }
 
@@ -749,6 +783,14 @@
         ReturnErrorOnFailure(err = attributePathListBuilder.GetError());
         ReturnErrorOnFailure(GenerateAttributePathList(attributePathListBuilder, aReadPrepareParams.mpAttributePathParamsList,
                                                        aReadPrepareParams.mAttributePathParamsListSize));
+        if (aReadPrepareParams.mDataVersionFilterListSize != 0 && aReadPrepareParams.mpDataVersionFilterList != nullptr)
+        {
+            DataVersionFilterIBs::Builder & dataVersionFilterListBuilder = request.CreateDataVersionFilters();
+            ReturnErrorOnFailure(request.GetError());
+            ReturnErrorOnFailure(GenerateDataVersionFilterList(dataVersionFilterListBuilder,
+                                                               aReadPrepareParams.mpDataVersionFilterList,
+                                                               aReadPrepareParams.mDataVersionFilterListSize));
+        }
     }
 
     if (aReadPrepareParams.mEventPathParamsListSize != 0 && aReadPrepareParams.mpEventPathParamsList != nullptr)
@@ -775,7 +817,6 @@
 
     request.IsFabricFiltered(aReadPrepareParams.mIsFabricFiltered).EndOfSubscribeRequestMessage();
     ReturnErrorOnFailure(err = request.GetError());
-
     ReturnErrorOnFailure(writer.Finalize(&msgBuf));
 
     mpExchangeCtx = mpExchangeMgr->NewContext(aReadPrepareParams.mSessionHolder.Get(), this);
@@ -831,5 +872,17 @@
     return true;
 }
 
+void ReadClient::UpdateDataVersionFilters(const ConcreteDataAttributePath & aPath)
+{
+    for (size_t index = 0; index < mReadPrepareParams.mDataVersionFilterListSize; index++)
+    {
+        if (mReadPrepareParams.mpDataVersionFilterList[index].mEndpointId == aPath.mEndpointId &&
+            mReadPrepareParams.mpDataVersionFilterList[index].mClusterId == aPath.mClusterId)
+        {
+            // Now we know the current version for this cluster is aPath.mDataVersion.
+            mReadPrepareParams.mpDataVersionFilterList[index].mDataVersion.SetValue(aPath.mDataVersion);
+        }
+    }
+}
 } // namespace app
 } // namespace chip
diff --git a/src/app/ReadClient.h b/src/app/ReadClient.h
index 24f731e..c8f30f3 100644
--- a/src/app/ReadClient.h
+++ b/src/app/ReadClient.h
@@ -110,14 +110,11 @@
          * receives an OnDone call to destroy the object.
          *
          * @param[in] aPath        The attribute path field in report response.
-         * @param[in] aVersion     The data version for cluster in report response.
          * @param[in] apData       The attribute data of the given path, will be a nullptr if status is not Success.
          * @param[in] aStatus      Attribute-specific status, containing an InteractionModel::Status code as well as an
          *                         optional cluster-specific status code.
          */
-        virtual void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
-                                     const StatusIB & aStatus)
-        {}
+        virtual void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) {}
 
         /**
          * OnSubscriptionEstablished will be called when a subscription is established for the given subscription transaction.
@@ -250,13 +247,13 @@
     // used.
     //
     // The application has to know to
-    // a) allocate a ReadPrepareParams object that will have fields mpEventPathParamsList and mpAttributePathParamsList with
-    // lifetimes as long as the ReadClient itself and b) free those up later in the call to OnDeallocatePaths. Note: At a given
-    // time in the system, you can either have a single subscription with re-sub enabled that that has mKeepSubscriptions = false,
-    // OR, multiple subs with re-sub enabled with mKeepSubscriptions = true. You shall not have a mix of both simultaneously.
-    // If SendAutoResubscribeRequest is called at all, it guarantees that it will call OnDeallocatePaths when OnDone is called.
-    // SendAutoResubscribeRequest is the only case that calls OnDeallocatePaths, since that's the only case when the consumer moved
-    // a ReadParams into the client.
+    // a) allocate a ReadPrepareParams object that will have fields mpEventPathParamsList and mpAttributePathParamsList and
+    // mpDataVersionFilterList with lifetimes as long as the ReadClient itself and b) free those up later in the call to
+    // OnDeallocatePaths. Note: At a given time in the system, you can either have a single subscription with re-sub enabled that
+    // that has mKeepSubscriptions = false, OR, multiple subs with re-sub enabled with mKeepSubscriptions = true. You shall not have
+    // a mix of both simultaneously. If SendAutoResubscribeRequest is called at all, it guarantees that it will call
+    // OnDeallocatePaths when OnDone is called. SendAutoResubscribeRequest is the only case that calls OnDeallocatePaths, since
+    // that's the only case when the consumer moved a ReadParams into the client.
     CHIP_ERROR SendAutoResubscribeRequest(ReadPrepareParams && aReadPrepareParams);
 
 private:
@@ -293,6 +290,8 @@
                                   size_t aEventPathParamsListSize);
     CHIP_ERROR GenerateAttributePathList(AttributePathIBs::Builder & aAttributePathIBsBuilder,
                                          AttributePathParams * apAttributePathParamsList, size_t aAttributePathParamsListSize);
+    CHIP_ERROR GenerateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
+                                             DataVersionFilter * apDataVersionFilterList, size_t aDataVersionFilterListSize);
     CHIP_ERROR ProcessAttributeReportIBs(TLV::TLVReader & aAttributeDataIBsReader);
     CHIP_ERROR ProcessEventReportIBs(TLV::TLVReader & aEventReportIBsReader);
 
@@ -310,7 +309,7 @@
     // Specialized request-sending functions.
     CHIP_ERROR SendReadRequest(ReadPrepareParams & aReadPrepareParams);
     CHIP_ERROR SendSubscribeRequest(ReadPrepareParams & aSubscribePrepareParams);
-
+    void UpdateDataVersionFilters(const ConcreteDataAttributePath & aPath);
     static void OnResubscribeTimerCallback(System::Layer * apSystemLayer, void * apAppState);
 
     /*
diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp
index 0f49aaa..9d3eeed 100644
--- a/src/app/ReadHandler.cpp
+++ b/src/app/ReadHandler.cpp
@@ -98,6 +98,7 @@
 
     InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpAttributeClusterInfoList);
     InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpEventClusterInfoList);
+    InteractionModelEngine::GetInstance()->ReleaseClusterInfoList(mpDataVersionFilterList);
 }
 
 void ReadHandler::Close()
@@ -296,7 +297,6 @@
 #if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
     ReturnErrorOnFailure(readRequestParser.CheckSchemaValidity());
 #endif
-
     err = readRequestParser.GetAttributeRequests(&attributePathListParser);
     if (err == CHIP_END_OF_TLV)
     {
@@ -305,6 +305,16 @@
     else if (err == CHIP_NO_ERROR)
     {
         ReturnErrorOnFailure(ProcessAttributePathList(attributePathListParser));
+        DataVersionFilterIBs::Parser dataVersionFilterListParser;
+        err = readRequestParser.GetDataVersionFilters(&dataVersionFilterListParser);
+        if (err == CHIP_END_OF_TLV)
+        {
+            err = CHIP_NO_ERROR;
+        }
+        else if (err == CHIP_NO_ERROR)
+        {
+            ReturnErrorOnFailure(ProcessDataVersionFilterList(dataVersionFilterListParser));
+        }
     }
     ReturnErrorOnFailure(err);
     err = readRequestParser.GetEventRequests(&eventPathListParser);
@@ -356,7 +366,6 @@
     while (CHIP_NO_ERROR == (err = reader.Next()))
     {
         VerifyOrExit(TLV::AnonymousTag() == reader.GetTag(), err = CHIP_ERROR_INVALID_TLV_TAG);
-        VerifyOrExit(TLV::kTLVType_List == reader.GetType(), err = CHIP_ERROR_WRONG_TLV_TYPE);
         ClusterInfo clusterInfo;
         AttributePathIB::Parser path;
         err = path.Init(reader);
@@ -421,6 +430,36 @@
     return err;
 }
 
+CHIP_ERROR ReadHandler::ProcessDataVersionFilterList(DataVersionFilterIBs::Parser & aDataVersionFilterListParser)
+{
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    TLV::TLVReader reader;
+    aDataVersionFilterListParser.GetReader(&reader);
+
+    while (CHIP_NO_ERROR == (err = reader.Next()))
+    {
+        VerifyOrReturnError(TLV::AnonymousTag() == reader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
+        ClusterInfo clusterInfo;
+        ClusterPathIB::Parser path;
+        DataVersionFilterIB::Parser filter;
+        ReturnErrorOnFailure(filter.Init(reader));
+        DataVersion version = 0;
+        ReturnErrorOnFailure(filter.GetDataVersion(&version));
+        clusterInfo.mDataVersion.SetValue(version);
+        ReturnErrorOnFailure(filter.GetPath(&path));
+        ReturnErrorOnFailure(path.GetEndpoint(&(clusterInfo.mEndpointId)));
+        ReturnErrorOnFailure(path.GetCluster(&(clusterInfo.mClusterId)));
+        VerifyOrReturnError(clusterInfo.IsValidDataVersionFilter(), CHIP_ERROR_IM_MALFORMED_DATA_VERSION_FILTER_IB);
+        ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->PushFront(mpDataVersionFilterList, clusterInfo));
+    }
+
+    if (CHIP_END_OF_TLV == err)
+    {
+        err = CHIP_NO_ERROR;
+    }
+    return err;
+}
+
 CHIP_ERROR ReadHandler::ProcessEventPaths(EventPathIBs::Parser & aEventPathsParser)
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
@@ -594,6 +633,16 @@
     else if (err == CHIP_NO_ERROR)
     {
         ReturnErrorOnFailure(ProcessAttributePathList(attributePathListParser));
+        DataVersionFilterIBs::Parser dataVersionFilterListParser;
+        err = subscribeRequestParser.GetDataVersionFilters(&dataVersionFilterListParser);
+        if (err == CHIP_END_OF_TLV)
+        {
+            err = CHIP_NO_ERROR;
+        }
+        else if (err == CHIP_NO_ERROR)
+        {
+            ReturnErrorOnFailure(ProcessDataVersionFilterList(dataVersionFilterListParser));
+        }
     }
     ReturnErrorOnFailure(err);
 
diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h
index aae661f..7f64082 100644
--- a/src/app/ReadHandler.h
+++ b/src/app/ReadHandler.h
@@ -132,8 +132,10 @@
     bool IsGeneratingReports() const { return mState == HandlerState::GeneratingReports; }
     bool IsAwaitingReportResponse() const { return mState == HandlerState::AwaitingReportResponse; }
 
+    CHIP_ERROR ProcessDataVersionFilterList(DataVersionFilterIBs::Parser & aDataVersionFilterListParser);
     ClusterInfo * GetAttributeClusterInfolist() { return mpAttributeClusterInfoList; }
     ClusterInfo * GetEventClusterInfolist() { return mpEventClusterInfoList; }
+    ClusterInfo * GetDataVersionFilterlist() const { return mpDataVersionFilterList; }
     EventNumber & GetEventMin() { return mEventMin; }
     PriorityLevel GetCurrentPriority() { return mCurrentPriority; }
 
@@ -240,6 +242,7 @@
     HandlerState mState                      = HandlerState::Idle;
     ClusterInfo * mpAttributeClusterInfoList = nullptr;
     ClusterInfo * mpEventClusterInfoList     = nullptr;
+    ClusterInfo * mpDataVersionFilterList    = nullptr;
 
     PriorityLevel mCurrentPriority = PriorityLevel::Invalid;
 
diff --git a/src/app/ReadPrepareParams.h b/src/app/ReadPrepareParams.h
index 5fc760d..45caaa5 100644
--- a/src/app/ReadPrepareParams.h
+++ b/src/app/ReadPrepareParams.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include <app/AttributePathParams.h>
+#include <app/DataVersionFilter.h>
 #include <app/EventPathParams.h>
 #include <app/InteractionModelTimeout.h>
 #include <app/util/basic-types.h>
@@ -45,6 +46,8 @@
     size_t mEventPathParamsListSize                 = 0;
     AttributePathParams * mpAttributePathParamsList = nullptr;
     size_t mAttributePathParamsListSize             = 0;
+    DataVersionFilter * mpDataVersionFilterList     = nullptr;
+    size_t mDataVersionFilterListSize               = 0;
     EventNumber mEventNumber                        = 0;
     System::Clock::Timeout mTimeout                 = kImMessageTimeout;
     uint16_t mMinIntervalFloorSeconds               = 0;
@@ -62,6 +65,8 @@
         mEventPathParamsListSize           = other.mEventPathParamsListSize;
         mpAttributePathParamsList          = other.mpAttributePathParamsList;
         mAttributePathParamsListSize       = other.mAttributePathParamsListSize;
+        mpDataVersionFilterList            = other.mpDataVersionFilterList;
+        mDataVersionFilterListSize         = other.mDataVersionFilterListSize;
         mEventNumber                       = other.mEventNumber;
         mMinIntervalFloorSeconds           = other.mMinIntervalFloorSeconds;
         mMaxIntervalCeilingSeconds         = other.mMaxIntervalCeilingSeconds;
@@ -85,6 +90,8 @@
         mEventPathParamsListSize           = other.mEventPathParamsListSize;
         mpAttributePathParamsList          = other.mpAttributePathParamsList;
         mAttributePathParamsListSize       = other.mAttributePathParamsListSize;
+        mpDataVersionFilterList            = other.mpDataVersionFilterList;
+        mDataVersionFilterListSize         = other.mDataVersionFilterListSize;
         mEventNumber                       = other.mEventNumber;
         mMinIntervalFloorSeconds           = other.mMinIntervalFloorSeconds;
         mMaxIntervalCeilingSeconds         = other.mMaxIntervalCeilingSeconds;
diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp
index 244e896..5cdd5a8 100644
--- a/src/app/reporting/Engine.cpp
+++ b/src/app/reporting/Engine.cpp
@@ -47,6 +47,24 @@
     mGlobalDirtySet.ReleaseAll();
 }
 
+bool Engine::IsClusterDataVersionMatch(ClusterInfo * aDataVersionFilterList, const ConcreteReadAttributePath & aPath)
+{
+    bool existPathMatch       = false;
+    bool existVersionMismatch = false;
+    for (auto filter = aDataVersionFilterList; filter != nullptr; filter = filter->mpNext)
+    {
+        if (aPath.mEndpointId == filter->mEndpointId && aPath.mClusterId == filter->mClusterId)
+        {
+            existPathMatch = true;
+            if (!IsClusterDataVersionEqual(filter->mEndpointId, filter->mClusterId, filter->mDataVersion.Value()))
+            {
+                existVersionMismatch = true;
+            }
+        }
+    }
+    return existPathMatch && !existVersionMismatch;
+}
+
 CHIP_ERROR
 Engine::RetrieveClusterData(const SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
                             AttributeReportIBs::Builder & aAttributeReportIBs, const ConcreteReadAttributePath & aPath,
@@ -111,6 +129,13 @@
                     continue;
                 }
             }
+            else
+            {
+                if (IsClusterDataVersionMatch(apReadHandler->GetDataVersionFilterlist(), readPath))
+                {
+                    continue;
+                }
+            }
 
             // If we are processing a read request, or the initial report of a subscription, just regard all paths as dirty paths.
             TLV::TLVWriter attributeBackup;
diff --git a/src/app/reporting/Engine.h b/src/app/reporting/Engine.h
index 1fa762b..b8f430b 100644
--- a/src/app/reporting/Engine.h
+++ b/src/app/reporting/Engine.h
@@ -118,6 +118,13 @@
                                    const ConcreteReadAttributePath & aClusterInfo,
                                    AttributeValueEncoder::AttributeEncodeState * apEncoderState);
 
+    // If version match, it means don't send, if version mismatch, it means send.
+    // If client sends the same path with multiple data versions, client will get the data back per the spec, because at least one
+    // of those will fail to match.  This function should return false if either nothing in the list matches the given
+    // endpoint+cluster in the path or there is an entry in the list that matches the endpoint+cluster in the path but does not
+    // match the current data version of that cluster.
+    bool IsClusterDataVersionMatch(ClusterInfo * aDataVersionFilterList, const ConcreteReadAttributePath & aPath);
+
     /**
      * Check all active subscription, if the subscription has no paths that intersect with global dirty set,
      * it would clear dirty flag for that subscription
diff --git a/src/app/tests/TestAttributeCache.cpp b/src/app/tests/TestAttributeCache.cpp
index f5d6048..6df0a7b 100644
--- a/src/app/tests/TestAttributeCache.cpp
+++ b/src/app/tests/TestAttributeCache.cpp
@@ -126,7 +126,6 @@
     System::PacketBufferTLVReader reader;
     ReadClient::Callback * callback = mReadCallback;
     StatusIB status;
-    DataVersion version = kUndefinedDataVersion;
     callback->OnReportBegin();
 
     uint8_t index = 0;
@@ -198,13 +197,13 @@
             writer.Finalize(&handle);
             reader.Init(std::move(handle));
             NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR);
-            callback->OnAttributeData(path, version, &reader, status);
+            callback->OnAttributeData(path, &reader, status);
         }
         else
         {
             ChipLogProgress(DataManagement, "\t -- Generating Status");
             status.mStatus = Protocols::InteractionModel::Status::Failure;
-            callback->OnAttributeData(path, version, nullptr, status);
+            callback->OnAttributeData(path, nullptr, status);
         }
 
         index++;
diff --git a/src/app/tests/TestBufferedReadCallback.cpp b/src/app/tests/TestBufferedReadCallback.cpp
index d53e36f..82f45c6 100644
--- a/src/app/tests/TestBufferedReadCallback.cpp
+++ b/src/app/tests/TestBufferedReadCallback.cpp
@@ -80,8 +80,7 @@
 
     void OnReportBegin() override;
     void OnReportEnd() override;
-    void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
-                         const StatusIB & aStatus) override;
+    void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override;
     void OnDone() override {}
 
     std::vector<ValidationInstruction> mInstructionList;
@@ -95,7 +94,7 @@
 
 void DataSeriesValidator::OnReportEnd() {}
 
-void DataSeriesValidator::OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
+void DataSeriesValidator::OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
                                           const StatusIB & aStatus)
 {
     uint32_t expectedListLength;
@@ -302,7 +301,6 @@
     ReadClient::Callback * callback = &mReadCallback;
     StatusIB status;
     bool hasData;
-    DataVersion version = kUndefinedDataVersion;
 
     callback->OnReportBegin();
 
@@ -403,7 +401,7 @@
             path.mListOp      = ConcreteDataAttributePath::ListOperation::ReplaceAll;
             status.mStatus    = Protocols::InteractionModel::Status::Failure;
             hasData           = false;
-            callback->OnAttributeData(path, version, &reader, status);
+            callback->OnAttributeData(path, &reader, status);
             break;
         }
 
@@ -414,7 +412,7 @@
             path.mListOp      = ConcreteDataAttributePath::ListOperation::ReplaceAll;
             status.mStatus    = Protocols::InteractionModel::Status::Failure;
             hasData           = false;
-            callback->OnAttributeData(path, version, &reader, status);
+            callback->OnAttributeData(path, &reader, status);
             break;
         }
 
@@ -432,7 +430,7 @@
                 writer.Finalize(&handle);
                 reader.Init(std::move(handle));
                 NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR);
-                callback->OnAttributeData(path, version, &reader, status);
+                callback->OnAttributeData(path, &reader, status);
             }
 
             ChipLogProgress(DataManagement, "\t -- Generating C0..C512");
@@ -455,7 +453,7 @@
                 writer.Finalize(&handle);
                 reader.Init(std::move(handle));
                 NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR);
-                callback->OnAttributeData(path, version, &reader, status);
+                callback->OnAttributeData(path, &reader, status);
             }
 
             break;
@@ -475,7 +473,7 @@
                 writer.Finalize(&handle);
                 reader.Init(std::move(handle));
                 NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR);
-                callback->OnAttributeData(path, version, &reader, status);
+                callback->OnAttributeData(path, &reader, status);
             }
 
             ChipLogProgress(DataManagement, "\t -- Generating D0..D512");
@@ -494,7 +492,7 @@
                 writer.Finalize(&handle);
                 reader.Init(std::move(handle));
                 NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR);
-                callback->OnAttributeData(path, version, &reader, status);
+                callback->OnAttributeData(path, &reader, status);
             }
 
             break;
@@ -509,7 +507,7 @@
             writer.Finalize(&handle);
             reader.Init(std::move(handle));
             NL_TEST_ASSERT(gSuite, reader.Next() == CHIP_NO_ERROR);
-            callback->OnAttributeData(path, version, &reader, status);
+            callback->OnAttributeData(path, &reader, status);
         }
 
         index++;
diff --git a/src/app/tests/TestMessageDef.cpp b/src/app/tests/TestMessageDef.cpp
index 37dead6..95c3cd5 100644
--- a/src/app/tests/TestMessageDef.cpp
+++ b/src/app/tests/TestMessageDef.cpp
@@ -652,7 +652,7 @@
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
     AttributePathIB::Parser attributePathParser;
-    chip::DataVersion version = chip::kUndefinedDataVersion;
+    chip::DataVersion version = 0;
 #if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
     err = aAttributeDataIBParser.CheckSchemaValidity();
     NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
diff --git a/src/app/tests/TestReadInteraction.cpp b/src/app/tests/TestReadInteraction.cpp
index 941f180..f5d4130 100644
--- a/src/app/tests/TestReadInteraction.cpp
+++ b/src/app/tests/TestReadInteraction.cpp
@@ -49,13 +49,16 @@
 uint8_t gInfoEventBuffer[128];
 uint8_t gCritEventBuffer[128];
 chip::app::CircularEventBuffer gCircularEventBuffer[3];
-chip::ClusterId kTestClusterId        = 6;
-chip::ClusterId kInvalidTestClusterId = 7;
-chip::EndpointId kTestEndpointId      = 1;
-chip::EventId kTestEventIdDebug       = 1;
-chip::EventId kTestEventIdCritical    = 2;
-uint8_t kTestFieldValue1              = 1;
-chip::TLV::Tag kTestEventTag          = chip::TLV::ContextTag(1);
+chip::ClusterId kTestClusterId          = 6;
+chip::ClusterId kInvalidTestClusterId   = 7;
+chip::EndpointId kTestEndpointId        = 1;
+chip::EventId kTestEventIdDebug         = 1;
+chip::EventId kTestEventIdCritical      = 2;
+uint8_t kTestFieldValue1                = 1;
+chip::TLV::Tag kTestEventTag            = chip::TLV::ContextTag(1);
+chip::EndpointId kInvalidTestEndpointId = 3;
+chip::DataVersion kTestDataVersion1     = 3;
+chip::DataVersion kTestDataVersion2     = 5;
 
 class TestContext : public chip::Test::AppContext
 {
@@ -143,8 +146,8 @@
         mGotEventResponse = true;
     }
 
-    void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::DataVersion aVersion,
-                         chip::TLV::TLVReader * apData, const chip::app::StatusIB & status) override
+    void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::TLV::TLVReader * apData,
+                         const chip::app::StatusIB & status) override
     {
         if (status.mStatus == chip::Protocols::InteractionModel::Status::Success)
         {
@@ -168,6 +171,11 @@
         {
             delete[] aReadPrepareParams.mpEventPathParamsList;
         }
+
+        if (aReadPrepareParams.mpDataVersionFilterList != nullptr)
+        {
+            delete[] aReadPrepareParams.mpDataVersionFilterList;
+        }
     }
 
     int mNumDataElementIndex               = 0;
@@ -230,6 +238,18 @@
     return AttributeValueEncoder(aAttributeReports, 0, aPath, 0).Encode(kTestFieldValue1);
 }
 
+bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion)
+{
+    if (kTestDataVersion1 == aRequiredVersion)
+    {
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
 class TestReadInteraction
 {
 public:
@@ -243,6 +263,10 @@
     static void TestReadHandlerInvalidAttributePath(nlTestSuite * apSuite, void * apContext);
     static void TestProcessSubscribeRequest(nlTestSuite * apSuite, void * apContext);
     static void TestReadRoundtrip(nlTestSuite * apSuite, void * apContext);
+    static void TestReadRoundtripWithDataVersionFilter(nlTestSuite * apSuite, void * apContext);
+    static void TestReadRoundtripWithNoMatchPathDataVersionFilter(nlTestSuite * apSuite, void * apContext);
+    static void TestReadRoundtripWithMultiSamePathDifferentDataVersionFilter(nlTestSuite * apSuite, void * apContext);
+    static void TestReadRoundtripWithSameDifferentPathsDataVersionFilter(nlTestSuite * apSuite, void * apContext);
     static void TestReadWildcard(nlTestSuite * apSuite, void * apContext);
     static void TestReadChunking(nlTestSuite * apSuite, void * apContext);
     static void TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * apContext);
@@ -728,6 +752,244 @@
     NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
 }
 
+void TestReadInteraction::TestReadRoundtripWithDataVersionFilter(nlTestSuite * apSuite, void * apContext)
+{
+    TestContext & ctx = *static_cast<TestContext *>(apContext);
+    CHIP_ERROR err    = CHIP_NO_ERROR;
+
+    Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr();
+    // Shouldn't have anything in the retransmit table when starting the test.
+    NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0);
+
+    GenerateEvents(apSuite, apContext);
+
+    MockInteractionModelApp delegate;
+    auto * engine = chip::app::InteractionModelEngine::GetInstance();
+    err           = engine->Init(&ctx.GetExchangeManager());
+    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse);
+
+    chip::app::AttributePathParams attributePathParams[2];
+    attributePathParams[0].mEndpointId  = kTestEndpointId;
+    attributePathParams[0].mClusterId   = kTestClusterId;
+    attributePathParams[0].mAttributeId = 1;
+
+    attributePathParams[1].mEndpointId  = kTestEndpointId;
+    attributePathParams[1].mClusterId   = kTestClusterId;
+    attributePathParams[1].mAttributeId = 2;
+    attributePathParams[1].mListIndex   = 1;
+
+    chip::app::DataVersionFilter dataVersionFilters[1];
+    dataVersionFilters[0].mEndpointId = kTestEndpointId;
+    dataVersionFilters[0].mClusterId  = kTestClusterId;
+    dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1);
+
+    ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice());
+    readPrepareParams.mpAttributePathParamsList    = attributePathParams;
+    readPrepareParams.mAttributePathParamsListSize = 2;
+    readPrepareParams.mpDataVersionFilterList      = dataVersionFilters;
+    readPrepareParams.mDataVersionFilterListSize   = 1;
+
+    {
+        app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate,
+                                   chip::app::ReadClient::InteractionType::Read);
+
+        err = readClient.SendRequest(readPrepareParams);
+        NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+
+        InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+        NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 0);
+
+        delegate.mNumAttributeResponse = 0;
+    }
+
+    NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0);
+    engine->Shutdown();
+    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+}
+
+void TestReadInteraction::TestReadRoundtripWithNoMatchPathDataVersionFilter(nlTestSuite * apSuite, void * apContext)
+{
+    TestContext & ctx = *static_cast<TestContext *>(apContext);
+    CHIP_ERROR err    = CHIP_NO_ERROR;
+
+    Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr();
+    // Shouldn't have anything in the retransmit table when starting the test.
+    NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0);
+
+    GenerateEvents(apSuite, apContext);
+
+    MockInteractionModelApp delegate;
+    auto * engine = chip::app::InteractionModelEngine::GetInstance();
+    err           = engine->Init(&ctx.GetExchangeManager());
+    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+
+    chip::app::AttributePathParams attributePathParams[2];
+    attributePathParams[0].mEndpointId  = kTestEndpointId;
+    attributePathParams[0].mClusterId   = kTestClusterId;
+    attributePathParams[0].mAttributeId = 1;
+
+    attributePathParams[1].mEndpointId  = kTestEndpointId;
+    attributePathParams[1].mClusterId   = kTestClusterId;
+    attributePathParams[1].mAttributeId = 2;
+    attributePathParams[1].mListIndex   = 1;
+
+    chip::app::DataVersionFilter dataVersionFilters[2];
+    dataVersionFilters[0].mEndpointId = kTestEndpointId;
+    dataVersionFilters[0].mClusterId  = kInvalidTestClusterId;
+    dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1);
+
+    dataVersionFilters[1].mEndpointId = kInvalidTestEndpointId;
+    dataVersionFilters[1].mClusterId  = kTestClusterId;
+    dataVersionFilters[1].mDataVersion.SetValue(kTestDataVersion2);
+
+    ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice());
+    readPrepareParams.mpAttributePathParamsList    = attributePathParams;
+    readPrepareParams.mAttributePathParamsListSize = 2;
+    readPrepareParams.mpDataVersionFilterList      = dataVersionFilters;
+    readPrepareParams.mDataVersionFilterListSize   = 2;
+
+    {
+        app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate,
+                                   chip::app::ReadClient::InteractionType::Read);
+
+        err = readClient.SendRequest(readPrepareParams);
+        NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+
+        InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+        NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2);
+        NL_TEST_ASSERT(apSuite, !delegate.mReadError);
+
+        delegate.mNumAttributeResponse = 0;
+    }
+
+    NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0);
+    engine->Shutdown();
+    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+}
+
+void TestReadInteraction::TestReadRoundtripWithMultiSamePathDifferentDataVersionFilter(nlTestSuite * apSuite, void * apContext)
+{
+    TestContext & ctx = *static_cast<TestContext *>(apContext);
+    CHIP_ERROR err    = CHIP_NO_ERROR;
+
+    Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr();
+    // Shouldn't have anything in the retransmit table when starting the test.
+    NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0);
+
+    GenerateEvents(apSuite, apContext);
+
+    MockInteractionModelApp delegate;
+    auto * engine = chip::app::InteractionModelEngine::GetInstance();
+    err           = engine->Init(&ctx.GetExchangeManager());
+    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse);
+
+    chip::app::AttributePathParams attributePathParams[2];
+    attributePathParams[0].mEndpointId  = kTestEndpointId;
+    attributePathParams[0].mClusterId   = kTestClusterId;
+    attributePathParams[0].mAttributeId = 1;
+
+    attributePathParams[1].mEndpointId  = kTestEndpointId;
+    attributePathParams[1].mClusterId   = kTestClusterId;
+    attributePathParams[1].mAttributeId = 2;
+    attributePathParams[1].mListIndex   = 1;
+
+    chip::app::DataVersionFilter dataVersionFilters[2];
+    dataVersionFilters[0].mEndpointId = kTestEndpointId;
+    dataVersionFilters[0].mClusterId  = kTestClusterId;
+    dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1);
+
+    dataVersionFilters[1].mEndpointId = kTestEndpointId;
+    dataVersionFilters[1].mClusterId  = kTestClusterId;
+    dataVersionFilters[1].mDataVersion.SetValue(kTestDataVersion2);
+
+    ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice());
+    readPrepareParams.mpAttributePathParamsList    = attributePathParams;
+    readPrepareParams.mAttributePathParamsListSize = 2;
+    readPrepareParams.mpDataVersionFilterList      = dataVersionFilters;
+    readPrepareParams.mDataVersionFilterListSize   = 2;
+
+    {
+        app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate,
+                                   chip::app::ReadClient::InteractionType::Read);
+
+        err = readClient.SendRequest(readPrepareParams);
+        NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+
+        InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+        NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 2);
+        NL_TEST_ASSERT(apSuite, !delegate.mReadError);
+
+        delegate.mNumAttributeResponse = 0;
+    }
+
+    NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0);
+    engine->Shutdown();
+    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+}
+
+void TestReadInteraction::TestReadRoundtripWithSameDifferentPathsDataVersionFilter(nlTestSuite * apSuite, void * apContext)
+{
+    TestContext & ctx = *static_cast<TestContext *>(apContext);
+    CHIP_ERROR err    = CHIP_NO_ERROR;
+
+    Messaging::ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr();
+    // Shouldn't have anything in the retransmit table when starting the test.
+    NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0);
+
+    GenerateEvents(apSuite, apContext);
+
+    MockInteractionModelApp delegate;
+    auto * engine = chip::app::InteractionModelEngine::GetInstance();
+    err           = engine->Init(&ctx.GetExchangeManager());
+    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    NL_TEST_ASSERT(apSuite, !delegate.mGotEventResponse);
+
+    chip::app::AttributePathParams attributePathParams[2];
+    attributePathParams[0].mEndpointId  = kTestEndpointId;
+    attributePathParams[0].mClusterId   = kTestClusterId;
+    attributePathParams[0].mAttributeId = 1;
+
+    attributePathParams[1].mEndpointId  = kTestEndpointId;
+    attributePathParams[1].mClusterId   = kTestClusterId;
+    attributePathParams[1].mAttributeId = 2;
+    attributePathParams[1].mListIndex   = 1;
+
+    chip::app::DataVersionFilter dataVersionFilters[2];
+    dataVersionFilters[0].mEndpointId = kTestEndpointId;
+    dataVersionFilters[0].mClusterId  = kTestClusterId;
+    dataVersionFilters[0].mDataVersion.SetValue(kTestDataVersion1);
+
+    dataVersionFilters[1].mEndpointId = kInvalidTestEndpointId;
+    dataVersionFilters[1].mClusterId  = kTestClusterId;
+    dataVersionFilters[1].mDataVersion.SetValue(kTestDataVersion2);
+
+    ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice());
+    readPrepareParams.mpAttributePathParamsList    = attributePathParams;
+    readPrepareParams.mAttributePathParamsListSize = 2;
+    readPrepareParams.mpDataVersionFilterList      = dataVersionFilters;
+    readPrepareParams.mDataVersionFilterListSize   = 2;
+
+    {
+        app::ReadClient readClient(chip::app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), delegate,
+                                   chip::app::ReadClient::InteractionType::Read);
+
+        err = readClient.SendRequest(readPrepareParams);
+        NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+
+        InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+        NL_TEST_ASSERT(apSuite, delegate.mNumAttributeResponse == 0);
+        NL_TEST_ASSERT(apSuite, !delegate.mReadError);
+
+        delegate.mNumAttributeResponse = 0;
+    }
+
+    NL_TEST_ASSERT(apSuite, engine->GetNumActiveReadClients() == 0);
+    engine->Shutdown();
+    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+}
+
 void TestReadInteraction::TestReadWildcard(nlTestSuite * apSuite, void * apContext)
 {
     TestContext & ctx = *static_cast<TestContext *>(apContext);
@@ -1497,6 +1759,10 @@
 const nlTest sTests[] =
 {
     NL_TEST_DEF("TestReadRoundtrip", chip::app::TestReadInteraction::TestReadRoundtrip),
+    NL_TEST_DEF("TestReadRoundtripWithDataVersionFilter", chip::app::TestReadInteraction::TestReadRoundtripWithDataVersionFilter),
+    NL_TEST_DEF("TestReadRoundtripWithNoMatchPathDataVersionFilter", chip::app::TestReadInteraction::TestReadRoundtripWithNoMatchPathDataVersionFilter),
+    NL_TEST_DEF("TestReadRoundtripWithMultiSamePathDifferentDataVersionFilter", chip::app::TestReadInteraction::TestReadRoundtripWithMultiSamePathDifferentDataVersionFilter),
+    NL_TEST_DEF("TestReadRoundtripWithSameDifferentPathsDataVersionFilter", chip::app::TestReadInteraction::TestReadRoundtripWithSameDifferentPathsDataVersionFilter),
     NL_TEST_DEF("TestReadWildcard", chip::app::TestReadInteraction::TestReadWildcard),
     NL_TEST_DEF("TestReadChunking", chip::app::TestReadInteraction::TestReadChunking),
     NL_TEST_DEF("TestSetDirtyBetweenChunks", chip::app::TestReadInteraction::TestSetDirtyBetweenChunks),
diff --git a/src/app/tests/integration/chip_im_initiator.cpp b/src/app/tests/integration/chip_im_initiator.cpp
index 56c0cad..6164700 100644
--- a/src/app/tests/integration/chip_im_initiator.cpp
+++ b/src/app/tests/integration/chip_im_initiator.cpp
@@ -145,8 +145,8 @@
             }
         }
     }
-    void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::DataVersion aVersion,
-                         chip::TLV::TLVReader * aData, const chip::app::StatusIB & status) override
+    void OnAttributeData(const chip::app::ConcreteDataAttributePath & aPath, chip::TLV::TLVReader * aData,
+                         const chip::app::StatusIB & status) override
     {}
 
     void OnError(CHIP_ERROR aError) override { printf("ReadError with err %" CHIP_ERROR_FORMAT, aError.Format()); }
@@ -667,6 +667,11 @@
     }
     return CHIP_NO_ERROR;
 }
+
+bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion)
+{
+    return true;
+}
 } // namespace app
 } // namespace chip
 
diff --git a/src/app/tests/integration/chip_im_responder.cpp b/src/app/tests/integration/chip_im_responder.cpp
index 481dbae..2dbce00 100644
--- a/src/app/tests/integration/chip_im_responder.cpp
+++ b/src/app/tests/integration/chip_im_responder.cpp
@@ -129,6 +129,11 @@
     err = apWriteHandler->AddStatus(attributePath, Protocols::InteractionModel::Status::Success);
     return err;
 }
+
+bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion)
+{
+    return true;
+}
 } // namespace app
 } // namespace chip
 
diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp
index f7bc0ab..a62e03c 100644
--- a/src/app/util/ember-compatibility-functions.cpp
+++ b/src/app/util/ember-compatibility-functions.cpp
@@ -403,7 +403,7 @@
 {
     AttributeValueEncoder::AttributeEncodeState state =
         (aEncoderState == nullptr ? AttributeValueEncoder::AttributeEncodeState() : *aEncoderState);
-    DataVersion version = kUndefinedDataVersion;
+    DataVersion version = 0;
     ReturnErrorOnFailure(ReadClusterDataVersion(aPath.mEndpointId, aPath.mClusterId, version));
     AttributeValueEncoder valueEncoder(aAttributeReports, aAccessingFabricIndex, aPath, version, aIsFabricFiltered, state);
     CHIP_ERROR err = aAccessInterface->Read(aPath, valueEncoder);
@@ -536,7 +536,7 @@
     AttributeDataIB::Builder & attributeDataIBBuilder = attributeReport.CreateAttributeData();
     ReturnErrorOnFailure(attributeDataIBBuilder.GetError());
 
-    DataVersion version = kUndefinedDataVersion;
+    DataVersion version = 0;
     ReturnErrorOnFailure(ReadClusterDataVersion(aPath.mEndpointId, aPath.mClusterId, version));
     attributeDataIBBuilder.DataVersion(version);
     ReturnErrorOnFailure(attributeDataIBBuilder.GetError());
@@ -980,6 +980,21 @@
     return apWriteHandler->AddStatus(aPath, status);
 }
 
+bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredVersion)
+{
+    DataVersion * version = emberAfDataVersionStorage(aEndpointId, aClusterId);
+    if (version == nullptr)
+    {
+        ChipLogError(DataManagement, "Endpoint %" PRIx16 ", Cluster " ChipLogFormatMEI " not found in IsClusterDataVersionEqual!",
+                     aEndpointId, ChipLogValueMEI(aClusterId));
+        return false;
+    }
+    else
+    {
+        return (*(version)) == aRequiredVersion;
+    }
+}
+
 } // namespace app
 } // namespace chip
 
diff --git a/src/controller/CHIPCluster.h b/src/controller/CHIPCluster.h
index 47ab6ce..942f287 100644
--- a/src/controller/CHIPCluster.h
+++ b/src/controller/CHIPCluster.h
@@ -187,15 +187,16 @@
      */
     template <typename AttributeInfo>
     CHIP_ERROR ReadAttribute(void * context, ReadResponseSuccessCallback<typename AttributeInfo::DecodableArgType> successCb,
-                             ReadResponseFailureCallback failureCb)
+                             ReadResponseFailureCallback failureCb, const Optional<DataVersion> & aDataVersion = NullOptional)
     {
         return ReadAttribute<typename AttributeInfo::DecodableType, typename AttributeInfo::DecodableArgType>(
-            context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), successCb, failureCb);
+            context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), successCb, failureCb, aDataVersion);
     }
 
     template <typename DecodableType, typename DecodableArgType>
     CHIP_ERROR ReadAttribute(void * context, ClusterId clusterId, AttributeId attributeId,
-                             ReadResponseSuccessCallback<DecodableArgType> successCb, ReadResponseFailureCallback failureCb)
+                             ReadResponseSuccessCallback<DecodableArgType> successCb, ReadResponseFailureCallback failureCb,
+                             const Optional<DataVersion> & aDataVersion = NullOptional)
     {
         VerifyOrReturnError(mDevice != nullptr, CHIP_ERROR_INCORRECT_STATE);
 
@@ -214,7 +215,8 @@
         };
 
         return Controller::ReadAttribute<DecodableType>(mDevice->GetExchangeManager(), mDevice->GetSecureSession().Value(),
-                                                        mEndpoint, clusterId, attributeId, onSuccessCb, onFailureCb);
+                                                        mEndpoint, clusterId, attributeId, onSuccessCb, onFailureCb, true,
+                                                        aDataVersion);
     }
 
     /**
@@ -225,18 +227,20 @@
     CHIP_ERROR SubscribeAttribute(void * context, ReadResponseSuccessCallback<typename AttributeInfo::DecodableArgType> reportCb,
                                   ReadResponseFailureCallback failureCb, uint16_t minIntervalFloorSeconds,
                                   uint16_t maxIntervalCeilingSeconds,
-                                  SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr)
+                                  SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr,
+                                  const Optional<DataVersion> & aDataVersion                = NullOptional)
     {
         return SubscribeAttribute<typename AttributeInfo::DecodableType, typename AttributeInfo::DecodableArgType>(
             context, AttributeInfo::GetClusterId(), AttributeInfo::GetAttributeId(), reportCb, failureCb, minIntervalFloorSeconds,
-            maxIntervalCeilingSeconds, subscriptionEstablishedCb);
+            maxIntervalCeilingSeconds, subscriptionEstablishedCb, aDataVersion);
     }
 
     template <typename DecodableType, typename DecodableArgType>
     CHIP_ERROR SubscribeAttribute(void * context, ClusterId clusterId, AttributeId attributeId,
                                   ReadResponseSuccessCallback<DecodableArgType> reportCb, ReadResponseFailureCallback failureCb,
                                   uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds,
-                                  SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr)
+                                  SubscriptionEstablishedCallback subscriptionEstablishedCb = nullptr,
+                                  const Optional<DataVersion> & aDataVersion                = NullOptional)
     {
         VerifyOrReturnError(mDevice != nullptr, CHIP_ERROR_INCORRECT_STATE);
 
@@ -261,9 +265,10 @@
             }
         };
 
-        return Controller::SubscribeAttribute<DecodableType>(
-            mDevice->GetExchangeManager(), mDevice->GetSecureSession().Value(), mEndpoint, clusterId, attributeId, onReportCb,
-            onFailureCb, minIntervalFloorSeconds, maxIntervalCeilingSeconds, onSubscriptionEstablishedCb);
+        return Controller::SubscribeAttribute<DecodableType>(mDevice->GetExchangeManager(), mDevice->GetSecureSession().Value(),
+                                                             mEndpoint, clusterId, attributeId, onReportCb, onFailureCb,
+                                                             minIntervalFloorSeconds, maxIntervalCeilingSeconds,
+                                                             onSubscriptionEstablishedCb, true, false, aDataVersion);
     }
 
     /**
diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp
index 3242226..47add2e 100644
--- a/src/controller/CHIPDeviceController.cpp
+++ b/src/controller/CHIPDeviceController.cpp
@@ -483,7 +483,6 @@
         constexpr EndpointId kBasicClusterEndpoint = 0;
         chip::Controller::BasicCluster cluster;
         cluster.Associate(device, kBasicClusterEndpoint);
-
         return cluster.ReadAttribute<app::Clusters::Basic::Attributes::VendorID::TypeInfo>(this, OnVIDReadResponse,
                                                                                            OnVIDPIDReadFailureResponse);
     }
diff --git a/src/controller/ReadInteraction.h b/src/controller/ReadInteraction.h
index 57101cf..9296f02 100644
--- a/src/controller/ReadInteraction.h
+++ b/src/controller/ReadInteraction.h
@@ -43,7 +43,8 @@
 
 template <typename DecodableAttributeType>
 CHIP_ERROR ReportAttribute(Messaging::ExchangeManager * exchangeMgr, EndpointId endpointId, ClusterId clusterId,
-                           AttributeId attributeId, ReportAttributeParams<DecodableAttributeType> && readParams)
+                           AttributeId attributeId, ReportAttributeParams<DecodableAttributeType> && readParams,
+                           const Optional<DataVersion> & aDataVersion = NullOptional)
 {
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
     CHIP_ERROR err                       = CHIP_NO_ERROR;
@@ -52,7 +53,14 @@
     VerifyOrReturnError(readPaths != nullptr, CHIP_ERROR_NO_MEMORY);
     readParams.mpAttributePathParamsList    = readPaths.get();
     readParams.mAttributePathParamsListSize = 1;
-
+    chip::Platform::UniquePtr<chip::app::DataVersionFilter> dataVersionFilters;
+    if (aDataVersion.HasValue())
+    {
+        dataVersionFilters = Platform::MakeUnique<app::DataVersionFilter>(endpointId, clusterId, aDataVersion.Value());
+        VerifyOrReturnError(dataVersionFilters != nullptr, CHIP_ERROR_NO_MEMORY);
+        readParams.mpDataVersionFilterList    = dataVersionFilters.get();
+        readParams.mDataVersionFilterListSize = 1;
+    }
     auto onDone = [](TypedReadAttributeCallback<DecodableAttributeType> * callback) { chip::Platform::Delete(callback); };
 
     auto callback = chip::Platform::MakeUnique<TypedReadAttributeCallback<DecodableAttributeType>>(
@@ -66,6 +74,8 @@
     if (readClient->IsSubscriptionType())
     {
         readPaths.release();
+        dataVersionFilters.release();
+
         err = readClient->SendAutoResubscribeRequest(std::move(readParams));
         ReturnErrorOnFailure(err);
     }
@@ -97,13 +107,13 @@
                          ClusterId clusterId, AttributeId attributeId,
                          typename TypedReadAttributeCallback<DecodableAttributeType>::OnSuccessCallbackType onSuccessCb,
                          typename TypedReadAttributeCallback<DecodableAttributeType>::OnErrorCallbackType onErrorCb,
-                         bool fabricFiltered = true)
+                         bool fabricFiltered = true, const Optional<DataVersion> & aDataVersion = NullOptional)
 {
     detail::ReportAttributeParams<DecodableAttributeType> params(sessionHandle);
     params.mOnReportCb       = onSuccessCb;
     params.mOnErrorCb        = onErrorCb;
     params.mIsFabricFiltered = fabricFiltered;
-    return detail::ReportAttribute(exchangeMgr, endpointId, clusterId, attributeId, std::move(params));
+    return detail::ReportAttribute(exchangeMgr, endpointId, clusterId, attributeId, std::move(params), aDataVersion);
 }
 
 /*
@@ -120,23 +130,23 @@
 ReadAttribute(Messaging::ExchangeManager * exchangeMgr, const SessionHandle & sessionHandle, EndpointId endpointId,
               typename TypedReadAttributeCallback<typename AttributeTypeInfo::DecodableType>::OnSuccessCallbackType onSuccessCb,
               typename TypedReadAttributeCallback<typename AttributeTypeInfo::DecodableType>::OnErrorCallbackType onErrorCb,
-              bool fabricFiltered = true)
+              bool fabricFiltered = true, const Optional<DataVersion> & aDataVersion = NullOptional)
 {
     return ReadAttribute<typename AttributeTypeInfo::DecodableType>(
         exchangeMgr, sessionHandle, endpointId, AttributeTypeInfo::GetClusterId(), AttributeTypeInfo::GetAttributeId(), onSuccessCb,
-        onErrorCb, fabricFiltered);
+        onErrorCb, fabricFiltered, aDataVersion);
 }
 
 // Helper for SubscribeAttribute to reduce the amount of code generated.
 template <typename DecodableAttributeType>
-CHIP_ERROR SubscribeAttribute(Messaging::ExchangeManager * exchangeMgr, const SessionHandle & sessionHandle, EndpointId endpointId,
-                              ClusterId clusterId, AttributeId attributeId,
-                              typename TypedReadAttributeCallback<DecodableAttributeType>::OnSuccessCallbackType onReportCb,
-                              typename TypedReadAttributeCallback<DecodableAttributeType>::OnErrorCallbackType onErrorCb,
-                              uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds,
-                              typename TypedReadAttributeCallback<DecodableAttributeType>::OnSubscriptionEstablishedCallbackType
-                                  onSubscriptionEstablishedCb = nullptr,
-                              bool fabricFiltered = true, bool keepPreviousSubscriptions = false)
+CHIP_ERROR SubscribeAttribute(
+    Messaging::ExchangeManager * exchangeMgr, const SessionHandle & sessionHandle, EndpointId endpointId, ClusterId clusterId,
+    AttributeId attributeId, typename TypedReadAttributeCallback<DecodableAttributeType>::OnSuccessCallbackType onReportCb,
+    typename TypedReadAttributeCallback<DecodableAttributeType>::OnErrorCallbackType onErrorCb, uint16_t minIntervalFloorSeconds,
+    uint16_t maxIntervalCeilingSeconds,
+    typename TypedReadAttributeCallback<DecodableAttributeType>::OnSubscriptionEstablishedCallbackType onSubscriptionEstablishedCb =
+        nullptr,
+    bool fabricFiltered = true, bool keepPreviousSubscriptions = false, const Optional<DataVersion> & aDataVersion = NullOptional)
 {
     detail::ReportAttributeParams<DecodableAttributeType> params(sessionHandle);
     params.mOnReportCb                  = onReportCb;
@@ -147,7 +157,7 @@
     params.mKeepSubscriptions           = keepPreviousSubscriptions;
     params.mReportType                  = app::ReadClient::InteractionType::Subscribe;
     params.mIsFabricFiltered            = fabricFiltered;
-    return detail::ReportAttribute(exchangeMgr, endpointId, clusterId, attributeId, std::move(params));
+    return detail::ReportAttribute(exchangeMgr, endpointId, clusterId, attributeId, std::move(params), aDataVersion);
 }
 
 /*
@@ -163,12 +173,12 @@
     uint16_t aMinIntervalFloorSeconds, uint16_t aMaxIntervalCeilingSeconds,
     typename TypedReadAttributeCallback<typename AttributeTypeInfo::DecodableType>::OnSubscriptionEstablishedCallbackType
         onSubscriptionEstablishedCb = nullptr,
-    bool fabricFiltered = true, bool keepPreviousSubscriptions = false)
+    bool fabricFiltered = true, bool keepPreviousSubscriptions = false, const Optional<DataVersion> & aDataVersion = NullOptional)
 {
     return SubscribeAttribute<typename AttributeTypeInfo::DecodableType>(
         exchangeMgr, sessionHandle, endpointId, AttributeTypeInfo::GetClusterId(), AttributeTypeInfo::GetAttributeId(), onReportCb,
         onErrorCb, aMinIntervalFloorSeconds, aMaxIntervalCeilingSeconds, onSubscriptionEstablishedCb, fabricFiltered,
-        keepPreviousSubscriptions);
+        keepPreviousSubscriptions, aDataVersion);
 }
 
 namespace detail {
diff --git a/src/controller/TypedReadCallback.h b/src/controller/TypedReadCallback.h
index a842f6d..12b4200 100644
--- a/src/controller/TypedReadCallback.h
+++ b/src/controller/TypedReadCallback.h
@@ -49,9 +49,9 @@
 {
 public:
     using OnSuccessCallbackType =
-        std::function<void(const app::ConcreteAttributePath & aPath, const DecodableAttributeType & aData)>;
-    using OnErrorCallbackType                   = std::function<void(const app::ConcreteAttributePath * aPath, CHIP_ERROR aError)>;
-    using OnDoneCallbackType                    = std::function<void(TypedReadAttributeCallback * callback)>;
+        std::function<void(const app::ConcreteDataAttributePath & aPath, const DecodableAttributeType & aData)>;
+    using OnErrorCallbackType = std::function<void(const app::ConcreteDataAttributePath * aPath, CHIP_ERROR aError)>;
+    using OnDoneCallbackType  = std::function<void(TypedReadAttributeCallback * callback)>;
     using OnSubscriptionEstablishedCallbackType = std::function<void()>;
 
     TypedReadAttributeCallback(ClusterId aClusterId, AttributeId aAttributeId, OnSuccessCallbackType aOnSuccess,
@@ -67,7 +67,7 @@
     void AdoptReadClient(Platform::UniquePtr<app::ReadClient> aReadClient) { mReadClient = std::move(aReadClient); }
 
 private:
-    void OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
+    void OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
                          const app::StatusIB & aStatus) override
     {
         CHIP_ERROR err = CHIP_NO_ERROR;
@@ -108,13 +108,12 @@
 
     void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override
     {
-        if (aReadPrepareParams.mpAttributePathParamsList != nullptr)
-        {
-            for (size_t i = 0; i < aReadPrepareParams.mAttributePathParamsListSize; i++)
-            {
-                chip::Platform::Delete<app::AttributePathParams>(&aReadPrepareParams.mpAttributePathParamsList[i]);
-            }
-        }
+        VerifyOrDie(aReadPrepareParams.mAttributePathParamsListSize == 1 &&
+                    aReadPrepareParams.mpAttributePathParamsList != nullptr);
+        chip::Platform::Delete<app::AttributePathParams>(aReadPrepareParams.mpAttributePathParamsList);
+
+        VerifyOrDie(aReadPrepareParams.mDataVersionFilterListSize == 1 && aReadPrepareParams.mpDataVersionFilterList != nullptr);
+        chip::Platform::Delete<app::DataVersionFilter>(aReadPrepareParams.mpDataVersionFilterList);
     }
 
     ClusterId mClusterId;
@@ -172,13 +171,8 @@
 
     void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override
     {
-        if (aReadPrepareParams.mpEventPathParamsList != nullptr)
-        {
-            for (size_t i = 0; i < aReadPrepareParams.mEventPathParamsListSize; i++)
-            {
-                chip::Platform::Delete<app::EventPathParams>(&aReadPrepareParams.mpEventPathParamsList[i]);
-            }
-        }
+        VerifyOrDie(aReadPrepareParams.mEventPathParamsListSize == 1 && aReadPrepareParams.mpEventPathParamsList != nullptr);
+        chip::Platform::Delete<app::EventPathParams>(aReadPrepareParams.mpEventPathParamsList);
     }
 
     void OnSubscriptionEstablished(uint64_t aSubscriptionId) override
diff --git a/src/controller/java/AndroidCallbacks.cpp b/src/controller/java/AndroidCallbacks.cpp
index cf31fae..cb73e9b 100644
--- a/src/controller/java/AndroidCallbacks.cpp
+++ b/src/controller/java/AndroidCallbacks.cpp
@@ -176,7 +176,7 @@
     env->CallVoidMethod(mReportCallbackRef, onReportMethod, map);
 }
 
-void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
+void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
                                      const app::StatusIB & aStatus)
 {
     CHIP_ERROR err           = CHIP_NO_ERROR;
diff --git a/src/controller/java/AndroidCallbacks.h b/src/controller/java/AndroidCallbacks.h
index f8ebed8..3935afb 100644
--- a/src/controller/java/AndroidCallbacks.h
+++ b/src/controller/java/AndroidCallbacks.h
@@ -52,7 +52,7 @@
 
     void OnReportEnd() override;
 
-    void OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
+    void OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
                          const app::StatusIB & aStatus) override;
 
     void OnError(CHIP_ERROR aError) override;
diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py
index 517deba..152b76d 100644
--- a/src/controller/python/chip/ChipDeviceCtrl.py
+++ b/src/controller/python/chip/ChipDeviceCtrl.py
@@ -581,7 +581,7 @@
         typing.Tuple[int, typing.Type[ClusterObjects.Cluster]],
         # Concrete path
         typing.Tuple[int, typing.Type[ClusterObjects.ClusterAttributeDescriptor]]
-    ]], returnClusterObject: bool = False, reportInterval: typing.Tuple[int, int] = None, fabricFiltered: bool = True):
+    ]], dataVersionFilters: typing.List[typing.Tuple[int, typing.Type[ClusterObjects.Cluster], int]] = None, returnClusterObject: bool = False, reportInterval: typing.Tuple[int, int] = None, fabricFiltered: bool = True):
         '''
         Read a list of attributes from a target node
 
@@ -614,6 +614,7 @@
 
         device = self.GetConnectedDeviceSync(nodeid)
         attrs = []
+        filters = []
         for v in attributes:
             endpoint = None
             cluster = None
@@ -641,8 +642,24 @@
                     raise ValueError("Unsupported Attribute Path")
             attrs.append(ClusterAttribute.AttributePath(
                 EndpointId=endpoint, Cluster=cluster, Attribute=attribute))
+        if dataVersionFilters != None:
+            for v in dataVersionFilters:
+                endpoint = None
+                cluster = None
+
+                # endpoint + (cluster) attribute / endpoint + cluster
+                endpoint = v[0]
+                if issubclass(v[1], ClusterObjects.Cluster):
+                    cluster = v[1]
+                else:
+                    raise ValueError("Unsupported Cluster Path")
+                dataVersion = v[2]
+                filters.append(ClusterAttribute.DataVersionFilter(
+                    EndpointId=endpoint, Cluster=cluster, DataVersion=dataVersion))
+            else:
+                filters = None
         res = self._ChipStack.Call(
-            lambda: ClusterAttribute.ReadAttributes(future, eventLoop, device, self, attrs, returnClusterObject, ClusterAttribute.SubscriptionParameters(reportInterval[0], reportInterval[1]) if reportInterval else None, fabricFiltered=fabricFiltered))
+            lambda: ClusterAttribute.ReadAttributes(future, eventLoop, device, self, attrs, filters, returnClusterObject, ClusterAttribute.SubscriptionParameters(reportInterval[0], reportInterval[1]) if reportInterval else None, fabricFiltered=fabricFiltered))
         if res != 0:
             raise self._ChipStack.ErrorToException(res)
         return await future
@@ -755,7 +772,7 @@
             nodeid, [(endpoint, attributeType)]))
         path = ClusterAttribute.AttributePath(
             EndpointId=endpoint, Attribute=attributeType)
-        return im.AttributeReadResult(path=im.AttributePath(nodeId=nodeid, endpointId=path.EndpointId, clusterId=path.ClusterId, attributeId=path.AttributeId), status=0, value=result[endpoint][clusterType][attributeType])
+        return im.AttributeReadResult(path=im.AttributePath(nodeId=nodeid, endpointId=path.EndpointId, clusterId=path.ClusterId, attributeId=path.AttributeId), status=0, value=result[0][endpoint][clusterType][attributeType])
 
     def ZCLWriteAttribute(self, cluster: str, attribute: str, nodeid, endpoint, groupid, value, blocking=True):
         req = None
@@ -775,7 +792,7 @@
             req = eval(f"GeneratedObjects.{cluster}.Attributes.{attribute}")
         except:
             raise UnknownAttribute(cluster, attribute)
-        return asyncio.run(self.ReadAttribute(nodeid, [(endpoint, req)], False, reportInterval=(minInterval, maxInterval)))
+        return asyncio.run(self.ReadAttribute(nodeid, [(endpoint, req)], None, False, reportInterval=(minInterval, maxInterval)))
 
     def ZCLCommandList(self):
         self.CheckIsActive()
diff --git a/src/controller/python/chip/clusters/Attribute.py b/src/controller/python/chip/clusters/Attribute.py
index 9620f1b..f7f0ace 100644
--- a/src/controller/python/chip/clusters/Attribute.py
+++ b/src/controller/python/chip/clusters/Attribute.py
@@ -84,6 +84,31 @@
 
 
 @dataclass
+class DataVersionFilter:
+    EndpointId: int = None
+    ClusterId: int = None
+    DataVersion: int = None
+
+    def __init__(self, EndpointId: int = None, Cluster=None, ClusterId=None, DataVersion=None):
+        self.EndpointId = EndpointId
+        if Cluster is not None:
+            # Wildcard read for a specific cluster
+            if (ClusterId is not None):
+                raise Warning(
+                    "Attribute, ClusterId and AttributeId is ignored when Cluster is specified")
+            self.ClusterId = Cluster.id
+            return
+        self.ClusterId = ClusterId
+        self.DataVersion = DataVersion
+
+    def __str__(self) -> str:
+        return f"{self.EndpointId}/{self.ClusterId}/{self.DataVersion}"
+
+    def __hash__(self):
+        return str(self).__hash__()
+
+
+@dataclass
 class TypedAttributePath:
     ''' Encapsulates an attribute path that has strongly typed references to cluster and attribute
         cluster object types. These types serve as keys into the attribute cache.
@@ -317,29 +342,43 @@
         default_factory=lambda: {})
     attributeCache: Dict[int, List[Cluster]] = field(
         default_factory=lambda: {})
+    versionList: Dict[int, Dict[int, Dict[int, int]]] = field(
+        default_factory=lambda: {})
 
-    def UpdateTLV(self, path: AttributePath, dataVersion: int, data: Union[bytes, ValueDecodeFailure]):
+    def UpdateTLV(self, path: AttributePath, dataVersion: int,  data: Union[bytes, ValueDecodeFailure]):
         ''' Store data in TLV since that makes it easiest to eventually convert to either the
             cluster or attribute view representations (see below in UpdateCachedData).
         '''
         if (path.EndpointId not in self.attributeTLVCache):
             self.attributeTLVCache[path.EndpointId] = {}
 
+        if (path.EndpointId not in self.versionList):
+            self.versionList[path.EndpointId] = {}
+
         endpointCache = self.attributeTLVCache[path.EndpointId]
+        endpoint = self.versionList[path.EndpointId]
         if (path.ClusterId not in endpointCache):
             endpointCache[path.ClusterId] = {}
 
+        if (path.ClusterId not in endpoint):
+            endpoint[path.ClusterId] = {}
+
         clusterCache = endpointCache[path.ClusterId]
+        cluster = endpoint[path.ClusterId]
         if (path.AttributeId not in clusterCache):
             clusterCache[path.AttributeId] = None
 
+        if (path.AttributeId not in cluster):
+            cluster[path.AttributeId] = None
+
         clusterCache[path.AttributeId] = data
+        cluster[path.AttributeId] = dataVersion
 
     def UpdateCachedData(self):
         ''' This converts the raw TLV data into a cluster object format.
 
             Two formats are available:
-                1. Attribute-View (returnClusterObject=False): Dict[EndpointId, Dict[ClusterObjectType, Dict[AttributeObjectType, AttributeValue]]]
+                1. Attribute-View (returnClusterObject=False): Dict[EndpointId, Dict[ClusterObjectType, Dict[AttributeObjectType, Dict[AttributeValue, DataVersion]]]]
                 2. Cluster-View (returnClusterObject=True): Dict[EndpointId, Dict[ClusterObjectType, ClusterValue]]
 
             In the attribute-view, only attributes that match the original path criteria are present in the dictionary. The attribute values can
@@ -639,12 +678,14 @@
             if (self._transactionType == TransactionType.READ_EVENTS):
                 self._future.set_result(self._events)
             else:
-                self._future.set_result(self._cache.attributeCache)
+                self._future.set_result(
+                    (self._cache.attributeCache, self._cache.versionList))
 
     def handleDone(self):
         self._event_loop.call_soon_threadsafe(self._handleDone)
 
     def handleReportBegin(self):
+        self._cache.versionList.clear()
         pass
 
     def handleReportEnd(self):
@@ -804,11 +845,12 @@
 )
 
 
-def ReadAttributes(future: Future, eventLoop, device, devCtrl, attributes: List[AttributePath], returnClusterObject: bool = True, subscriptionParameters: SubscriptionParameters = None, fabricFiltered: bool = True) -> int:
+def ReadAttributes(future: Future, eventLoop, device, devCtrl, attributes: List[AttributePath], dataVersionFilters: List[DataVersionFilter] = None, returnClusterObject: bool = True, subscriptionParameters: SubscriptionParameters = None, fabricFiltered: bool = True) -> int:
     handle = chip.native.GetLibraryHandle()
     transaction = AsyncReadTransaction(
         future, eventLoop, devCtrl, TransactionType.READ_ATTRIBUTES, returnClusterObject)
 
+    dataVersionFilterLength = 0
     readargs = []
     for attr in attributes:
         path = chip.interaction_model.AttributePathIBstruct.parse(
@@ -822,6 +864,30 @@
         path = chip.interaction_model.AttributePathIBstruct.build(path)
         readargs.append(ctypes.c_char_p(path))
 
+    if dataVersionFilters is not None:
+        dataVersionFilterLength = len(dataVersionFilters)
+        for f in dataVersionFilters:
+            filter = chip.interaction_model.DataVersionFilterIBstruct.parse(
+                b'\xff' * chip.interaction_model.DataVersionFilterIBstruct.sizeof())
+            if f.EndpointId is not None:
+                filter.EndpointId = f.EndpointId
+            else:
+                raise ValueError(
+                    f"DataVersionFilter must provide EndpointId.")
+            if f.ClusterId is not None:
+                filter.ClusterId = f.ClusterId
+            else:
+                raise ValueError(
+                    f"DataVersionFilter must provide ClusterId.")
+            if f.DataVersion is not None:
+                filter.DataVersion = f.DataVersion
+            else:
+                raise ValueError(
+                    f"DataVersionFilter must provide DataVersion.")
+            filter = chip.interaction_model.DataVersionFilterIBstruct.build(
+                filter)
+            readargs.append(ctypes.c_char_p(filter))
+
     ctypes.pythonapi.Py_IncRef(ctypes.py_object(transaction))
     minInterval = 0
     maxInterval = 0
@@ -844,7 +910,9 @@
         ctypes.byref(readCallbackObj),
         device,
         ctypes.c_char_p(params),
-        ctypes.c_size_t(len(attributes)), *readargs)
+        ctypes.c_size_t(len(attributes)),
+        ctypes.c_size_t(len(attributes) + dataVersionFilterLength),
+        *readargs)
 
     transaction.SetClientObjPointers(readClientObj, readCallbackObj)
 
diff --git a/src/controller/python/chip/clusters/attribute.cpp b/src/controller/python/chip/clusters/attribute.cpp
index 2fd4195..02bdba6 100644
--- a/src/controller/python/chip/clusters/attribute.cpp
+++ b/src/controller/python/chip/clusters/attribute.cpp
@@ -42,6 +42,7 @@
     chip::EndpointId endpointId;
     chip::ClusterId clusterId;
     chip::AttributeId attributeId;
+    chip::DataVersion dataVersion;
 };
 
 struct __attribute__((packed)) EventPath
@@ -51,6 +52,13 @@
     chip::EventId eventId;
 };
 
+struct __attribute__((packed)) DataVersionFilter
+{
+    chip::EndpointId endpointId;
+    chip::ClusterId clusterId;
+    chip::DataVersion dataVersion;
+};
+
 using OnReadAttributeDataCallback       = void (*)(PyObject * appContext, chip::DataVersion version, chip::EndpointId endpointId,
                                              chip::ClusterId clusterId, chip::AttributeId attributeId,
                                              std::underlying_type_t<Protocols::InteractionModel::Status> imstatus, uint8_t * data,
@@ -79,8 +87,7 @@
 
     app::BufferedReadCallback * GetBufferedReadCallback() { return &mBufferedReadCallback; }
 
-    void OnAttributeData(const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
-                         const StatusIB & aStatus) override
+    void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override
     {
         //
         // We shouldn't be getting list item operations in the provided path since that should be handled by the buffered read
@@ -108,7 +115,7 @@
             size = writer.GetLengthWritten();
         }
 
-        gOnReadAttributeDataCallback(mAppContext, aVersion, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId,
+        gOnReadAttributeDataCallback(mAppContext, aPath.mDataVersion, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId,
                                      to_underlying(aStatus.mStatus), buffer.get(), size);
     }
 
@@ -164,6 +171,11 @@
         {
             delete[] aReadPrepareParams.mpEventPathParamsList;
         }
+
+        if (aReadPrepareParams.mpDataVersionFilterList != nullptr)
+        {
+            delete[] aReadPrepareParams.mpDataVersionFilterList;
+        }
     }
 
     void OnReportEnd() override { gOnReportEndCallback(mAppContext); }
@@ -200,7 +212,7 @@
                                                                 uint16_t timedWriteTimeoutMs, size_t n, ...);
 chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, ReadClient ** pReadClient,
                                                               ReadClientCallback ** pCallback, DeviceProxy * device,
-                                                              uint8_t * readParamsBuf, size_t n, ...);
+                                                              uint8_t * readParamsBuf, size_t n, size_t total, ...);
 }
 
 using OnWriteResponseCallback = void (*)(PyObject * appContext, chip::EndpointId endpointId, chip::ClusterId clusterId,
@@ -328,7 +340,7 @@
 
 chip::ChipError::StorageType pychip_ReadClient_ReadAttributes(void * appContext, ReadClient ** pReadClient,
                                                               ReadClientCallback ** pCallback, DeviceProxy * device,
-                                                              uint8_t * readParamsBuf, size_t n, ...)
+                                                              uint8_t * readParamsBuf, size_t n, size_t total, ...)
 {
     CHIP_ERROR err                 = CHIP_NO_ERROR;
     PyReadAttributeParams pyParams = {};
@@ -337,10 +349,12 @@
 
     std::unique_ptr<ReadClientCallback> callback = std::make_unique<ReadClientCallback>(appContext);
 
+    size_t m = total - n;
     va_list args;
-    va_start(args, n);
+    va_start(args, total);
 
     std::unique_ptr<AttributePathParams[]> readPaths(new AttributePathParams[n]);
+    std::unique_ptr<chip::app::DataVersionFilter[]> dataVersionFilters(new chip::app::DataVersionFilter[m]);
     std::unique_ptr<ReadClient> readClient;
 
     {
@@ -355,6 +369,16 @@
         }
     }
 
+    for (size_t j = 0; j < m; j++)
+    {
+        void * filter = va_arg(args, void *);
+
+        python::DataVersionFilter filterObj;
+        memcpy(&filterObj, filter, sizeof(python::DataVersionFilter));
+
+        dataVersionFilters[j] = chip::app::DataVersionFilter(filterObj.endpointId, filterObj.clusterId, filterObj.dataVersion);
+    }
+
     Optional<SessionHandle> session = device->GetSecureSession();
     VerifyOrExit(session.HasValue(), err = CHIP_ERROR_NOT_CONNECTED);
 
@@ -366,7 +390,14 @@
         ReadPrepareParams params(session.Value());
         params.mpAttributePathParamsList    = readPaths.get();
         params.mAttributePathParamsListSize = n;
-        params.mIsFabricFiltered            = pyParams.isFabricFiltered;
+        if (m != 0)
+        {
+            params.mpDataVersionFilterList    = dataVersionFilters.get();
+            params.mDataVersionFilterListSize = m;
+        }
+
+        params.mIsFabricFiltered = pyParams.isFabricFiltered;
+
         if (pyParams.isSubscription)
         {
             params.mMinIntervalFloorSeconds   = pyParams.minInterval;
diff --git a/src/controller/python/chip/interaction_model/__init__.py b/src/controller/python/chip/interaction_model/__init__.py
index 2bd9042..79e9464 100644
--- a/src/controller/python/chip/interaction_model/__init__.py
+++ b/src/controller/python/chip/interaction_model/__init__.py
@@ -22,7 +22,7 @@
 
 """Provides Python APIs for CHIP."""
 import enum
-from .delegate import AttributePath, AttributePathIBstruct, EventPath, EventPathIBstruct
+from .delegate import AttributePath, AttributePathIBstruct, EventPath, EventPathIBstruct, DataVersionFilterIBstruct
 
 from chip.exceptions import ChipStackException
 
@@ -53,7 +53,7 @@
     UnsupportedRead = 0x8f
     Deprecated90 = 0x90
     Deprecated91 = 0x91
-    Reserved92 = 0x92
+    DataVersionMismatch = 0x92
     Deprecated93 = 0x93
     Timeout = 0x94
     Reserved95 = 0x95
diff --git a/src/controller/python/chip/interaction_model/delegate.py b/src/controller/python/chip/interaction_model/delegate.py
index 6ad6bd5..dbbd818 100644
--- a/src/controller/python/chip/interaction_model/delegate.py
+++ b/src/controller/python/chip/interaction_model/delegate.py
@@ -51,6 +51,7 @@
     "EndpointId" / Int16ul,
     "ClusterId" / Int32ul,
     "AttributeId" / Int32ul,
+    "DataVersion" / Int32ul,
 )
 
 # EventPath should not contain padding
@@ -60,6 +61,12 @@
     "EventId" / Int32ul,
 )
 
+DataVersionFilterIBstruct = Struct(
+    "EndpointId" / Int16ul,
+    "ClusterId" / Int32ul,
+    "DataVersion" / Int32ul,
+)
+
 
 @dataclass
 class AttributePath:
diff --git a/src/controller/python/test/test_scripts/base.py b/src/controller/python/test/test_scripts/base.py
index 4114faf..a33fe9b 100644
--- a/src/controller/python/test/test_scripts/base.py
+++ b/src/controller/python/test/test_scripts/base.py
@@ -190,8 +190,8 @@
         data2 = await devCtrl2.ReadAttribute(nodeid, [(Clusters.OperationalCredentials.Attributes.NOCs)], fabricFiltered=False)
 
         # Read out noclist from each fabric, and each should contain two NOCs.
-        nocList1 = data1[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.NOCs]
-        nocList2 = data2[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.NOCs]
+        nocList1 = data1[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.NOCs]
+        nocList2 = data2[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.NOCs]
 
         if (len(nocList1) != 2 or len(nocList2) != 2):
             self.logger.error("Got back invalid nocList")
@@ -201,8 +201,8 @@
         data2 = await devCtrl2.ReadAttribute(nodeid, [(Clusters.OperationalCredentials.Attributes.CurrentFabricIndex)], fabricFiltered=False)
 
         # Read out current fabric from each fabric, and both should be different.
-        currentFabric1 = data1[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex]
-        currentFabric2 = data2[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex]
+        currentFabric1 = data1[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex]
+        currentFabric2 = data2[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex]
         if (currentFabric1 == currentFabric2):
             self.logger.error(
                 "Got back fabric indices that match for two different fabrics!")
diff --git a/src/controller/python/test/test_scripts/cluster_objects.py b/src/controller/python/test/test_scripts/cluster_objects.py
index 02c1761..32ee18c 100644
--- a/src/controller/python/test/test_scripts/cluster_objects.py
+++ b/src/controller/python/test/test_scripts/cluster_objects.py
@@ -39,10 +39,11 @@
 
 
 def VerifyDecodeSuccess(values):
-    for endpoint in values:
-        for cluster in values[endpoint]:
-            for attribute in values[endpoint][cluster]:
-                v = values[endpoint][cluster][attribute]
+    print(f"{values}")
+    for endpoint in values[0]:
+        for cluster in values[0][endpoint]:
+            for attribute in values[0][endpoint][cluster]:
+                v = values[0][endpoint][cluster][attribute]
                 print(f"EP{endpoint}/{attribute} = {v}")
                 if (isinstance(v, ValueDecodeFailure)):
                     if _IgnoreAttributeDecodeFailure((endpoint, cluster, attribute)):
@@ -50,7 +51,20 @@
                             f"Ignoring attribute decode failure for path {endpoint}/{attribute}")
                     else:
                         raise AssertionError(
-                            f"Cannot decode value for path {k}, got error: '{str(v.Data.Reason)}', raw TLV data: '{v.Data.TLVValue}'")
+                            f"Cannot decode value for path {endpoint}/{attribute}, got error: '{str(v.Reason)}', raw TLV data: '{v.TLVValue}'")
+
+    for endpoint in values[1]:
+        for cluster in values[1][endpoint]:
+            for attribute in values[1][endpoint][cluster]:
+                v = values[1][endpoint][cluster][attribute]
+                print(f"EP{endpoint}/{attribute} version = {v}")
+                if (isinstance(v, ValueDecodeFailure)):
+                    if _IgnoreAttributeDecodeFailure((endpoint, cluster, attribute)):
+                        print(
+                            f"Ignoring attribute version decode failure for path {endpoint}/{attribute}")
+                    else:
+                        raise AssertionError(
+                            f"Cannot decode value for path {endpoint}/{attribute}, got error: '{str(v.Reason)}', raw TLV data: '{v.TLVValue}'")
 
 
 def _AssumeEventsDecodeSuccess(values):
@@ -107,9 +121,9 @@
         res = await devCtrl.WriteAttribute(nodeid=NODE_ID,
                                            attributes=[
                                                (0, Clusters.Basic.Attributes.NodeLabel(
-                                                   "Test")),
+                                                   "Test"), 0),
                                                (0, Clusters.Basic.Attributes.Location(
-                                                   "A loooong string"))
+                                                   "A loooong string"), 0)
                                            ])
         expectedRes = [
             AttributeStatus(Path=AttributePath(EndpointId=0, ClusterId=40,
@@ -181,9 +195,9 @@
             (0, Clusters.Basic.Attributes.HardwareVersion),
         ]
         res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=req)
-        if ((0 not in res) or (Clusters.Basic not in res[0]) or (len(res[0][Clusters.Basic]) != 3)):
+        if ((0 not in res[0]) or (Clusters.Basic not in res[0][0]) or (len(res[0][0][Clusters.Basic]) != 3)):
             raise AssertionError(
-                f"Got back {len(res)} data items instead of 3")
+                f"Got back {len(res[0])} data items instead of 3")
         VerifyDecodeSuccess(res)
 
         logger.info("2: Reading Ex Cx A*")
@@ -226,23 +240,23 @@
 
         logger.info("7: Reading Chunked List")
         res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=[(1, Clusters.TestCluster.Attributes.ListLongOctetString)])
-        if res[1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListLongOctetString] != [b'0123456789abcdef' * 32] * 4:
+        if res[0][1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListLongOctetString] != [b'0123456789abcdef' * 32] * 4:
             raise AssertionError("Unexpected read result")
 
         logger.info("*: Getting current fabric index")
         res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=[(0, Clusters.OperationalCredentials.Attributes.CurrentFabricIndex)])
-        fabricIndex = res[0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex]
+        fabricIndex = res[0][0][Clusters.OperationalCredentials][Clusters.OperationalCredentials.Attributes.CurrentFabricIndex]
 
         logger.info("8: Read without fabric filter")
         res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=[(1, Clusters.TestCluster.Attributes.ListFabricScoped)], fabricFiltered=False)
-        if len(res[1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped]) <= 1:
+        if len(res[0][1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped]) <= 1:
             raise AssertionError("Expect more elements in the response")
 
         logger.info("9: Read with fabric filter")
         res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=[(1, Clusters.TestCluster.Attributes.ListFabricScoped)], fabricFiltered=True)
-        if len(res[1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped]) != 1:
+        if len(res[0][1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped]) != 1:
             raise AssertionError("Expect exact one element in the response")
-        if res[1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped][0].fabricIndex != fabricIndex:
+        if res[0][1][Clusters.TestCluster][Clusters.TestCluster.Attributes.ListFabricScoped][0].fabricIndex != fabricIndex:
             raise AssertionError(
                 "Expect the fabric index matches the one current reading")
 
@@ -307,7 +321,7 @@
         await devCtrl.WriteAttribute(nodeid=NODE_ID,
                                      attributes=[
                                          (1, Clusters.TestCluster.Attributes.TimedWriteBoolean(
-                                             True)),
+                                             True), 0),
                                      ],
                                      timedRequestTimeoutMs=1000)
 
@@ -325,7 +339,7 @@
             await devCtrl.WriteAttribute(nodeid=NODE_ID,
                                          attributes=[
                                              (1, Clusters.TestCluster.Attributes.TimedWriteBoolean(
-                                                 True)),
+                                                 True), 0),
                                          ],
                                          timedRequestTimeoutMs=10)
             raise AssertionError("Timeout expected!")
@@ -347,13 +361,52 @@
             await devCtrl.WriteAttribute(nodeid=NODE_ID,
                                          attributes=[
                                              (1, Clusters.TestCluster.Attributes.TimedWriteBoolean(
-                                                 True)),
+                                                 True), 0),
                                          ])
             raise AssertionError("The write request should be rejected.")
         except ValueError:
             pass
 
     @classmethod
+    async def TestReadWriteAttributeRequestsWithVersion(cls, devCtrl):
+        logger.info("TestReadWriteAttributeRequestsWithVersion")
+        req = [
+            (0, Clusters.Basic.Attributes.VendorName)
+        ]
+        res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=req)
+        VerifyDecodeSuccess(res)
+        data_version = res[1][0][40][1]
+
+        res = await devCtrl.WriteAttribute(nodeid=NODE_ID,
+                                           attributes=[
+                                               (0, Clusters.Basic.Attributes.NodeLabel(
+                                                   "Test"), 0)
+                                           ])
+        expectedRes = [
+            AttributeStatus(Path=AttributePath(EndpointId=0, ClusterId=40,
+                                               AttributeId=5), Status=chip.interaction_model.Status.Success),
+        ]
+
+        if res != expectedRes:
+            for i in range(len(res)):
+                if res[i] != expectedRes[i]:
+                    logger.error(
+                        f"Item {i} is not expected, expect {expectedRes[i]} got {res[i]}")
+            raise AssertionError("Write returned unexpected result.")
+
+        req = [
+            (0, Clusters.Basic.Attributes.VendorName),
+        ]
+        res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=req, dataVersionFilters=[(0, Clusters.Basic, data_version)])
+        VerifyDecodeSuccess(res)
+        new_data_version = res[1][0][40][1]
+        if (data_version + 1) != new_data_version:
+            raise AssertionError("Version mistmatch happens.")
+
+        res = await devCtrl.ReadAttribute(nodeid=NODE_ID, attributes=req, dataVersionFilters=[(0, Clusters.Basic, new_data_version)])
+        VerifyDecodeSuccess(res)
+
+    @classmethod
     async def RunTest(cls, devCtrl):
         try:
             cls.TestAPI()
@@ -361,6 +414,7 @@
             await cls.RoundTripTestWithBadEndpoint(devCtrl)
             await cls.SendCommandWithResponse(devCtrl)
             await cls.TestReadEventRequests(devCtrl, 1)
+            await cls.TestReadWriteAttributeRequestsWithVersion(devCtrl)
             await cls.TestReadAttributeRequests(devCtrl)
             await cls.TestSubscribeAttribute(devCtrl)
             # Note: Write will change some attribute values, always put it after read tests
diff --git a/src/controller/python/test/test_scripts/network_commissioning.py b/src/controller/python/test/test_scripts/network_commissioning.py
index a76236a..523fe58 100644
--- a/src/controller/python/test/test_scripts/network_commissioning.py
+++ b/src/controller/python/test/test_scripts/network_commissioning.py
@@ -72,7 +72,7 @@
             (endpointId, Clusters.NetworkCommissioning.Attributes.FeatureMap)],
             returnClusterObject=True)
         self.log_interface_basic_info(
-            res[endpointId][Clusters.NetworkCommissioning])
+            res[0][endpointId][Clusters.NetworkCommissioning])
         logger.info(f"Finished getting basic information of the endpoint")
 
         # Scan networks
@@ -87,7 +87,7 @@
         # Remove existing network
         logger.info(f"Check network list")
         res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True)
-        networkList = res[endpointId][Clusters.NetworkCommissioning].networks
+        networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks
         logger.info(f"Got network list: {networkList}")
         if len(networkList) != 0:
             logger.info(f"Removing existing network")
@@ -110,7 +110,7 @@
 
         logger.info(f"Check network list")
         res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True)
-        networkList = res[endpointId][Clusters.NetworkCommissioning].networks
+        networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks
         logger.info(f"Got network list: {networkList}")
         if len(networkList) != 1:
             raise AssertionError(
@@ -133,7 +133,7 @@
 
         logger.info(f"Check network is connected")
         res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True)
-        networkList = res[endpointId][Clusters.NetworkCommissioning].networks
+        networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks
         logger.info(f"Got network list: {networkList}")
         if len(networkList) != 1:
             raise AssertionError(
@@ -156,7 +156,7 @@
             (endpointId, Clusters.NetworkCommissioning.Attributes.FeatureMap)],
             returnClusterObject=True)
         self.log_interface_basic_info(
-            res[endpointId][Clusters.NetworkCommissioning])
+            res[0][endpointId][Clusters.NetworkCommissioning])
         logger.info(f"Finished getting basic information of the endpoint")
 
         # Scan networks
@@ -171,7 +171,7 @@
         # Remove existing network
         logger.info(f"Check network list")
         res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True)
-        networkList = res[endpointId][Clusters.NetworkCommissioning].networks
+        networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks
         logger.info(f"Got network list: {networkList}")
         if len(networkList) != 0:
             logger.info(f"Removing existing network")
@@ -194,7 +194,7 @@
 
         logger.info(f"Check network list")
         res = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(endpointId, Clusters.NetworkCommissioning.Attributes.Networks)], returnClusterObject=True)
-        networkList = res[endpointId][Clusters.NetworkCommissioning].networks
+        networkList = res[0][endpointId][Clusters.NetworkCommissioning].networks
         logger.info(f"Got network list: {networkList}")
         if len(networkList) != 1:
             raise AssertionError(
@@ -233,6 +233,7 @@
         try:
             endpoints = await self._devCtrl.ReadAttribute(nodeid=self._nodeid, attributes=[(Clusters.NetworkCommissioning.Attributes.FeatureMap)], returnClusterObject=True)
             logger.info(endpoints)
+            endpoints = endpoints[0]
             for endpoint, obj in endpoints.items():
                 clus = obj[Clusters.NetworkCommissioning]
                 if clus.featureMap == WIFI_NETWORK_FEATURE_MAP:
diff --git a/src/controller/tests/TestReadChunking.cpp b/src/controller/tests/TestReadChunking.cpp
index d704e16..9baab88 100644
--- a/src/controller/tests/TestReadChunking.cpp
+++ b/src/controller/tests/TestReadChunking.cpp
@@ -96,7 +96,7 @@
 {
 public:
     TestReadCallback() : mBufferedCallback(*this) {}
-    void OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
+    void OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
                          const app::StatusIB & aStatus) override;
 
     void OnDone() override;
@@ -108,7 +108,7 @@
     app::BufferedReadCallback mBufferedCallback;
 };
 
-void TestReadCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData,
+void TestReadCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData,
                                        const app::StatusIB & aStatus)
 {
     if (aPath.mAttributeId != kTestListAttribute)
diff --git a/src/controller/tests/data_model/TestCommands.cpp b/src/controller/tests/data_model/TestCommands.cpp
index aedc62f..393c191 100644
--- a/src/controller/tests/data_model/TestCommands.cpp
+++ b/src/controller/tests/data_model/TestCommands.cpp
@@ -148,20 +148,6 @@
 
     return Status::Success;
 }
-
-CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
-                                 const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports,
-                                 AttributeValueEncoder::AttributeEncodeState * apEncoderState)
-{
-    return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
-}
-
-CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath,
-                                  TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
-{
-    return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
-}
-
 } // namespace app
 } // namespace chip
 
diff --git a/src/controller/tests/data_model/TestRead.cpp b/src/controller/tests/data_model/TestRead.cpp
index dcf98a4..2451eda 100644
--- a/src/controller/tests/data_model/TestRead.cpp
+++ b/src/controller/tests/data_model/TestRead.cpp
@@ -36,6 +36,7 @@
 namespace {
 
 constexpr EndpointId kTestEndpointId = 1;
+constexpr DataVersion kDataVersion   = 5;
 
 enum ResponseDirective
 {
@@ -49,34 +50,10 @@
 
 namespace chip {
 namespace app {
-
-void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, chip::TLV::TLVReader & aReader,
-                                  CommandHandler * apCommandObj)
-{}
-
-InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath)
-{
-    // Mock cluster catalog, only support commands on one cluster on one endpoint.
-    using InteractionModel::Status;
-
-    if (aCommandPath.mEndpointId != kTestEndpointId)
-    {
-        return Status::UnsupportedEndpoint;
-    }
-
-    if (aCommandPath.mClusterId != TestCluster::Id)
-    {
-        return Status::UnsupportedCluster;
-    }
-
-    return Status::Success;
-}
-
 CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
                                  const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports,
                                  AttributeValueEncoder::AttributeEncodeState * apEncoderState)
 {
-
     if (responseDirective == kSendDataResponse)
     {
         if (aPath.mClusterId == app::Clusters::TestCluster::Id &&
@@ -88,7 +65,7 @@
                                                aIsFabricFiltered, state);
 
             return valueEncoder.EncodeList([aSubjectDescriptor](const auto & encoder) -> CHIP_ERROR {
-                chip::app::Clusters::TestCluster::Structs::TestFabricScoped::Type val;
+                app::Clusters::TestCluster::Structs::TestFabricScoped::Type val;
                 val.fabricIndex = aSubjectDescriptor.fabricIndex;
                 ReturnErrorOnFailure(encoder.Encode(val));
                 val.fabricIndex = (val.fabricIndex == 1) ? 2 : 1;
@@ -114,7 +91,8 @@
                 i++;
             }
 
-            attributeData.DataVersion(0);
+            attributeData.DataVersion(kDataVersion);
+            ReturnErrorOnFailure(attributeData.GetError());
             AttributePathIB::Builder & attributePath = attributeData.CreatePath();
             attributePath.Endpoint(aPath.mEndpointId)
                 .Cluster(aPath.mClusterId)
@@ -123,7 +101,7 @@
             ReturnErrorOnFailure(attributePath.GetError());
 
             ReturnErrorOnFailure(DataModel::Encode(*(attributeData.GetWriter()),
-                                                   chip::TLV::ContextTag(chip::to_underlying(AttributeDataIB::Tag::kData)), value));
+                                                   TLV::ContextTag(to_underlying(AttributeDataIB::Tag::kData)), value));
             ReturnErrorOnFailure(attributeData.EndOfAttributeDataIB().GetError());
             return attributeReport.EndOfAttributeReportIB().GetError();
         }
@@ -148,12 +126,17 @@
     return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
 }
 
-CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath,
-                                  TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
+bool IsClusterDataVersionEqual(EndpointId aEndpointId, ClusterId aClusterId, DataVersion aRequiredDataVersion)
 {
-    return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
+    if (aRequiredDataVersion == kDataVersion)
+    {
+        return true;
+    }
+    else
+    {
+        return false;
+    }
 }
-
 } // namespace app
 } // namespace chip
 
@@ -165,12 +148,14 @@
     TestReadInteraction() {}
 
     static void TestReadAttributeResponse(nlTestSuite * apSuite, void * apContext);
+    static void TestReadDataVersionFilter(nlTestSuite * apSuite, void * apContext);
     static void TestReadAttributeError(nlTestSuite * apSuite, void * apContext);
     static void TestReadAttributeTimeout(nlTestSuite * apSuite, void * apContext);
     static void TestReadEventResponse(nlTestSuite * apSuite, void * apContext);
     static void TestReadFabricScopedWithoutFabricFilter(nlTestSuite * apSuite, void * apContext);
     static void TestReadFabricScopedWithFabricFilter(nlTestSuite * apSuite, void * apContext);
     static void TestReadHandler_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext);
+    static void TestReadHandler_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext);
     static void TestReadHandlerResourceExhaustion_MultipleSubscriptions(nlTestSuite * apSuite, void * apContext);
     static void TestReadHandlerResourceExhaustion_MultipleReads(nlTestSuite * apSuite, void * apContext);
 
@@ -187,9 +172,10 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) {
+    auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath,
+                                                      const auto & dataResponse) {
         uint8_t i = 0;
-
+        NL_TEST_ASSERT(apSuite, attributePath.mDataVersion == kDataVersion);
         auto iter = dataResponse.begin();
         while (iter.Next())
         {
@@ -197,29 +183,72 @@
             NL_TEST_ASSERT(apSuite, item.fabricIndex == i);
             i++;
         }
-
         NL_TEST_ASSERT(apSuite, i == 4);
         NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR);
-
         onSuccessCbInvoked = true;
     };
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
+    auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
         onFailureCbInvoked = true;
     };
 
-    chip::Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
-        &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb);
+    Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(&ctx.GetExchangeManager(), sessionHandle,
+                                                                                        kTestEndpointId, onSuccessCb, onFailureCb);
 
     ctx.DrainAndServiceIO();
-    chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+    app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
     ctx.DrainAndServiceIO();
 
     NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
+    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+}
+
+void TestReadInteraction::TestReadDataVersionFilter(nlTestSuite * apSuite, void * apContext)
+{
+    TestContext & ctx       = *static_cast<TestContext *>(apContext);
+    auto sessionHandle      = ctx.GetSessionBobToAlice();
+    bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
+
+    responseDirective = kSendDataResponse;
+
+    // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
+    // not safe to do so.
+    auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath,
+                                                      const auto & dataResponse) {
+        uint8_t i = 0;
+        auto iter = dataResponse.begin();
+        while (iter.Next())
+        {
+            auto & item = iter.GetValue();
+            NL_TEST_ASSERT(apSuite, item.fabricIndex == i);
+            i++;
+        }
+        NL_TEST_ASSERT(apSuite, i == 4);
+        NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR);
+        onSuccessCbInvoked = true;
+    };
+
+    // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
+    // not safe to do so.
+    auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
+        onFailureCbInvoked = true;
+    };
+
+    Optional<DataVersion> dataVersion(kDataVersion);
+    Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
+        &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, true, dataVersion);
+
+    ctx.DrainAndServiceIO();
+    app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+    ctx.DrainAndServiceIO();
+
+    NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && !onFailureCbInvoked);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
     NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
 }
 
@@ -243,16 +272,16 @@
         onFailureCbInvoked = true;
     };
 
-    chip::Controller::ReadEvent<TestCluster::Events::TestEvent::DecodableType>(&ctx.GetExchangeManager(), sessionHandle,
-                                                                               kTestEndpointId, onSuccessCb, onFailureCb);
+    Controller::ReadEvent<TestCluster::Events::TestEvent::DecodableType>(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId,
+                                                                         onSuccessCb, onFailureCb);
 
     ctx.DrainAndServiceIO();
-    chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+    app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
     ctx.DrainAndServiceIO();
 
     NL_TEST_ASSERT(apSuite, !onFailureCbInvoked);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
     NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
 }
 
@@ -266,27 +295,27 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) {
+    auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
         onSuccessCbInvoked = true;
     };
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
+    auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
         NL_TEST_ASSERT(apSuite, aError.IsIMStatus() && app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::Busy);
         onFailureCbInvoked = true;
     };
 
-    chip::Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
-        &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb);
+    Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(&ctx.GetExchangeManager(), sessionHandle,
+                                                                                        kTestEndpointId, onSuccessCb, onFailureCb);
 
     ctx.DrainAndServiceIO();
-    chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+    app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
     ctx.DrainAndServiceIO();
 
     NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
     NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
 }
 
@@ -300,19 +329,19 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) {
+    auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
         onSuccessCbInvoked = true;
     };
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
+    auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
         NL_TEST_ASSERT(apSuite, aError == CHIP_ERROR_TIMEOUT);
         onFailureCbInvoked = true;
     };
 
-    chip::Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
-        &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb);
+    Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(&ctx.GetExchangeManager(), sessionHandle,
+                                                                                        kTestEndpointId, onSuccessCb, onFailureCb);
 
     ctx.DrainAndServiceIO();
 
@@ -330,12 +359,12 @@
     // NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 1);
 
     ctx.DrainAndServiceIO();
-    chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+    app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
     ctx.DrainAndServiceIO();
 
     ctx.ExpireSessionAliceToBob();
 
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
 
     //
     // Let's put back the sessions so that the next tests (which assume a valid initialized set of sessions)
@@ -361,13 +390,13 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [&numSuccessCalls](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) {
+    auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
         numSuccessCalls++;
     };
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onFailureCb = [&apSuite](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
+    auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
         //
         // We shouldn't be encountering any failures in this test.
         //
@@ -384,7 +413,7 @@
     for (int i = 0; i < (CHIP_IM_MAX_NUM_READ_HANDLER + 1); i++)
     {
         NL_TEST_ASSERT(apSuite,
-                       chip::Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
+                       Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
                            &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
                            onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR);
     }
@@ -396,7 +425,71 @@
     for (int i = 0; i < 10 && (numSubscriptionEstablishedCalls != (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); i++)
     {
         ctx.DrainAndServiceIO();
-        chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+        app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+        ctx.DrainAndServiceIO();
+    }
+
+    NL_TEST_ASSERT(apSuite, numSuccessCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1));
+    NL_TEST_ASSERT(apSuite, numSubscriptionEstablishedCalls == (CHIP_IM_MAX_NUM_READ_HANDLER + 1));
+
+    app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
+
+    //
+    // TODO: Figure out why I cannot enable this line below.
+    //
+    // NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+}
+
+void TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFilter(nlTestSuite * apSuite, void * apContext)
+{
+    TestContext & ctx                        = *static_cast<TestContext *>(apContext);
+    auto sessionHandle                       = ctx.GetSessionBobToAlice();
+    uint32_t numSuccessCalls                 = 0;
+    uint32_t numSubscriptionEstablishedCalls = 0;
+
+    responseDirective = kSendDataResponse;
+
+    // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
+    // not safe to do so.
+    auto onSuccessCb = [apSuite, &numSuccessCalls](const app::ConcreteDataAttributePath & attributePath,
+                                                   const auto & dataResponse) {
+        NL_TEST_ASSERT(apSuite, attributePath.mDataVersion == kDataVersion);
+        numSuccessCalls++;
+    };
+
+    // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
+    // not safe to do so.
+    auto onFailureCb = [&apSuite](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
+        //
+        // We shouldn't be encountering any failures in this test.
+        //
+        NL_TEST_ASSERT(apSuite, false);
+    };
+
+    auto onSubscriptionEstablishedCb = [&numSubscriptionEstablishedCalls]() { numSubscriptionEstablishedCalls++; };
+
+    //
+    // Try to issue parallel subscriptions that will exceed the value for CHIP_IM_MAX_NUM_READ_HANDLER.
+    // If heap allocation is correctly setup, this should result in it successfully servicing more than the number
+    // present in that define.
+    //
+    chip::Optional<chip::DataVersion> dataVersion(1);
+    for (int i = 0; i < (CHIP_IM_MAX_NUM_READ_HANDLER + 1); i++)
+    {
+        NL_TEST_ASSERT(apSuite,
+                       Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
+                           &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
+                           onSubscriptionEstablishedCb, false, true, dataVersion) == CHIP_NO_ERROR);
+    }
+
+    //
+    // It may take a couple of service calls since we may hit the limit of CHIP_IM_MAX_REPORTS_IN_FLIGHT
+    // reports.
+    //
+    for (int i = 0; i < 10 && (numSubscriptionEstablishedCalls != (CHIP_IM_MAX_NUM_READ_HANDLER + 1)); i++)
+    {
+        ctx.DrainAndServiceIO();
+        app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
         ctx.DrainAndServiceIO();
     }
 
@@ -423,13 +516,13 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [&numSuccessCalls](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) {
+    auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
         numSuccessCalls++;
     };
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
+    auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
         numFailureCalls++;
 
         NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(ResourceExhausted));
@@ -443,14 +536,13 @@
     // since the second subscription below should fail correctly.
     //
     app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(2);
-
     NL_TEST_ASSERT(apSuite,
-                   chip::Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
+                   Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
                        &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
                        onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR);
 
     NL_TEST_ASSERT(apSuite,
-                   chip::Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
+                   Controller::SubscribeAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
                        &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, 0, 10,
                        onSubscriptionEstablishedCb, false, true) == CHIP_NO_ERROR);
 
@@ -461,7 +553,7 @@
     for (int i = 0; i < 10; i++)
     {
         ctx.DrainAndServiceIO();
-        chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+        app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
         ctx.DrainAndServiceIO();
     }
 
@@ -490,13 +582,13 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [&numSuccessCalls](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) {
+    auto onSuccessCb = [&numSuccessCalls](const app::ConcreteDataAttributePath & attributePath, const auto & dataResponse) {
         numSuccessCalls++;
     };
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
+    auto onFailureCb = [&apSuite, &numFailureCalls](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
         numFailureCalls++;
 
         NL_TEST_ASSERT(apSuite, aError == CHIP_IM_GLOBAL_STATUS(ResourceExhausted));
@@ -506,7 +598,7 @@
     app::InteractionModelEngine::GetInstance()->SetHandlerCapacity(0);
 
     NL_TEST_ASSERT(apSuite,
-                   chip::Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
+                   Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>(
                        &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb) == CHIP_NO_ERROR);
 
     //
@@ -516,7 +608,7 @@
     for (int i = 0; i < 10; i++)
     {
         ctx.DrainAndServiceIO();
-        chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+        app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
         ctx.DrainAndServiceIO();
     }
 
@@ -552,7 +644,8 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) {
+    auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath,
+                                                      const auto & dataResponse) {
         size_t len = 0;
 
         NL_TEST_ASSERT(apSuite, dataResponse.ComputeSize(&len) == CHIP_NO_ERROR);
@@ -563,20 +656,20 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
+    auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
         onFailureCbInvoked = true;
     };
 
-    chip::Controller::ReadAttribute<TestCluster::Attributes::ListFabricScoped::TypeInfo>(
+    Controller::ReadAttribute<TestCluster::Attributes::ListFabricScoped::TypeInfo>(
         &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, false /* fabric filtered */);
 
     ctx.DrainAndServiceIO();
-    chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+    app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
     ctx.DrainAndServiceIO();
 
     NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
     NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
 }
 
@@ -600,7 +693,8 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) {
+    auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteDataAttributePath & attributePath,
+                                                      const auto & dataResponse) {
         size_t len = 0;
 
         NL_TEST_ASSERT(apSuite, dataResponse.ComputeSize(&len) == CHIP_NO_ERROR);
@@ -620,20 +714,20 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
+    auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteDataAttributePath * attributePath, CHIP_ERROR aError) {
         onFailureCbInvoked = true;
     };
 
-    chip::Controller::ReadAttribute<TestCluster::Attributes::ListFabricScoped::TypeInfo>(
+    Controller::ReadAttribute<TestCluster::Attributes::ListFabricScoped::TypeInfo>(
         &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb, true /* fabric filtered */);
 
     ctx.DrainAndServiceIO();
-    chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
+    app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run();
     ctx.DrainAndServiceIO();
 
     NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
-    NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0);
+    NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0);
     NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
 }
 
@@ -641,12 +735,14 @@
 const nlTest sTests[] =
 {
     NL_TEST_DEF("TestReadAttributeResponse", TestReadInteraction::TestReadAttributeResponse),
+    NL_TEST_DEF("TestReadDataVersionFilter", TestReadInteraction::TestReadDataVersionFilter),
     NL_TEST_DEF("TestReadEventResponse", TestReadInteraction::TestReadEventResponse),
     NL_TEST_DEF("TestReadAttributeError", TestReadInteraction::TestReadAttributeError),
     NL_TEST_DEF("TestReadFabricScopedWithoutFabricFilter", TestReadInteraction::TestReadFabricScopedWithoutFabricFilter),
     NL_TEST_DEF("TestReadFabricScopedWithFabricFilter", TestReadInteraction::TestReadFabricScopedWithFabricFilter),
     NL_TEST_DEF("TestReadAttributeTimeout", TestReadInteraction::TestReadAttributeTimeout),
     NL_TEST_DEF("TestReadHandler_MultipleSubscriptions", TestReadInteraction::TestReadHandler_MultipleSubscriptions),
+    NL_TEST_DEF("TestReadHandler_MultipleSubscriptionsWithDataVersionFilter", TestReadInteraction::TestReadHandler_MultipleSubscriptionsWithDataVersionFilter),
     NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleSubscriptions", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleSubscriptions),
     NL_TEST_DEF("TestReadHandlerResourceExhaustion_MultipleReads", TestReadInteraction::TestReadHandlerResourceExhaustion_MultipleReads),
     NL_TEST_SENTINEL()
diff --git a/src/controller/tests/data_model/TestWrite.cpp b/src/controller/tests/data_model/TestWrite.cpp
index e97feae..c13f547 100644
--- a/src/controller/tests/data_model/TestWrite.cpp
+++ b/src/controller/tests/data_model/TestWrite.cpp
@@ -53,35 +53,6 @@
 namespace chip {
 namespace app {
 
-void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, chip::TLV::TLVReader & aReader,
-                                  CommandHandler * apCommandObj)
-{}
-
-InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath)
-{
-    // Mock cluster catalog, only support commands on one cluster on one endpoint.
-    using InteractionModel::Status;
-
-    if (aCommandPath.mEndpointId != kTestEndpointId)
-    {
-        return Status::UnsupportedEndpoint;
-    }
-
-    if (aCommandPath.mClusterId != TestCluster::Id)
-    {
-        return Status::UnsupportedCluster;
-    }
-
-    return Status::Success;
-}
-
-CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered,
-                                 const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports,
-                                 AttributeValueEncoder::AttributeEncodeState * apEncoderState)
-{
-    return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
-}
-
 CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath,
                                   TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
 {
diff --git a/src/darwin/Framework/CHIP/CHIPDevice.mm b/src/darwin/Framework/CHIP/CHIPDevice.mm
index d75bd39..6dd9f6e 100644
--- a/src/darwin/Framework/CHIP/CHIPDevice.mm
+++ b/src/darwin/Framework/CHIP/CHIPDevice.mm
@@ -96,8 +96,7 @@
 
     void OnReportEnd() override;
 
-    void OnAttributeData(
-        const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, const StatusIB & aStatus) override;
+    void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override;
 
     void OnError(CHIP_ERROR aError) override;
 
@@ -215,7 +214,7 @@
 }
 
 void SubscriptionCallback::OnAttributeData(
-    const ConcreteDataAttributePath & aPath, DataVersion aVersion, TLV::TLVReader * apData, const StatusIB & aStatus)
+    const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus)
 {
     if (aPath.IsListItemOperation()) {
         ReportError(CHIP_ERROR_INCORRECT_STATE);
diff --git a/src/lib/core/CHIPError.cpp b/src/lib/core/CHIPError.cpp
index ea22ad3..f3dd028 100644
--- a/src/lib/core/CHIPError.cpp
+++ b/src/lib/core/CHIPError.cpp
@@ -545,9 +545,6 @@
     case CHIP_ERROR_INCOMPATIBLE_SCHEMA_VERSION.AsInteger():
         desc = "Incompatible data schema version";
         break;
-    case CHIP_ERROR_MISMATCH_UPDATE_REQUIRED_VERSION.AsInteger():
-        desc = "Update Required Version mismatch";
-        break;
     case CHIP_ERROR_ACCESS_DENIED.AsInteger():
         desc = "The CHIP message is not granted access";
         break;
diff --git a/src/lib/core/CHIPError.h b/src/lib/core/CHIPError.h
index c5eba86..7420d85 100644
--- a/src/lib/core/CHIPError.h
+++ b/src/lib/core/CHIPError.h
@@ -1895,14 +1895,6 @@
 #define CHIP_ERROR_INCOMPATIBLE_SCHEMA_VERSION                 CHIP_CORE_ERROR(0xa3)
 
 /**
- *  @def CHIP_ERROR_MISMATCH_UPDATE_REQUIRED_VERSION
- *
- *  @brief
- *    Encountered a mismatch between update required version and current version
- */
-#define CHIP_ERROR_MISMATCH_UPDATE_REQUIRED_VERSION            CHIP_CORE_ERROR(0xa4)
-
-/**
  *  @def CHIP_ERROR_ACCESS_DENIED
  *
  *  @brief
diff --git a/src/lib/core/DataModelTypes.h b/src/lib/core/DataModelTypes.h
index d931944..3dccb05 100644
--- a/src/lib/core/DataModelTypes.h
+++ b/src/lib/core/DataModelTypes.h
@@ -48,7 +48,7 @@
 constexpr EndpointId kInvalidEndpointId     = 0xFFFF;
 constexpr EndpointId kRootEndpointId        = 0;
 constexpr ListIndex kInvalidListIndex       = 0xFFFF; // List index is a uint16 thus 0xFFFF is a invalid list index.
-constexpr DataVersion kUndefinedDataVersion = 0;
+
 // These are MEIs, 0xFFFF is not a valid manufacturer code,
 // thus 0xFFFF'FFFF is not a valid MEI.
 static constexpr ClusterId kInvalidClusterId     = 0xFFFF'FFFF;
diff --git a/src/lib/core/tests/TestCHIPErrorStr.cpp b/src/lib/core/tests/TestCHIPErrorStr.cpp
index 32a71ff..1f8e5e7 100644
--- a/src/lib/core/tests/TestCHIPErrorStr.cpp
+++ b/src/lib/core/tests/TestCHIPErrorStr.cpp
@@ -210,7 +210,6 @@
     CHIP_ERROR_PROFILE_STRING_CONTEXT_ALREADY_REGISTERED,
     CHIP_ERROR_PROFILE_STRING_CONTEXT_NOT_REGISTERED,
     CHIP_ERROR_INCOMPATIBLE_SCHEMA_VERSION,
-    CHIP_ERROR_MISMATCH_UPDATE_REQUIRED_VERSION,
     CHIP_ERROR_ACCESS_DENIED,
     CHIP_ERROR_UNKNOWN_RESOURCE_ID,
     CHIP_ERROR_VERSION_MISMATCH,
diff --git a/src/platform/CYW30739/args.gni b/src/platform/CYW30739/args.gni
index a49440e..43f6ec4 100644
--- a/src/platform/CYW30739/args.gni
+++ b/src/platform/CYW30739/args.gni
@@ -29,6 +29,8 @@
 chip_device_platform = "cyw30739"
 chip_mdns = "platform"
 
+# Trying to fit into the available flash by disabling optional logging for now
+chip_detail_logging = false
 chip_enable_openthread = true
 
 lwip_platform = "cyw30739"