| /* | 
 |  * | 
 |  *    Copyright (c) 2020 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 implements the ExchangeContext class. | 
 |  * | 
 |  */ | 
 |  | 
 | #include <inttypes.h> | 
 | #include <stdint.h> | 
 | #include <stdlib.h> | 
 |  | 
 | #include <app/icd/server/ICDServerConfig.h> | 
 | #if CHIP_CONFIG_ENABLE_ICD_SERVER | 
 | #include <app/icd/server/ICDNotifier.h> // nogncheck | 
 | #endif | 
 | #include <lib/core/CHIPCore.h> | 
 | #include <lib/core/CHIPEncoding.h> | 
 | #include <lib/core/CHIPKeyIds.h> | 
 | #include <lib/support/Defer.h> | 
 | #include <lib/support/TypeTraits.h> | 
 | #include <lib/support/logging/CHIPLogging.h> | 
 | #include <messaging/ApplicationExchangeDispatch.h> | 
 | #include <messaging/EphemeralExchangeDispatch.h> | 
 | #include <messaging/ExchangeContext.h> | 
 | #include <messaging/ExchangeMgr.h> | 
 | #include <platform/LockTracker.h> | 
 | #include <protocols/Protocols.h> | 
 | #include <protocols/secure_channel/Constants.h> | 
 |  | 
 | using namespace chip::Encoding; | 
 | using namespace chip::Inet; | 
 | using namespace chip::System; | 
 |  | 
 | namespace chip { | 
 | namespace Messaging { | 
 |  | 
 | static void DefaultOnMessageReceived(ExchangeContext * ec, Protocols::Id protocolId, uint8_t msgType, uint32_t messageCounter, | 
 |                                      PacketBufferHandle && payload) | 
 | { | 
 |     ChipLogError(ExchangeManager, | 
 |                  "Dropping unexpected message of type " ChipLogFormatMessageType " with protocolId " ChipLogFormatProtocolId | 
 |                  " and MessageCounter:" ChipLogFormatMessageCounter " on exchange " ChipLogFormatExchange, | 
 |                  msgType, ChipLogValueProtocolId(protocolId), messageCounter, ChipLogValueExchange(ec)); | 
 | } | 
 |  | 
 | bool ExchangeContext::IsInitiator() const | 
 | { | 
 |     return mFlags.Has(Flags::kFlagInitiator); | 
 | } | 
 |  | 
 | bool ExchangeContext::IsResponseExpected() const | 
 | { | 
 |     return mFlags.Has(Flags::kFlagResponseExpected); | 
 | } | 
 |  | 
 | void ExchangeContext::SetResponseExpected(bool inResponseExpected) | 
 | { | 
 |     mFlags.Set(Flags::kFlagResponseExpected, inResponseExpected); | 
 |     SetWaitingForResponseOrAck(inResponseExpected); | 
 | } | 
 |  | 
 | void ExchangeContext::UseSuggestedResponseTimeout(Timeout applicationProcessingTimeout) | 
 | { | 
 |     SetResponseTimeout(mSession->ComputeRoundTripTimeout(applicationProcessingTimeout, !HasReceivedAtLeastOneMessage())); | 
 | } | 
 |  | 
 | void ExchangeContext::SetResponseTimeout(Timeout timeout) | 
 | { | 
 |     mResponseTimeout = timeout; | 
 | } | 
 |  | 
 | CHIP_ERROR ExchangeContext::SendMessage(Protocols::Id protocolId, uint8_t msgType, PacketBufferHandle && msgBuf, | 
 |                                         const SendFlags & sendFlags) | 
 | { | 
 |     // This is the first point all outgoing messages funnel through.  Ensure | 
 |     // that our message sends are all synchronized correctly. | 
 |     assertChipStackLockedByCurrentThread(); | 
 |  | 
 |     bool isStandaloneAck = | 
 |         (protocolId == Protocols::SecureChannel::Id) && msgType == to_underlying(Protocols::SecureChannel::MsgType::StandaloneAck); | 
 |  | 
 |     VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INTERNAL); | 
 |     VerifyOrReturnError(mSession, CHIP_ERROR_CONNECTION_ABORTED); | 
 |  | 
 |     // Don't let method get called on a freed object. | 
 |     VerifyOrDie(mExchangeMgr != nullptr && GetReferenceCount() > 0); | 
 |  | 
 |     // we hold the exchange context here in case the entity that | 
 |     // originally generated it tries to close it as a result of | 
 |     // an error arising below. at the end, we have to close it. | 
 |     ExchangeHandle ref(*this); | 
 |  | 
 |     // If session requires MRP, NoAutoRequestAck send flag is not specified and is not a group exchange context, request reliable | 
 |     // transmission. | 
 |     bool reliableTransmissionRequested = | 
 |         GetSessionHandle()->AllowsMRP() && !sendFlags.Has(SendMessageFlags::kNoAutoRequestAck) && !IsGroupExchangeContext(); | 
 |  | 
 |     bool currentMessageExpectResponse = false; | 
 |     // If a response message is expected... | 
 |     if (sendFlags.Has(SendMessageFlags::kExpectResponse) && !IsGroupExchangeContext()) | 
 |     { | 
 |         // Only one 'response expected' message can be outstanding at a time. | 
 |         if (IsResponseExpected()) | 
 |         { | 
 |             // TODO: add a test for this case. | 
 |             return CHIP_ERROR_INCORRECT_STATE; | 
 |         } | 
 |  | 
 |         SetResponseExpected(true); | 
 |  | 
 |         // Arm the response timer if a timeout has been specified. | 
 |         if (mResponseTimeout > System::Clock::kZero) | 
 |         { | 
 |             CHIP_ERROR err = StartResponseTimer(); | 
 |             if (err != CHIP_NO_ERROR) | 
 |             { | 
 |                 SetResponseExpected(false); | 
 |                 return err; | 
 |             } | 
 |             currentMessageExpectResponse = true; | 
 |         } | 
 |     } | 
 |  | 
 |     { | 
 |         // ExchangeContext for group are supposed to always be Initiator | 
 |         if (IsGroupExchangeContext() && !IsInitiator()) | 
 |         { | 
 |             return CHIP_ERROR_INTERNAL; | 
 |         } | 
 |  | 
 |         // | 
 |         // It is possible that we might have evicted a session as a side-effect of processing an inbound message on this exchange. | 
 |         // We cannot proceed any further sending a message since we don't have an attached session, so let's error out. | 
 |         // | 
 |         // This should not happen to well-behaved logic attempting to sending messages on exchanges, so let's print out a warning | 
 |         // to ensure it alerts someone to fixing their logic... | 
 |         // | 
 |         if (!mSession) | 
 |         { | 
 |             ChipLogError(ExchangeManager, | 
 |                          "WARNING: We shouldn't be sending a message on an exchange that has no attached session..."); | 
 |             return CHIP_ERROR_MISSING_SECURE_SESSION; | 
 |         } | 
 |  | 
 |         SessionHandle session = GetSessionHandle(); | 
 |         CHIP_ERROR err; | 
 |  | 
 | #if CONFIG_BUILD_FOR_HOST_UNIT_TEST | 
 |         if (mInjectedFailures.Has(InjectedFailureType::kFailOnSend)) | 
 |         { | 
 |             err = CHIP_ERROR_SENDING_BLOCKED; | 
 |         } | 
 |         else | 
 |         { | 
 | #endif | 
 |             err = mDispatch.SendMessage(GetExchangeMgr()->GetSessionManager(), session, mExchangeId, IsInitiator(), | 
 |                                         GetReliableMessageContext(), reliableTransmissionRequested, protocolId, msgType, | 
 |                                         std::move(msgBuf)); | 
 | #if CONFIG_BUILD_FOR_HOST_UNIT_TEST | 
 |         } | 
 | #endif | 
 |         if (err != CHIP_NO_ERROR) | 
 |         { | 
 |             // We should only cancel the response timer if the ExchangeContext fails to send the message that expects a | 
 |             // response. | 
 |             if (currentMessageExpectResponse) | 
 |             { | 
 |                 CancelResponseTimer(); | 
 |                 SetResponseExpected(false); | 
 |             } | 
 |  | 
 |             // If we can't even send a message (send failed with a non-transient | 
 |             // error), mark the session as defunct, just like we would if we | 
 |             // thought we sent the message and never got a response. | 
 |             if (session->IsSecureSession() && session->AsSecureSession()->IsCASESession()) | 
 |             { | 
 |                 session->AsSecureSession()->MarkAsDefunct(); | 
 |             } | 
 |         } | 
 |         else | 
 |         { | 
 | #if CHIP_CONFIG_ENABLE_ICD_SERVER | 
 |             app::ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); | 
 | #endif // CHIP_CONFIG_ENABLE_ICD_SERVER | 
 |  | 
 |             // Standalone acks are not application-level message sends. | 
 |             if (!isStandaloneAck) | 
 |             { | 
 |                 // | 
 |                 // Once we've sent the message successfully, we can clear out the WillSendMessage flag. | 
 |                 // | 
 |                 mFlags.Clear(Flags::kFlagWillSendMessage); | 
 |                 MessageHandled(); | 
 |             } | 
 |         } | 
 |  | 
 |         return err; | 
 |     } | 
 | } | 
 |  | 
 | void ExchangeContext::DoClose(bool clearRetransTable) | 
 | { | 
 |     if (mFlags.Has(Flags::kFlagClosed)) | 
 |     { | 
 |         return; | 
 |     } | 
 |  | 
 |     mFlags.Set(Flags::kFlagClosed); | 
 |  | 
 |     // Clear protocol callbacks | 
 |     if (mDelegate != nullptr) | 
 |     { | 
 |         mDelegate->OnExchangeClosing(this); | 
 |     } | 
 |     mDelegate = nullptr; | 
 |  | 
 |     // Closure of an exchange context is based on ref counting. The Protocol, when it calls DoClose(), indicates that | 
 |     // it is done with the exchange context and the message layer sets all callbacks to NULL and does not send anything | 
 |     // received on the exchange context up to higher layers.  At this point, the message layer needs to handle the | 
 |     // remaining work to be done on that exchange, (e.g. send all pending acks) before truly cleaning it up. | 
 |     FlushAcks(); | 
 |  | 
 |     // In case the protocol wants a harder release of the EC right away, such as calling Abort(), exchange | 
 |     // needs to clear the MRP retransmission table immediately. | 
 |     if (clearRetransTable) | 
 |     { | 
 |         mExchangeMgr->GetReliableMessageMgr()->ClearRetransTable(this); | 
 |     } | 
 |  | 
 |     if (IsResponseExpected()) | 
 |     { | 
 |         // Cancel the response timer. | 
 |         CancelResponseTimer(); | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  *  Gracefully close an exchange context. This call decrements the | 
 |  *  reference count and releases the exchange when the reference | 
 |  *  count goes to zero. | 
 |  * | 
 |  */ | 
 | void ExchangeContext::Close() | 
 | { | 
 |     VerifyOrDie(mExchangeMgr != nullptr && GetReferenceCount() > 0); | 
 |  | 
 | #if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) | 
 |     ChipLogDetail(ExchangeManager, "ec - close[" ChipLogFormatExchange "], %s", ChipLogValueExchange(this), __func__); | 
 | #endif | 
 |  | 
 |     DoClose(false); | 
 |     Release(); | 
 | } | 
 | /** | 
 |  *  Abort the Exchange context immediately and release all | 
 |  *  references to it. | 
 |  * | 
 |  */ | 
 | void ExchangeContext::Abort() | 
 | { | 
 |     VerifyOrDie(mExchangeMgr != nullptr && GetReferenceCount() > 0); | 
 |  | 
 | #if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) | 
 |     ChipLogDetail(ExchangeManager, "ec - abort[" ChipLogFormatExchange "], %s", ChipLogValueExchange(this), __func__); | 
 | #endif | 
 |  | 
 |     DoClose(true); | 
 |     Release(); | 
 | } | 
 |  | 
 | void ExchangeContextDeletor::Release(ExchangeContext * ec) | 
 | { | 
 |     ec->mExchangeMgr->ReleaseContext(ec); | 
 | } | 
 |  | 
 | ExchangeContext::ExchangeContext(ExchangeManager * em, uint16_t ExchangeId, const SessionHandle & session, bool Initiator, | 
 |                                  ExchangeDelegate * delegate, bool isEphemeralExchange) : | 
 |     mDispatch(GetMessageDispatch(isEphemeralExchange, delegate)), | 
 |     mSession(*this) | 
 | { | 
 |     VerifyOrDieWithObject(mExchangeMgr == nullptr, this); | 
 |  | 
 |     mExchangeMgr = em; | 
 |     mExchangeId  = ExchangeId; | 
 |     mSession.Grab(session); | 
 |     mFlags.Set(Flags::kFlagInitiator, Initiator); | 
 |     mFlags.Set(Flags::kFlagEphemeralExchange, isEphemeralExchange); | 
 |     mDelegate = delegate; | 
 |  | 
 |     // | 
 |     // If we're an initiator and we just created this exchange, we obviously did so to send a message. Let's go ahead and | 
 |     // set the flag on this to correctly mark it as so. | 
 |     // | 
 |     // This only applies to non-ephemeral exchanges. Ephemeral exchanges do not have an intention of sending out a message | 
 |     // since they're created expressly for the purposes of sending out a standalone ACK when the message could not be handled | 
 |     // through normal means. | 
 |     // | 
 |     if (Initiator && !isEphemeralExchange) | 
 |     { | 
 |         WillSendMessage(); | 
 |     } | 
 |  | 
 |     SetAckPending(false); | 
 |  | 
 |     // Try to use MRP by default, if it is allowed. | 
 |     SetAutoRequestAck(session->AllowsMRP()); | 
 |  | 
 | #if CHIP_CONFIG_ENABLE_ICD_SERVER | 
 |     // TODO(#33075) : Add check for group context to not a req since it serves no purpose | 
 |     app::ICDNotifier::GetInstance().NotifyActiveRequestNotification(app::ICDListener::KeepActiveFlag::kExchangeContextOpen); | 
 | #endif | 
 |  | 
 | #if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) | 
 |     ChipLogDetail(ExchangeManager, "ec++ id: " ChipLogFormatExchange, ChipLogValueExchange(this)); | 
 | #endif | 
 |     SYSTEM_STATS_INCREMENT(chip::System::Stats::kExchangeMgr_NumContexts); | 
 | } | 
 |  | 
 | ExchangeContext::~ExchangeContext() | 
 | { | 
 |     VerifyOrDieWithObject(mExchangeMgr != nullptr && GetReferenceCount() == 0, this); | 
 |  | 
 |     // | 
 |     // Ensure that DoClose has been called by the time we get here. If not, we have a leak somewhere. | 
 |     // | 
 |     VerifyOrDieWithObject(mFlags.Has(Flags::kFlagClosed), this); | 
 |  | 
 | #if CHIP_CONFIG_ENABLE_ICD_SERVER | 
 |     // TODO(#33075) : Add check for group context to not a req since it serves no purpose | 
 |     app::ICDNotifier::GetInstance().NotifyActiveRequestWithdrawal(app::ICDListener::KeepActiveFlag::kExchangeContextOpen); | 
 | #endif // CHIP_CONFIG_ENABLE_ICD_SERVER | 
 |  | 
 |     // Ideally, in this scenario, the retransmit table should | 
 |     // be clear of any outstanding messages for this context and | 
 |     // the boolean parameter passed to DoClose() should not matter. | 
 |  | 
 |     DoClose(false); | 
 |     mExchangeMgr = nullptr; | 
 |  | 
 | #if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) | 
 |     ChipLogDetail(ExchangeManager, "ec-- id: " ChipLogFormatExchange, ChipLogValueExchange(this)); | 
 | #endif | 
 |     SYSTEM_STATS_DECREMENT(chip::System::Stats::kExchangeMgr_NumContexts); | 
 | } | 
 |  | 
 | bool ExchangeContext::MatchExchange(const SessionHandle & session, const PacketHeader & packetHeader, | 
 |                                     const PayloadHeader & payloadHeader) | 
 | { | 
 |     // A given message is part of a particular exchange if... | 
 |     return | 
 |  | 
 |         // The exchange identifier of the message matches the exchange identifier of the context. | 
 |         (mExchangeId == payloadHeader.GetExchangeID()) | 
 |  | 
 |         // AND The Session associated with the incoming message matches the Session associated with the exchange. | 
 |         && (mSession.Contains(session)) | 
 |  | 
 |         // TODO: This check should be already implied by the equality of session check, | 
 |         // It should be removed after we have implemented the temporary node id for PASE and CASE sessions | 
 |         && (IsEncryptionRequired() == packetHeader.IsEncrypted()) | 
 |  | 
 |         // AND The message was sent by an initiator and the exchange context is a responder (IsInitiator==false) | 
 |         //    OR The message was sent by a responder and the exchange context is an initiator (IsInitiator==true) (for the broadcast | 
 |         //    case, the initiator is ill defined) | 
 |  | 
 |         && (payloadHeader.IsInitiator() != IsInitiator()); | 
 | } | 
 |  | 
 | void ExchangeContext::OnSessionReleased() | 
 | { | 
 |     if (ShouldIgnoreSessionRelease()) | 
 |     { | 
 |         return; | 
 |     } | 
 |  | 
 |     if (mFlags.Has(Flags::kFlagClosed)) | 
 |     { | 
 |         // Exchange is already being closed. It may occur when closing an exchange after sending | 
 |         // RemoveFabric response which triggers removal of all sessions for the given fabric. | 
 |         mExchangeMgr->GetReliableMessageMgr()->ClearRetransTable(this); | 
 |         return; | 
 |     } | 
 |  | 
 |     // Hold a ref to ourselves so we can make calls into our delegate that might | 
 |     // decrease our refcount without worrying about use-after-free. | 
 |     ExchangeHandle ref(*this); | 
 |  | 
 |     // | 
 |     // If a send is not expected (either because we're waiting for a response OR | 
 |     // we're in the middle of processing a OnMessageReceived call), we can go ahead | 
 |     // and notify our delegate and abort the exchange since we still own the ref. | 
 |     // | 
 |     if (!IsSendExpected()) | 
 |     { | 
 |         if (IsResponseExpected()) | 
 |         { | 
 |             // If we're waiting on a response, we now know it's never going to show up | 
 |             // and we should notify our delegate accordingly. | 
 |             CancelResponseTimer(); | 
 |             // We want to Abort, not just Close, so that RMP bits are cleared, so | 
 |             // don't let NotifyResponseTimeout close us. | 
 |             NotifyResponseTimeout(/* aCloseIfNeeded = */ false); | 
 |         } | 
 |  | 
 |         Abort(); | 
 |     } | 
 |     else | 
 |     { | 
 |         DoClose(true /* clearRetransTable */); | 
 |     } | 
 | } | 
 |  | 
 | CHIP_ERROR ExchangeContext::StartResponseTimer() | 
 | { | 
 |     System::Layer * lSystemLayer = mExchangeMgr->GetSessionManager()->SystemLayer(); | 
 |     if (lSystemLayer == nullptr) | 
 |     { | 
 |         // this is an assertion error, which shall never happen | 
 |         return CHIP_ERROR_INTERNAL; | 
 |     } | 
 |  | 
 |     return lSystemLayer->StartTimer(mResponseTimeout, HandleResponseTimeout, this); | 
 | } | 
 |  | 
 | void ExchangeContext::CancelResponseTimer() | 
 | { | 
 |     System::Layer * lSystemLayer = mExchangeMgr->GetSessionManager()->SystemLayer(); | 
 |     if (lSystemLayer == nullptr) | 
 |     { | 
 |         // this is an assertion error, which shall never happen | 
 |         return; | 
 |     } | 
 |  | 
 |     lSystemLayer->CancelTimer(HandleResponseTimeout, this); | 
 | } | 
 |  | 
 | void ExchangeContext::HandleResponseTimeout(System::Layer * aSystemLayer, void * aAppState) | 
 | { | 
 |     ExchangeContext * ec = reinterpret_cast<ExchangeContext *>(aAppState); | 
 |  | 
 |     if (ec == nullptr) | 
 |         return; | 
 |  | 
 |     ec->NotifyResponseTimeout(/* aCloseIfNeeded = */ true); | 
 | } | 
 |  | 
 | void ExchangeContext::NotifyResponseTimeout(bool aCloseIfNeeded) | 
 | { | 
 |     // Grab the value of WaitingForResponseOrAck() before we mess with our state. | 
 |     bool gotMRPAck = !WaitingForResponseOrAck(); | 
 |  | 
 |     SetResponseExpected(false); | 
 |  | 
 |     // Hold a ref to ourselves so we can make calls into our delegate that might | 
 |     // decrease our refcount (e.g. by expiring out session) without worrying | 
 |     // about use-after-free. | 
 |     ExchangeHandle ref(*this); | 
 |  | 
 |     // mSession might be null if this timeout is due to the session being | 
 |     // evicted. | 
 |     if (mSession) | 
 |     { | 
 |         // If we timed out _after_ getting an ack for the message, that means | 
 |         // the session is probably fine (since our message and the ack got | 
 |         // through), so don't mark the session defunct if we got an MRP ack. | 
 |         if (!gotMRPAck) | 
 |         { | 
 |             if (mSession->IsSecureSession() && mSession->AsSecureSession()->IsCASESession()) | 
 |             { | 
 |                 mSession->AsSecureSession()->MarkAsDefunct(); | 
 |             } | 
 |             mSession->NotifySessionHang(); | 
 |         } | 
 |     } | 
 |  | 
 |     ExchangeDelegate * delegate = GetDelegate(); | 
 |  | 
 |     // Call the user's timeout handler. | 
 |     if (delegate != nullptr) | 
 |     { | 
 |         delegate->OnResponseTimeout(this); | 
 |     } | 
 |  | 
 |     if (aCloseIfNeeded) | 
 |     { | 
 |         MessageHandled(); | 
 |     } | 
 | } | 
 |  | 
 | CHIP_ERROR ExchangeContext::HandleMessage(uint32_t messageCounter, const PayloadHeader & payloadHeader, MessageFlags msgFlags, | 
 |                                           PacketBufferHandle && msgBuf) | 
 | { | 
 |     // We hold a reference to the ExchangeContext here to | 
 |     // guard against Close() calls(decrementing the reference | 
 |     // count) by the protocol before the CHIP Exchange | 
 |     // layer has completed its work on the ExchangeContext. | 
 |     ExchangeHandle ref(*this); | 
 |  | 
 |     bool isStandaloneAck = payloadHeader.HasMessageType(Protocols::SecureChannel::MsgType::StandaloneAck); | 
 |     bool isDuplicate     = msgFlags.Has(MessageFlagValues::kDuplicateMessage); | 
 |  | 
 |     auto deferred = MakeDefer([&]() { | 
 |         // Duplicates and standalone acks are not application-level messages, so they should generally not lead to any state | 
 |         // changes.  The one exception to that is that if we have a null mDelegate then our lifetime is not application-defined, | 
 |         // since we don't interact with the application at that point.  That can happen when we are already closed (in which case | 
 |         // MessageHandled is a no-op) or if we were just created to send a standalone ack for this incoming message, in which case | 
 |         // we should treat it as an app-level message for purposes of our state. | 
 |         if ((isStandaloneAck || isDuplicate) && mDelegate != nullptr) | 
 |         { | 
 |             return; | 
 |         } | 
 |  | 
 |         MessageHandled(); | 
 |     }); | 
 |  | 
 |     if (mSession->AllowsMRP()) | 
 |     { | 
 |         if (mDispatch.IsReliableTransmissionAllowed()) | 
 |         { | 
 |             if (!msgFlags.Has(MessageFlagValues::kDuplicateMessage) && payloadHeader.IsAckMsg() && | 
 |                 payloadHeader.GetAckMessageCounter().HasValue()) | 
 |             { | 
 |                 HandleRcvdAck(payloadHeader.GetAckMessageCounter().Value()); | 
 |             } | 
 |  | 
 |             if (payloadHeader.NeedsAck()) | 
 |             { | 
 |                 // An acknowledgment needs to be sent back to the peer for this message on this exchange, | 
 |                 HandleNeedsAck(messageCounter, msgFlags); | 
 |             } | 
 |         } | 
 |  | 
 |         if (IsAckPending() && !mDelegate) | 
 |         { | 
 |             // The incoming message wants an ack, but we have no delegate, so | 
 |             // there's not going to be a response to piggyback on.  Just flush the | 
 |             // ack out right now. | 
 |             ReturnErrorOnFailure(FlushAcks()); | 
 |         } | 
 |  | 
 |         // The SecureChannel::StandaloneAck message type is only used for MRP; do not pass such messages to the application layer. | 
 |         if (isStandaloneAck) | 
 |         { | 
 |             return CHIP_NO_ERROR; | 
 |         } | 
 |     } // AllowsMRP | 
 |  | 
 |     // Since the message is duplicate, let's not forward it up the stack | 
 |     if (isDuplicate) | 
 |     { | 
 |         return CHIP_NO_ERROR; | 
 |     } | 
 |  | 
 |     if (mSession->AllowsMRP()) | 
 |     { | 
 |         if (IsEphemeralExchange()) | 
 |         { | 
 |             // The EphemeralExchange has done its job, since StandaloneAck is sent in previous FlushAcks() call. | 
 |             return CHIP_NO_ERROR; | 
 |         } | 
 |  | 
 |         if (IsWaitingForAck()) | 
 |         { | 
 |             // The only way we can get here is a spec violation on the other side: | 
 |             // we sent a message that needs an ack, and the other side responded | 
 |             // with a message that does not contain an ack for the message we sent. | 
 |             // Just drop this message; if we delivered it to our delegate it might | 
 |             // try to send another message-needing-an-ack in response, which would | 
 |             // violate our internal invariants. | 
 |             ChipLogError(ExchangeManager, "Dropping message without piggyback ack when we are waiting for an ack."); | 
 |             return CHIP_ERROR_INCORRECT_STATE; | 
 |         } | 
 |     } // AllowsMRP | 
 |  | 
 | #if CHIP_CONFIG_ENABLE_ICD_SERVER | 
 |     // message received | 
 |     app::ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); | 
 | #endif // CHIP_CONFIG_ENABLE_ICD_SERVER | 
 |  | 
 |     // Set kFlagReceivedAtLeastOneMessage to true since we have received at least one new application level message | 
 |     SetHasReceivedAtLeastOneMessage(true); | 
 |  | 
 |     // Don't send messages on to our delegate if our dispatch does not allow | 
 |     // those messages.  Those messages should also not be treated as responses, | 
 |     // since if our delegate is expecting a response we will not notify it about | 
 |     // these messages. | 
 |     if (mDispatch.MessagePermitted(payloadHeader.GetProtocolID(), payloadHeader.GetMessageType())) | 
 |     { | 
 |         if (IsResponseExpected()) | 
 |         { | 
 |             // Since we got the response, cancel the response timer. | 
 |             CancelResponseTimer(); | 
 |  | 
 |             // If the context was expecting a response to a previously sent message, this message | 
 |             // is implicitly that response. | 
 |             SetResponseExpected(false); | 
 |         } | 
 |  | 
 |         if (mDelegate != nullptr) | 
 |         { | 
 |             return mDelegate->OnMessageReceived(this, payloadHeader, std::move(msgBuf)); | 
 |         } | 
 |     } | 
 |  | 
 |     DefaultOnMessageReceived(this, payloadHeader.GetProtocolID(), payloadHeader.GetMessageType(), messageCounter, | 
 |                              std::move(msgBuf)); | 
 |     return CHIP_NO_ERROR; | 
 | } | 
 |  | 
 | void ExchangeContext::MessageHandled() | 
 | { | 
 |     if (mFlags.Has(Flags::kFlagClosed) || IsResponseExpected() || IsSendExpected()) | 
 |     { | 
 |         return; | 
 |     } | 
 |  | 
 |     Close(); | 
 | } | 
 |  | 
 | ExchangeMessageDispatch & ExchangeContext::GetMessageDispatch(bool isEphemeralExchange, ExchangeDelegate * delegate) | 
 | { | 
 |     if (isEphemeralExchange) | 
 |         return EphemeralExchangeDispatch::Instance(); | 
 |  | 
 |     if (delegate != nullptr) | 
 |         return delegate->GetMessageDispatch(); | 
 |  | 
 |     return ApplicationExchangeDispatch::Instance(); | 
 | } | 
 |  | 
 | void ExchangeContext::AbortAllOtherCommunicationOnFabric() | 
 | { | 
 |     if (!mSession || !mSession->IsSecureSession()) | 
 |     { | 
 |         ChipLogError(ExchangeManager, "AbortAllOtherCommunicationOnFabric called when we don't have a PASE/CASE session"); | 
 |         return; | 
 |     } | 
 |  | 
 |     // Save our session so it does not actually go away. | 
 |     Optional<SessionHandle> session = mSession.Get(); | 
 |  | 
 |     SetIgnoreSessionRelease(true); | 
 |  | 
 |     GetExchangeMgr()->GetSessionManager()->ExpireAllSessionsForFabric(mSession->GetFabricIndex()); | 
 |  | 
 |     mSession.GrabExpiredSession(session.Value()); | 
 |  | 
 |     SetIgnoreSessionRelease(false); | 
 | } | 
 |  | 
 | void ExchangeContext::ExchangeSessionHolder::GrabExpiredSession(const SessionHandle & session) | 
 | { | 
 |     VerifyOrDieWithObject(session->AsSecureSession()->IsPendingEviction(), this); | 
 |     GrabUnchecked(session); | 
 | } | 
 |  | 
 | #if INET_CONFIG_ENABLE_TCP_ENDPOINT | 
 | void ExchangeContext::OnSessionConnectionClosed(CHIP_ERROR conErr) | 
 | { | 
 |     // TODO: Handle connection closure at the ExchangeContext level. | 
 | } | 
 | #endif // INET_CONFIG_ENABLE_TCP_ENDPOINT | 
 |  | 
 | } // namespace Messaging | 
 | } // namespace chip |