Modifying ReplacementProductListManager to be generic (#28185)

* Modifying ReplacementProductListManager to be generic

Addressing feedback from PR #28095 and also addressing #28148

This change simplifies the implementation of the ReplacementProductList and also introduces a DynamicReplacementProductList example which better represents devices fetching this product list from flash.

It includes some simplifications to the structure of the code and a few additional unit tests to exercise the behavior.

Tested to ensure functionality still works as expected using the all-clusters-app as well as the resource-monitoring-app
diff --git a/examples/all-clusters-app/all-clusters-common/include/resource-monitoring-instances.h b/examples/all-clusters-app/all-clusters-common/include/resource-monitoring-instances.h
index c12a686..276e423 100644
--- a/examples/all-clusters-app/all-clusters-common/include/resource-monitoring-instances.h
+++ b/examples/all-clusters-app/all-clusters-common/include/resource-monitoring-instances.h
@@ -55,23 +55,9 @@
                  aResetConditionCommandSupported){};
 };
 
-class StaticReplacementProductListManager : public chip::app::Clusters::ResourceMonitoring::ReplacementProductListManager
+class ImmutableReplacementProductListManager : public chip::app::Clusters::ResourceMonitoring::ReplacementProductListManager
 {
 public:
-    uint8_t Size() override { return mReplacementProductListSize; };
-
-    CHIP_ERROR Next(chip::app::Clusters::ResourceMonitoring::Attributes::ReplacementProductStruct::Type & item) override;
-
-    ~StaticReplacementProductListManager() {}
-    StaticReplacementProductListManager(
-        chip::app::Clusters::ResourceMonitoring::Attributes::ReplacementProductStruct::Type * aReplacementProductsList,
-        uint8_t aReplacementProductListSize)
-    {
-        mReplacementProductsList    = aReplacementProductsList;
-        mReplacementProductListSize = aReplacementProductListSize;
-    }
-
-private:
-    chip::app::Clusters::ResourceMonitoring::Attributes::ReplacementProductStruct::Type * mReplacementProductsList;
-    uint8_t mReplacementProductListSize;
+    CHIP_ERROR
+    Next(chip::app::Clusters::ResourceMonitoring::ReplacementProductStruct & item) override;
 };
diff --git a/examples/all-clusters-app/all-clusters-common/src/resource-monitoring-instances.cpp b/examples/all-clusters-app/all-clusters-common/src/resource-monitoring-instances.cpp
index de7b46d..dc42835 100644
--- a/examples/all-clusters-app/all-clusters-common/src/resource-monitoring-instances.cpp
+++ b/examples/all-clusters-app/all-clusters-common/src/resource-monitoring-instances.cpp
@@ -36,20 +36,7 @@
 
 static HepaFilterMonitoringInstance * gHepaFilterInstance                       = nullptr;
 static ActivatedCarbonFilterMonitoringInstance * gActivatedCarbonFilterInstance = nullptr;
-
-static ResourceMonitoring::Attributes::ReplacementProductStruct::Type sReplacementProductsList[] = {
-    { .productIdentifierType  = ProductIdentifierTypeEnum::kUpc,
-      .productIdentifierValue = CharSpan::fromCharString("111112222233") },
-    { .productIdentifierType = ProductIdentifierTypeEnum::kGtin8, .productIdentifierValue = CharSpan::fromCharString("gtin8xxx") },
-    { .productIdentifierType  = ProductIdentifierTypeEnum::kEan,
-      .productIdentifierValue = CharSpan::fromCharString("4444455555666") },
-    { .productIdentifierType  = ProductIdentifierTypeEnum::kGtin14,
-      .productIdentifierValue = CharSpan::fromCharString("gtin14xxxxxxxx") },
-    { .productIdentifierType  = ProductIdentifierTypeEnum::kOem,
-      .productIdentifierValue = CharSpan::fromCharString("oem20xxxxxxxxxxxxxxx") },
-};
-StaticReplacementProductListManager sReplacementProductListManager(&sReplacementProductsList[0],
-                                                                   ArraySize(sReplacementProductsList));
+static ImmutableReplacementProductListManager sReplacementProductListManager;
 
 //-- Activated Carbon Filter Monitoring Instance methods
 CHIP_ERROR ActivatedCarbonFilterMonitoringInstance::AppInit()
@@ -106,14 +93,40 @@
     gHepaFilterInstance->Init();
 }
 
-CHIP_ERROR StaticReplacementProductListManager::Next(Attributes::ReplacementProductStruct::Type & item)
+CHIP_ERROR ImmutableReplacementProductListManager::Next(ReplacementProductStruct & item)
 {
-    if (mIndex < mReplacementProductListSize)
+    if (mIndex >= kReplacementProductListMaxSize)
     {
-        item = mReplacementProductsList[mIndex];
-        mIndex++;
-        return CHIP_NO_ERROR;
+        return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED;
     }
 
-    return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED;
+    switch (mIndex)
+    {
+    case 0: {
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kUpc);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("111112222233"));
+        break;
+    case 1:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kGtin8);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("gtin8xxx"));
+        break;
+    case 2:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kEan);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("4444455555666"));
+        break;
+    case 3:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kGtin14);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("gtin14xxxxxxxx"));
+        break;
+    case 4:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kOem);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("oem20xxxxxxxxxxxxxxx"));
+        break;
+    default:
+        return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED;
+        break;
+    }
+    }
+    mIndex++;
+    return CHIP_NO_ERROR;
 }
diff --git a/examples/placeholder/linux/apps/app1/BUILD.gn b/examples/placeholder/linux/apps/app1/BUILD.gn
index 595a0b7..99b3547 100644
--- a/examples/placeholder/linux/apps/app1/BUILD.gn
+++ b/examples/placeholder/linux/apps/app1/BUILD.gn
@@ -30,6 +30,7 @@
   ]
 
   sources = [
+    "../../resource-monitoring-instances.cpp",
     "../../src/bridged-actions-stub.cpp",
     "../../static-supported-modes-manager.cpp",
   ]
diff --git a/examples/placeholder/linux/apps/app2/BUILD.gn b/examples/placeholder/linux/apps/app2/BUILD.gn
index 7d8f30a..9177797 100644
--- a/examples/placeholder/linux/apps/app2/BUILD.gn
+++ b/examples/placeholder/linux/apps/app2/BUILD.gn
@@ -30,6 +30,7 @@
   ]
 
   sources = [
+    "../../resource-monitoring-instances.cpp",
     "../../src/bridged-actions-stub.cpp",
     "../../static-supported-modes-manager.cpp",
   ]
