/* * * Copyright (c) 2022 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. */ #pragma once #include #include #include #include #include #include namespace chip { /** * Application callback function when a cluster associated with a binding changes. * * The connection is managed by the stack and peer_device is guaranteed to be available. * The application shall decide the content to be sent to the peer. * * For unicast bindings peer_device will be a connected peer and group will be empty. * For multicast bindings peer_device will be nullptr. * * E.g. The application will send on/off commands to peer for the OnOff cluster. * * The handler is not allowed to hold onto the pointer to the SessionHandler that is passed in. */ using BoundDeviceChangedHandler = void (*)(const EmberBindingTableEntry & binding, OperationalDeviceProxy * peer_device, void * context); /** * Application callback function when a context used in NotifyBoundClusterChanged will not be needed and should be * released. */ using BoundDeviceContextReleaseHandler = PendingNotificationContextReleaseHandler; struct BindingManagerInitParams { FabricTable * mFabricTable = nullptr; CASESessionManager * mCASESessionManager = nullptr; PersistentStorageDelegate * mStorage = nullptr; bool mEstablishConnectionOnInit = true; }; /** * * The BindingManager class manages the connections for unicast bindings and notifies the application * when a binding is ready to be communicated with. * * A CASE connection will be triggered when: * - During init of the BindingManager, unless the application actively disables this using mEstablishConnectionOnInit * - The binding cluster adds a unicast entry to the binding table. * - A watched cluster changes with a unicast binding but we cannot find an active connection to the peer. * * The class uses an LRU mechanism to choose the connection to eliminate when there is no space for a new connection. * The BindingManager class will not actively re-establish connection and will connect on-demand (when binding cluster * or watched cluster is changed). * */ class BindingManager { public: BindingManager() {} void RegisterBoundDeviceChangedHandler(BoundDeviceChangedHandler handler) { mBoundDeviceChangedHandler = handler; } /* * Registers handler that will be called when context used in NotifyBoundClusterChanged will not be needed and could be * released. * */ void RegisterBoundDeviceContextReleaseHandler(BoundDeviceContextReleaseHandler handler) { mPendingNotificationMap.RegisterPendingNotificationContextReleaseHandler(handler); } CHIP_ERROR Init(const BindingManagerInitParams & params); /* * Notifies the BindingManager that a new unicast binding is created. * */ CHIP_ERROR UnicastBindingCreated(uint8_t fabricIndex, NodeId nodeId); /* * Notifies the BindingManager that a unicast binding is about to be removed from the given index. * */ CHIP_ERROR UnicastBindingRemoved(uint8_t bindingEntryId); /* * Notifies the BindingManager that a fabric is removed from the device * */ void FabricRemoved(FabricIndex fabricIndex); /* * Notify a cluster change to **all** bound devices associated with the (endpoint, cluster) tuple. * * For unicast bindings with an active session and multicast bindings, the BoundDeviceChangedHandler * will be called before the function returns. * * For unicast bindings without an active session, the notification will be queued and a new session will * be initiated. The BoundDeviceChangedHandler will be called once the session is established. * */ CHIP_ERROR NotifyBoundClusterChanged(EndpointId endpoint, ClusterId cluster, void * context); static BindingManager & GetInstance() { return sBindingManager; } private: /* * Used when providing OnConnection/Failure callbacks to CASESessionManager when establishing session. * * Since the BindingManager calls EstablishConnection inside of a loop, and it is possible that the * callback is called some time after the loop is completed, we need a separate callbacks for each * connection we are trying to establish. Failure to provide different instances of the callback * to CASESessionManager may result in the callback only be called for that last EstablishConnection * that was called when it establishes the connections asynchronously. * */ class ConnectionCallback { public: ConnectionCallback(BindingManager & bindingManager) : mBindingManager(bindingManager), mOnConnectedCallback(HandleDeviceConnected, this), mOnConnectionFailureCallback(HandleDeviceConnectionFailure, this) {} Callback::Callback * GetOnDeviceConnected() { return &mOnConnectedCallback; } Callback::Callback * GetOnDeviceConnectionFailure() { return &mOnConnectionFailureCallback; } private: static void HandleDeviceConnected(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) { ConnectionCallback * _this = static_cast(context); _this->mBindingManager.HandleDeviceConnected(exchangeMgr, sessionHandle); Platform::Delete(_this); } static void HandleDeviceConnectionFailure(void * context, const ScopedNodeId & peerId, CHIP_ERROR error) { ConnectionCallback * _this = static_cast(context); _this->mBindingManager.HandleDeviceConnectionFailure(peerId, error); Platform::Delete(_this); } BindingManager & mBindingManager; Callback::Callback mOnConnectedCallback; Callback::Callback mOnConnectionFailureCallback; }; static BindingManager sBindingManager; CHIP_ERROR EstablishConnection(const ScopedNodeId & nodeId); PendingNotificationMap mPendingNotificationMap; BoundDeviceChangedHandler mBoundDeviceChangedHandler; BindingManagerInitParams mInitParams; void HandleDeviceConnected(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle); void HandleDeviceConnectionFailure(const ScopedNodeId & peerId, CHIP_ERROR error); // Used to keep track of synchronous failures from FindOrEstablishSession. CHIP_ERROR mLastSessionEstablishmentError; }; } // namespace chip