blob: 8f8eb825553bf576f63c0050cd93ef06fd552b86 [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 <gtest/gtest.h>
#include <app/icd/server/ICDServerConfig.h>
#include <messaging/ExchangeContext.h>
#include <messaging/ExchangeMgr.h>
#include <messaging/ReliableMessageProtocolConfig.h>
#include <messaging/tests/MessagingContext.h>
#include <protocols/echo/Echo.h>
#include <system/SystemPacketBuffer.h>
#include <transport/SessionManager.h>
#if CHIP_CONFIG_ENABLE_ICD_SERVER
#include <app/icd/server/ICDConfigurationData.h> // nogncheck
#endif
#if CHIP_CRYPTO_PSA
#include "psa/crypto.h"
#endif
namespace {
using namespace chip;
using namespace chip::Messaging;
using namespace chip::System;
using namespace chip::System::Clock::Literals;
using namespace chip::Protocols;
struct TestAbortExchangesForFabric : public chip::Test::LoopbackMessagingContext, public ::testing::Test
{
static void SetUpTestSuite() { chip::Test::LoopbackMessagingContext::SetUpTestSuite(); }
static void TearDownTestSuite() { chip::Test::LoopbackMessagingContext::TearDownTestSuite(); }
void SetUp() override
{
#if CHIP_CRYPTO_PSA
ASSERT_EQ(psa_crypto_init(), PSA_SUCCESS);
#endif
chip::Test::LoopbackMessagingContext::SetUp();
}
void TearDown() override { chip::Test::LoopbackMessagingContext::TearDown(); }
void CommonCheckAbortAllButOneExchange(bool dropResponseMessages);
};
class MockAppDelegate : public ExchangeDelegate
{
public:
CHIP_ERROR OnMessageReceived(ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && buffer) override
{
mOnMessageReceivedCalled = true;
return CHIP_NO_ERROR;
}
void OnResponseTimeout(ExchangeContext * ec) override {}
bool mOnMessageReceivedCalled = false;
};
void TestAbortExchangesForFabric::CommonCheckAbortAllButOneExchange(bool dropResponseMessages)
{
// We want to have two sessions using the same fabric id that we use for
// creating our exchange contexts. That lets us test exchanges on the same
// session as the "special exchange" as well as on other sessions.
auto & sessionManager = GetSecureSessionManager();
// Use key ids that are not going to collide with anything else that ctx is
// doing.
// TODO: These should really be CASE sessions...
SessionHolder session1;
CHIP_ERROR err = sessionManager.InjectCaseSessionWithTestKey(session1, 100, 101, GetAliceFabric()->GetNodeId(),
GetBobFabric()->GetNodeId(), GetAliceFabricIndex(),
GetBobAddress(), CryptoContext::SessionRole::kInitiator, {});
EXPECT_EQ(err, CHIP_NO_ERROR);
SessionHolder session1Reply;
err = sessionManager.InjectCaseSessionWithTestKey(session1Reply, 101, 100, GetBobFabric()->GetNodeId(),
GetAliceFabric()->GetNodeId(), GetBobFabricIndex(), GetAliceAddress(),
CryptoContext::SessionRole::kResponder, {});
EXPECT_EQ(err, CHIP_NO_ERROR);
// TODO: Ideally this would go to a different peer, but we don't have that
// set up right now: only Alice and Bob have useful node ids and whatnot.
SessionHolder session2;
err = sessionManager.InjectCaseSessionWithTestKey(session2, 200, 201, GetAliceFabric()->GetNodeId(),
GetBobFabric()->GetNodeId(), GetAliceFabricIndex(), GetBobAddress(),
CryptoContext::SessionRole::kInitiator, {});
EXPECT_EQ(err, CHIP_NO_ERROR);
SessionHolder session2Reply;
err = sessionManager.InjectCaseSessionWithTestKey(session2Reply, 101, 100, GetBobFabric()->GetNodeId(),
GetAliceFabric()->GetNodeId(), GetBobFabricIndex(), GetAliceAddress(),
CryptoContext::SessionRole::kResponder, {});
EXPECT_EQ(err, CHIP_NO_ERROR);
auto & exchangeMgr = GetExchangeManager();
MockAppDelegate delegate;
Echo::EchoServer server;
err = server.Init(&exchangeMgr);
EXPECT_EQ(err, CHIP_NO_ERROR);
auto & loopback = GetLoopback();
auto trySendMessage = [&](ExchangeContext * exchange, SendMessageFlags flags) {
PacketBufferHandle buffer = MessagePacketBuffer::New(0);
EXPECT_FALSE(buffer.IsNull());
return exchange->SendMessage(Echo::MsgType::EchoRequest, std::move(buffer), flags);
};
auto sendAndDropMessage = [&](ExchangeContext * exchange, SendMessageFlags flags) {
// Send a message on the given exchange with the given flags, make sure
// it's not delivered.
loopback.mNumMessagesToDrop = 1;
loopback.mDroppedMessageCount = 0;
err = trySendMessage(exchange, flags);
EXPECT_EQ(err, CHIP_NO_ERROR);
DrainAndServiceIO();
EXPECT_FALSE(delegate.mOnMessageReceivedCalled);
EXPECT_EQ(loopback.mDroppedMessageCount, 1u);
};
ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr();
// We want to test three possible exchange states:
// 1) Closed but waiting for ack.
// 2) Waiting for a response.
// 3) Waiting for a send.
auto * waitingForAck1 = exchangeMgr.NewContext(session1.Get().Value(), &delegate);
ASSERT_NE(waitingForAck1, nullptr);
sendAndDropMessage(waitingForAck1, SendMessageFlags::kNone);
EXPECT_EQ(rm->TestGetCountRetransTable(), 1);
auto * waitingForAck2 = exchangeMgr.NewContext(session2.Get().Value(), &delegate);
ASSERT_NE(waitingForAck2, nullptr);
sendAndDropMessage(waitingForAck2, SendMessageFlags::kNone);
EXPECT_EQ(rm->TestGetCountRetransTable(), 2);
auto * waitingForIncomingMessage1 = exchangeMgr.NewContext(session1.Get().Value(), &delegate);
ASSERT_NE(waitingForIncomingMessage1, nullptr);
sendAndDropMessage(waitingForIncomingMessage1, SendMessageFlags::kExpectResponse);
EXPECT_EQ(rm->TestGetCountRetransTable(), 3);
auto * waitingForIncomingMessage2 = exchangeMgr.NewContext(session2.Get().Value(), &delegate);
ASSERT_NE(waitingForIncomingMessage2, nullptr);
sendAndDropMessage(waitingForIncomingMessage2, SendMessageFlags::kExpectResponse);
EXPECT_EQ(rm->TestGetCountRetransTable(), 4);
auto * waitingForSend1 = exchangeMgr.NewContext(session1.Get().Value(), &delegate);
ASSERT_NE(waitingForSend1, nullptr);
waitingForSend1->WillSendMessage();
auto * waitingForSend2 = exchangeMgr.NewContext(session2.Get().Value(), &delegate);
ASSERT_NE(waitingForSend2, nullptr);
waitingForSend2->WillSendMessage();
// Grab handles to our sessions now, before we evict things.
const auto & sessionHandle1 = session1.Get();
const auto & sessionHandle2 = session2.Get();
session1->AsSecureSession()->SetRemoteSessionParameters(
ReliableMessageProtocolConfig(chip::Test::MessagingContext::kResponsiveIdleRetransTimeout,
chip::Test::MessagingContext::kResponsiveActiveRetransTimeout));
session1Reply->AsSecureSession()->SetRemoteSessionParameters(
ReliableMessageProtocolConfig(chip::Test::MessagingContext::kResponsiveIdleRetransTimeout,
chip::Test::MessagingContext::kResponsiveActiveRetransTimeout));
EXPECT_TRUE(session1);
EXPECT_TRUE(session2);
auto * specialExhange = exchangeMgr.NewContext(session1.Get().Value(), &delegate);
specialExhange->AbortAllOtherCommunicationOnFabric();
EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
EXPECT_FALSE(session1);
EXPECT_FALSE(session2);
EXPECT_EQ(exchangeMgr.NewContext(sessionHandle1.Value(), &delegate), nullptr);
EXPECT_EQ(exchangeMgr.NewContext(sessionHandle2.Value(), &delegate), nullptr);
// Make sure we can't send messages on any of the other exchanges.
EXPECT_NE(trySendMessage(waitingForSend1, SendMessageFlags::kExpectResponse), CHIP_NO_ERROR);
EXPECT_NE(trySendMessage(waitingForSend2, SendMessageFlags::kExpectResponse), CHIP_NO_ERROR);
// Make sure we can send a message on the special exchange.
EXPECT_FALSE(delegate.mOnMessageReceivedCalled);
err = trySendMessage(specialExhange, SendMessageFlags::kNone);
EXPECT_EQ(err, CHIP_NO_ERROR);
// Should be waiting for an ack now.
EXPECT_EQ(rm->TestGetCountRetransTable(), 1);
if (dropResponseMessages)
{
//
// This version of the test allows us to validate logic that marks expired sessions as defunct
// on encountering an MRP failure.
//
loopback.mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount;
loopback.mDroppedMessageCount = 0;
//
// We've set the session into responsive mode by altering the MRP intervals, so we should be able to
// trigger a MRP failure due to timing out waiting for an ACK.
//
auto waitTimeout = System::Clock::Milliseconds32(1000);
#if CHIP_CONFIG_ENABLE_ICD_SERVER
// If running as an ICD, increase waitTimeout to account for the polling interval
waitTimeout += ICDConfigurationData::GetInstance().GetFastPollingInterval();
#endif
GetIOContext().DriveIOUntil(waitTimeout, [&]() { return false; });
}
else
{
DrainAndServiceIO();
}
// Should not get an app-level response, since we are not expecting one.
EXPECT_FALSE(delegate.mOnMessageReceivedCalled);
// We should have gotten our ack.
EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
waitingForSend1->Close();
waitingForSend2->Close();
loopback.mNumMessagesToDrop = 0;
loopback.mDroppedMessageCount = 0;
}
TEST_F(TestAbortExchangesForFabric, CheckAbortAllButOneExchange)
{
CommonCheckAbortAllButOneExchange(false);
}
TEST_F(TestAbortExchangesForFabric, CheckAbortAllButOneExchangeResponseTimeout)
{
CommonCheckAbortAllButOneExchange(true);
}
} // namespace