diff --git a/src/app/device/BUILD.gn b/src/app/device/BUILD.gn new file mode 100644 index 00000000000000..6cd59e54aa250d --- /dev/null +++ b/src/app/device/BUILD.gn @@ -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" ] +} diff --git a/src/app/device/OperationalDeviceProxy.cpp b/src/app/device/OperationalDeviceProxy.cpp new file mode 100644 index 00000000000000..4bb1a06e3189e5 --- /dev/null +++ b/src/app/device/OperationalDeviceProxy.cpp @@ -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 + +namespace chip { +namespace app { +namespace device { + +CHIP_ERROR OperationalDeviceProxy::Connect(Callback::Callback * onConnection, + Callback::Callback * 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_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 * onConnection, + Callback::Callback * 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 * cb = + Callback::Callback::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 * cb = + Callback::Callback::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(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(context); + operationalDevice->DequeueConnectionSuccessCallbacks(false); + operationalDevice->DequeueConnectionFailureCallbacks(error, true); +} + +} // namespace device +} // namespace app +} // namespace chip diff --git a/src/app/device/OperationalDeviceProxy.h b/src/app/device/OperationalDeviceProxy.h new file mode 100644 index 00000000000000..65c1bd1fca7d47 --- /dev/null +++ b/src/app/device/OperationalDeviceProxy.h @@ -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 + +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 * onConnection, + Callback::Callback * 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 * onConnection, + Callback::Callback * 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 mOnDeviceConnectedCallback; + chip::Callback::Callback mOnDeviceConnectionFailureCallback; +}; + +} // namespace device +} // namespace app +} // namespace chip diff --git a/src/controller/CHIPDevice.cpp b/src/controller/CHIPDevice.cpp index b4cab82e96cf37..bf65c6c9ce68e5 100644 --- a/src/controller/CHIPDevice.cpp +++ b/src/controller/CHIPDevice.cpp @@ -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); diff --git a/src/controller/CHIPDevice.h b/src/controller/CHIPDevice.h index 011e92b6241392..e1c51e9daf6ba5 100644 --- a/src/controller/CHIPDevice.h +++ b/src/controller/CHIPDevice.h @@ -29,8 +29,8 @@ #include #include #include +#include #include -#include #include #include #include diff --git a/src/lib/support/logging/CHIPLogging.cpp b/src/lib/support/logging/CHIPLogging.cpp index b092e500d15cd7..d7a3f06af8c616 100644 --- a/src/lib/support/logging/CHIPLogging.cpp +++ b/src/lib/support/logging/CHIPLogging.cpp @@ -91,8 +91,9 @@ static const char ModuleNames[] = "-\0\0" // None "SPL" // SetupPayload "SVR" // AppServer "DIS" // Discovery - "IM" // InteractionModel + "IM\0" // InteractionModel "TST" // Test + "ODP" // OperationalDeviceProxy ; #define ModuleNamesCount ((sizeof(ModuleNames) - 1) / chip::Logging::kMaxModuleNameLen) diff --git a/src/lib/support/logging/Constants.h b/src/lib/support/logging/Constants.h index b78150a5e523b8..ddc6beacbbf28e 100644 --- a/src/lib/support/logging/Constants.h +++ b/src/lib/support/logging/Constants.h @@ -56,6 +56,7 @@ enum LogModule kLogModule_Discovery, kLogModule_InteractionModel, kLogModule_Test, + kLogModule_OperationalDeviceProxy, kLogModule_Max };