diff --git a/examples/placeholder/linux/include/resource-monitoring-instances.h b/examples/placeholder/linux/include/resource-monitoring-instances.h
new file mode 100644
index 0000000..276e423
--- /dev/null
+++ b/examples/placeholder/linux/include/resource-monitoring-instances.h
@@ -0,0 +1,63 @@
+/*
+ *
+ *    Copyright (c) 2023 Project CHIP Authors
+ *
+ *    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.
+ */
+
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h>
+#include <app/clusters/resource-monitoring-server/resource-monitoring-server.h>
+
+/// This is an application level Instance to handle ActivatedCarbonfilterMonitoringInstance commands according to the specific
+/// business logic.
+class ActivatedCarbonFilterMonitoringInstance : public chip::app::Clusters::ResourceMonitoring::Instance
+{
+private:
+    CHIP_ERROR AppInit() override;
+    chip::Protocols::InteractionModel::Status PreResetCondition() override;
+    chip::Protocols::InteractionModel::Status PostResetCondition() override;
+
+public:
+    ActivatedCarbonFilterMonitoringInstance(
+        chip::EndpointId aEndpointId, uint32_t aFeature,
+        chip::app::Clusters::ResourceMonitoring::Attributes::DegradationDirection::TypeInfo::Type aDegradationDirection,
+        bool aResetConditionCommandSupported) :
+        Instance(aEndpointId, chip::app::Clusters::ActivatedCarbonFilterMonitoring::Id, aFeature, aDegradationDirection,
+                 aResetConditionCommandSupported){};
+};
+
+/// This is an application level instance to handle HepaFilterMonitoringInstance commands according to the specific business logic.
+class HepaFilterMonitoringInstance : public chip::app::Clusters::ResourceMonitoring::Instance
+{
+private:
+    CHIP_ERROR AppInit() override;
+    chip::Protocols::InteractionModel::Status PreResetCondition() override;
+    chip::Protocols::InteractionModel::Status PostResetCondition() override;
+
+public:
+    HepaFilterMonitoringInstance(
+        chip::EndpointId aEndpointId, uint32_t aFeature,
+        chip::app::Clusters::ResourceMonitoring::Attributes::DegradationDirection::TypeInfo::Type aDegradationDirection,
+        bool aResetConditionCommandSupported) :
+        Instance(aEndpointId, chip::app::Clusters::HepaFilterMonitoring::Id, aFeature, aDegradationDirection,
+                 aResetConditionCommandSupported){};
+};
+
+class ImmutableReplacementProductListManager : public chip::app::Clusters::ResourceMonitoring::ReplacementProductListManager
+{
+public:
+    CHIP_ERROR
+    Next(chip::app::Clusters::ResourceMonitoring::ReplacementProductStruct & item) override;
+};
diff --git a/examples/placeholder/linux/resource-monitoring-instances.cpp b/examples/placeholder/linux/resource-monitoring-instances.cpp
new file mode 100644
index 0000000..a030bee
--- /dev/null
+++ b/examples/placeholder/linux/resource-monitoring-instances.cpp
@@ -0,0 +1,133 @@
+/*
+ *
+ *    Copyright (c) 2023 Project CHIP Authors
+ *
+ *    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.
+ */
+
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h>
+#include <app/clusters/resource-monitoring-server/resource-monitoring-server.h>
+#include <resource-monitoring-instances.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::ResourceMonitoring;
+using chip::Protocols::InteractionModel::Status;
+
+constexpr std::bitset<4> gHepaFilterFeatureMap{ static_cast<uint32_t>(Feature::kCondition) |
+                                                static_cast<uint32_t>(Feature::kWarning) |
+                                                static_cast<uint32_t>(Feature::kReplacementProductList) };
+constexpr std::bitset<4> gActivatedCarbonFeatureMap{ static_cast<uint32_t>(Feature::kCondition) |
+                                                     static_cast<uint32_t>(Feature::kWarning) |
+                                                     static_cast<uint32_t>(Feature::kReplacementProductList) };
+
+static HepaFilterMonitoringInstance * gHepaFilterInstance                       = nullptr;
+static ActivatedCarbonFilterMonitoringInstance * gActivatedCarbonFilterInstance = nullptr;
+
+static ImmutableReplacementProductListManager sReplacementProductListManager;
+
+//-- Activated Carbon Filter Monitoring Instance methods
+CHIP_ERROR ActivatedCarbonFilterMonitoringInstance::AppInit()
+{
+    ChipLogDetail(Zcl, "ActivatedCarbonFilterMonitoringDelegate::Init()");
+    SetReplacementProductListManagerInstance(&sReplacementProductListManager);
+    return CHIP_NO_ERROR;
+}
+
+Status ActivatedCarbonFilterMonitoringInstance::PreResetCondition()
+{
+    ChipLogDetail(Zcl, "ActivatedCarbonFilterMonitoringInstance::PreResetCondition()");
+    return Status::Success;
+}
+
+Status ActivatedCarbonFilterMonitoringInstance::PostResetCondition()
+{
+    ChipLogDetail(Zcl, "ActivatedCarbonFilterMonitoringInstance::PostResetCondition()");
+    return Status::Success;
+}
+
+//-- Hepa Filter Monitoring instance methods
+CHIP_ERROR HepaFilterMonitoringInstance::AppInit()
+{
+    ChipLogDetail(Zcl, "HepaFilterMonitoringInstance::Init()");
+    SetReplacementProductListManagerInstance(&sReplacementProductListManager);
+    return CHIP_NO_ERROR;
+}
+
+Status HepaFilterMonitoringInstance::PreResetCondition()
+{
+    ChipLogDetail(Zcl, "HepaFilterMonitoringInstance::PreResetCondition()");
+    return Status::Success;
+}
+
+Status HepaFilterMonitoringInstance::PostResetCondition()
+{
+    ChipLogDetail(Zcl, "HepaFilterMonitoringInstance::PostResetCondition()");
+    return Status::Success;
+}
+
+void emberAfActivatedCarbonFilterMonitoringClusterInitCallback(chip::EndpointId endpoint)
+{
+    VerifyOrDie(gActivatedCarbonFilterInstance == nullptr);
+    gActivatedCarbonFilterInstance = new ActivatedCarbonFilterMonitoringInstance(
+        endpoint, static_cast<uint32_t>(gActivatedCarbonFeatureMap.to_ulong()), DegradationDirectionEnum::kDown, true);
+    gActivatedCarbonFilterInstance->Init();
+}
+void emberAfHepaFilterMonitoringClusterInitCallback(chip::EndpointId endpoint)
+{
+    VerifyOrDie(gHepaFilterInstance == nullptr);
+    gHepaFilterInstance = new HepaFilterMonitoringInstance(endpoint, static_cast<uint32_t>(gHepaFilterFeatureMap.to_ulong()),
+                                                           DegradationDirectionEnum::kDown, true);
+    gHepaFilterInstance->Init();
+}
+
+CHIP_ERROR ImmutableReplacementProductListManager::Next(ReplacementProductStruct & item)
+{
+    if (mIndex >= kReplacementProductListMaxSize)
+    {
+        return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED;
+    }
+
+    switch (mIndex)
+    {
+    case 0: {
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kUpc);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("111112222233"));
+        break;
+    case 1:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kGtin8);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("gtin8xxx"));
+        break;
+    case 2:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kEan);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("4444455555666"));
+        break;
+    case 3:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kGtin14);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("gtin14xxxxxxxx"));
+        break;
+    case 4:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kOem);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("oem20xxxxxxxxxxxxxxx"));
+        break;
+    default:
+        return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED;
+        break;
+    }
+    }
+    mIndex++;
+    return CHIP_NO_ERROR;
+}
diff --git a/examples/resource-monitoring-app/linux/BUILD.gn b/examples/resource-monitoring-app/linux/BUILD.gn
index c5e769a..e9d5e39 100644
--- a/examples/resource-monitoring-app/linux/BUILD.gn
+++ b/examples/resource-monitoring-app/linux/BUILD.gn
@@ -30,7 +30,7 @@
 
 executable("chip-resource-monitoring-app") {
   sources = [
-    "${chip_root}/examples/resource-monitoring-app/resource-monitoring-common/src/StaticReplacementProductListManager.cpp",
+    "${chip_root}/examples/resource-monitoring-app/resource-monitoring-common/src/ReplacementProductListManager.cpp",
     "${chip_root}/examples/resource-monitoring-app/resource-monitoring-common/src/instances/ActivatedCarbonFilterMonitoring.cpp",
     "${chip_root}/examples/resource-monitoring-app/resource-monitoring-common/src/instances/HepaFilterMonitoring.cpp",
     "include/CHIPProjectAppConfig.h",
diff --git a/examples/resource-monitoring-app/resource-monitoring-common/include/ImmutableReplacementProductListManager.h b/examples/resource-monitoring-app/resource-monitoring-common/include/ImmutableReplacementProductListManager.h
new file mode 100644
index 0000000..2dcbff4
--- /dev/null
+++ b/examples/resource-monitoring-app/resource-monitoring-common/include/ImmutableReplacementProductListManager.h
@@ -0,0 +1,44 @@
+/*
+ *
+ *    Copyright (c) 2023 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/clusters/resource-monitoring-server/replacement-product-list-manager.h>
+#include <app/util/af.h>
+#include <app/util/config.h>
+
+namespace chip {
+namespace app {
+namespace Clusters {
+namespace ResourceMonitoring {
+
+/**
+ * This implementation returns an immutable list of replacement products.
+ * It holds ReplacementProductListManager::kReplacementProductListMaxSize products in the list.
+ */
+
+class ImmutableReplacementProductListManager : public ReplacementProductListManager
+{
+public:
+    CHIP_ERROR Next(ReplacementProductStruct & item) override;
+};
+
+} // namespace ResourceMonitoring
+} // namespace Clusters
+} // namespace app
+} // namespace chip
diff --git a/examples/resource-monitoring-app/resource-monitoring-common/include/StaticReplacementProductListManager.h b/examples/resource-monitoring-app/resource-monitoring-common/include/StaticReplacementProductListManager.h
index cb83148..26601a2 100644
--- a/examples/resource-monitoring-app/resource-monitoring-common/include/StaticReplacementProductListManager.h
+++ b/examples/resource-monitoring-app/resource-monitoring-common/include/StaticReplacementProductListManager.h
@@ -34,20 +34,17 @@
 class StaticReplacementProductListManager : public ReplacementProductListManager
 {
 public:
-    uint8_t Size() override { return mReplacementProductListSize; };
-
-    CHIP_ERROR Next(Attributes::ReplacementProductStruct::Type & item) override;
+    CHIP_ERROR Next(ReplacementProductStruct & item) override;
 
     ~StaticReplacementProductListManager() {}
-    StaticReplacementProductListManager(Attributes::ReplacementProductStruct::Type * aReplacementProductsList,
-                                        uint8_t aReplacementProductListSize)
+    StaticReplacementProductListManager(ReplacementProductStruct * aReplacementProductsList, uint8_t aReplacementProductListSize)
     {
         mReplacementProductsList    = aReplacementProductsList;
         mReplacementProductListSize = aReplacementProductListSize;
     }
 
 private:
-    Attributes::ReplacementProductStruct::Type * mReplacementProductsList;
+    ReplacementProductStruct * mReplacementProductsList;
     uint8_t mReplacementProductListSize;
 };
 
