From 57489d1a4df3c58f99c5ef1cd8ba6256ff31cab6 Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Thu, 14 Nov 2024 19:13:09 -0800 Subject: [PATCH] [Fabric-Sync] Port sync-device command from fabric-admin (#36507) --- examples/fabric-sync/admin/DeviceManager.cpp | 72 +++++++++++ examples/fabric-sync/admin/DeviceManager.h | 38 ++++++ examples/fabric-sync/admin/FabricAdmin.cpp | 21 ++++ examples/fabric-sync/admin/FabricAdmin.h | 3 + .../bridge/include/FabricAdminDelegate.h | 12 ++ examples/fabric-sync/shell/BUILD.gn | 2 + examples/fabric-sync/shell/ShellCommands.cpp | 38 +++++- .../fabric-sync/shell/SyncDeviceCommand.cpp | 112 ++++++++++++++++++ .../fabric-sync/shell/SyncDeviceCommand.h | 39 ++++++ 9 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 examples/fabric-sync/shell/SyncDeviceCommand.cpp create mode 100644 examples/fabric-sync/shell/SyncDeviceCommand.h diff --git a/examples/fabric-sync/admin/DeviceManager.cpp b/examples/fabric-sync/admin/DeviceManager.cpp index 6dfdad1213bc68..69aa0a583f7273 100644 --- a/examples/fabric-sync/admin/DeviceManager.cpp +++ b/examples/fabric-sync/admin/DeviceManager.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,9 @@ namespace admin { 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::SetRemoteBridgeNodeId(chip::NodeId nodeId) } } +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 @@ SyncedDevice * DeviceManager::FindDeviceByNode(NodeId nodeId) 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::OpenLocalBridgeCommissioningWindow(uint32_t iterations, uint } } +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 e114a62748549a..9afb1eec7d033f 100644 --- a/examples/fabric-sync/admin/DeviceManager.h +++ b/examples/fabric-sync/admin/DeviceManager.h @@ -72,6 +72,10 @@ class DeviceManager 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. * @@ -97,6 +101,40 @@ class DeviceManager void OpenLocalBridgeCommissioningWindow(uint32_t iterations, uint16_t commissioningTimeoutSec, uint16_t discriminator, 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. * diff --git a/examples/fabric-sync/admin/FabricAdmin.cpp b/examples/fabric-sync/admin/FabricAdmin.cpp index 65b5a5a8e8fc30..4e170ce78db043 100644 --- a/examples/fabric-sync/admin/FabricAdmin.cpp +++ b/examples/fabric-sync/admin/FabricAdmin.cpp @@ -38,6 +38,27 @@ FabricAdmin & FabricAdmin::Instance() 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(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 1219e594fa8c5f..bec98dc4140183 100644 --- a/examples/fabric-sync/admin/FabricAdmin.h +++ b/examples/fabric-sync/admin/FabricAdmin.h @@ -42,6 +42,9 @@ class FabricAdmin final : public bridge::FabricAdminDelegate 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 8b67ffd5b29ed7..1637c5294fb7a7 100644 --- a/examples/fabric-sync/bridge/include/FabricAdminDelegate.h +++ b/examples/fabric-sync/bridge/include/FabricAdminDelegate.h @@ -30,6 +30,18 @@ class FabricAdminDelegate public: 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. * diff --git a/examples/fabric-sync/shell/BUILD.gn b/examples/fabric-sync/shell/BUILD.gn index 436f8b5160e66c..3aa56122254a4a 100644 --- a/examples/fabric-sync/shell/BUILD.gn +++ b/examples/fabric-sync/shell/BUILD.gn @@ -39,6 +39,8 @@ source_set("shell") { "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 3bab442aa5fc47..4923823481635a 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 #include @@ -129,11 +130,11 @@ static CHIP_ERROR HandleAddDeviceCommand(int argc, char ** argv) // Parse arguments chip::NodeId nodeId = static_cast(strtoull(argv[1], nullptr, 10)); - uint32_t setupPINCode = static_cast(strtoul(argv[2], nullptr, 10)); + uint32_t payload = static_cast(strtoul(argv[2], nullptr, 10)); const char * remoteAddr = argv[3]; uint16_t remotePort = static_cast(strtoul(argv[4], nullptr, 10)); - auto command = std::make_unique(nodeId, setupPINCode, remoteAddr, remotePort); + auto command = std::make_unique(nodeId, payload, remoteAddr, remotePort); CHIP_ERROR result = command->RunCommand(); if (result == CHIP_NO_ERROR) @@ -173,6 +174,35 @@ static CHIP_ERROR HandleRemoveDeviceCommand(int argc, char ** argv) 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(strtoul(argv[1], nullptr, 10)); + + auto command = std::make_unique(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 @@ static CHIP_ERROR AppPlatformHandler(int argc, char ** argv) { 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 00000000000000..871c8d68feedb7 --- /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 +#include +#include + +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 00000000000000..270f57d8893f03 --- /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 +#include + +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