blob: e10ef84ce911bea657f8bda8f099b59b88106ddd [file] [log] [blame]
/*
*
* Copyright (c) 2020-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.
*/
/**
* @file
* This file defines the classes corresponding to CHIP Exchange Context.
*
*/
#pragma once
#include <lib/core/ReferenceCounted.h>
#include <lib/support/BitFlags.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/ReferenceCountedHandle.h>
#include <lib/support/TypeTraits.h>
#include <messaging/ExchangeDelegate.h>
#include <messaging/Flags.h>
#include <messaging/ReliableMessageContext.h>
#include <protocols/Protocols.h>
#include <transport/SessionManager.h>
namespace chip {
namespace Messaging {
class ExchangeManager;
class ExchangeContext;
class ExchangeMessageDispatch;
using ExchangeHandle = ReferenceCountedHandle<ExchangeContext>;
class ExchangeContextDeletor
{
public:
static void Release(ExchangeContext * obj);
};
/**
* @brief
* This class represents an ongoing conversation (ExchangeContext) between two or more nodes.
* It defines methods for encoding and communicating CHIP messages within an ExchangeContext
* over various transport mechanisms, for example, TCP, UDP, or CHIP Reliable Messaging.
*
*/
class DLL_EXPORT ExchangeContext : public ReliableMessageContext,
public ReferenceCounted<ExchangeContext, ExchangeContextDeletor>,
public SessionDelegate
{
friend class ExchangeManager;
friend class ExchangeContextDeletor;
public:
typedef System::Clock::Timeout Timeout; // Type used to express the timeout in this ExchangeContext
ExchangeContext(ExchangeManager * em, uint16_t ExchangeId, const SessionHandle & session, bool Initiator,
ExchangeDelegate * delegate, bool isEphemeralExchange = false);
~ExchangeContext() override;
/**
* Determine whether the context is the initiator of the exchange.
*
* @return Returns 'true' if it is the initiator, else 'false'.
*/
bool IsInitiator() const;
bool IsEncryptionRequired() const { return mDispatch.IsEncryptionRequired(); }
bool IsGroupExchangeContext() const { return mSession && mSession->IsGroupSession(); }
// Implement SessionDelegate
NewSessionHandlingPolicy GetNewSessionHandlingPolicy() override { return NewSessionHandlingPolicy::kStayAtOldSession; }
void OnSessionReleased() override;
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
void OnSessionConnectionClosed(CHIP_ERROR conErr) override;
#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT
/**
* Send a CHIP message on this exchange.
*
* If SendMessage returns success and the message was not expecting a
* response, the exchange will close itself before returning, unless the
* message being sent is a standalone ack. If SendMessage returns failure,
* the caller is responsible for deciding what to do (e.g. closing the
* exchange, trying to re-establish a secure session, etc).
*
* @param[in] protocolId The protocol identifier of the CHIP message to be sent.
*
* @param[in] msgType The message type of the corresponding protocol.
*
* @param[in] msgPayload A handle to the packet buffer holding the CHIP message.
*
* @param[in] sendFlags Flags set by the application for the CHIP message being sent.
*
* @retval #CHIP_ERROR_INVALID_ARGUMENT if an invalid argument was passed to this SendMessage API.
* @retval #CHIP_ERROR_NOT_CONNECTED if the context was associated with a connection that is now
* closed.
* @retval #CHIP_ERROR_INCORRECT_STATE if the state of the exchange context is incorrect.
* @retval #CHIP_NO_ERROR if the CHIP layer successfully sent the message down to the
* network layer.
*/
CHIP_ERROR SendMessage(Protocols::Id protocolId, uint8_t msgType, System::PacketBufferHandle && msgPayload,
const SendFlags & sendFlags = SendFlags(SendMessageFlags::kNone));
/**
* A strongly-message-typed version of SendMessage.
*/
template <typename MessageType, typename = std::enable_if_t<std::is_enum<MessageType>::value>>
CHIP_ERROR SendMessage(MessageType msgType, System::PacketBufferHandle && msgPayload,
const SendFlags & sendFlags = SendFlags(SendMessageFlags::kNone))
{
return SendMessage(Protocols::MessageTypeTraits<MessageType>::ProtocolId(), to_underlying(msgType), std::move(msgPayload),
sendFlags);
}
/**
* A notification that we will have SendMessage called on us in the future
* (and should stay open until that happens).
*/
void WillSendMessage() { mFlags.Set(Flags::kFlagWillSendMessage); }
/**
* Handle a received CHIP message on this exchange.
*
* @param[in] messageCounter The message counter of the packet.
* @param[in] payloadHeader A reference to the PayloadHeader object.
* @param[in] msgFlags The message flags corresponding to the received message
* @param[in] msgBuf A handle to the packet buffer holding the CHIP message.
*
* @retval #CHIP_ERROR_INVALID_ARGUMENT if an invalid argument was passed to this HandleMessage API.
* @retval #CHIP_ERROR_INCORRECT_STATE if the state of the exchange context is incorrect.
* @retval #CHIP_NO_ERROR if the CHIP layer successfully delivered the message up to the
* protocol layer.
*/
CHIP_ERROR HandleMessage(uint32_t messageCounter, const PayloadHeader & payloadHeader, MessageFlags msgFlags,
System::PacketBufferHandle && msgBuf);
ExchangeDelegate * GetDelegate() const { return mDelegate; }
void SetDelegate(ExchangeDelegate * delegate) { mDelegate = delegate; }
ExchangeManager * GetExchangeMgr() const { return mExchangeMgr; }
ReliableMessageContext * GetReliableMessageContext() { return static_cast<ReliableMessageContext *>(this); };
SessionHandle GetSessionHandle() const
{
VerifyOrDieWithObject(mSession, this);
auto sessionHandle = mSession.Get();
return std::move(sessionHandle.Value());
}
bool HasSessionHandle() const { return mSession; }
uint16_t GetExchangeId() const { return mExchangeId; }
/*
* In order to use reference counting (see refCount below) we use a hold/free paradigm where users of the exchange
* can hold onto it while it's out of their direct control to make sure it isn't closed before everyone's ready.
* A customized version of reference counting is used since there are some extra stuff to do within Release.
*/
void Close();
void Abort();
// Applies a suggested response timeout value based on the session type and the given upper layer processing time for
// the next message to the exchange. The exchange context must have a valid session when calling this function.
//
// This function is an equivalent of SetResponseTimeout(mSession->ComputeRoundTripTimeout(applicationProcessingTimeout))
void UseSuggestedResponseTimeout(Timeout applicationProcessingTimeout);
// Set the response timeout for the exchange context, regardless of the underlying session type. Using
// UseSuggestedResponseTimeout to set a timeout based on the type of the session and the application processing time instead of
// using this function is recommended.
//
// If a timeout of 0 is provided, it implies no response is expected. Consequently, ExchangeDelegate::OnResponseTimeout will not
// be called.
//
void SetResponseTimeout(Timeout timeout);
// This API is used by commands that need to shut down all existing
// sessions/exchanges on a fabric but need to make sure the response to the
// command still goes out on the exchange the command came in on. This API
// will ensure that all secure sessions for the fabric this exchanges is on
// are released except the one this exchange is using, and will release
// that session once this exchange is done sending the response.
//
// This API is a no-op if called on an exchange that is not using a
// SecureSession.
void AbortAllOtherCommunicationOnFabric();
/**
* Determine whether a response is currently expected for a message that was sent over
* this exchange. While this is true, attempts to send other messages that expect a response
* will fail.
*
* @return Returns 'true' if response expected, else 'false'.
*/
bool IsResponseExpected() const;
/**
* Determine whether we are expecting our consumer to send a message on
* this exchange (i.e. WillSendMessage was called and the message has not
* yet been sent).
*/
bool IsSendExpected() const { return mFlags.Has(Flags::kFlagWillSendMessage); }
/**
* Tracks whether we have received at least one application level message
* during the life-time of this exchange
*
* @return Returns 'true' if we have received at least one message, else 'false'
*/
inline bool HasReceivedAtLeastOneMessage() { return mFlags.Has(Flags::kFlagReceivedAtLeastOneMessage); }
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
SessionHolder & GetSessionHolder() { return mSession; }
enum class InjectedFailureType : uint8_t
{
kFailOnSend = 0x01
};
void InjectFailure(InjectedFailureType failureType) { mInjectedFailures.Set(failureType); }
void ClearInjectedFailures() { mInjectedFailures.ClearAll(); }
#endif
void DumpToLog() const
{
ChipLogError(ExchangeManager, "ExchangeContext: " ChipLogFormatExchangeId " delegate=" ChipLogFormatRtti,
ChipLogValueExchangeId(GetExchangeId(), IsInitiator()), ChipLogValueRtti(mDelegate));
}
private:
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
BitFlags<InjectedFailureType> mInjectedFailures;
#endif
class ExchangeSessionHolder : public SessionHolderWithDelegate
{
public:
ExchangeSessionHolder(ExchangeContext & exchange) : SessionHolderWithDelegate(exchange) {}
void GrabExpiredSession(const SessionHandle & session);
};
Timeout mResponseTimeout{ 0 }; // Maximum time to wait for response (in milliseconds); 0 disables response timeout.
ExchangeDelegate * mDelegate = nullptr;
ExchangeManager * mExchangeMgr = nullptr;
ExchangeMessageDispatch & mDispatch;
ExchangeSessionHolder mSession; // The connection state
uint16_t mExchangeId; // Assigned exchange ID.
/**
* Track whether we are now expecting a response to a message sent via this exchange (because that
* message had the kExpectResponse flag set in its sendFlags).
*
* @param[in] inResponseExpected A Boolean indicating whether (true) or not
* (false) a response is currently expected on this
* exchange.
*/
void SetResponseExpected(bool inResponseExpected);
/**
* Search for an existing exchange that the message applies to.
*
* @param[in] session The secure session of the received message.
*
* @param[in] packetHeader A reference to the PacketHeader object.
*
* @param[in] payloadHeader A reference to the PayloadHeader object.
*
* @retval true If a match is found.
* @retval false If a match is not found.
*/
bool MatchExchange(const SessionHandle & session, const PacketHeader & packetHeader, const PayloadHeader & payloadHeader);
/**
* Notify our delegate, if any, that we have timed out waiting for a
* response. If aCloseIfNeeded is true, check whether the exchange needs to
* be closed.
*/
void NotifyResponseTimeout(bool aCloseIfNeeded);
CHIP_ERROR StartResponseTimer();
void CancelResponseTimer();
static void HandleResponseTimeout(System::Layer * aSystemLayer, void * aAppState);
void DoClose(bool clearRetransTable);
/**
* We have handled an application-level message in some way and should
* re-evaluate out state to see whether we should still be open.
*/
void MessageHandled();
static ExchangeMessageDispatch & GetMessageDispatch(bool isEphemeralExchange, ExchangeDelegate * delegate);
// If SetAutoReleaseSession() is called, this exchange must be using a SecureSession, and should
// evict it when the exchange is done with all its work (including any MRP traffic).
inline void SetIgnoreSessionRelease(bool ignore) { mFlags.Set(Flags::kFlagIgnoreSessionRelease, ignore); }
inline bool ShouldIgnoreSessionRelease() { return mFlags.Has(Flags::kFlagIgnoreSessionRelease); }
inline void SetHasReceivedAtLeastOneMessage(bool hasReceivedMessage)
{
mFlags.Set(Flags::kFlagReceivedAtLeastOneMessage, hasReceivedMessage);
}
};
} // namespace Messaging
} // namespace chip