diff --git a/examples/resource-monitoring-app/resource-monitoring-common/src/ReplacementProductListManager.cpp b/examples/resource-monitoring-app/resource-monitoring-common/src/ReplacementProductListManager.cpp
new file mode 100644
index 0000000..f450df6
--- /dev/null
+++ b/examples/resource-monitoring-app/resource-monitoring-common/src/ReplacementProductListManager.cpp
@@ -0,0 +1,77 @@
+/*
+ *
+ *    Copyright (c) 2023 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.
+ */
+
+#include <ImmutableReplacementProductListManager.h>
+#include <StaticReplacementProductListManager.h>
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h>
+#include <lib/core/CHIPError.h>
+
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::ResourceMonitoring;
+
+CHIP_ERROR StaticReplacementProductListManager::Next(ReplacementProductStruct & item)
+{
+    if (mIndex < mReplacementProductListSize)
+    {
+        item = mReplacementProductsList[mIndex];
+        mIndex++;
+        return CHIP_NO_ERROR;
+    }
+
+    return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED;
+}
+
+CHIP_ERROR ImmutableReplacementProductListManager::Next(ReplacementProductStruct & item)
+{
+    if (mIndex >= kReplacementProductListMaxSize)
+    {
+        return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED;
+    }
+
+    switch (mIndex)
+    {
+    case 0: {
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kUpc);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("111112222233"));
+        break;
+    case 1:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kGtin8);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("gtin8xxx"));
+        break;
+    case 2:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kEan);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("4444455555666"));
+        break;
+    case 3:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kGtin14);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("gtin14xxxxxxxx"));
+        break;
+    case 4:
+        item.SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum::kOem);
+        item.SetProductIdentifierValue(CharSpan::fromCharString("oem20xxxxxxxxxxxxxxx"));
+        break;
+    default:
+        return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED;
+        break;
+    }
+    }
+    mIndex++;
+    return CHIP_NO_ERROR;
+}
diff --git a/examples/resource-monitoring-app/resource-monitoring-common/src/StaticReplacementProductListManager.cpp b/examples/resource-monitoring-app/resource-monitoring-common/src/StaticReplacementProductListManager.cpp
deleted file mode 100644
index 4c657ff..0000000
--- a/examples/resource-monitoring-app/resource-monitoring-common/src/StaticReplacementProductListManager.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- *
- *    Copyright (c) 2023 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.
- */
-
-#include <StaticReplacementProductListManager.h>
-#include <app-common/zap-generated/ids/Attributes.h>
-#include <app-common/zap-generated/ids/Clusters.h>
-#include <app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h>
-#include <lib/core/CHIPError.h>
-
-using namespace chip::app::Clusters;
-using namespace chip::app::Clusters::ResourceMonitoring;
-
-CHIP_ERROR StaticReplacementProductListManager::Next(Attributes::ReplacementProductStruct::Type & item)
-{
-    if (mIndex < mReplacementProductListSize)
-    {
-        item = mReplacementProductsList[mIndex];
-        mIndex++;
-        return CHIP_NO_ERROR;
-    }
-
-    return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED;
-}
diff --git a/examples/resource-monitoring-app/resource-monitoring-common/src/instances/ActivatedCarbonFilterMonitoring.cpp b/examples/resource-monitoring-app/resource-monitoring-common/src/instances/ActivatedCarbonFilterMonitoring.cpp
index 778bb9d..3ab33e6 100644
--- a/examples/resource-monitoring-app/resource-monitoring-common/src/instances/ActivatedCarbonFilterMonitoring.cpp
+++ b/examples/resource-monitoring-app/resource-monitoring-common/src/instances/ActivatedCarbonFilterMonitoring.cpp
@@ -32,19 +32,15 @@
 using namespace chip::app::Clusters;
 using namespace chip::app::Clusters::ActivatedCarbonFilterMonitoring;
 using namespace chip::app::Clusters::ResourceMonitoring;
+using namespace chip::app::Clusters::ResourceMonitoring::Attributes;
 using chip::Protocols::InteractionModel::Status;
 
