| /* |
| * |
| * 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. |
| * |
| */ |
| #ifndef __STDC_FORMAT_MACROS |
| #define __STDC_FORMAT_MACROS |
| #endif |
| |
| #ifndef __STDC_LIMIT_MACROS |
| #define __STDC_LIMIT_MACROS |
| #endif |
| |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| |
| #include <core/CHIPCore.h> |
| #include <core/CHIPEncoding.h> |
| #include <core/CHIPKeyIds.h> |
| #include <messaging/ExchangeContext.h> |
| #include <messaging/ExchangeMgr.h> |
| #include <protocols/Protocols.h> |
| #include <protocols/secure_channel/Constants.h> |
| #include <support/logging/CHIPLogging.h> |
| #include <system/SystemTimer.h> |
| |
| using namespace chip::Encoding; |
| using namespace chip::Inet; |
| using namespace chip::System; |
| |
| namespace chip { |
| namespace Messaging { |
| |
| static void DefaultOnMessageReceived(ExchangeContext * ec, const PacketHeader & packetHeader, Protocols::Id protocolId, |
| uint8_t msgType, PacketBufferHandle && payload) |
| { |
| ChipLogError(ExchangeManager, "Dropping unexpected message %08" PRIX32 ":%d %04" PRIX16 " MsgId:%08" PRIX32, |
| protocolId.ToFullyQualifiedSpecForm(), msgType, ec->GetExchangeId(), packetHeader.GetMessageId()); |
| } |
| |
| 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); |
| } |
| |
| void ExchangeContext::SetResponseTimeout(Timeout timeout) |
| { |
| mResponseTimeout = timeout; |
| } |
| |
| CHIP_ERROR ExchangeContext::SendMessage(Protocols::Id protocolId, uint8_t msgType, PacketBufferHandle && msgBuf, |
| const SendFlags & sendFlags) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| Transport::PeerConnectionState * state = nullptr; |
| |
| VerifyOrReturnError(mExchangeMgr != nullptr, CHIP_ERROR_INTERNAL); |
| |
| // 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. |
| Retain(); |
| |
| bool reliableTransmissionRequested = true; |
| |
| state = mExchangeMgr->GetSessionMgr()->GetPeerConnectionState(mSecureSession); |
| // If sending via UDP and NoAutoRequestAck send flag is not specificed, request reliable transmission. |
| if (state != nullptr && state->GetPeerAddress().GetTransportType() != Transport::Type::kUdp) |
| { |
| reliableTransmissionRequested = false; |
| } |
| else |
| { |
| reliableTransmissionRequested = !sendFlags.Has(SendMessageFlags::kNoAutoRequestAck); |
| } |
| |
| // If a response message is expected... |
| if (sendFlags.Has(SendMessageFlags::kExpectResponse)) |
| { |
| // Only one 'response expected' message can be outstanding at a time. |
| VerifyOrExit(!IsResponseExpected(), err = CHIP_ERROR_INCORRECT_STATE); |
| |
| SetResponseExpected(true); |
| |
| // Arm the response timer if a timeout has been specified. |
| if (mResponseTimeout > 0) |
| { |
| err = StartResponseTimer(); |
| SuccessOrExit(err); |
| } |
| } |
| |
| err = mDispatch->SendMessage(mSecureSession, mExchangeId, IsInitiator(), GetReliableMessageContext(), |
| reliableTransmissionRequested, protocolId, msgType, std::move(msgBuf)); |
| |
| exit: |
| if (err != CHIP_NO_ERROR && IsResponseExpected()) |
| { |
| CancelResponseTimer(); |
| SetResponseExpected(false); |
| } |
| |
| // Release the reference to the exchange context acquired above. Under normal circumstances |
| // this will merely decrement the reference count, without actually freeing the exchange context. |
| // However if one of the function calls in this method resulted in a callback to the protocol, |
| // the protocol may have released its reference, resulting in the exchange context actually |
| // being freed here. |
| Release(); |
| |
| return err; |
| } |
| |
| void ExchangeContext::DoClose(bool clearRetransTable) |
| { |
| // 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(static_cast<ReliableMessageContext *>(this)); |
| } |
| |
| // 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 id: %d [%04" PRIX16 "], %s", (this - mExchangeMgr->mContextPool.begin()), mExchangeId, |
| __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 id: %d [%04" PRIX16 "], %s", (this - mExchangeMgr->mContextPool.begin()), mExchangeId, |
| __func__); |
| #endif |
| |
| DoClose(true); |
| Release(); |
| } |
| |
| void ExchangeContextDeletor::Release(ExchangeContext * ec) |
| { |
| ec->mExchangeMgr->ReleaseContext(ec); |
| } |
| |
| ExchangeContext::ExchangeContext(ExchangeManager * em, uint16_t ExchangeId, SecureSessionHandle session, bool Initiator, |
| ExchangeDelegate * delegate) |
| { |
| VerifyOrDie(mExchangeMgr == nullptr); |
| |
| mExchangeMgr = em; |
| mExchangeId = ExchangeId; |
| mSecureSession = session; |
| mFlags.Set(Flags::kFlagInitiator, Initiator); |
| mDelegate = delegate; |
| |
| ExchangeMessageDispatch * dispatch = nullptr; |
| if (delegate != nullptr) |
| { |
| dispatch = delegate->GetMessageDispatch(em->GetReliableMessageMgr(), em->GetSessionMgr()); |
| if (dispatch == nullptr) |
| { |
| dispatch = &em->mDefaultExchangeDispatch; |
| } |
| } |
| else |
| { |
| dispatch = &em->mDefaultExchangeDispatch; |
| } |
| VerifyOrDie(dispatch != nullptr); |
| mDispatch = dispatch->Retain(); |
| |
| SetDropAckDebug(false); |
| SetAckPending(false); |
| SetMsgRcvdFromPeer(false); |
| SetAutoRequestAck(true); |
| |
| #if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) |
| ChipLogDetail(ExchangeManager, "ec++ id: %d", ExchangeId); |
| #endif |
| SYSTEM_STATS_INCREMENT(chip::System::Stats::kExchangeMgr_NumContexts); |
| } |
| |
| ExchangeContext::~ExchangeContext() |
| { |
| VerifyOrDie(mExchangeMgr != nullptr && GetReferenceCount() == 0); |
| |
| // 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 (mExchangeACL != nullptr) |
| { |
| chip::Platform::Delete(mExchangeACL); |
| mExchangeACL = nullptr; |
| } |
| |
| if (mDispatch != nullptr) |
| { |
| mDispatch->Release(); |
| mDispatch = nullptr; |
| } |
| |
| #if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) |
| ChipLogDetail(ExchangeManager, "ec-- id: %d", mExchangeId); |
| #endif |
| SYSTEM_STATS_DECREMENT(chip::System::Stats::kExchangeMgr_NumContexts); |
| } |
| |
| bool ExchangeContext::MatchExchange(SecureSessionHandle 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 message was received from the peer node associated with the exchange |
| && (mSecureSession == session) |
| |
| // AND The message's source Node ID matches the peer Node ID associated with the exchange, or the peer Node ID of the |
| // exchange is 'any'. |
| && ((mSecureSession.GetPeerNodeId() == kAnyNodeId) || |
| (packetHeader.GetSourceNodeId().HasValue() && mSecureSession.GetPeerNodeId() == packetHeader.GetSourceNodeId().Value())) |
| |
| // 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::OnConnectionExpired() |
| { |
| // Reset our mSecureSession to a default-initialized (hence not matching any |
| // connection state) value, because it's still referencing the now-expired |
| // connection. This will mean that no more messages can be sent via this |
| // exchange, which seems fine given the semantics of connection expiration. |
| mSecureSession = SecureSessionHandle(); |
| |
| if (!IsResponseExpected()) |
| { |
| // Nothing to do in this case |
| return; |
| } |
| |
| // 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(); |
| SetResponseExpected(false); |
| NotifyResponseTimeout(); |
| } |
| |
| CHIP_ERROR ExchangeContext::StartResponseTimer() |
| { |
| System::Layer * lSystemLayer = mExchangeMgr->GetSessionMgr()->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->GetSessionMgr()->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, System::Error aError) |
| { |
| ExchangeContext * ec = reinterpret_cast<ExchangeContext *>(aAppState); |
| |
| if (ec == nullptr) |
| return; |
| |
| // NOTE: we don't set mResponseExpected to false here because the response could still arrive. If the user |
| // wants to never receive the response, they must close the exchange context. |
| |
| ec->NotifyResponseTimeout(); |
| } |
| |
| void ExchangeContext::NotifyResponseTimeout() |
| { |
| ExchangeDelegate * delegate = GetDelegate(); |
| |
| // Call the user's timeout handler. |
| if (delegate != nullptr) |
| { |
| delegate->OnResponseTimeout(this); |
| } |
| } |
| |
| CHIP_ERROR ExchangeContext::HandleMessage(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, |
| const Transport::PeerAddress & peerAddress, 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. |
| Retain(); |
| |
| CHIP_ERROR err = mDispatch->OnMessageReceived(payloadHeader, packetHeader.GetMessageId(), peerAddress, msgFlags, |
| GetReliableMessageContext()); |
| SuccessOrExit(err); |
| |
| // The SecureChannel::StandaloneAck message type is only used for MRP; do not pass such messages to the application layer. |
| if (payloadHeader.HasMessageType(Protocols::SecureChannel::MsgType::StandaloneAck)) |
| { |
| ExitNow(err = CHIP_NO_ERROR); |
| } |
| |
| // Since the message is duplicate, let's not forward it up the stack |
| if (msgFlags.Has(MessageFlagValues::kDuplicateMessage)) |
| { |
| ExitNow(err = CHIP_NO_ERROR); |
| } |
| |
| // 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) |
| { |
| err = mDelegate->OnMessageReceived(this, packetHeader, payloadHeader, std::move(msgBuf)); |
| } |
| else |
| { |
| DefaultOnMessageReceived(this, packetHeader, payloadHeader.GetProtocolID(), payloadHeader.GetMessageType(), |
| std::move(msgBuf)); |
| } |
| |
| exit: |
| // Release the reference to the ExchangeContext that was held at the beginning of this function. |
| // This call should also do the needful of closing the ExchangeContext if the protocol has |
| // already made a prior call to Close(). |
| Release(); |
| |
| return err; |
| } |
| |
| } // namespace Messaging |
| } // namespace chip |