[Fabric-Sync] Port sync-device command from fabric-admin (#36507)

diff --git a/examples/fabric-sync/admin/DeviceManager.cpp b/examples/fabric-sync/admin/DeviceManager.cpp
index 6dfdad1..69aa0a5 100644
--- a/examples/fabric-sync/admin/DeviceManager.cpp
+++ b/examples/fabric-sync/admin/DeviceManager.cpp
@@ -20,6 +20,7 @@
 #include <LinuxCommissionableDataProvider.h>
 #include <app/server/CommissioningWindowManager.h>
 #include <app/server/Server.h>
+#include <bridge/include/FabricBridge.h>
 #include <crypto/RandUtils.h>
 #include <lib/support/StringBuilder.h>
 
@@ -33,6 +34,9 @@
 namespace {
 
 constexpr EndpointId kAggregatorEndpointId = 1;
+constexpr uint16_t kWindowTimeout          = 300;
+constexpr uint16_t kIteration              = 1000;
+constexpr uint16_t kMaxDiscriminatorLength = 4095;
 
 } // namespace
 
@@ -75,6 +79,13 @@
     }
 }
 
+void DeviceManager::AddSyncedDevice(const SyncedDevice & device)
+{
+    mSyncedDevices.insert(device);
+    ChipLogProgress(NotSpecified, "Added synced device: NodeId:" ChipLogFormatX64 ", EndpointId %u",
+                    ChipLogValueX64(device.GetNodeId()), device.GetEndpointId());
+}
+
 SyncedDevice * DeviceManager::FindDeviceByEndpoint(EndpointId endpointId)
 {
     for (auto & device : mSyncedDevices)
@@ -99,6 +110,27 @@
     return nullptr;
 }
 
+void DeviceManager::RemoveSyncedDevice(chip::ScopedNodeId scopedNodeId)
+{
+    NodeId nodeId = scopedNodeId.GetNodeId();
+
+    if (bridge::FabricBridge::Instance().RemoveSynchronizedDevice(scopedNodeId) != CHIP_NO_ERROR)
+    {
+        ChipLogError(NotSpecified, "Failed to remove Node ID:" ChipLogFormatX64, ChipLogValueX64(nodeId));
+    }
+
+    SyncedDevice * device = FindDeviceByNode(nodeId);
+    if (device == nullptr)
+    {
+        ChipLogProgress(NotSpecified, "No device found with NodeId:" ChipLogFormatX64, ChipLogValueX64(nodeId));
+        return;
+    }
+
+    mSyncedDevices.erase(*device);
+    ChipLogProgress(NotSpecified, "Removed synced device: NodeId:" ChipLogFormatX64 ", EndpointId %u",
+                    ChipLogValueX64(device->GetNodeId()), device->GetEndpointId());
+}
+
 void DeviceManager::OpenLocalBridgeCommissioningWindow(uint32_t iterations, uint16_t commissioningTimeoutSec,
                                                        uint16_t discriminator, const ByteSpan & salt, const ByteSpan & verifier)
 {
@@ -132,6 +164,46 @@
     }
 }
 