-static ResourceMonitoring::Attributes::ReplacementProductStruct::Type sActivatedCarbonFilterReplacementProductsList[] = {
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kUpc,
-      .productIdentifierValue = CharSpan::fromCharString("111112222233") },
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kGtin8,
-      .productIdentifierValue = CharSpan::fromCharString("gtin8xca") },
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kEan,
-      .productIdentifierValue = CharSpan::fromCharString("4444455555666") },
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kGtin14,
-      .productIdentifierValue = CharSpan::fromCharString("gtin14xcarbonx") },
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kOem,
-      .productIdentifierValue = CharSpan::fromCharString("oem20xcarbonxxxxxxxx") },
+static ReplacementProductStruct sActivatedCarbonFilterReplacementProductsList[] = {
+    { ResourceMonitoring::ProductIdentifierTypeEnum::kUpc, CharSpan::fromCharString("111112222233") },
+    { ResourceMonitoring::ProductIdentifierTypeEnum::kGtin8, CharSpan::fromCharString("gtin8xca") },
+    { ResourceMonitoring::ProductIdentifierTypeEnum::kEan, CharSpan::fromCharString("4444455555666") },
+    { ResourceMonitoring::ProductIdentifierTypeEnum::kGtin14, CharSpan::fromCharString("gtin14xcarbonx") },
+    { ResourceMonitoring::ProductIdentifierTypeEnum::kOem, CharSpan::fromCharString("oem20xcarbonxxxxxxxx") },
 };
 StaticReplacementProductListManager
     sActivatedCarbonFilterReplacementProductListManager(&sActivatedCarbonFilterReplacementProductsList[0],
diff --git a/examples/resource-monitoring-app/resource-monitoring-common/src/instances/HepafilterMonitoring.cpp b/examples/resource-monitoring-app/resource-monitoring-common/src/instances/HepafilterMonitoring.cpp
index c0ee95e..6404f97 100644
--- a/examples/resource-monitoring-app/resource-monitoring-common/src/instances/HepafilterMonitoring.cpp
+++ b/examples/resource-monitoring-app/resource-monitoring-common/src/instances/HepafilterMonitoring.cpp
@@ -16,7 +16,7 @@
  *    limitations under the License.
  */
 
-#include <StaticReplacementProductListManager.h>
+#include <ImmutableReplacementProductListManager.h>
 #include <app-common/zap-generated/ids/Attributes.h>
 #include <app-common/zap-generated/ids/Clusters.h>
 #include <app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h>
@@ -31,22 +31,10 @@
 using namespace chip;
 using namespace chip::app::Clusters;
 using namespace chip::app::Clusters::ResourceMonitoring;
+using namespace chip::app::Clusters::ResourceMonitoring::Attributes;
 using chip::Protocols::InteractionModel::Status;
 
-static ResourceMonitoring::Attributes::ReplacementProductStruct::Type sHepaFilterReplacementProductsList[] = {
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kUpc,
-      .productIdentifierValue = CharSpan::fromCharString("111112222233") },
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kGtin8,
-      .productIdentifierValue = CharSpan::fromCharString("gtin8xhe") },
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kEan,
-      .productIdentifierValue = CharSpan::fromCharString("4444455555666") },
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kGtin14,
-      .productIdentifierValue = CharSpan::fromCharString("gtin14xhepaxxx") },
-    { .productIdentifierType  = ResourceMonitoring::ProductIdentifierTypeEnum::kOem,
-      .productIdentifierValue = CharSpan::fromCharString("oem20xhepaxxxxxxxxxx") },
-};
-StaticReplacementProductListManager sHepaFilterReplacementProductListManager(&sHepaFilterReplacementProductsList[0],
-                                                                             ArraySize(sHepaFilterReplacementProductsList));
+static ImmutableReplacementProductListManager sHepaFilterReplacementProductListManager;
 
 //-- Hepa filter Monitoring instance methods
 CHIP_ERROR HepaFilterMonitoringInstance::AppInit()
diff --git a/src/app/clusters/resource-monitoring-server/replacement-product-list-manager.h b/src/app/clusters/resource-monitoring-server/replacement-product-list-manager.h
index b252597..bd63dab 100644
--- a/src/app/clusters/resource-monitoring-server/replacement-product-list-manager.h
+++ b/src/app/clusters/resource-monitoring-server/replacement-product-list-manager.h
@@ -34,15 +34,24 @@
 class ReplacementProductListManager
 {
 public:
+    // The max replacement product list size as defined in the specification
+    static constexpr size_t kReplacementProductListMaxSize = 5u;
+
     ReplacementProductListManager() {}
     virtual ~ReplacementProductListManager() = default;
 
     void Reset() { mIndex = 0; }
 
-    // Returns total size of Replacement Products List.
-    virtual uint8_t Size() = 0;
-
-    virtual CHIP_ERROR Next(Attributes::ReplacementProductStruct::Type & item) = 0;
+    /**
+     * Iterates through the entries in the ReplacementProductListManager. Each call to this function copies the next item into
+     * the out param. Calls to this function will return CHIP_NO_ERROR if there are still valid elements in the list. The function
+     * will return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED if the end of the list has been reached.
+     *
+     * @param[out] item An out parameter that has a copy of the item retrieved in the list.
+     * @return CHIP_NO_ERROR if the pointer to the list element has moved to the next element and there are still valid remaining
+     * entries in the list. Otherwise returns CHIP_ERROR_PROVIDER_LIST_EXHAUSTED if the list has hit the last element.
+     */
+    virtual CHIP_ERROR Next(ReplacementProductStruct & item) = 0;
 
 protected:
     uint8_t mIndex;
diff --git a/src/app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.cpp b/src/app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.cpp
index 932c95c..34388cb 100644
--- a/src/app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.cpp
+++ b/src/app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.cpp
@@ -44,22 +44,6 @@
 } // namespace ResetCondition.
 } // namespace Commands
 
-namespace Attributes {
-namespace ReplacementProductStruct {
-
-CHIP_ERROR Type::Encode(TLV::TLVWriter & writer, TLV::Tag tag) const
-{
-    TLV::TLVType outer;
-    ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outer));
-    ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(Fields::kProductIdentifierType), productIdentifierType));
-    ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(Fields::kProductIdentifierValue), productIdentifierValue));
-    ReturnErrorOnFailure(writer.EndContainer(outer));
-    return CHIP_NO_ERROR;
-}
-
-} // namespace ReplacementProductStruct
-} // namespace Attributes
-
 } // namespace ResourceMonitoring
 } // namespace Clusters
 } // namespace app
