blob: 611e2039b886b51a34d36101a4587861dd7f41b9 [file] [log] [blame]
/*
* Copyright (c) 2022 Project CHIP Authors
* All rights reserved.
*
* 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 unit tests for aborting existing exchanges (except
* one) for a fabric.
*/
#include "messaging/ExchangeDelegate.h"
#include "system/SystemClock.h"
#include <lib/support/UnitTestContext.h>
#include <lib/support/UnitTestRegistration.h>
#include <lib/support/UnitTestUtils.h>
#include <messaging/ExchangeContext.h>
#include <messaging/ExchangeHolder.h>
#include <messaging/ExchangeMgr.h>
#include <messaging/tests/MessagingContext.h>
#include <protocols/Protocols.h>
#include <system/SystemPacketBuffer.h>
#include <transport/SessionManager.h>
namespace chip {
namespace Protocols {
//
// Let's create a mock protocol that encapsulates a 3 message exchange to test out the ExchangeHolder
// and the various states the underlying exchange might be set to, altering the clean-up behavior
// the holder will execute depending on those states.
//
namespace MockProtocol {
static constexpr Id Id(VendorId::TestVendor1, 1);
enum class MessageType : uint8_t
{
kMsg1 = 0x01,
kMsg2 = 0x02,
kMsg3 = 0x03
};
} // namespace MockProtocol
template <>
struct MessageTypeTraits<MockProtocol::MessageType>
{
static constexpr const Protocols::Id & ProtocolId() { return MockProtocol::Id; }
};
} // namespace Protocols
} // namespace chip
namespace {
using namespace chip;
using namespace chip::Messaging;
using namespace chip::System;
using namespace chip::Protocols;
using TestContext = Test::LoopbackMessagingContext;
TestContext * gCtx = nullptr;
class MockProtocolResponder : public ExchangeDelegate, public Messaging::UnsolicitedMessageHandler
{
public:
enum class BehaviorModifier : uint8_t
{
kNone = 0x00,
kHoldMsg2 = 0x01,
kErrMsg2 = 0x02,
kExpireSessionBeforeMsg2Send = 0x04,
kExpireSessionAfterMsg2Send = 0x08,
kExpireSessionAfterMsg3Receive = 0x10,
};
template <typename... Args>
MockProtocolResponder(BehaviorModifier modifier1, Args &&... args) :
mExchangeCtx(*this), mBehaviorModifier(modifier1, std::forward<Args>(args)...)
{
VerifyOrDie(gCtx != nullptr);
gCtx->GetExchangeManager().RegisterUnsolicitedMessageHandlerForProtocol(chip::Protocols::MockProtocol::Id, this);
ChipLogDetail(ExchangeManager, "[%p] MockProtocolResponder: %p", this, &mExchangeCtx);
}
MockProtocolResponder(BehaviorModifier modifier = BehaviorModifier::kNone) : mExchangeCtx(*this)
{
VerifyOrDie(gCtx != nullptr);
mBehaviorModifier.Set(modifier);
gCtx->GetExchangeManager().RegisterUnsolicitedMessageHandlerForProtocol(chip::Protocols::MockProtocol::Id, this);
ChipLogDetail(ExchangeManager, "[%p] MockProtocolResponder: %p", this, &mExchangeCtx);
}
~MockProtocolResponder()
{
ChipLogDetail(ExchangeManager, "[%p] ~MockProtocolResponder", this);
gCtx->GetExchangeManager().UnregisterUnsolicitedMessageHandlerForProtocol(chip::Protocols::MockProtocol::Id);
}
bool DidInteractionSucceed() { return mInteractionSucceeded; }
private:
CHIP_ERROR OnMessageReceived(ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && buffer) override;
CHIP_ERROR OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader, ExchangeDelegate *& newDelegate) override
{
newDelegate = this;
return CHIP_NO_ERROR;
}
void OnResponseTimeout(ExchangeContext * ec) override {}
ExchangeHolder mExchangeCtx;
BitFlags<BehaviorModifier> mBehaviorModifier = BehaviorModifier::kNone;
bool mInteractionSucceeded = false;
};
class MockProtocolInitiator : public ExchangeDelegate
{
public:
enum class BehaviorModifier : uint8_t
{
kNone = 0x00,
kHoldMsg3 = 0x01,
kErrMsg1 = 0x02,
kErrMsg3 = 0x04,
kDontSendMsg1 = 0x08,
kExpireSessionBeforeMsg1Send = 0x10,
kExpireSessionAfterMsg1Send = 0x12,
kExpireSessionBeforeMsg3Send = 0x14,
kExpireSessionAfterMsg3Send = 0x18,
};
MockProtocolInitiator(BehaviorModifier modifier = BehaviorModifier::kNone) : mExchangeCtx(*this)
{
mBehaviorModifier.Set(modifier);
ChipLogDetail(ExchangeManager, "[%p] MockProtocolInitiator: %p", this, &mExchangeCtx);
}
template <typename... Args>
MockProtocolInitiator(BehaviorModifier modifier1, Args &&... args) :
mExchangeCtx(*this), mBehaviorModifier(modifier1, std::forward<Args>(args)...)
{
ChipLogDetail(ExchangeManager, "[%p] MockProtocolInitiator: %p", this, &mExchangeCtx);
}
~MockProtocolInitiator() { ChipLogDetail(ExchangeManager, "[%p] ~MockProtocolInitiator", this); }
CHIP_ERROR StartInteraction(SessionHandle & sessionHandle);
bool DidInteractionSucceed() { return mInteractionSucceeded; }
private:
CHIP_ERROR OnMessageReceived(ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && buffer) override;
void OnResponseTimeout(ExchangeContext * ec) override {}
ExchangeHolder mExchangeCtx;
BitFlags<BehaviorModifier> mBehaviorModifier = BehaviorModifier::kNone;
bool mInteractionSucceeded = false;
};
CHIP_ERROR MockProtocolResponder::OnMessageReceived(ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && buffer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
if (payloadHeader.HasMessageType(chip::Protocols::MockProtocol::MessageType::kMsg1))
{
//
// This is the first message in the exchange - let's have our holder start managing the exchange by grabbing it.
//
mExchangeCtx.Grab(ec);
if (!mBehaviorModifier.Has(BehaviorModifier::kHoldMsg2))
{
PacketBufferHandle respBuffer = MessagePacketBuffer::New(0);
VerifyOrReturnError(!buffer.IsNull(), CHIP_ERROR_NO_MEMORY);
if (mBehaviorModifier.Has(BehaviorModifier::kErrMsg2))
{
mExchangeCtx->InjectFailure(ExchangeContext::InjectedFailureType::kFailOnSend);
}
if (mBehaviorModifier.Has(BehaviorModifier::kExpireSessionBeforeMsg2Send))
{
mExchangeCtx->GetSessionHolder().Release();
mExchangeCtx->OnSessionReleased();
}
if (mExchangeCtx)
{
err = mExchangeCtx->SendMessage(chip::Protocols::MockProtocol::MessageType::kMsg2, std::move(respBuffer),
SendMessageFlags::kExpectResponse);
if (mExchangeCtx)
{
mExchangeCtx->ClearInjectedFailures();
}
ReturnErrorOnFailure(err);
}
if (mBehaviorModifier.Has(BehaviorModifier::kExpireSessionAfterMsg2Send))
{
mExchangeCtx->GetSessionHolder().Release();
mExchangeCtx->OnSessionReleased();
}
}
else
{
mExchangeCtx->WillSendMessage();
}
}
else if (payloadHeader.HasMessageType(chip::Protocols::MockProtocol::MessageType::kMsg3))
{
if (mBehaviorModifier.Has(BehaviorModifier::kExpireSessionAfterMsg3Receive))
{
mExchangeCtx->GetSessionHolder().Release();
mExchangeCtx->OnSessionReleased();
}
mInteractionSucceeded = true;
}
else
{
err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
}
return err;
}
CHIP_ERROR MockProtocolInitiator::StartInteraction(SessionHandle & sessionHandle)
{
PacketBufferHandle buffer = MessagePacketBuffer::New(0);
VerifyOrReturnError(!buffer.IsNull(), CHIP_ERROR_NO_MEMORY);
auto exchange = gCtx->GetExchangeManager().NewContext(sessionHandle, this);
VerifyOrReturnError(exchange != nullptr, CHIP_ERROR_NO_MEMORY);
//
// This is the first exchange in this interaction - let's have our holder start managing the exchange by grabbing it.
//
mExchangeCtx.Grab(exchange);
if (mBehaviorModifier.Has(BehaviorModifier::kErrMsg1))
{
mExchangeCtx->InjectFailure(ExchangeContext::InjectedFailureType::kFailOnSend);
}
if (mBehaviorModifier.Has(BehaviorModifier::kExpireSessionBeforeMsg1Send))
{
mExchangeCtx->GetSessionHolder().Release();
mExchangeCtx->OnSessionReleased();
}
if (!mBehaviorModifier.Has(BehaviorModifier::kDontSendMsg1))
{
auto err = mExchangeCtx->SendMessage(chip::Protocols::MockProtocol::MessageType::kMsg1, std::move(buffer),
SendMessageFlags::kExpectResponse);
if (mExchangeCtx)
{
mExchangeCtx->ClearInjectedFailures();
}
ReturnErrorOnFailure(err);
}
if (mBehaviorModifier.Has(BehaviorModifier::kExpireSessionAfterMsg1Send))
{
mExchangeCtx->GetSessionHolder().Release();
mExchangeCtx->OnSessionReleased();
}
return CHIP_NO_ERROR;
}
CHIP_ERROR MockProtocolInitiator::OnMessageReceived(ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && buffer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
if (payloadHeader.HasMessageType(chip::Protocols::MockProtocol::MessageType::kMsg2))
{
if (!mBehaviorModifier.Has(BehaviorModifier::kHoldMsg3))
{
if (mBehaviorModifier.Has(BehaviorModifier::kExpireSessionBeforeMsg3Send))
{
mExchangeCtx->GetSessionHolder().Release();
mExchangeCtx->OnSessionReleased();
}
PacketBufferHandle respBuffer = MessagePacketBuffer::New(0);
VerifyOrReturnError(!buffer.IsNull(), CHIP_ERROR_NO_MEMORY);
if (mBehaviorModifier.Has(BehaviorModifier::kErrMsg3))
{
mExchangeCtx->InjectFailure(ExchangeContext::InjectedFailureType::kFailOnSend);
}
if (mExchangeCtx)
{
err = mExchangeCtx->SendMessage(chip::Protocols::MockProtocol::MessageType::kMsg3, std::move(respBuffer),
SendMessageFlags::kNone);
if (mExchangeCtx)
{
mExchangeCtx->ClearInjectedFailures();
}
ReturnErrorOnFailure(err);
}
if (mBehaviorModifier.Has(BehaviorModifier::kExpireSessionAfterMsg3Send))
{
mExchangeCtx->GetSessionHolder().Release();
mExchangeCtx->OnSessionReleased();
}
mInteractionSucceeded = true;
}
else
{
mExchangeCtx->WillSendMessage();
}
}
else
{
err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
}
return err;
}
void TestExchangeHolder(nlTestSuite * inSuite, void * inContext)
{
TestContext & ctx = *reinterpret_cast<TestContext *>(inContext);
gCtx = &ctx;
auto sessionHandle = ctx.GetSessionAliceToBob();
ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive);
//
// #1: Initiator (AllocExchange)
//
// The initiator just allocated the exchange, but doesn't send a message on it.
//
// Then, destroy both objects. Initiator's holder should correctly abort the exchange since it still owns
// it.
//
{
ChipLogProgress(ExchangeManager, "-------- #1: Initiator (AllocExchange) ----------");
{
MockProtocolInitiator initiator(MockProtocolInitiator::BehaviorModifier::kDontSendMsg1);
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #2: Initiator --X Msg1
//
// Inject a failure to transmit Msg1. This should retain the WillSendMessage flag on the initiator's exchange.
//
// Then, destroy both objects. Initiator's holder should correctly abort the exchange since it's still has the
// WillSendMessage flag on it.
//
//
{
ChipLogProgress(ExchangeManager, "-------- #2: Initiator --X (SendErr) Msg1 --------- ");
{
MockProtocolInitiator initiator(MockProtocolInitiator::BehaviorModifier::kErrMsg1);
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
}
//
// Service IO AFTER the objects above cease to exist to prevent Msg1 from getting to Responder. This also
// flush any pending messages in the queue.
//
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #3: Initiator --X (Session Released Before) -- Msg1
//
// Inject a release of the session associated with the exchange on the initiator before sending Msg1. This
// should just close out the exchange without releasing the ref.
//
// Then, destroy both objects. The initiator's holder should correctly abort the exchange since WillSendMessage
// should still be present on the EC.
//
{
ChipLogProgress(ExchangeManager, "-------- #3: Initiator --X (SessionReleased before) Msg1 --------- ");
{
MockProtocolInitiator initiator(MockProtocolInitiator::BehaviorModifier::kExpireSessionBeforeMsg1Send);
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
}
//
// Service IO AFTER the objects above cease to exist to prevent Msg1 from getting to Responder. This also
// flush any pending messages in the queue.
//
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #4: Initiator --X (SendErr + Session Released After) -- Msg1
//
// Inject an error at Msg1 transmission followed by the release of a session (scenario in #21544). This should
// just close out the exchange without releasing the ref since the WillSendMessage flag should still be set.
//
// Then, destroy both objects. The initiator's holder should correctly abort the exchange since WillSendMessage
// should still be present on the EC.
//
{
ChipLogProgress(ExchangeManager, "-------- #4: Initiator --X (SendErr + SessionReleased after) Msg1 --------- ");
{
MockProtocolInitiator initiator(MockProtocolInitiator::BehaviorModifier::kExpireSessionAfterMsg1Send,
MockProtocolInitiator::BehaviorModifier::kErrMsg1);
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
}
//
// Service IO AFTER the objects above cease to exist to prevent Msg1 from getting to Responder. This also
// flush any pending messages in the queue.
//
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #5: Initiator >--- Msg1 --X Responder.
//
// Initiator sends Msg1 to Responder, but we set it up such that Responder doesn't actually
// receive the message.
//
// Then, destroy both objects. Initiator's holder should correctly abort the exchange since it's waiting for
// a response.
//
{
ChipLogProgress(ExchangeManager, "-------- #5: Initiator >-- Msg1 --X Responder ---------");
{
MockProtocolInitiator initiator;
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
}
//
// Service IO AFTER the objects above cease to exist to prevent Msg1 from getting to Responder. This also
// flush any pending messages in the queue.
//
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #6: Initiator --- Msg1 --> Responder (WillSend)
//
// Initiator sends Msg1 to Responder, which is received successfully. However, Responder
// doesn't send a response right away (calls WillSendMessage() on the EC).
//
// Then, destroy both objects. Initiator's holder should correctly abort the exchange since it's waiting for
// a response, and so should the Responder's holder since it has yet to send a message.
//
{
{
ChipLogProgress(ExchangeManager, "-------- #6: Initiator >-- Msg1 --> Responder (WillSend) ---------");
MockProtocolInitiator initiator;
MockProtocolResponder responder(MockProtocolResponder::BehaviorModifier::kHoldMsg2);
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #7: Initiator --- Msg1 --> Responder
// Msg2 (SendErr) X-- Responder
//
// Inject an error in the responder when attempting to send Msg2.
//
// Then, destroy both objects. The holder on the responder should abort the exchange since
// the transmission failed, and the ref is still with the holder. The holder on the initiator
// should abort the exchange since it is waiting for a response.
//
//
{
{
ChipLogProgress(ExchangeManager, "-------- #7: Msg2 (SendFailure) X-- Responder ---------");
MockProtocolInitiator initiator;
MockProtocolResponder responder(MockProtocolResponder::BehaviorModifier::kErrMsg2);
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #8: Initiator --- Msg1 --> Responder
// Msg2 (SessionReleased before) X-- Responder
//
// Release the session right before sending Msg2 on the responder. This should abort the underlying exchange
// immediately since neither WillSendMessage or ResponseExpected flags are set.
//
// Then, destroy both objects. The holders on both should just null out their internal reference to the EC.
//
{
{
ChipLogProgress(ExchangeManager, "-------- #8: Msg2 (SessionReleased Before) X-- Responder ---------");
MockProtocolInitiator initiator;
MockProtocolResponder responder(MockProtocolResponder::BehaviorModifier::kExpireSessionBeforeMsg2Send);
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #9: Initiator --- Msg1 --> Responder
// Msg2 (SendErr + SessionReleased after) X-- Responder
//
// Trigger a send error when sending Msg2 from the responder, and release the session immediately after. This should still
// preserve the WillSendMessage flags on the exchange and just close out the EC without releasing the ref.
//
// Then, destroy both objects. The holders on both should abort their respective ECs.
//
{
{
ChipLogProgress(ExchangeManager, "-------- #9: Msg2 (SendErr + SessionReleased after) X-- Responder ---------");
MockProtocolInitiator initiator;
MockProtocolResponder responder(MockProtocolResponder::BehaviorModifier::kErrMsg2,
MockProtocolResponder::BehaviorModifier::kExpireSessionAfterMsg2Send);
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #10: Initiator --- Msg1 --> Responder
// (WillSend) Initiator <-- Msg2 <-- Responder
//
// Initiator receives Msg2 back from Responder, but calls WillSend on that EC.
//
// Then, destroy both objects. Initiator's holder should correctly abort the exchange since it's waiting
// to send a response, and Responder's holder should abort as well since it's waiting for a response.
//
{
{
ChipLogProgress(ExchangeManager, "-------- #10: (WillSend) Initiator <-- Msg2 <-- Responder ---------");
MockProtocolInitiator initiator(MockProtocolInitiator::BehaviorModifier::kHoldMsg3);
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #11: Initiator --- Msg1 --> Responder
// Initiator <-- Msg2 <-- Responder
// Initiator (SessionReleased before) X-- Msg3
//
// Release the session right before the initiator sends Msg3. This should abort the underlying EC immediately on the initiator.
//
// Then destroy both objects. Both holders on the initiator and responder should be pointing to null.
//
{
{
ChipLogProgress(ExchangeManager, "-------- #11: Initiator --X (SessionReleased before) Msg3 ------------");
MockProtocolInitiator initiator(MockProtocolInitiator::BehaviorModifier::kExpireSessionBeforeMsg3Send);
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #12: Initiator --- Msg1 --> Responder
// Initiator <-- Msg2 <-- Responder
// Initiator X (SendErr + SessionReleased after) -- Msg3
//
// Trigger a send error on the initiator when sending Msg3, followed by a session release. Since a send was initiated, the ref
// is with the initiator's holder and the EC will just close itself out without removing the ref.
//
// Then, destroy both objects. The responder's holder will have a null ref but the initiator's holder will have a non-null ref,
// and should abort it.
//
{
{
ChipLogProgress(ExchangeManager, "-------- #12: Initiator --X (SendErr + SessionReleased after) Msg3 ------------");
MockProtocolInitiator initiator(MockProtocolInitiator::BehaviorModifier::kErrMsg3,
MockProtocolInitiator::BehaviorModifier::kExpireSessionAfterMsg3Send);
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #13: Initiator --- Msg1 --> Responder
// Initiator <-- Msg2 <-- Responder
// Initiator >-- Msg3 --> Responder
//
// Initiator sends final message in exchange to Responder, which is received successfully.
//
// Then, destroy both objects. Initiator's holder should NOT abort the underlying exchange since
// it has sent the final message in the exchange, while responder's holder should NOT abor the underlying
// exchange either since it is not going to send any further messages on the exchange.
//
{
{
ChipLogProgress(ExchangeManager, "-------- #13: Initiator >-- Msg3 --> Responder ---------");
MockProtocolInitiator initiator;
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #14: Initiator --- Msg1 --> Responder
// Initiator <-- Msg2 <-- Responder
// Initiator >-- Msg3 --> Responder (SessionReleased)
//
// Released the session right on reception of Msg3 on the responder. Since there no responses expected or send expected,
// the EC aborts immediately.
//
// Then, destroy both objects. Both holders should be point to null and should do nothing.
//
{
{
ChipLogProgress(ExchangeManager, "-------- #14: Initiator >-- Msg3 --> Responder (SessionReleased) ---------");
MockProtocolInitiator initiator;
MockProtocolResponder responder(MockProtocolResponder::BehaviorModifier::kExpireSessionAfterMsg3Receive);
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
//
// Because of the session expiration right after Msg3 is received, it causes an abort of the underlying EC
// on the reponder side. This means that Msg3 won't be ACK'ed. Msg3 on the initiator side remains un-acked. Since
// the exchange was just closed and not aborted on the initiator side, it is still sitting in the retransmission table
// and consequently, the exchange still has a ref-count of 1. If we were to just check the number of active
// exchanges, it would still show 1 active exchange.
//
// This will only be released once the re-transmission table
// entry has been removed. To make this happen, drive the IO forward enough that a single re-transmission happens. This
// will result in a duplicate message ACK being delivered by the responder, causing the EC to finally get released.
//
ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(5),
[&]() { return ctx.GetExchangeManager().GetNumActiveExchanges() == 0; });
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #15: Initiator --- Msg1 --> Responder (WillSend)
// Initiator --- Msg1 --> Responder (WillSend)
//
// Similar to #6, except we have Initiator start the interaction again. This validates
// ExchangeHolder::Grab in correctly aborting a previous exchange and acquiring a new one.
//
// Then, destroy both objects. Both holders should abort the exchange (see #6).
//
{
{
ChipLogProgress(ExchangeManager, "-------- #15: Initiator >-- Msg1 --> Responder (WillSend) X2 ---------");
MockProtocolInitiator initiator;
MockProtocolResponder responder(MockProtocolResponder::BehaviorModifier::kHoldMsg2);
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
ctx.DrainAndServiceIO();
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
//
// #16: Initiator --- Msg1 --> Responder
// Initiator <-- Msg2 <-- Responder
// Initiator >-- Msg3 --> Responder
//
// X2
//
// We do the entire interaction twice. This validates ExchangeHolder::Grab in correctly releasing a reference
// to a previous exchange (but not aborting it) and acquiring a new one.
//
// Then, destroy both objects. Both holders should release their reference without aborting.
//
{
{
ChipLogProgress(ExchangeManager, "-------- #16: Initiator >-- Msg3 --> Responder X2 ---------");
MockProtocolInitiator initiator;
MockProtocolResponder responder;
auto err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
err = initiator.StartInteraction(sessionHandle);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
ctx.DrainAndServiceIO();
}
NL_TEST_ASSERT(inSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}
}
// Test Suite
/**
* Test Suite that lists all the test functions.
*/
// clang-format off
const nlTest sTests[] =
{
NL_TEST_DEF("TestExchangeHolder", TestExchangeHolder),
NL_TEST_SENTINEL()
};
// clang-format on
// clang-format off
nlTestSuite sSuite =
{
"Test-TestExchangeHolder",
&sTests[0],
TestContext::Initialize,
TestContext::Finalize
};
// clang-format on
} // anonymous namespace
/**
* Main
*/
int TestExchangeHolder()
{
return chip::ExecuteTestsWithContext<TestContext>(&sSuite);
}
CHIP_REGISTER_TEST_SUITE(TestExchangeHolder);