| /* |
| * 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 <lib/core/Optional.h> |
| #include <lib/support/IntrusiveList.h> |
| #include <messaging/ExchangeContext.h> |
| |
| 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 |