diff --git a/src/app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h b/src/app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h
index 995f42b..970a85c 100644
--- a/src/app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h
+++ b/src/app/clusters/resource-monitoring-server/resource-monitoring-cluster-objects.h
@@ -29,7 +29,9 @@
 namespace Clusters {
 namespace ResourceMonitoring {
 
-static constexpr std::array<ClusterId, 2> AliasedClusters = { HepaFilterMonitoring::Id, ActivatedCarbonFilterMonitoring::Id };
+// max of 20 characters as defined by the constraint on the ProductIdentifierValue in the specification
+static constexpr size_t kProductIdentifierValueMaxNameLength = 20u;
+static constexpr std::array<ClusterId, 2> AliasedClusters    = { HepaFilterMonitoring::Id, ActivatedCarbonFilterMonitoring::Id };
 
 // Enum for ChangeIndicationEnum
 enum class ChangeIndicationEnum : uint8_t
@@ -74,6 +76,74 @@
     kOem    = 0x04
 };
 
+// A struct used during reads of the ReplacementProductList to store a single list instance we request
+// from the application.
+//
+// Inherit from an auto-generated struct to pick up the implementation bits, but make
+// it private inheritance so people can't accidentally use this struct where the other
+// is expected.
+struct ReplacementProductStruct : private HepaFilterMonitoring::Structs::ReplacementProductStruct::Type
+{
+private:
+    char productIdentifierValueBuffer[kProductIdentifierValueMaxNameLength];
+
+public:
+    static constexpr bool kIsFabricScoped = false;
+    virtual ~ReplacementProductStruct()   = default;
+    ReplacementProductStruct() {}
+    ReplacementProductStruct(ResourceMonitoring::ProductIdentifierTypeEnum aProductIdentifierType,
+                             chip::CharSpan aProductIdentifierValue)
+    {
+        SetProductIdentifierType(aProductIdentifierType);
+        SetProductIdentifierValue(aProductIdentifierValue);
+    }
+
+    ReplacementProductStruct & operator=(const ReplacementProductStruct & aReplacementProductStruct)
+    {
+        SetProductIdentifierType(aReplacementProductStruct.GetProductIdentifierType());
+        SetProductIdentifierValue(aReplacementProductStruct.GetProductIdentifierValue());
+        return *this;
+    }
+
+    using HepaFilterMonitoring::Structs::ReplacementProductStruct::Type::Encode;
+
+    /**
+     * Sets the product identifier type.
+     *
+     * @param aProductIdentifierType The product identifier type.
+     */
+    void SetProductIdentifierType(ResourceMonitoring::ProductIdentifierTypeEnum aProductIdentifierType)
+    {
+        productIdentifierType = static_cast<HepaFilterMonitoring::ProductIdentifierTypeEnum>(aProductIdentifierType);
+    }
+
+    /**
+     * Sets the product identifier value.
+     * This implementation will copy the argument into this struct's buffer.
+     *
+     * @param aProductIdentifierValue The value of the product identifier to set.
+     * @return CHIP_ERROR_INVALID_ARGUMENT when aProductIdentifierValue is invalid
+     * or the size exceeds kProductIdentifierValueMaxNameLength, returns CHIP_NO_ERROR
+     * otherwise.
+     */
+    CHIP_ERROR SetProductIdentifierValue(chip::CharSpan aProductIdentifierValue)
+    {
+        VerifyOrReturnError(IsSpanUsable(aProductIdentifierValue), CHIP_ERROR_INVALID_ARGUMENT);
+        VerifyOrReturnError(aProductIdentifierValue.size() <= sizeof(productIdentifierValueBuffer), CHIP_ERROR_INVALID_ARGUMENT);
+
+        memcpy(productIdentifierValueBuffer, aProductIdentifierValue.data(), aProductIdentifierValue.size());
+        productIdentifierValue = CharSpan(productIdentifierValueBuffer, aProductIdentifierValue.size());
+
+        return CHIP_NO_ERROR;
+    }
+
+    ProductIdentifierTypeEnum GetProductIdentifierType() const
+    {
+        return static_cast<ProductIdentifierTypeEnum>(productIdentifierType);
+    };
+    chip::CharSpan GetProductIdentifierValue() const { return productIdentifierValue; };
+};
+
 namespace Attributes {
 
 namespace Condition {
@@ -141,42 +211,9 @@
 };
 } // namespace LastChangedTime
 
-namespace ReplacementProductStruct {
-enum class Fields : uint8_t
-{
-    kProductIdentifierType  = 0,
-    kProductIdentifierValue = 1,
-};
-
-struct Type
-{
-public:
-    ProductIdentifierTypeEnum productIdentifierType = static_cast<ProductIdentifierTypeEnum>(0);
-    chip::CharSpan productIdentifierValue;
-
-    CHIP_ERROR Decode(TLV::TLVReader & reader);
-
-    static constexpr bool kIsFabricScoped = false;
-
-    CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag) const;
-};
-
-using DecodableType = Type;
-
-} // namespace ReplacementProductStruct
-
 namespace ReplacementProductList {
 static constexpr AttributeId Id = 0x00000005;
-struct TypeInfo
-{
-    using Type             = chip::app::DataModel::List<const ReplacementProductStruct::Type>;
-    using DecodableType    = chip::app::DataModel::DecodableList<ReplacementProductStruct::Type>;
-    using DecodableArgType = const chip::app::DataModel::DecodableList<ReplacementProductStruct::Type> &;
-
-    static constexpr AttributeId GetAttributeId() { return Attributes::ReplacementProductList::Id; }
-    static constexpr bool MustUseTimedWrite() { return false; }
-};
-} // namespace ReplacementProductList
+}
 
 namespace GeneratedCommandList {
 static constexpr AttributeId Id = Globals::Attributes::GeneratedCommandList::Id;
diff --git a/src/app/clusters/resource-monitoring-server/resource-monitoring-server.cpp b/src/app/clusters/resource-monitoring-server/resource-monitoring-server.cpp
index 2cbcb43..c568746 100644
--- a/src/app/clusters/resource-monitoring-server/resource-monitoring-server.cpp
+++ b/src/app/clusters/resource-monitoring-server/resource-monitoring-server.cpp
@@ -244,10 +244,10 @@
 
 CHIP_ERROR Instance::ReadReplacableProductList(AttributeValueEncoder & aEncoder)
 {
-    CHIP_ERROR err;
-    if (Instance::HasFeature(ResourceMonitoring::Feature::kReplacementProductList))
+    CHIP_ERROR err = CHIP_NO_ERROR;
+    if (HasFeature(ResourceMonitoring::Feature::kReplacementProductList))
     {
-        ReplacementProductListManager * productListManagerInstance = Instance::GetReplacementProductListManagerInstance();
+        ReplacementProductListManager * productListManagerInstance = GetReplacementProductListManagerInstance();
         if (nullptr == productListManagerInstance)
         {
             aEncoder.EncodeEmptyList();
@@ -256,16 +256,19 @@
 
         productListManagerInstance->Reset();
 
-        err = aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
-            Attributes::ReplacementProductStruct::Type replacementProductStruct;
-            while (productListManagerInstance->Next(replacementProductStruct) == CHIP_NO_ERROR)
+        err = aEncoder.EncodeList([productListManagerInstance](const auto & encoder) -> CHIP_ERROR {
+            ReplacementProductStruct replacementProductStruct;
+            CHIP_ERROR iteratorError = productListManagerInstance->Next(replacementProductStruct);
+
+            while (CHIP_NO_ERROR == iteratorError)
             {
                 ReturnErrorOnFailure(encoder.Encode(replacementProductStruct));
+                iteratorError = productListManagerInstance->Next(replacementProductStruct);
             }
-            return CHIP_NO_ERROR;
+            return (CHIP_ERROR_PROVIDER_LIST_EXHAUSTED == iteratorError) ? CHIP_NO_ERROR : iteratorError;
         });
     }
-    return CHIP_NO_ERROR;
+    return err;
 }
 
 // Implements the read functionality for non-standard attributes.
diff --git a/src/app/tests/suites/TestActivatedCarbonFilterMonitoring.yaml b/src/app/tests/suites/TestActivatedCarbonFilterMonitoring.yaml
new file mode 100644
index 0000000..c47e01f
--- /dev/null
+++ b/src/app/tests/suites/TestActivatedCarbonFilterMonitoring.yaml
@@ -0,0 +1,57 @@
+# Copyright (c) 2021 Project CHIP Authors
+#
+# 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.
+
+name: Activated Carbon Filter Monitoring Cluster Tests
+
+config:
+    nodeId: 0x12344321
+    cluster: "Activated Carbon Filter Monitoring"
+    endpoint: 1
+
+tests:
+    - label: "Wait for the commissioned device to be retrieved"
+      cluster: "DelayCommands"
+      command: "WaitForCommissionee"
+      arguments:
+          values:
+              - name: "nodeId"
+                value: nodeId
+
+    - label: "Read Replacement Product List"
+      command: "readAttribute"
+      attribute: "ReplacementProductList"
+      response:
+          value:
+              [
+                  {
+                      ProductIdentifierType: 0,
+                      ProductIdentifierValue: "111112222233",
+                  },
+                  {
+                      ProductIdentifierType: 1,
+                      ProductIdentifierValue: "gtin8xxx",
+                  },
+                  {
+                      ProductIdentifierType: 2,
+                      ProductIdentifierValue: "4444455555666",
+                  },
+                  {
+                      ProductIdentifierType: 3,
+                      ProductIdentifierValue: "gtin14xxxxxxxx",
+                  },
+                  {
+                      ProductIdentifierType: 4,
+                      ProductIdentifierValue: "oem20xxxxxxxxxxxxxxx",
+                  },
+              ]
diff --git a/src/app/tests/suites/TestHepaFilterMonitoring.yaml b/src/app/tests/suites/TestHepaFilterMonitoring.yaml
new file mode 100644
index 0000000..44b79ee
--- /dev/null
+++ b/src/app/tests/suites/TestHepaFilterMonitoring.yaml
@@ -0,0 +1,57 @@
+# Copyright (c) 2021 Project CHIP Authors
+#
+# 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.
+
+name: HEPA Filter Monitoring Cluster Tests
+
+config:
+    nodeId: 0x12344321
+    cluster: "HEPA Filter Monitoring"
+    endpoint: 1
+
+tests:
+    - label: "Wait for the commissioned device to be retrieved"
+      cluster: "DelayCommands"
+      command: "WaitForCommissionee"
+      arguments:
+          values:
+              - name: "nodeId"
+                value: nodeId
+
+    - label: "Read Replacement Product List"
+      command: "readAttribute"
+      attribute: "ReplacementProductList"
+      response:
+          value:
+              [
+                  {
+                      ProductIdentifierType: 0,
+                      ProductIdentifierValue: "111112222233",
+                  },
+                  {
+                      ProductIdentifierType: 1,
+                      ProductIdentifierValue: "gtin8xxx",
+                  },
+                  {
+                      ProductIdentifierType: 2,
+                      ProductIdentifierValue: "4444455555666",
+                  },
+                  {
+                      ProductIdentifierType: 3,
+                      ProductIdentifierValue: "gtin14xxxxxxxx",
+                  },
+                  {
+                      ProductIdentifierType: 4,
+                      ProductIdentifierValue: "oem20xxxxxxxxxxxxxxx",
+                  },
+              ]
diff --git a/src/app/tests/suites/ciTests.json b/src/app/tests/suites/ciTests.json
index 377dd56..a22aea1 100644
--- a/src/app/tests/suites/ciTests.json
+++ b/src/app/tests/suites/ciTests.json
@@ -303,6 +303,8 @@
     ],
     "Scenes": ["Test_TC_S_1_1"],
     "ResourceMonitoring": [
+        "TestActivatedCarbonFilterMonitoring",
+        "TestHepaFilterMonitoring",
         "Test_TC_ACFREMON_1_1",
         "Test_TC_ACFREMON_2_1",
         "Test_TC_HEPAFREMON_1_1",
diff --git a/zzz_generated/chip-tool/zap-generated/test/Commands.h b/zzz_generated/chip-tool/zap-generated/test/Commands.h
index e800416..a07a8e2 100644
--- a/zzz_generated/chip-tool/zap-generated/test/Commands.h
+++ b/zzz_generated/chip-tool/zap-generated/test/Commands.h
@@ -322,6 +322,8 @@
         printf("TestGroupKeyManagementCluster\n");
         printf("Test_TC_G_1_1\n");
         printf("Test_TC_G_2_1\n");
+        printf("TestActivatedCarbonFilterMonitoring\n");
+        printf("TestHepaFilterMonitoring\n");
         printf("Test_TC_ACFREMON_1_1\n");
         printf("Test_TC_ACFREMON_2_1\n");
         printf("Test_TC_HEPAFREMON_1_1\n");
@@ -113646,6 +113648,236 @@
     }
 };
 
