Skip to content

Commit

Permalink
[Fabric-Sync] Port commissioner control protocol implementation (#36427)
Browse files Browse the repository at this point in the history
* [Fabric-Sync] Port commissioner control protocol

* Add device synchronization

* Fix compile error

* Update examples/fabric-sync/admin/DeviceSubscription.cpp

Co-authored-by: Terence Hampson <[email protected]>

* Address review comments

* Add debug log

* Update examples/fabric-sync/admin/DeviceManager.cpp

Co-authored-by: Terence Hampson <[email protected]>

* Address review comments

* Add endpoint check

---------

Co-authored-by: Terence Hampson <[email protected]>
  • Loading branch information
yufengwangca and tehampson authored Nov 14, 2024
1 parent 39e83c9 commit dac4a53
Show file tree
Hide file tree
Showing 42 changed files with 3,624 additions and 17 deletions.
18 changes: 18 additions & 0 deletions examples/fabric-admin/device_manager/CommissionerControl.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
* 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 "CommissionerControl.h"
#include "DeviceManager.h"

Expand Down
28 changes: 28 additions & 0 deletions examples/fabric-sync/admin/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,44 @@
import("//build_overrides/chip.gni")
import("${chip_root}/src/app/chip_data_model.gni")

config("config") {
include_dirs = [
".",
"${chip_root}/examples/fabric-sync",
"${chip_root}/examples/platform/linux",
"${chip_root}/src/lib",
]
}

source_set("fabric-admin-lib") {
public_configs = [ ":config" ]

sources = [
"BridgeSubscription.cpp",
"BridgeSubscription.h",
"CommissionerControl.cpp",
"CommissionerControl.h",
"DeviceManager.cpp",
"DeviceManager.h",
"DeviceSubscription.cpp",
"DeviceSubscription.h",
"DeviceSubscriptionManager.cpp",
"DeviceSubscriptionManager.h",
"DeviceSynchronization.cpp",
"DeviceSynchronization.h",
"FabricAdmin.cpp",
"FabricAdmin.h",
"FabricSyncGetter.cpp",
"FabricSyncGetter.h",
"PairingManager.cpp",
"PairingManager.h",
"UniqueIdGetter.cpp",
"UniqueIdGetter.h",
]

deps = [
"${chip_root}/examples/fabric-sync/bridge:fabric-bridge-lib",
"${chip_root}/examples/platform/linux:app-main",
"${chip_root}/src/lib",
]
}
163 changes: 163 additions & 0 deletions examples/fabric-sync/admin/BridgeSubscription.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* 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 "BridgeSubscription.h"
#include "DeviceManager.h"

using namespace ::chip;
using namespace ::chip::app;
using chip::app::ReadClient;

namespace admin {

namespace {

constexpr uint16_t kSubscribeMinInterval = 0;
constexpr uint16_t kSubscribeMaxInterval = 60;

void OnDeviceConnectedWrapper(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle)
{
reinterpret_cast<BridgeSubscription *>(context)->OnDeviceConnected(exchangeMgr, sessionHandle);
}

void OnDeviceConnectionFailureWrapper(void * context, const ScopedNodeId & peerId, CHIP_ERROR error)
{
reinterpret_cast<BridgeSubscription *>(context)->OnDeviceConnectionFailure(peerId, error);
}

} // namespace

BridgeSubscription::BridgeSubscription() :
mOnDeviceConnectedCallback(OnDeviceConnectedWrapper, this),
mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureWrapper, this)
{}

CHIP_ERROR BridgeSubscription::StartSubscription(Controller::DeviceController & controller, NodeId nodeId, EndpointId endpointId)
{
assertChipStackLockedByCurrentThread();

VerifyOrDie(!subscriptionStarted); // Ensure it's not called multiple times.

// Mark as started
subscriptionStarted = true;

mEndpointId = endpointId;

CHIP_ERROR err = controller.GetConnectedDevice(nodeId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback);
if (err != CHIP_NO_ERROR)
{
ChipLogError(NotSpecified, "Failed to connect to remote fabric sync bridge %" CHIP_ERROR_FORMAT, err.Format());
}
return err;
}

void BridgeSubscription::OnAttributeData(const ConcreteDataAttributePath & path, TLV::TLVReader * data, const StatusIB & status)
{
if (!status.IsSuccess())
{
ChipLogError(NotSpecified, "Response Failure: %" CHIP_ERROR_FORMAT, status.ToChipError().Format());
return;
}

if (data == nullptr)
{
ChipLogError(NotSpecified, "Response Failure: No Data");
return;
}

DeviceMgr().HandleAttributeData(path, *data);
}

void BridgeSubscription::OnEventData(const app::EventHeader & eventHeader, TLV::TLVReader * data, const app::StatusIB * status)
{
if (status != nullptr)
{
CHIP_ERROR error = status->ToChipError();
if (CHIP_NO_ERROR != error)
{
ChipLogError(NotSpecified, "Response Failure: %" CHIP_ERROR_FORMAT, error.Format());
return;
}
}

if (data == nullptr)
{
ChipLogError(NotSpecified, "Response Failure: No Data");
return;
}

DeviceMgr().HandleEventData(eventHeader, *data);
}

void BridgeSubscription::OnError(CHIP_ERROR error)
{
ChipLogProgress(NotSpecified, "Error on remote fabric sync bridge subscription: %" CHIP_ERROR_FORMAT, error.Format());
}

void BridgeSubscription::OnDone(ReadClient * apReadClient)
{
mClient.reset();
ChipLogProgress(NotSpecified, "The remote fabric sync bridge subscription is terminated");

// Reset the subscription state to allow retry
subscriptionStarted = false;

// TODO:(#36092) Fabric-Admin should attempt to re-subscribe when the subscription to the remote bridge is terminated.
}

void BridgeSubscription::OnDeviceConnected(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle)
{
mClient = std::make_unique<ReadClient>(app::InteractionModelEngine::GetInstance(), &exchangeMgr /* echangeMgr */,
*this /* callback */, ReadClient::InteractionType::Subscribe);
VerifyOrDie(mClient);

AttributePathParams readPaths[1];
readPaths[0] = AttributePathParams(mEndpointId, Clusters::Descriptor::Id, Clusters::Descriptor::Attributes::PartsList::Id);

EventPathParams eventPaths[1];
eventPaths[0] = EventPathParams(mEndpointId, Clusters::CommissionerControl::Id,
Clusters::CommissionerControl::Events::CommissioningRequestResult::Id);
eventPaths[0].mIsUrgentEvent = true;

ReadPrepareParams readParams(sessionHandle);

readParams.mpAttributePathParamsList = readPaths;
readParams.mAttributePathParamsListSize = 1;
readParams.mpEventPathParamsList = eventPaths;
readParams.mEventPathParamsListSize = 1;
readParams.mMinIntervalFloorSeconds = kSubscribeMinInterval;
readParams.mMaxIntervalCeilingSeconds = kSubscribeMaxInterval;
readParams.mKeepSubscriptions = true;

CHIP_ERROR err = mClient->SendRequest(readParams);

if (err != CHIP_NO_ERROR)
{
ChipLogError(NotSpecified, "Failed to issue subscription to the Descriptor Cluster of the remote bridged device.");
OnDone(nullptr);
return;
}
}

void BridgeSubscription::OnDeviceConnectionFailure(const ScopedNodeId & peerId, CHIP_ERROR error)
{
ChipLogError(NotSpecified, "BridgeSubscription failed to connect to " ChipLogFormatX64, ChipLogValueX64(peerId.GetNodeId()));
OnDone(nullptr);
}

} // namespace admin
81 changes: 81 additions & 0 deletions examples/fabric-sync/admin/BridgeSubscription.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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 <app/ReadClient.h>
#include <controller/CHIPDeviceController.h>

