/* * 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. */ #pragma once #include #include #include #ifndef CHIP_EXCHANGE_HOLDER_DETAIL_LOGGING #define CHIP_EXCHANGE_HOLDER_DETAIL_LOGGING 0 #endif // CHIP_EXCHANGE_HOLDER_DETAIL_LOGGING namespace chip { namespace Messaging { /** * @brief * This provides a RAII'fied wrapper for an ExchangeContext that automatically manages * cleaning up the EC when the holder ceases to exist, or acquires a new exchange. This is * meant to be used by application and protocol logic code that would otherwise need to closely * manage their internal pointers to an ExchangeContext and correctly * null-it out/abort it depending on the circumstances. This relies on clear rules * established by ExchangeContext and the transfer of ownership at various points * in its lifetime. * * An equivalent but simplified version of the rules around exchange management as specified in * ExchangeDelegate.h are provided here for consumers: * * 1. When an exchange is allocated, the holder takes over ownership of the exchange when Grab() is invoked. * Until a message is sent successfully, the holder will automatically manage the exchange until its * destructor or Release() is invoked. * * 2. If you send a message successfully that doesn't require a response, invoking Get() on the holder there-after will return * nullptr. * * 3. If you send a message successfully that does require a response, invoking Get() on the holder will return a valid * pointer until the response is received or times out. * * 4. On reception of a message on an exchange, if you return from OnMessageReceived() and no messages were sent on that exchange, * invoking Get() on the holder will return a nullptr. * * 5. If you invoke WillSendMessage() on the exchange in your implementation of OnMessageReceived indicating a desire to send a * message later on the exchange, invoking Get() on the holder will return a valid exchange until SendMessage() on the exchange * is called, at which point, rules 2 and 3 apply. * * 6. This is a delegate forwarder - consumers can still register to be an ExchangeDelegate * and get notified of all relevant happenings on that delegate interface. * * 7. At no point shall you call Abort/Close/Release/Retain on the exchange tracked by the holder. * */ class ExchangeHolder : public ExchangeDelegate { public: /** * @brief * Constructor that takes an ExchangeDelegate that is forwarded all relevant * calls from the underlying exchange. */ ExchangeHolder(ExchangeDelegate & delegate) : mpExchangeDelegate(delegate) {} virtual ~ExchangeHolder() { #if CHIP_EXCHANGE_HOLDER_DETAIL_LOGGING ChipLogDetail(ExchangeManager, "[%p] ~ExchangeHolder", this); #endif Release(); } bool Contains(const ExchangeContext * exchange) const { return mpExchangeCtx == exchange; } /** * @brief * Replaces the held exchange and associated delegate to instead track the given ExchangeContext, aborting * and dereferencing any previously held exchange as necessary. This method should be called whenever protocol logic * that is managing this holder is transitioning from an outdated Exchange to a new one, often during * the start of a new transaction. */ void Grab(ExchangeContext * exchange) { VerifyOrDie(exchange != nullptr); Release(); mpExchangeCtx = exchange; mpExchangeCtx->SetDelegate(this); #if CHIP_EXCHANGE_HOLDER_DETAIL_LOGGING ChipLogDetail(ExchangeManager, "[%p] ExchangeHolder::Grab: Acquired EC %p", this, exchange); #endif } /* * @brief * This shuts down the exchange (if a valid one is being tracked) and releases our reference to it. */ void Release() { #if CHIP_EXCHANGE_HOLDER_DETAIL_LOGGING ChipLogDetail(ExchangeManager, "[%p] ExchangeHolder::Release: mpExchangeCtx = %p", this, mpExchangeCtx); #endif if (mpExchangeCtx) { mpExchangeCtx->SetDelegate(nullptr); /** * Shutting down the exchange requires calling Abort() on the exchange selectively in the following scenarios: * 1. The exchange is currently awaiting a response. This would have happened if our consumer just sent a message * on the exchange and is awaiting a response. Since we no longer care to wait for the response, we don't care about * doing MRP retries for the send we just did, so abort the exchange. * * 2. Our consumer has signaled an interest in sending a message. This could have been signaled right at exchange * creation time as the initiator, or when handling a message and the consumer intends to send a response, albeit, * asynchronously. In both cases, the stack expects the exchange consumer to close/abort the EC if it no longer has * interest in it. Since we don't have a pending message at this point, calling Abort is OK here as well. * */ if (mpExchangeCtx->IsResponseExpected() || mpExchangeCtx->IsSendExpected()) { #if CHIP_EXCHANGE_HOLDER_DETAIL_LOGGING ChipLogDetail(ExchangeManager, "[%p] ExchangeHolder::Release: Aborting!", this); #endif mpExchangeCtx->Abort(); } } mpExchangeCtx = nullptr; } explicit operator bool() const { return mpExchangeCtx != nullptr; } ExchangeContext * Get() const { return mpExchangeCtx; } ExchangeContext * operator->() const { VerifyOrDie(mpExchangeCtx != nullptr); return mpExchangeCtx; } private: CHIP_ERROR OnMessageReceived(ExchangeContext * ec, const PayloadHeader & payloadHeader, System::PacketBufferHandle && payload) override { return mpExchangeDelegate.OnMessageReceived(ec, payloadHeader, std::move(payload)); } void OnResponseTimeout(ExchangeContext * ec) override { return mpExchangeDelegate.OnResponseTimeout(ec); } void OnExchangeClosing(ExchangeContext * ec) override { #if CHIP_EXCHANGE_HOLDER_DETAIL_LOGGING ChipLogDetail(ExchangeManager, "[%p] ExchangeHolder::OnExchangeClosing: mpExchangeCtx: %p", this, mpExchangeCtx); #endif if (mpExchangeCtx) { mpExchangeCtx->SetDelegate(nullptr); /** * Unless our consumer has signalled an intention to send a message in the future, the exchange * is owned by the exchange layer and it will automatically handle releasing the ref. So, just null * out our reference to it. */ if (!mpExchangeCtx->IsSendExpected()) { #if CHIP_EXCHANGE_HOLDER_DETAIL_LOGGING ChipLogDetail(ExchangeManager, "[%p] ExchangeHolder::OnExchangeClosing: nulling out ref...", this); #endif mpExchangeCtx = nullptr; } } mpExchangeDelegate.OnExchangeClosing(ec); } ExchangeMessageDispatch & GetMessageDispatch() override { return mpExchangeDelegate.GetMessageDispatch(); } ExchangeDelegate & mpExchangeDelegate; ExchangeContext * mpExchangeCtx = nullptr; }; } // namespace Messaging } // namespace chip