Update network-commissioning to use `AddResponse` for responding to scan responses  (#33687)

* Add support for thread encoding to tlv

* Preserve awkward sort inside a loop

* Add a TODO comment ... insertion sort in a loop is awkward

* Make use of the new class

* Add ifdefs

* Fix out parameter to be a reference

* Restyle

* Comment typo fix and add one more comment

* Remove comment: we encode right away, so no lifetime issue

* Update src/app/clusters/network-commissioning/network-commissioning.cpp

Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>

* Update src/app/clusters/network-commissioning/network-commissioning.cpp

Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>

* Fix ifdef

* Comment update

---------

Co-authored-by: Andrei Litvin <andreilitvin@google.com>
Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/app/clusters/network-commissioning/network-commissioning.cpp b/src/app/clusters/network-commissioning/network-commissioning.cpp
index daa8435..f2e7403 100644
--- a/src/app/clusters/network-commissioning/network-commissioning.cpp
+++ b/src/app/clusters/network-commissioning/network-commissioning.cpp
@@ -115,6 +115,225 @@
     return features;
 }
 
+/// Performs an auto-release of the given item, generally an `Iterator` type
+/// like Wifi or Thread scan results.
+template <typename T>
+class AutoRelease
+{
+public:
+    AutoRelease(T * iterator) : mValue(iterator) {}
+    ~AutoRelease()
+    {
+        if (mValue != nullptr)
+        {
+            mValue->Release();
+        }
+    }
+
+private:
+    T * mValue;
+};
+
+/// Convenience macro to auto-create a variable for you to release the given name at
+/// the exit of the current scope.
+#define DEFER_AUTO_RELEASE(name) AutoRelease autoRelease##__COUNTER__(name)
+
+#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION || CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP
+
+/// Handles encoding a WifiScanResponseIterator into a TLV response structure
+class WifiScanResponseToTLV : public chip::app::DataModel::EncodableToTLV
+{
+public:
+    WifiScanResponseToTLV(Status status, CharSpan debugText, WiFiScanResponseIterator * networks) :
+        mStatus(status), mDebugText(debugText), mNetworks(networks)
+    {}
+
+    CHIP_ERROR EncodeTo(TLV::TLVWriter & writer, TLV::Tag tag) const override;
+
+private:
+    Status mStatus;
+    CharSpan mDebugText;
+    WiFiScanResponseIterator * mNetworks;
+};
+
+CHIP_ERROR WifiScanResponseToTLV::EncodeTo(TLV::TLVWriter & writer, TLV::Tag tag) const
+{
+    TLV::TLVType outerType;
+    ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outerType));
+
+    ReturnErrorOnFailure(writer.Put(TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kNetworkingStatus), mStatus));
+    if (mDebugText.size() != 0)
+    {
+        ReturnErrorOnFailure(
+            DataModel::Encode(writer, TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kDebugText), mDebugText));
+    }
+
+    {
+        TLV::TLVType listContainerType;
+        ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kWiFiScanResults),
+                                                   TLV::kTLVType_Array, listContainerType));
+
+        if ((mStatus == Status::kSuccess) && (mNetworks != nullptr))
+        {
+            WiFiScanResponse scanResponse;
+            size_t networksEncoded = 0;
+            while (mNetworks->Next(scanResponse))
+            {
+                Structs::WiFiInterfaceScanResultStruct::Type result;
+                result.security = scanResponse.security;
+                result.ssid     = ByteSpan(scanResponse.ssid, scanResponse.ssidLen);
+                result.bssid    = ByteSpan(scanResponse.bssid, sizeof(scanResponse.bssid));
+                result.channel  = scanResponse.channel;
+                result.wiFiBand = scanResponse.wiFiBand;
+                result.rssi     = scanResponse.rssi;
+                ReturnErrorOnFailure(DataModel::Encode(writer, TLV::AnonymousTag(), result));
+
+                ++networksEncoded;
+                if (networksEncoded >= kMaxNetworksInScanResponse)
+                {
+                    break;
+                }
+            }
+        }
+
+        ReturnErrorOnFailure(writer.EndContainer(listContainerType));
+    }
+
+    return writer.EndContainer(outerType);
+}
+
+#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION || CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP
+
+#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
+
+/// Handles encoding a ThreadScanResponseIterator into a TLV response structure.
+class ThreadScanResponseToTLV : public chip::app::DataModel::EncodableToTLV
+{
+public:
+    ThreadScanResponseToTLV(Status status, CharSpan debugText, ThreadScanResponseIterator * networks) :
+        mStatus(status), mDebugText(debugText), mNetworks(networks)
+    {}
+
+    CHIP_ERROR EncodeTo(TLV::TLVWriter & writer, TLV::Tag tag) const override;
+
+private:
+    Status mStatus;
+    CharSpan mDebugText;
+    ThreadScanResponseIterator * mNetworks;
+
+    /// Fills up scanResponseArray with valid and de-duplicated thread responses from mNetworks.
+    /// Handles sorting and keeping only larger rssi
+    ///
+    /// Returns the valid list of scan responses into `validResponses`, which is only valid
+    /// as long as scanResponseArray is valid.
+    CHIP_ERROR LoadResponses(Platform::ScopedMemoryBuffer<ThreadScanResponse> & scanResponseArray,
+                             Span<ThreadScanResponse> & validResponses) const;
+};
+
+CHIP_ERROR ThreadScanResponseToTLV::LoadResponses(Platform::ScopedMemoryBuffer<ThreadScanResponse> & scanResponseArray,
+                                                  Span<ThreadScanResponse> & validResponses) const
+{
+    VerifyOrReturnError(scanResponseArray.Alloc(chip::min(mNetworks->Count(), kMaxNetworksInScanResponse)), CHIP_ERROR_NO_MEMORY);
+
+    ThreadScanResponse scanResponse;
+    size_t scanResponseArrayLength = 0;
+    for (; mNetworks != nullptr && mNetworks->Next(scanResponse);)
+    {
+        if ((scanResponseArrayLength == kMaxNetworksInScanResponse) &&
+            (scanResponseArray[scanResponseArrayLength - 1].rssi > scanResponse.rssi))
+        {
+            continue;
+        }
+
+        bool isDuplicated = false;
+
+        for (size_t i = 0; i < scanResponseArrayLength; i++)
+        {
+            if ((scanResponseArray[i].panId == scanResponse.panId) &&
+                (scanResponseArray[i].extendedPanId == scanResponse.extendedPanId))
+            {
+                if (scanResponseArray[i].rssi < scanResponse.rssi)
+                {
+                    scanResponseArray[i] = scanResponseArray[--scanResponseArrayLength];
+                }
+                else
+                {
+                    isDuplicated = true;
+                }
+                break;
+            }
+        }
+
+        if (isDuplicated)
+        {
+            continue;
+        }
+
+        if (scanResponseArrayLength < kMaxNetworksInScanResponse)
+        {
+            scanResponseArrayLength++;
+        }
+        scanResponseArray[scanResponseArrayLength - 1] = scanResponse;
+
+        // TODO: this is a sort (insertion sort even, so O(n^2)) in a O(n) loop.
+        ///      There should be some better alternatives to not have some O(n^3) processing complexity.
+        Sorting::InsertionSort(scanResponseArray.Get(), scanResponseArrayLength,
+                               [](const ThreadScanResponse & a, const ThreadScanResponse & b) -> bool { return a.rssi > b.rssi; });
+    }
+
+    validResponses = Span<ThreadScanResponse>(scanResponseArray.Get(), scanResponseArrayLength);
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ThreadScanResponseToTLV::EncodeTo(TLV::TLVWriter & writer, TLV::Tag tag) const
+{
+    Platform::ScopedMemoryBuffer<ThreadScanResponse> responseArray;
+    Span<ThreadScanResponse> responseSpan;
+
+    ReturnErrorOnFailure(LoadResponses(responseArray, responseSpan));
+
+    TLV::TLVType outerType;
+    ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outerType));
+
+    ReturnErrorOnFailure(writer.Put(TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kNetworkingStatus), mStatus));
+    if (mDebugText.size() != 0)
+    {
+        ReturnErrorOnFailure(
+            DataModel::Encode(writer, TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kDebugText), mDebugText));
+    }
+
+    {
+        TLV::TLVType listContainerType;
+        ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kThreadScanResults),
+                                                   TLV::kTLVType_Array, listContainerType));
+
+        for (const ThreadScanResponse & response : responseSpan)
+        {
+            Structs::ThreadInterfaceScanResultStruct::Type result;
+            uint8_t extendedAddressBuffer[Thread::kSizeExtendedPanId];
+
+            Encoding::BigEndian::Put64(extendedAddressBuffer, response.extendedAddress);
+            result.panId           = response.panId;
+            result.extendedPanId   = response.extendedPanId;
+            result.networkName     = CharSpan(response.networkName, response.networkNameLen);
+            result.channel         = response.channel;
+            result.version         = response.version;
+            result.extendedAddress = ByteSpan(extendedAddressBuffer);
+            result.rssi            = response.rssi;
+            result.lqi             = response.lqi;
+
+            ReturnErrorOnFailure(DataModel::Encode(writer, TLV::AnonymousTag(), result));
+        }
+
+        ReturnErrorOnFailure(writer.EndContainer(listContainerType));
+    }
+
+    return writer.EndContainer(outerType);
+}
+
+#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD
+
 } // namespace
 
 Instance::Instance(EndpointId aEndpointId, WiFiDriver * apDelegate) :
