Skip to content

Commit

Permalink
Add OperationalDeviceProxy class (#10400)
Browse files Browse the repository at this point in the history
* Add OperationalDeviceProxy class

* This class currently acts as a wrapper to the Device class
* This class is to be used to establish a secure session between two devices on the operational network via CASE

* Address code review comments

* Make Server the delegate to Exchange Manager

- Server will demux and forward the OnNewConnection to OperationalDeviceProxy
- Guard against calling Device::OnNewConnection if the secure session is initiated by another device

* Apply restyle and move enqueue to prevent dequeue

* Set imDelegate to handle response callback

* Remove OperationalDeviceProxy object from Server class

- Also remove Server as the delegate to ExchangeMgr since there is no longer an OperationalDeviceProxy class to inform
  • Loading branch information
carol-apple authored Oct 21, 2021
1 parent 77a2223 commit 6e22d13
Show file tree
Hide file tree
Showing 7 changed files with 394 additions and 2 deletions.
39 changes: 39 additions & 0 deletions src/app/device/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright (c) 2021 Project CHIP Authors
#
# 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.

import("//build_overrides/chip.gni")
import("${chip_root}/src/app/common_flags.gni")

config("device_config") {
defines = []

if (chip_app_use_echo) {
defines += [ "CHIP_APP_USE_ECHO" ]
}
}

static_library("device") {
output_name = "libCHIPAppDevice"

sources = [
"OperationalDeviceProxy.cpp",
"OperationalDeviceProxy.h",
]

public_configs = [ ":device_config" ]

cflags = [ "-Wconversion" ]

public_deps = [ "${chip_root}/src/controller" ]
}
158 changes: 158 additions & 0 deletions src/app/device/OperationalDeviceProxy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
*
* Copyright (c) 2021 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 <app/device/OperationalDeviceProxy.h>

namespace chip {
namespace app {
namespace device {

CHIP_ERROR OperationalDeviceProxy::Connect(Callback::Callback<OnOperationalDeviceConnected> * onConnection,
Callback::Callback<OnOperationalDeviceConnectionFailure> * onFailure)
{

// Secure session already established
if (mState == State::SecureConnected)
{
onConnection->mCall(onConnection->mContext, this);
return CHIP_NO_ERROR;
}

VerifyOrReturnError(mInitParams.exchangeMgr != nullptr, CHIP_ERROR_INTERNAL);

EnqueueConnectionCallbacks(onConnection, onFailure);

Controller::ControllerDeviceInitParams initParams = {
.sessionManager = mInitParams.sessionManager,
.exchangeMgr = mInitParams.exchangeMgr,
.storageDelegate = nullptr,
.idAllocator = mInitParams.idAllocator,
.fabricsTable = mInitParams.fabricsTable,
// TODO: Investigate where instantiation of this should reside
.imDelegate = chip::Platform::New<chip::Controller::DeviceControllerInteractionModelDelegate>(),
};

CHIP_ERROR err = CHIP_NO_ERROR;
mDevice.Init(initParams, mNodeId, mAddress, mFabricIndex);
mDevice.OperationalCertProvisioned();
mDevice.SetActive(true);
err = mDevice.EstablishConnectivity(&mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback);
SuccessOrExit(err);

exit:
if (err != CHIP_NO_ERROR)
{
DequeueConnectionSuccessCallbacks(false);
DequeueConnectionFailureCallbacks(err, true);
}

return err;
}

CHIP_ERROR OperationalDeviceProxy::UpdateAddress(const Transport::PeerAddress & address)
{
mAddress = address;
return CHIP_NO_ERROR;
}

void OperationalDeviceProxy::EnqueueConnectionCallbacks(Callback::Callback<OnOperationalDeviceConnected> * onConnection,
Callback::Callback<OnOperationalDeviceConnectionFailure> * onFailure)
{
if (onConnection != nullptr)
{
mConnectionSuccess.Enqueue(onConnection->Cancel());
}

if (onFailure != nullptr)
{
mConnectionFailure.Enqueue(onFailure->Cancel());
}
}

void OperationalDeviceProxy::DequeueConnectionSuccessCallbacks(bool executeCallback)
{
Callback::Cancelable ready;
mConnectionSuccess.DequeueAll(ready);
while (ready.mNext != &ready)
{
Callback::Callback<OnOperationalDeviceConnected> * cb =
Callback::Callback<OnOperationalDeviceConnected>::FromCancelable(ready.mNext);

cb->Cancel();
if (executeCallback)
{
cb->mCall(cb->mContext, this);
}
}
}

void OperationalDeviceProxy::DequeueConnectionFailureCallbacks(CHIP_ERROR error, bool executeCallback)
{
Callback::Cancelable ready;
mConnectionFailure.DequeueAll(ready);
while (ready.mNext != &ready)
{
Callback::Callback<OnOperationalDeviceConnectionFailure> * cb =
Callback::Callback<OnOperationalDeviceConnectionFailure>::FromCancelable(ready.mNext);

cb->Cancel();
if (executeCallback)
{
cb->mCall(cb->mContext, this, error);
}
}
}

void OperationalDeviceProxy::OnNewConnection(SessionHandle session)
{
// TODO: The delegate to ExchangeMgr and should demux and inform this class of connection changes
// If the secure session established is initiated by another device
if (!mDevice.IsActive() || mDevice.IsSecureConnected())
{
return;
}

mDevice.OnNewConnection(session);
}

void OperationalDeviceProxy::OnConnectionExpired(SessionHandle session)
{
mDevice.OnConnectionExpired(session);
}

void OperationalDeviceProxy::OnDeviceConnectedFn(void * context, Controller::Device * device)
{
VerifyOrReturn(context != nullptr, ChipLogError(OperationalDeviceProxy, "%s: invalid context", __FUNCTION__));
OperationalDeviceProxy * operationalDevice = reinterpret_cast<OperationalDeviceProxy *>(context);
// TODO: The state update should be updated when the delegate to ExchangeMgr informs of connection changes
operationalDevice->mState = State::SecureConnected;
operationalDevice->DequeueConnectionFailureCallbacks(CHIP_NO_ERROR, false);
operationalDevice->DequeueConnectionSuccessCallbacks(true);
}

void OperationalDeviceProxy::OnDeviceConnectionFailureFn(void * context, NodeId deviceId, CHIP_ERROR error)
{
VerifyOrReturn(context != nullptr, ChipLogError(OperationalDeviceProxy, "%s: invalid context", __FUNCTION__));
OperationalDeviceProxy * operationalDevice = reinterpret_cast<OperationalDeviceProxy *>(context);
operationalDevice->DequeueConnectionSuccessCallbacks(false);
operationalDevice->DequeueConnectionFailureCallbacks(error, true);
}

} // namespace device
} // namespace app
} // namespace chip
187 changes: 187 additions & 0 deletions src/app/device/OperationalDeviceProxy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
*
* Copyright (c) 2021 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 <controller/CHIPDevice.h>

namespace chip {
namespace app {
namespace device {

/**
* This struct contains device specific parameters that are needed to establish a secure session. The
* pointers passed in are not owned by this object and should have a lifetime beyond this object.
*/
struct OperationalDeviceProxyInitParams
{
SessionManager * sessionManager = nullptr;
Messaging::ExchangeManager * exchangeMgr = nullptr;
SessionIDAllocator * idAllocator = nullptr;
FabricTable * fabricsTable = nullptr;
};

class OperationalDeviceProxy;

// TODO: https://github.com/project-chip/connectedhomeip/issues/10423 will provide a refactor of the `Device`
// class. When that happens, the type of the last param for this callback may change as the registrar of this
// callback would need to be able to associate the peer device with the cluster command being setn.
typedef void (*OnOperationalDeviceConnected)(void * context, OperationalDeviceProxy * operationalDeviceProxy);
typedef void (*OnOperationalDeviceConnectionFailure)(void * context, OperationalDeviceProxy * operationalDeviceProxy,
CHIP_ERROR error);

/**
* @class OperationalDeviceProxy
*
* @brief This is a device proxy class for any two devices on the operational network to establish a
* secure session with each other via CASE. To establish a secure session, the caller of this class
* must supply the node ID, fabric index, as well as other device specific parameters to the peer node
* it wants to communicate with.
*/
class DLL_EXPORT OperationalDeviceProxy
{
public:
virtual ~OperationalDeviceProxy() {}
OperationalDeviceProxy() :
mOnDeviceConnectedCallback(OnDeviceConnectedFn, this), mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this)
{}

/**
* @brief
* Initialize an operational device object with node ID, fabric index and other
* device specific parameters used to establish a secure session.
*
* @param[in] nodeId Node ID of the device in which the secure session is established for
* @param[in] fabricIndex Fabric index of the device in which the secure session is established for
* @param[in] initParams Device specific parameters used in establishing the secure session
*/
void Init(NodeId nodeId, FabricIndex fabricIndex, OperationalDeviceProxyInitParams initParams)
{
VerifyOrReturn(initParams.sessionManager != nullptr);
VerifyOrReturn(initParams.exchangeMgr != nullptr);
VerifyOrReturn(initParams.idAllocator != nullptr);
VerifyOrReturn(initParams.fabricsTable != nullptr);

mNodeId = nodeId;
mFabricIndex = fabricIndex;
mInitParams = initParams;
}

/**
* @brief
* Establish a secure session with the device via CASE.
*
* On establishing the session, the callback function `onConnection` will be called. If the
* session setup fails, `onFailure` will be called.
*
* If the session already exists, `onConnection` will be called immediately.
*
* @param[in] onConnection Callback to call when secure session successfully established
* @param[in] onFailure Callback to call when secure session fails to be established
*/
CHIP_ERROR Connect(Callback::Callback<OnOperationalDeviceConnected> * onConnection,
Callback::Callback<OnOperationalDeviceConnectionFailure> * onFailure);

/**
* @brief
* Update address of the device. The address is used as part of secure session establishment
* and therefore, must be updated before a secure session is established.
*
* @param[in] address Address of the device in which the secure session is established for
*/
// TODO: After a requested CHIP node ID has been successfully resolved, call this to update
CHIP_ERROR UpdateAddress(const Transport::PeerAddress & address);

/**
* @brief
* Called when a secure session is being established
*
* @param[in] session The handle to the secure session
*/
void OnNewConnection(SessionHandle session);

/**
* @brief
* Called when a secure session is closing
*
* @param[in] session The handle to the secure session
*/
void OnConnectionExpired(SessionHandle session);

chip::Controller::Device & GetDevice() { return mDevice; }

private:
enum class State
{
Uninitialized,
Initialized,
Connecting,
SecureConnected,
};

/* Node ID assigned to the device */
NodeId mNodeId = kUndefinedNodeId;

/* Fabric index of the device */
FabricIndex mFabricIndex = kUndefinedFabricIndex;

/* Device specific parameters needed to establish a secure session */
OperationalDeviceProxyInitParams mInitParams;

/* Address used to communicate with the device */
Transport::PeerAddress mAddress = Transport::PeerAddress::UDP(Inet::IPAddress::Any);

/* Current state of the proxy */
State mState = State::Uninitialized;

/* Tracker of callbacks for the device */
Callback::CallbackDeque mConnectionSuccess;
Callback::CallbackDeque mConnectionFailure;

// TODO: https://github.com/project-chip/connectedhomeip/issues/10423 will provide a refactor of the `Device`
// class. When that happens, this class will no longer act as a wrapper to the `Device` class. This class
// should not need to hold a Device class object.
Controller::Device mDevice;

/**
* ----- Member functions -----
*/
void EnqueueConnectionCallbacks(Callback::Callback<OnOperationalDeviceConnected> * onConnection,
Callback::Callback<OnOperationalDeviceConnectionFailure> * onFailure);

void DequeueConnectionSuccessCallbacks(bool executeCallback);
void DequeueConnectionFailureCallbacks(CHIP_ERROR error, bool executeCallback);

/**
* ----- Wrapper callbacks for Device class -----
*/
// TODO: https://github.com/project-chip/connectedhomeip/issues/10423 will provide a refactor of the `Device`
// class. When that happens, these callbacks are no longer needed. They are currently being used to forward
// callbacks from the `Device` class to the users of the `OperationalDeviceProxy` class. Once the
// `OperationalDeviceProxy` class is no longer a wrapper to the `Device` class, the former will no longer
// need to register for the following callbacks to the `Device` class.
static void OnDeviceConnectedFn(void * context, chip::Controller::Device * device);
static void OnDeviceConnectionFailureFn(void * context, NodeId deviceId, CHIP_ERROR error);

chip::Callback::Callback<chip::Controller::OnDeviceConnected> mOnDeviceConnectedCallback;
chip::Callback::Callback<chip::Controller::OnDeviceConnectionFailure> mOnDeviceConnectionFailureCallback;
};

} // namespace device
} // namespace app
} // namespace chip
6 changes: 6 additions & 0 deletions src/controller/CHIPDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,12 @@ CHIP_ERROR Device::Persist()

void Device::OnNewConnection(SessionHandle session)
{
// Only allow update if the session has been initialized and matches
if (mSecureSession.HasValue() && !MatchesSession(session))
{
return;
}

mState = ConnectionState::SecureConnected;
mSecureSession.SetValue(session);

Expand Down
Loading

0 comments on commit 6e22d13

Please sign in to comment.