blob: 50a90f42b99ba3d436c98c1515ab53b018b4e1c6 [file] [log] [blame]
/*
* 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>
#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