@@ -986,8 +1205,9 @@
 
 void Instance::OnFinished(Status status, CharSpan debugText, ThreadScanResponseIterator * networks)
 {
+    DEFER_AUTO_RELEASE(networks);
+
 #if CHIP_DEVICE_CONFIG_ENABLE_THREAD
-    CHIP_ERROR err        = CHIP_NO_ERROR;
     auto commandHandleRef = std::move(mAsyncCommandHandle);
     auto commandHandle    = commandHandleRef.Get();
     if (commandHandle == nullptr)
@@ -999,111 +1219,21 @@
 
     SetLastNetworkingStatusValue(MakeNullable(status));
 
-    TLV::TLVWriter * writer;
-    TLV::TLVType listContainerType;
-    ThreadScanResponse scanResponse;
-    Platform::ScopedMemoryBuffer<ThreadScanResponse> scanResponseArray;
-    size_t scanResponseArrayLength = 0;
-    uint8_t extendedAddressBuffer[Thread::kSizeExtendedPanId];
+    ThreadScanResponseToTLV responseBuilder(status, debugText, networks);
+    commandHandle->AddResponse(mPath, Commands::ScanNetworksResponse::Id, responseBuilder);
 
-    const CommandHandler::InvokeResponseParameters prepareParams(mPath);
-    SuccessOrExit(
-        err = commandHandle->PrepareInvokeResponseCommand(
-            ConcreteCommandPath(mPath.mEndpointId, NetworkCommissioning::Id, Commands::ScanNetworksResponse::Id), prepareParams));
-    VerifyOrExit((writer = commandHandle->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
-
-    SuccessOrExit(err = writer->Put(TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kNetworkingStatus), status));
-    if (debugText.size() != 0)
-    {
-        SuccessOrExit(
-            err = DataModel::Encode(*writer, TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kDebugText), debugText));
-    }
-    SuccessOrExit(err = writer->StartContainer(TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kThreadScanResults),
-                                               TLV::TLVType::kTLVType_Array, listContainerType));
-
-    // If no network was found, we encode an empty list, don't call a zero-sized alloc.
-    if ((status == Status::kSuccess) && (networks->Count() > 0))
-    {
-        VerifyOrExit(scanResponseArray.Alloc(chip::min(networks->Count(), kMaxNetworksInScanResponse)), err = CHIP_ERROR_NO_MEMORY);
-        for (; networks != nullptr && networks->Next(scanResponse);)
-        {
-            if ((scanResponseArrayLength == kMaxNetworksInScanResponse) &&
-                (scanResponseArray[scanResponseArrayLength - 1].rssi > scanResponse.rssi))
-            {
-                continue;
-            }
-
-            bool isDuplicated = false;
-
-            for (size_t i = 0; i < scanResponseArrayLength; i++)
-            {
-                if ((scanResponseArray[i].panId == scanResponse.panId) &&
-                    (scanResponseArray[i].extendedPanId == scanResponse.extendedPanId))
-                {
-                    if (scanResponseArray[i].rssi < scanResponse.rssi)
-                    {
-                        scanResponseArray[i] = scanResponseArray[--scanResponseArrayLength];
-                    }
-                    else
-                    {
-                        isDuplicated = true;
-                    }
-                    break;
-                }
-            }
-
-            if (isDuplicated)
-            {
-                continue;
-            }
-
-            if (scanResponseArrayLength < kMaxNetworksInScanResponse)
-            {
-                scanResponseArrayLength++;
-            }
-            scanResponseArray[scanResponseArrayLength - 1] = scanResponse;
-            Sorting::InsertionSort(
-                scanResponseArray.Get(), scanResponseArrayLength,
-                [](const ThreadScanResponse & a, const ThreadScanResponse & b) -> bool { return a.rssi > b.rssi; });
-        }
-
-        for (size_t i = 0; i < scanResponseArrayLength; i++)
-        {
-            Structs::ThreadInterfaceScanResultStruct::Type result;
-            Encoding::BigEndian::Put64(extendedAddressBuffer, scanResponseArray[i].extendedAddress);
-            result.panId           = scanResponseArray[i].panId;
-            result.extendedPanId   = scanResponseArray[i].extendedPanId;
-            result.networkName     = CharSpan(scanResponseArray[i].networkName, scanResponseArray[i].networkNameLen);
-            result.channel         = scanResponseArray[i].channel;
-            result.version         = scanResponseArray[i].version;
-            result.extendedAddress = ByteSpan(extendedAddressBuffer);
-            result.rssi            = scanResponseArray[i].rssi;
-            result.lqi             = scanResponseArray[i].lqi;
-
-            SuccessOrExit(err = DataModel::Encode(*writer, TLV::AnonymousTag(), result));
-        }
-    }
-
-    SuccessOrExit(err = writer->EndContainer(listContainerType));
-    SuccessOrExit(err = commandHandle->FinishCommand());
-
-exit:
-    if (err != CHIP_NO_ERROR)
-    {
-        ChipLogError(Zcl, "Failed to encode response: %" CHIP_ERROR_FORMAT, err.Format());
-    }
     if (status == Status::kSuccess)
     {
         CommitSavedBreadcrumb();
     }
