[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