#include <memory>
#include <optional>

namespace admin {

/**
* @brief Class used to subscribe to attributes and events from the remote bridged device.
*
* The Descriptor Cluster contains attributes such as the Parts List, which provides a list
* of endpoints or devices that are part of a composite device or bridge. The CommissionerControl
* Cluster generates events related to commissioning requests, which can be monitored to track
* device commissioning status.
*
* When subscribing to attributes and events of a bridged device from another fabric, the class:
* - Establishes a secure session with the device (if needed) via CASE (Chip over
* Authenticated Session Establishment) session.
* - Subscribes to the specified attributes in the Descriptor Cluster (e.g., Parts List) and
* events in the CommissionerControl Cluster (e.g., CommissioningRequestResult) of the remote
* device on the specified node and endpoint.
* - Invokes the provided callback upon successful or unsuccessful subscription, allowing
* further handling of data or errors.
*
* This class also implements the necessary callbacks to handle attribute data reports, event data,
* errors, and session establishment procedures.
*/
class BridgeSubscription : public chip::app::ReadClient::Callback
{
public:
BridgeSubscription();

CHIP_ERROR StartSubscription(chip::Controller::DeviceController & controller, chip::NodeId nodeId, chip::EndpointId endpointId);

///////////////////////////////////////////////////////////////
// ReadClient::Callback implementation
///////////////////////////////////////////////////////////////
void OnAttributeData(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data,
const chip::app::StatusIB & status) override;
void OnEventData(const chip::app::EventHeader & eventHeader, chip::TLV::TLVReader * data,
const chip::app::StatusIB * status) override;
void OnError(CHIP_ERROR error) override;
void OnDone(chip::app::ReadClient * apReadClient) override;

///////////////////////////////////////////////////////////////
// callbacks for CASE session establishment
///////////////////////////////////////////////////////////////
void OnDeviceConnected(chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle);
void OnDeviceConnectionFailure(const chip::ScopedNodeId & peerId, CHIP_ERROR error);

private:
std::unique_ptr<chip::app::ReadClient> mClient;

chip::Callback::Callback<chip::OnDeviceConnected> mOnDeviceConnectedCallback;
chip::Callback::Callback<chip::OnDeviceConnectionFailure> mOnDeviceConnectionFailureCallback;
chip::EndpointId mEndpointId;
bool subscriptionStarted = false;
};

} // namespace admin
Loading

0 comments on commit dac4a53

Please sign in to comment.