+class TestActivatedCarbonFilterMonitoringSuite : public TestCommand
+{
+public:
+    TestActivatedCarbonFilterMonitoringSuite(CredentialIssuerCommands * credsIssuerConfig) :
+        TestCommand("TestActivatedCarbonFilterMonitoring", 2, credsIssuerConfig)
+    {
+        AddArgument("nodeId", 0, UINT64_MAX, &mNodeId);
+        AddArgument("cluster", &mCluster);
+        AddArgument("endpoint", 0, UINT16_MAX, &mEndpoint);
+        AddArgument("timeout", 0, UINT16_MAX, &mTimeout);
+    }
+
+    ~TestActivatedCarbonFilterMonitoringSuite() {}
+
+    chip::System::Clock::Timeout GetWaitDuration() const override
+    {
+        return chip::System::Clock::Seconds16(mTimeout.ValueOr(kTimeoutInSeconds));
+    }
+
+private:
+    chip::Optional<chip::NodeId> mNodeId;
+    chip::Optional<chip::CharSpan> mCluster;
+    chip::Optional<chip::EndpointId> mEndpoint;
+    chip::Optional<uint16_t> mTimeout;
+
+    chip::EndpointId GetEndpoint(chip::EndpointId endpoint) { return mEndpoint.HasValue() ? mEndpoint.Value() : endpoint; }
+
+    //
+    // Tests methods
+    //
+
+    void OnResponse(const chip::app::StatusIB & status, chip::TLV::TLVReader * data) override
+    {
+        bool shouldContinue = false;
+
+        switch (mTestIndex - 1)
+        {
+        case 0:
+            VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0));
+            shouldContinue = true;
+            break;
+        case 1:
+            VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0));
+            {
+                chip::app::DataModel::DecodableList<
+                    chip::app::Clusters::ActivatedCarbonFilterMonitoring::Structs::ReplacementProductStruct::DecodableType>
+                    value;
+                VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value)));
+                {
+                    auto iter_0 = value.begin();
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 0));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[0].productIdentifierType", iter_0.GetValue().productIdentifierType, 0U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[0].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue,
+                                                      chip::CharSpan("111112222233", 12)));
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 1));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[1].productIdentifierType", iter_0.GetValue().productIdentifierType, 1U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[1].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue, chip::CharSpan("gtin8xxx", 8)));
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 2));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[2].productIdentifierType", iter_0.GetValue().productIdentifierType, 2U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[2].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue,
+                                                      chip::CharSpan("4444455555666", 13)));
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 3));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[3].productIdentifierType", iter_0.GetValue().productIdentifierType, 3U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[3].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue,
+                                                      chip::CharSpan("gtin14xxxxxxxx", 14)));
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 4));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[4].productIdentifierType", iter_0.GetValue().productIdentifierType, 4U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[4].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue,
+                                                      chip::CharSpan("oem20xxxxxxxxxxxxxxx", 20)));
+                    VerifyOrReturn(CheckNoMoreListItems<decltype(value)>("replacementProductList", iter_0, 5));
+                }
+            }
+            break;
+        default:
+            LogErrorOnFailure(ContinueOnChipMainThread(CHIP_ERROR_INVALID_ARGUMENT));
+        }
+
+        if (shouldContinue)
+        {
+            ContinueOnChipMainThread(CHIP_NO_ERROR);
+        }
+    }
+
+    CHIP_ERROR DoTestStep(uint16_t testIndex) override
+    {
+        using namespace chip::app::Clusters;
+        switch (testIndex)
+        {
+        case 0: {
+            LogStep(0, "Wait for the commissioned device to be retrieved");
+            ListFreer listFreer;
+            chip::app::Clusters::DelayCommands::Commands::WaitForCommissionee::Type value;
+            value.nodeId = mNodeId.HasValue() ? mNodeId.Value() : 305414945ULL;
+            return WaitForCommissionee(kIdentityAlpha, value);
+        }
+        case 1: {
+            LogStep(1, "Read Replacement Product List");
+            return ReadAttribute(kIdentityAlpha, GetEndpoint(1), ActivatedCarbonFilterMonitoring::Id,
+                                 ActivatedCarbonFilterMonitoring::Attributes::ReplacementProductList::Id, true, chip::NullOptional);
+        }
+        }
+        return CHIP_NO_ERROR;
+    }
+};
+
+class TestHepaFilterMonitoringSuite : public TestCommand
+{
+public:
+    TestHepaFilterMonitoringSuite(CredentialIssuerCommands * credsIssuerConfig) :
+        TestCommand("TestHepaFilterMonitoring", 2, credsIssuerConfig)
+    {
+        AddArgument("nodeId", 0, UINT64_MAX, &mNodeId);
+        AddArgument("cluster", &mCluster);
+        AddArgument("endpoint", 0, UINT16_MAX, &mEndpoint);
+        AddArgument("timeout", 0, UINT16_MAX, &mTimeout);
+    }
+
+    ~TestHepaFilterMonitoringSuite() {}
+
+    chip::System::Clock::Timeout GetWaitDuration() const override
+    {
+        return chip::System::Clock::Seconds16(mTimeout.ValueOr(kTimeoutInSeconds));
+    }
+
+private:
+    chip::Optional<chip::NodeId> mNodeId;
+    chip::Optional<chip::CharSpan> mCluster;
+    chip::Optional<chip::EndpointId> mEndpoint;
+    chip::Optional<uint16_t> mTimeout;
+
+    chip::EndpointId GetEndpoint(chip::EndpointId endpoint) { return mEndpoint.HasValue() ? mEndpoint.Value() : endpoint; }
+
+    //
+    // Tests methods
+    //
+
+    void OnResponse(const chip::app::StatusIB & status, chip::TLV::TLVReader * data) override
+    {
+        bool shouldContinue = false;
+
+        switch (mTestIndex - 1)
+        {
+        case 0:
+            VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0));
+            shouldContinue = true;
+            break;
+        case 1:
+            VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0));
+            {
+                chip::app::DataModel::DecodableList<
+                    chip::app::Clusters::HepaFilterMonitoring::Structs::ReplacementProductStruct::DecodableType>
+                    value;
+                VerifyOrReturn(CheckDecodeValue(chip::app::DataModel::Decode(*data, value)));
+                {
+                    auto iter_0 = value.begin();
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 0));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[0].productIdentifierType", iter_0.GetValue().productIdentifierType, 0U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[0].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue,
+                                                      chip::CharSpan("111112222233", 12)));
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 1));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[1].productIdentifierType", iter_0.GetValue().productIdentifierType, 1U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[1].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue, chip::CharSpan("gtin8xxx", 8)));
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 2));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[2].productIdentifierType", iter_0.GetValue().productIdentifierType, 2U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[2].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue,
+                                                      chip::CharSpan("4444455555666", 13)));
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 3));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[3].productIdentifierType", iter_0.GetValue().productIdentifierType, 3U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[3].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue,
+                                                      chip::CharSpan("gtin14xxxxxxxx", 14)));
+                    VerifyOrReturn(CheckNextListItemDecodes<decltype(value)>("replacementProductList", iter_0, 4));
+                    VerifyOrReturn(
+                        CheckValue("replacementProductList[4].productIdentifierType", iter_0.GetValue().productIdentifierType, 4U));
+                    VerifyOrReturn(CheckValueAsString("replacementProductList[4].productIdentifierValue",
+                                                      iter_0.GetValue().productIdentifierValue,
+                                                      chip::CharSpan("oem20xxxxxxxxxxxxxxx", 20)));
+                    VerifyOrReturn(CheckNoMoreListItems<decltype(value)>("replacementProductList", iter_0, 5));
+                }
+            }
+            break;
+        default:
+            LogErrorOnFailure(ContinueOnChipMainThread(CHIP_ERROR_INVALID_ARGUMENT));
+        }
+
+        if (shouldContinue)
+        {
+            ContinueOnChipMainThread(CHIP_NO_ERROR);
+        }
+    }
+
+    CHIP_ERROR DoTestStep(uint16_t testIndex) override
+    {
+        using namespace chip::app::Clusters;
+        switch (testIndex)
+        {
+        case 0: {
+            LogStep(0, "Wait for the commissioned device to be retrieved");
+            ListFreer listFreer;
+            chip::app::Clusters::DelayCommands::Commands::WaitForCommissionee::Type value;
+            value.nodeId = mNodeId.HasValue() ? mNodeId.Value() : 305414945ULL;
+            return WaitForCommissionee(kIdentityAlpha, value);
+        }
+        case 1: {
+            LogStep(1, "Read Replacement Product List");
+            return ReadAttribute(kIdentityAlpha, GetEndpoint(1), HepaFilterMonitoring::Id,
+                                 HepaFilterMonitoring::Attributes::ReplacementProductList::Id, true, chip::NullOptional);
+        }
+        }
+        return CHIP_NO_ERROR;
+    }
+};
+
 class Test_TC_ACFREMON_1_1Suite : public TestCommand
 {
 public:
@@ -144954,6 +145186,8 @@
         make_unique<TestGroupKeyManagementClusterSuite>(credsIssuerConfig),
         make_unique<Test_TC_G_1_1Suite>(credsIssuerConfig),
         make_unique<Test_TC_G_2_1Suite>(credsIssuerConfig),
+        make_unique<TestActivatedCarbonFilterMonitoringSuite>(credsIssuerConfig),
+        make_unique<TestHepaFilterMonitoringSuite>(credsIssuerConfig),
         make_unique<Test_TC_ACFREMON_1_1Suite>(credsIssuerConfig),
         make_unique<Test_TC_ACFREMON_2_1Suite>(credsIssuerConfig),
         make_unique<Test_TC_HEPAFREMON_1_1Suite>(credsIssuerConfig),
diff --git a/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h
index 3d69b11..125b351 100644
--- a/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h
+++ b/zzz_generated/darwin-framework-tool/zap-generated/test/Commands.h
@@ -286,6 +286,8 @@
         printf("TestGroupsCluster\n");
         printf("TestGroupKeyManagementCluster\n");
         printf("Test_TC_G_1_1\n");
+        printf("TestActivatedCarbonFilterMonitoring\n");
+        printf("TestHepaFilterMonitoring\n");
         printf("Test_TC_ACFREMON_1_1\n");
         printf("Test_TC_ACFREMON_2_1\n");
         printf("Test_TC_HEPAFREMON_1_1\n");
@@ -172696,6 +172698,295 @@
     }
 };
 