+void DeviceManager::OpenDeviceCommissioningWindow(ScopedNodeId scopedNodeId, uint32_t iterations, uint16_t commissioningTimeoutSec,
+                                                  uint16_t discriminator, const ByteSpan & salt, const ByteSpan & verifier)
+{
+    // PairingManager isn't currently capable of OpenCommissioningWindow on a device of a fabric that it doesn't have
+    // the controller for. Currently no implementation need this functionality, but should they need it they will hit
+    // the verify or die below and it will be the responsiblity of whoever requires that functionality to implement.
+    VerifyOrDie(PairingManager::Instance().CurrentCommissioner().GetFabricIndex() == scopedNodeId.GetFabricIndex());
+    ChipLogProgress(NotSpecified, "Opening commissioning window for Node ID: " ChipLogFormatX64,
+                    ChipLogValueX64(scopedNodeId.GetNodeId()));
+
+    // Open the commissioning window of a device within its own fabric.
+    CHIP_ERROR err = PairingManager::Instance().OpenCommissioningWindow(
+        scopedNodeId.GetNodeId(), kRootEndpointId, commissioningTimeoutSec, iterations, discriminator, salt, verifier);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(NotSpecified, "Failed to open commissioning window: %s", ErrorStr(err));
+    }
+}
+
+void DeviceManager::OpenRemoteDeviceCommissioningWindow(EndpointId remoteEndpointId)
+{
+    // Open the commissioning window of a device from another fabric via its fabric bridge.
+    // This method constructs and sends a command to open the commissioning window for a device
+    // that is part of a different fabric, accessed through a fabric bridge.
+
+    // Use random discriminator to have less chance of collision.
+    uint16_t discriminator =
+        Crypto::GetRandU16() % (kMaxDiscriminatorLength + 1); // Include the upper limit kMaxDiscriminatorLength
+
+    ByteSpan emptySalt;
+    ByteSpan emptyVerifier;
+
+    CHIP_ERROR err = PairingManager::Instance().OpenCommissioningWindow(mRemoteBridgeNodeId, remoteEndpointId, kWindowTimeout,
+                                                                        kIteration, discriminator, emptySalt, emptyVerifier);
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(NotSpecified, "Failed to open commissioning window: %s", ErrorStr(err));
+    }
+}
+
 CHIP_ERROR DeviceManager::PairRemoteFabricBridge(NodeId nodeId, uint32_t setupPINCode, const char * deviceRemoteIp,
                                                  uint16_t deviceRemotePort)
 {
diff --git a/examples/fabric-sync/admin/DeviceManager.h b/examples/fabric-sync/admin/DeviceManager.h
index e114a62..9afb1ee 100644
--- a/examples/fabric-sync/admin/DeviceManager.h
+++ b/examples/fabric-sync/admin/DeviceManager.h
@@ -72,6 +72,10 @@
 
     bool IsFabricSyncReady() const { return mRemoteBridgeNodeId != chip::kUndefinedNodeId; }
 
+    void AddSyncedDevice(const SyncedDevice & device);
+
+    void RemoveSyncedDevice(chip::ScopedNodeId scopedNodeId);
+
     /**
      * @brief Determines whether a given nodeId corresponds to the remote bridge device.
      *
@@ -98,6 +102,40 @@
                                             const chip::ByteSpan & salt, const chip::ByteSpan & verifier);
 
     /**
+     * @brief Open the commissioning window for a specific device within its own fabric.
+     *
+     * This function initiates the process to open the commissioning window for a device identified by the given node ID.
+     *
+     * @param scopedNodeId         The scoped node ID of the device that should open the commissioning window.
+     * @param iterations           The number of PBKDF (Password-Based Key Derivation Function) iterations to use
+     *                             for deriving the PAKE (Password Authenticated Key Exchange) verifier.
+     * @param commissioningTimeoutSec The time in seconds before the commissioning window closes. This value determines
+     *                             how long the commissioning window remains open for incoming connections.
+     * @param discriminator        The device-specific discriminator, determined during commissioning, which helps
+     *                             to uniquely identify the device among others.
+     * @param salt                 The salt used in the cryptographic operations for commissioning.
+     * @param verifier             The PAKE verifier used to authenticate the commissioning process.
+     *
+     */
+    void OpenDeviceCommissioningWindow(chip::ScopedNodeId scopedNodeId, uint32_t iterations, uint16_t commissioningTimeoutSec,
+                                       uint16_t discriminator, const chip::ByteSpan & salt, const chip::ByteSpan & verifier);
+
+    /**
+     * @brief Open the commissioning window of a device from another fabric via its fabric bridge.
+     *
+     * This function initiates the process to open the commissioning window for a device that belongs to another
+     * fabric, accessed through a fabric bridge.
+     *
+     * @param remoteEndpointId The endpoint ID of the remote device that should open the commissioning window.
+     *                         This endpoint is associated with the device in the other fabric, accessed via the
+     *                         fabric bridge.
+     *
+     * @note This function is used when the device to be commissioned is part of a different fabric and must be
+     *       accessed through an intermediary fabric bridge.
+     */
+    void OpenRemoteDeviceCommissioningWindow(chip::EndpointId remoteEndpointId);
+
+    /**
      * @brief Pair a remote fabric bridge with a given node ID.
      *
      * This function initiates the pairing process for a remote fabric bridge using the specified parameters.
diff --git a/examples/fabric-sync/admin/FabricAdmin.cpp b/examples/fabric-sync/admin/FabricAdmin.cpp
index 65b5a5a..4e170ce 100644
--- a/examples/fabric-sync/admin/FabricAdmin.cpp
+++ b/examples/fabric-sync/admin/FabricAdmin.cpp
@@ -38,6 +38,27 @@
     return sInstance;
 }
 
+CHIP_ERROR FabricAdmin::OpenCommissioningWindow(Controller::CommissioningWindowVerifierParams params, FabricIndex fabricIndex)
+{
+    ScopedNodeId scopedNodeId(params.GetNodeId(), fabricIndex);
+    uint32_t iterations              = params.GetIteration();
+    uint16_t discriminator           = params.GetDiscriminator();
+    uint16_t commissioningTimeoutSec = static_cast<uint16_t>(params.GetTimeout().count());
+
+    // Log request details for debugging purposes
+    ChipLogProgress(NotSpecified,
+                    "Received OpenCommissioningWindow request: NodeId " ChipLogFormatX64
+                    ", Timeout: %u, Iterations: %u, Discriminator: %u",
+                    ChipLogValueX64(scopedNodeId.GetNodeId()), commissioningTimeoutSec, iterations, discriminator);
+
+    // Open the device commissioning window with provided salt and verifier data
+    DeviceManager::Instance().OpenDeviceCommissioningWindow(scopedNodeId, iterations, commissioningTimeoutSec, discriminator,
+                                                            ByteSpan(params.GetSalt().data(), params.GetSalt().size()),
+                                                            ByteSpan(params.GetVerifier().data(), params.GetVerifier().size()));
+
+    return CHIP_NO_ERROR;
+}
+
 CHIP_ERROR
 FabricAdmin::CommissionRemoteBridge(Controller::CommissioningWindowPasscodeParams params, VendorId vendorId, uint16_t productId)
 {
diff --git a/examples/fabric-sync/admin/FabricAdmin.h b/examples/fabric-sync/admin/FabricAdmin.h
index 1219e59..bec98dc 100644
--- a/examples/fabric-sync/admin/FabricAdmin.h
+++ b/examples/fabric-sync/admin/FabricAdmin.h
@@ -42,6 +42,9 @@
 public:
     static FabricAdmin & Instance();
 
+    CHIP_ERROR OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams params,
+                                       chip::FabricIndex fabricIndex) override;
+
     CHIP_ERROR
     CommissionRemoteBridge(chip::Controller::CommissioningWindowPasscodeParams params, chip::VendorId vendorId,
                            uint16_t productId) override;
diff --git a/examples/fabric-sync/bridge/include/FabricAdminDelegate.h b/examples/fabric-sync/bridge/include/FabricAdminDelegate.h
index 8b67ffd..1637c52 100644
--- a/examples/fabric-sync/bridge/include/FabricAdminDelegate.h
+++ b/examples/fabric-sync/bridge/include/FabricAdminDelegate.h
@@ -31,6 +31,18 @@
     virtual ~FabricAdminDelegate() = default;
 
     /**
+     * Opens a commissioning window for a specified node using pre-computed PAKE passcode verifier.
+     *
+     * @param params    Params for opening the commissioning window using verifier.
+     * @return CHIP_ERROR An error code indicating the success or failure of the operation.
+     * - CHIP_NO_ERROR: The RPC command was successfully sent.
+     * - CHIP_ERROR_BUSY: Another commissioning window is currently in progress.
+     * - CHIP_ERROR_INTERNAL: An internal error occurred.
+     */
+    virtual CHIP_ERROR OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams params,
+                                               chip::FabricIndex fabricIndex) = 0;
+
+    /**
      * Reverse commission a bridge using the specified parameters.
      *
      * This function initiates the commissioning process for a bridge node, utilizing
diff --git a/examples/fabric-sync/shell/BUILD.gn b/examples/fabric-sync/shell/BUILD.gn
index 436f8b5..3aa5612 100644
--- a/examples/fabric-sync/shell/BUILD.gn
+++ b/examples/fabric-sync/shell/BUILD.gn
@@ -39,6 +39,8 @@
     "RemoveDeviceCommand.h",
     "ShellCommands.cpp",
     "ShellCommands.h",
+    "SyncDeviceCommand.cpp",
+    "SyncDeviceCommand.h",
   ]
 
   deps = [
diff --git a/examples/fabric-sync/shell/ShellCommands.cpp b/examples/fabric-sync/shell/ShellCommands.cpp
index 3bab442..4923823 100644
--- a/examples/fabric-sync/shell/ShellCommands.cpp
+++ b/examples/fabric-sync/shell/ShellCommands.cpp
@@ -19,6 +19,7 @@
 #include "AddDeviceCommand.h"
 #include "RemoveBridgeCommand.h"
 #include "RemoveDeviceCommand.h"
+#include "SyncDeviceCommand.h"
 
 #include <admin/DeviceManager.h>
 #include <inttypes.h>
@@ -129,11 +130,11 @@
 
     // Parse arguments
     chip::NodeId nodeId     = static_cast<chip::NodeId>(strtoull(argv[1], nullptr, 10));
-    uint32_t setupPINCode   = static_cast<uint32_t>(strtoul(argv[2], nullptr, 10));
+    uint32_t payload        = static_cast<uint32_t>(strtoul(argv[2], nullptr, 10));
     const char * remoteAddr = argv[3];
     uint16_t remotePort     = static_cast<uint16_t>(strtoul(argv[4], nullptr, 10));
 
-    auto command = std::make_unique<commands::AddDeviceCommand>(nodeId, setupPINCode, remoteAddr, remotePort);
+    auto command = std::make_unique<commands::AddDeviceCommand>(nodeId, payload, remoteAddr, remotePort);
 
     CHIP_ERROR result = command->RunCommand();
     if (result == CHIP_NO_ERROR)
@@ -173,6 +174,35 @@
     return result;
 }
 
+static CHIP_ERROR HandleSyncDeviceCommand(int argc, char ** argv)
+{
+    if (argc != 2)
+    {
+        fprintf(stderr, "Invalid arguments. Usage: app sync-device\n");
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+
+    // Check if there is already an active command
+    if (commands::CommandRegistry::Instance().IsCommandActive())
+    {
+        fprintf(stderr, "Another command is currently active. Please wait until it completes.\n");
+        return CHIP_ERROR_BUSY;
+    }
+
+    // Parse arguments
+    chip::EndpointId endpointId = static_cast<chip::EndpointId>(strtoul(argv[1], nullptr, 10));
+
+    auto command = std::make_unique<commands::SyncDeviceCommand>(endpointId);
+
+    CHIP_ERROR result = command->RunCommand();
+    if (result == CHIP_NO_ERROR)
+    {
+        commands::CommandRegistry::Instance().SetActiveCommand(std::move(command));
+    }
+
+    return result;
+}
+
 static CHIP_ERROR AppPlatformHandler(int argc, char ** argv)
 {
     CHIP_ERROR error = CHIP_NO_ERROR;
@@ -201,6 +231,10 @@
     {
         return HandleRemoveDeviceCommand(argc, argv);
     }
+    else if (strcmp(argv[0], "sync-device") == 0)
+    {
+        return HandleSyncDeviceCommand(argc, argv);
+    }
     else
     {
         return CHIP_ERROR_INVALID_ARGUMENT;
diff --git a/examples/fabric-sync/shell/SyncDeviceCommand.cpp b/examples/fabric-sync/shell/SyncDeviceCommand.cpp
new file mode 100644
index 0000000..871c8d6
--- /dev/null
+++ b/examples/fabric-sync/shell/SyncDeviceCommand.cpp
@@ -0,0 +1,112 @@
+/*
+ *   Copyright (c) 2024 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 "SyncDeviceCommand.h"
+
+#include <admin/DeviceManager.h>
+#include <lib/shell/streamer.h>
+#include <setup_payload/ManualSetupPayloadGenerator.h>
+
+using namespace ::chip;
+
+namespace {
+
+// Constants
+constexpr uint32_t kCommissionPrepareTimeMs = 500;
+
+} // namespace
+
+namespace commands {
+
+SyncDeviceCommand::SyncDeviceCommand(EndpointId remoteEndpointId) : mRemoteEndpointId(remoteEndpointId) {}
+
+void SyncDeviceCommand::OnCommissioningWindowOpened(NodeId deviceId, CHIP_ERROR err, SetupPayload payload)
+{
+    ChipLogProgress(NotSpecified, "FabricSyncDeviceCommand::OnCommissioningWindowOpened");
+
+    if (err == CHIP_NO_ERROR)
+    {
+        char payloadBuffer[admin::kMaxManualCodeLength + 1];
+        MutableCharSpan manualCode(payloadBuffer);
+        CHIP_ERROR error = ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(manualCode);
+        if (error == CHIP_NO_ERROR)
+        {
+            NodeId nodeId = admin::DeviceManager::Instance().GetNextAvailableNodeId();
+
+            admin::PairingManager::Instance().SetPairingDelegate(this);
+            mAssignedNodeId = nodeId;
+
+            usleep(kCommissionPrepareTimeMs * 1000);
+
+            admin::DeviceManager::Instance().PairRemoteDevice(nodeId, payloadBuffer);
+        }
+        else
+        {
+            ChipLogError(NotSpecified, "Unable to generate manual code for setup payload: %" CHIP_ERROR_FORMAT, error.Format());
+        }
+    }
+    else
+    {
+        ChipLogError(NotSpecified,
+                     "Failed to open synced device (0x:" ChipLogFormatX64 ") commissioning window: %" CHIP_ERROR_FORMAT,
+                     ChipLogValueX64(deviceId), err.Format());
+    }
+}
+
+void SyncDeviceCommand::OnCommissioningComplete(NodeId deviceId, CHIP_ERROR err)
+{
+    if (mAssignedNodeId != deviceId)
+    {
+        // Ignore if the deviceId does not match the mAssignedNodeId.
+        // This scenario should not occur because no other device should be commissioned during the fabric sync process.
+        return;
+    }
+
+    if (err == CHIP_NO_ERROR)
+    {
+        admin::DeviceManager::Instance().AddSyncedDevice(admin::SyncedDevice(mAssignedNodeId, mRemoteEndpointId));
+    }
+    else
+    {
+        ChipLogError(NotSpecified, "Failed to pair synced device (0x:" ChipLogFormatX64 ") with error: %" CHIP_ERROR_FORMAT,
+                     ChipLogValueX64(deviceId), err.Format());
+    }
+
+    CommandRegistry::Instance().ResetActiveCommand();
+}
+
+CHIP_ERROR SyncDeviceCommand::RunCommand()
+{
+    if (!admin::DeviceManager::Instance().IsFabricSyncReady())
+    {
+        // print to console
+        Shell::streamer_t * sout = Shell::streamer_get();
+        Shell::streamer_printf(sout, "Remote Fabric Bridge has already been configured.\n");
+
+        return CHIP_ERROR_INCORRECT_STATE;
+    }
+
+    ChipLogProgress(NotSpecified, "Running SyncDeviceCommand with EndpointId: %u", mRemoteEndpointId);
+
+    admin::PairingManager::Instance().SetOpenCommissioningWindowDelegate(this);
+    admin::DeviceManager::Instance().OpenRemoteDeviceCommissioningWindow(mRemoteEndpointId);
+
+    return CHIP_NO_ERROR;
+}
+
+} // namespace commands
diff --git a/examples/fabric-sync/shell/SyncDeviceCommand.h b/examples/fabric-sync/shell/SyncDeviceCommand.h
new file mode 100644
index 0000000..270f57d
--- /dev/null
+++ b/examples/fabric-sync/shell/SyncDeviceCommand.h
@@ -0,0 +1,39 @@
+/*
+ *   Copyright (c) 2024 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 <CommandRegistry.h>
+#include <admin/PairingManager.h>
+
+namespace commands {
+
+class SyncDeviceCommand : public Command, public admin::CommissioningWindowDelegate, public admin::PairingDelegate
+{
+public:
+    SyncDeviceCommand(chip::EndpointId remoteEndpointId);
+    void OnCommissioningWindowOpened(chip::NodeId deviceId, CHIP_ERROR status, chip::SetupPayload payload) override;
+    void OnCommissioningComplete(chip::NodeId deviceId, CHIP_ERROR err) override;
+    CHIP_ERROR RunCommand() override;
+
+private:
+    chip::EndpointId mRemoteEndpointId = chip::kInvalidEndpointId;
+    chip::NodeId mAssignedNodeId       = chip::kUndefinedNodeId;
+};
+
+} // namespace commands