/* * * 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 #include #include #include #include #include #include #include #include #include namespace chip { namespace Messaging { class ExchangeManager; class ExchangeContext; class ExchangeMessageDispatch; using ExchangeHandle = ReferenceCountedHandle; 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, 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 ::value>> CHIP_ERROR SendMessage(MessageType msgType, System::PacketBufferHandle && msgPayload, const SendFlags & sendFlags = SendFlags(SendMessageFlags::kNone)) { return SendMessage(Protocols::MessageTypeTraits::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(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 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