+class TestActivatedCarbonFilterMonitoring : public TestCommandBridge {
+public:
+    // NOLINTBEGIN(clang-analyzer-nullability.NullPassedToNonnull): Test constructor nullability not enforced
+    TestActivatedCarbonFilterMonitoring()
+        : TestCommandBridge("TestActivatedCarbonFilterMonitoring")
+        , mTestIndex(0)
+    {
+        AddArgument("nodeId", 0, UINT64_MAX, &mNodeId);
+        AddArgument("cluster", &mCluster);
+        AddArgument("endpoint", 0, UINT16_MAX, &mEndpoint);
+        AddArgument("timeout", 0, UINT16_MAX, &mTimeout);
+    }
+    // NOLINTEND(clang-analyzer-nullability.NullPassedToNonnull)
+
+    ~TestActivatedCarbonFilterMonitoring() {}
+
+    /////////// TestCommand Interface /////////
+    void NextTest() override
+    {
+        CHIP_ERROR err = CHIP_NO_ERROR;
+
+        if (0 == mTestIndex) {
+            ChipLogProgress(chipTool, " **** Test Start: TestActivatedCarbonFilterMonitoring\n");
+        }
+
+        if (mTestCount == mTestIndex) {
+            ChipLogProgress(chipTool, " **** Test Complete: TestActivatedCarbonFilterMonitoring\n");
+            SetCommandExitStatus(CHIP_NO_ERROR);
+            return;
+        }
+
+        Wait();
+
+        // Ensure we increment mTestIndex before we start running the relevant
+        // command.  That way if we lose the timeslice after we send the message
+        // but before our function call returns, we won't end up with an
+        // incorrect mTestIndex value observed when we get the response.
+        switch (mTestIndex++) {
+        case 0:
+            ChipLogProgress(chipTool, " ***** Test Step 0 : Wait for the commissioned device to be retrieved\n");
+            err = TestWaitForTheCommissionedDeviceToBeRetrieved_0();
+            break;
+        case 1:
+            ChipLogProgress(chipTool, " ***** Test Step 1 : Read Replacement Product List\n");
+            err = TestReadReplacementProductList_1();
+            break;
+        }
+
+        if (CHIP_NO_ERROR != err) {
+            ChipLogError(chipTool, " ***** Test Failure: %s\n", chip::ErrorStr(err));
+            SetCommandExitStatus(err);
+        }
+    }
+
+    void OnStatusUpdate(const chip::app::StatusIB & status) override
+    {
+        switch (mTestIndex - 1) {
+        case 0:
+            VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0));
+            break;
+        case 1:
+            VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0));
+            break;
+        }
+
+        // Go on to the next test.
+        ContinueOnChipMainThread(CHIP_NO_ERROR);
+    }
+
+    chip::System::Clock::Timeout GetWaitDuration() const override
+    {
+        return chip::System::Clock::Seconds16(mTimeout.ValueOr(kTimeoutInSeconds));
+    }
+
+private:
+    std::atomic_uint16_t mTestIndex;
+    const uint16_t mTestCount = 2;
+
+    chip::Optional<chip::NodeId> mNodeId;
+    chip::Optional<chip::CharSpan> mCluster;
+    chip::Optional<chip::EndpointId> mEndpoint;
+    chip::Optional<uint16_t> mTimeout;
+
+    CHIP_ERROR TestWaitForTheCommissionedDeviceToBeRetrieved_0()
+    {
+
+        chip::app::Clusters::DelayCommands::Commands::WaitForCommissionee::Type value;
+        value.nodeId = mNodeId.HasValue() ? mNodeId.Value() : 305414945ULL;
+        return WaitForCommissionee("alpha", value);
+    }
+
+    CHIP_ERROR TestReadReplacementProductList_1()
+    {
+
+        MTRBaseDevice * device = GetDevice("alpha");
+        __auto_type * cluster = [[MTRBaseClusterActivatedCarbonFilterMonitoring alloc] initWithDevice:device
+                                                                                           endpointID:@(1)
+                                                                                                queue:mCallbackQueue];
+        VerifyOrReturnError(cluster != nil, CHIP_ERROR_INCORRECT_STATE);
+
+        [cluster readAttributeReplacementProductListWithCompletion:^(NSArray * _Nullable value, NSError * _Nullable err) {
+            NSLog(@"Read Replacement Product List Error: %@", err);
+
+            VerifyOrReturn(CheckValue("status", err ? err.code : 0, 0));
+
+            {
+                id actualValue = value;
+                VerifyOrReturn(CheckValue("ReplacementProductList", [actualValue count], static_cast<uint32_t>(5)));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[0]).productIdentifierType,
+                    0U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[0]).productIdentifierValue,
+                    @"111112222233"));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[1]).productIdentifierType,
+                    1U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[1]).productIdentifierValue,
+                    @"gtin8xxx"));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[2]).productIdentifierType,
+                    2U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[2]).productIdentifierValue,
+                    @"4444455555666"));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[3]).productIdentifierType,
+                    3U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[3]).productIdentifierValue,
+                    @"gtin14xxxxxxxx"));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[4]).productIdentifierType,
+                    4U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRActivatedCarbonFilterMonitoringClusterReplacementProductStruct *) actualValue[4]).productIdentifierValue,
+                    @"oem20xxxxxxxxxxxxxxx"));
+            }
+
+            NextTest();
+        }];
+
+        return CHIP_NO_ERROR;
+    }
+};
+
+class TestHepaFilterMonitoring : public TestCommandBridge {
+public:
+    // NOLINTBEGIN(clang-analyzer-nullability.NullPassedToNonnull): Test constructor nullability not enforced
+    TestHepaFilterMonitoring()
+        : TestCommandBridge("TestHepaFilterMonitoring")
+        , mTestIndex(0)
+    {
+        AddArgument("nodeId", 0, UINT64_MAX, &mNodeId);
+        AddArgument("cluster", &mCluster);
+        AddArgument("endpoint", 0, UINT16_MAX, &mEndpoint);
+        AddArgument("timeout", 0, UINT16_MAX, &mTimeout);
+    }
+    // NOLINTEND(clang-analyzer-nullability.NullPassedToNonnull)
+
+    ~TestHepaFilterMonitoring() {}
+
+    /////////// TestCommand Interface /////////
+    void NextTest() override
+    {
+        CHIP_ERROR err = CHIP_NO_ERROR;
+
+        if (0 == mTestIndex) {
+            ChipLogProgress(chipTool, " **** Test Start: TestHepaFilterMonitoring\n");
+        }
+
+        if (mTestCount == mTestIndex) {
+            ChipLogProgress(chipTool, " **** Test Complete: TestHepaFilterMonitoring\n");
+            SetCommandExitStatus(CHIP_NO_ERROR);
+            return;
+        }
+
+        Wait();
+
+        // Ensure we increment mTestIndex before we start running the relevant
+        // command.  That way if we lose the timeslice after we send the message
+        // but before our function call returns, we won't end up with an
+        // incorrect mTestIndex value observed when we get the response.
+        switch (mTestIndex++) {
+        case 0:
+            ChipLogProgress(chipTool, " ***** Test Step 0 : Wait for the commissioned device to be retrieved\n");
+            err = TestWaitForTheCommissionedDeviceToBeRetrieved_0();
+            break;
+        case 1:
+            ChipLogProgress(chipTool, " ***** Test Step 1 : Read Replacement Product List\n");
+            err = TestReadReplacementProductList_1();
+            break;
+        }
+
+        if (CHIP_NO_ERROR != err) {
+            ChipLogError(chipTool, " ***** Test Failure: %s\n", chip::ErrorStr(err));
+            SetCommandExitStatus(err);
+        }
+    }
+
+    void OnStatusUpdate(const chip::app::StatusIB & status) override
+    {
+        switch (mTestIndex - 1) {
+        case 0:
+            VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0));
+            break;
+        case 1:
+            VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), 0));
+            break;
+        }
+
+        // Go on to the next test.
+        ContinueOnChipMainThread(CHIP_NO_ERROR);
+    }
+
+    chip::System::Clock::Timeout GetWaitDuration() const override
+    {
+        return chip::System::Clock::Seconds16(mTimeout.ValueOr(kTimeoutInSeconds));
+    }
+
+private:
+    std::atomic_uint16_t mTestIndex;
+    const uint16_t mTestCount = 2;
+
+    chip::Optional<chip::NodeId> mNodeId;
+    chip::Optional<chip::CharSpan> mCluster;
+    chip::Optional<chip::EndpointId> mEndpoint;
+    chip::Optional<uint16_t> mTimeout;
+
+    CHIP_ERROR TestWaitForTheCommissionedDeviceToBeRetrieved_0()
+    {
+
+        chip::app::Clusters::DelayCommands::Commands::WaitForCommissionee::Type value;
+        value.nodeId = mNodeId.HasValue() ? mNodeId.Value() : 305414945ULL;
+        return WaitForCommissionee("alpha", value);
+    }
+
+    CHIP_ERROR TestReadReplacementProductList_1()
+    {
+
+        MTRBaseDevice * device = GetDevice("alpha");
+        __auto_type * cluster = [[MTRBaseClusterHEPAFilterMonitoring alloc] initWithDevice:device
+                                                                                endpointID:@(1)
+                                                                                     queue:mCallbackQueue];
+        VerifyOrReturnError(cluster != nil, CHIP_ERROR_INCORRECT_STATE);
+
+        [cluster readAttributeReplacementProductListWithCompletion:^(NSArray * _Nullable value, NSError * _Nullable err) {
+            NSLog(@"Read Replacement Product List Error: %@", err);
+
+            VerifyOrReturn(CheckValue("status", err ? err.code : 0, 0));
+
+            {
+                id actualValue = value;
+                VerifyOrReturn(CheckValue("ReplacementProductList", [actualValue count], static_cast<uint32_t>(5)));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[0]).productIdentifierType, 0U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[0]).productIdentifierValue,
+                    @"111112222233"));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[1]).productIdentifierType, 1U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[1]).productIdentifierValue,
+                    @"gtin8xxx"));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[2]).productIdentifierType, 2U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[2]).productIdentifierValue,
+                    @"4444455555666"));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[3]).productIdentifierType, 3U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[3]).productIdentifierValue,
+                    @"gtin14xxxxxxxx"));
+                VerifyOrReturn(CheckValue("ProductIdentifierType",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[4]).productIdentifierType, 4U));
+                VerifyOrReturn(CheckValueAsString("ProductIdentifierValue",
+                    ((MTRHEPAFilterMonitoringClusterReplacementProductStruct *) actualValue[4]).productIdentifierValue,
+                    @"oem20xxxxxxxxxxxxxxx"));
+            }
+
+            NextTest();
+        }];
+
+        return CHIP_NO_ERROR;
+    }
+};
+
 class Test_TC_ACFREMON_1_1 : public TestCommandBridge {
 public:
     // NOLINTBEGIN(clang-analyzer-nullability.NullPassedToNonnull): Test constructor nullability not enforced
@@ -174471,6 +174762,8 @@
         make_unique<TestGroupsCluster>(),
         make_unique<TestGroupKeyManagementCluster>(),
         make_unique<Test_TC_G_1_1>(),
+        make_unique<TestActivatedCarbonFilterMonitoring>(),
+        make_unique<TestHepaFilterMonitoring>(),
         make_unique<Test_TC_ACFREMON_1_1>(),
         make_unique<Test_TC_ACFREMON_2_1>(),
         make_unique<Test_TC_HEPAFREMON_1_1>(),