-    networks->Release();
 #endif
 }
 
 void Instance::OnFinished(Status status, CharSpan debugText, WiFiScanResponseIterator * networks)
 {
+    DEFER_AUTO_RELEASE(networks);
+
 #if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION || CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP
-    CHIP_ERROR err        = CHIP_NO_ERROR;
     auto commandHandleRef = std::move(mAsyncCommandHandle);
     auto commandHandle    = commandHandleRef.Get();
     if (commandHandle == nullptr)
@@ -1122,63 +1252,13 @@
 
     SetLastNetworkingStatusValue(MakeNullable(status));
 
-    TLV::TLVWriter * writer;
-    TLV::TLVType listContainerType;
-    WiFiScanResponse scanResponse;
-    size_t networksEncoded = 0;
+    WifiScanResponseToTLV responseBuilder(status, debugText, networks);
+    commandHandle->AddResponse(mPath, Commands::ScanNetworksResponse::Id, responseBuilder);
 
-    const CommandHandler::InvokeResponseParameters prepareParams(mPath);
-    SuccessOrExit(
-        err = commandHandle->PrepareInvokeResponseCommand(
-            ConcreteCommandPath(mPath.mEndpointId, NetworkCommissioning::Id, Commands::ScanNetworksResponse::Id), prepareParams));
-    VerifyOrExit((writer = commandHandle->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
-
-    SuccessOrExit(err = writer->Put(TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kNetworkingStatus), status));
-    if (debugText.size() != 0)
-    {
-        SuccessOrExit(
-            err = DataModel::Encode(*writer, TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kDebugText), debugText));
-    }
-    SuccessOrExit(err = writer->StartContainer(TLV::ContextTag(Commands::ScanNetworksResponse::Fields::kWiFiScanResults),
-                                               TLV::TLVType::kTLVType_Array, listContainerType));
-
-    // Only encode results on success, to avoid stale contents on partial failure.
-    if ((status == Status::kSuccess) && (networks != nullptr))
-    {
-        while (networks->Next(scanResponse))
-        {
-            Structs::WiFiInterfaceScanResultStruct::Type result;
-            result.security = scanResponse.security;
-            result.ssid     = ByteSpan(scanResponse.ssid, scanResponse.ssidLen);
-            result.bssid    = ByteSpan(scanResponse.bssid, sizeof(scanResponse.bssid));
-            result.channel  = scanResponse.channel;
-            result.wiFiBand = scanResponse.wiFiBand;
-            result.rssi     = scanResponse.rssi;
-            SuccessOrExit(err = DataModel::Encode(*writer, TLV::AnonymousTag(), result));
-
-            ++networksEncoded;
-            if (networksEncoded >= kMaxNetworksInScanResponse)
-            {
-                break;
-            }
-        }
-    }
-
-    SuccessOrExit(err = writer->EndContainer(listContainerType));
-    SuccessOrExit(err = commandHandle->FinishCommand());
-exit:
-    if (err != CHIP_NO_ERROR)
-    {
-        ChipLogError(Zcl, "Failed to encode response: %" CHIP_ERROR_FORMAT, err.Format());
-    }
     if (status == Status::kSuccess)
     {
         CommitSavedBreadcrumb();
     }
-    if (networks != nullptr)
-    {
-        networks->Release();
-    }
 #endif
 }