blob: 7dbbeba654117fe9302566c051e571667d635336 [file] [log] [blame]
/*
*
* Copyright (c) 2020-2021 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 the SessionManager implementation.
*/
#include "system/SystemClock.h"
#include <lib/core/CHIPCore.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/UnitTestRegistration.h>
#include <transport/SecureSessionTable.h>
#include <transport/SessionHolder.h>
#include <nlbyteorder.h>
#include <nlunit-test.h>
#include <errno.h>
#include <vector>
namespace chip {
namespace Transport {
class TestSecureSessionTable
{
public:
//
// This test specifically validates eviction of sessions in the session table
// with various scenarios based on the existing set of sessions in the table
// and a provided session eviction hint
//
static void ValidateSessionSorting(nlTestSuite * inSuite, void * inContext);
private:
struct SessionParameters
{
ScopedNodeId mPeer;
System::Clock::Timestamp mLastActivityTime;
SecureSession::State mState;
};
//
// This listener lets us track which sessions get evicted by
// using a SessionHolderWithDelegate to get notified on session release.
//
class SessionNotificationListener : public SessionDelegate
{
public:
SessionNotificationListener(const SessionHandle & session) : mSessionHolder(*this) { mSessionHolder.Grab(session); }
void OnSessionReleased() { mSessionReleased = true; }
NewSessionHandlingPolicy GetNewSessionHandlingPolicy() { return NewSessionHandlingPolicy::kStayAtOldSession; }
SessionHolderWithDelegate mSessionHolder;
bool mSessionReleased = false;
};
static constexpr FabricIndex kFabric1 = 1;
static constexpr FabricIndex kFabric2 = 2;
static constexpr FabricIndex kFabric3 = 3;
//
// Allocates a new secure session given an eviction hint. The session that was evicted is compared against the provided
// evictedSessionIndex (which indexes into the provided SessionParameter table) to validate that it matches.
//
void AllocateSession(const ScopedNodeId & sessionEvictionHint, std::vector<SessionParameters> & sessionParameters,
uint16_t evictedSessionIndex);
//
// Reset our internal SecureSessionTable list and create a new one given the provided parameters.
//
void CreateSessionTable(std::vector<SessionParameters> & sessionParams);
nlTestSuite * mTestSuite;
Platform::UniquePtr<SecureSessionTable> mSessionTable;
std::vector<Platform::UniquePtr<SessionNotificationListener>> mSessionList;
};
void TestSecureSessionTable::AllocateSession(const ScopedNodeId & sessionEvictionHint,
std::vector<SessionParameters> & sessionParameters, uint16_t evictedSessionIndex)
{
auto session = mSessionTable->CreateNewSecureSession(SecureSession::Type::kCASE, sessionEvictionHint);
NL_TEST_ASSERT(mTestSuite, session.HasValue());
NL_TEST_ASSERT(mTestSuite, mSessionList[evictedSessionIndex].get()->mSessionReleased == true);
}
void TestSecureSessionTable::CreateSessionTable(std::vector<SessionParameters> & sessionParams)
{
mSessionList.clear();
mSessionTable = Platform::MakeUnique<SecureSessionTable>();
NL_TEST_ASSERT(mTestSuite, mSessionTable.get() != nullptr);
mSessionTable->Init();
mSessionTable->SetMaxSessionTableSize(static_cast<uint32_t>(sessionParams.size()));
for (unsigned int i = 0; i < sessionParams.size(); i++)
{
auto session = mSessionTable->CreateNewSecureSession(SecureSession::Type::kCASE, ScopedNodeId());
NL_TEST_ASSERT(mTestSuite, session.HasValue());
session.Value()->AsSecureSession()->Activate(
ScopedNodeId(1, sessionParams[i].mPeer.GetFabricIndex()), sessionParams[i].mPeer, CATValues(), static_cast<uint16_t>(i),
ReliableMessageProtocolConfig(System::Clock::Milliseconds32(0), System::Clock::Milliseconds32(0),
System::Clock::Milliseconds16(0)));
// Make sure we set up our holder _before_ the session goes into a state
// other than active, because holders refuse to hold non-active
// sessions.
mSessionList.push_back(Platform::MakeUnique<SessionNotificationListener>(session.Value()));
session.Value()->AsSecureSession()->mLastActivityTime = sessionParams[i].mLastActivityTime;
session.Value()->AsSecureSession()->mState = sessionParams[i].mState;
}
}
void TestSecureSessionTable::ValidateSessionSorting(nlTestSuite * inSuite, void * inContext)
{
Platform::UniquePtr<TestSecureSessionTable> & _this = *static_cast<Platform::UniquePtr<TestSecureSessionTable> *>(inContext);
_this->mTestSuite = inSuite;
//
// This validates basic eviction. The table is full of sessions from Fabric1 from the same
// Node (2). Eviction should select the oldest session in the table (with timestamp 1) and evict that
//
{
ChipLogProgress(SecureChannel, "-------- Validating Basic Eviction (Matching Hint's Fabric) --------");
std::vector<SessionParameters> sessionParamList = {
{ { 2, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(2, kFabric1), sessionParamList, 4);
}
//
// This validates basic eviction, with the sessionHint indicating a request from a different fabric than
// those in the table. Nothing changes from the example above since the sessions in the table are over minima,
// so it will just reap the oldest session in the table (with timestamp 1 again).
//
//
{
ChipLogProgress(SecureChannel, "-------- Validating Basic Eviction (No Match for Hint's Fabric) --------");
std::vector<SessionParameters> sessionParamList = {
{ { 2, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(2, kFabric2), sessionParamList, 4);
}
//
// This validates evicting an over-minima fabric from the session table where there
// are sessions from two fabrics, Fabric1 and Fabric2.
//
// Fabric1 has 2 sessions, and Fabric2 has 4 sessions. Fabric2 will be selected since
// it has more sessions than Fabric2.
//
// Within that set, there are more sessions to Node 2 than others, so the oldest one
// in that set (timestamp 3) will be selected.
//
{
ChipLogProgress(SecureChannel, "-------- Over-minima Fabric Eviction ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 2, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 2, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 1, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 2, kFabric2 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(2, kFabric1), sessionParamList, 1);
}
//
// This validates evicting an over-minima fabric from the session table where there
// are sessions from two fabrics, Fabric1 and Fabric2.
//
// Fabric1 has 2 sessions, and Fabric2 has 3 sessions. Fabric2 will be selected since
// it has more sessions than Fabric2.
//
// Within that set, there are more sessions to Node 2 than others, except one session
// is in the pairing state. So the active one will be selected instead.
//
{
ChipLogProgress(SecureChannel, "-------- Over-minima Fabric Eviction (State) ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 2, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 2, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kEstablishing },
{ { 1, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 2, kFabric2 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(2, kFabric1), sessionParamList, 3);
}
//
// This validates evicting an over-minima fabric from the session table where there
// are sessions from two fabrics, Fabric1 and Fabric2.
//
// Fabric1 has 2 sessions, and Fabric2 has 4 sessions. Fabric2 will be selected since
// it has more sessions than Fabric1.
//
// Within that set, there are equal sessions to each node, so the session with the
// older timestamp will be selected.
//
{
ChipLogProgress(SecureChannel, "-------- Over-minima Fabric Eviction ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 2, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 1, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 1, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 2, kFabric2 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(2, kFabric1), sessionParamList, 4);
}
//
// This validates evicting from a table with equally loaded fabrics. In this scenario,
// bias is given to the fabric that matches that of the eviction hint.
//
// There are more sessions to Node 2 in that fabric, so despite there be a match to
// Node 3 in the table, the older session to Node 2 will be evicted.
//
{
ChipLogProgress(SecureChannel, "-------- Equal Fabrics Eviction (Un-equal # Sessions / Node) ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 2, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 1, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 3, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(3, kFabric1), sessionParamList, 2);
}
//
// This validates evicting from a table with equally loaded fabrics. In this scenario,
// bias is given to the fabric that matches that of the eviction hint.
//
// There is an equal number sessions to nodes 1, 2, and 3 in that fabric, so the Node
// that matches the session eviction hint will be selected.
//
// All the sessions in the table are defunct, because for unique active
// sessions eviction hints are ignored.
//
{
ChipLogProgress(
SecureChannel,
"-------- Equal Fabrics Eviction (Single equal # Sessions to Nodes, Hint Match On Fabric & Node) ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 1, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kDefunct },
{ { 1, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kDefunct },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kDefunct },
{ { 3, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kDefunct },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kDefunct },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kDefunct },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(3, kFabric1), sessionParamList, 3);
}
//
// This validates evicting from a table with equally loaded fabrics. In this scenario,
// bias is given to the fabric that matches that of the eviction hint.
//
// There is an equal number sessions to nodes 1, 2, and 3 in that fabric, so the Node
// that matches the session eviction hint will be selected.
//
// All the peers in this table have two sessions to them, so that we pay
// attention to the eviction hint. The older of the two should be selected.
//
{
ChipLogProgress(
SecureChannel,
"-------- Equal Fabrics Eviction (Multiple equal # Sessions to Nodes, Hint Match On Fabric & Node) ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 1, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 1, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 3, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 1, kFabric1 }, System::Clock::Timestamp(10), SecureSession::State::kActive },
{ { 1, kFabric2 }, System::Clock::Timestamp(4), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 3, kFabric1 }, System::Clock::Timestamp(8), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(3, kFabric1), sessionParamList, 3);
}
//
// This validates evicting from a table with equally loaded fabrics. In this scenario,
// bias is given to the fabric that matches that of the eviction hint.
//
// There is an equal sessions to nodes 1, 2, and 3 in that fabric, and only
// one per node. Since all the sessions are active, the eviction hint's
// node id will be ignored and the oldest session on the fabric will be selected.
//
{
ChipLogProgress(SecureChannel,
"-------- Equal Fabrics Eviction (Equal # Sessions to Nodes, Hint Match On Fabric & Node, hint node "
"ignored) ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 1, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 1, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 3, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(3, kFabric1), sessionParamList, 2);
}
//
// This validates evicting from a table with equally loaded fabrics. In this scenario,
// bias is given to the fabric that matches that of the eviction hint.
//
// There is an equal sessions to nodes 1, 2, and 3 in that fabric, and only
// one per node. Since the hinted session is active, the eviction hint's
// node id will be ignored and the defunct session will be selected, even
// though it's the newest one.
//
{
ChipLogProgress(SecureChannel,
"-------- Equal Fabrics Eviction (Equal # Sessions to Nodes, Hint Match On Fabric & Node, hint node "
"ignored and state wins) ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 1, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kDefunct },
{ { 1, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 3, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(3, kFabric1), sessionParamList, 0);
}
//
// Similar to above, except that the eviction hint matches a given fabric (kFabric1) in the
// session table, but not any nodes. In this case, the oldest session in that fabric is selected
// for eviction from the table.
//
{
ChipLogProgress(SecureChannel,
"-------- Equal Fabrics Eviction (Equal # of Sessions to Nodes, Hint Match on Fabric ONLY) ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 1, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 1, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 3, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(4, kFabric1), sessionParamList, 2);
}
//
// Similar to above, except the eviction hint does not match any fabric in the session table.
// Given all fabrics are within minimas, the oldest session is then selected.
//
{
ChipLogProgress(SecureChannel, "-------- Equal Fabrics Eviction (Equal # of Sessions to Nodes, No Hint Match) ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 1, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 1, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
{ { 3, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kActive },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(4, kFabric3), sessionParamList, 4);
}
//
// Similar to above, except the oldest session happens to not be an active one. Instead,
// select the next oldest active session.
//
{
ChipLogProgress(
SecureChannel,
"-------- Equal Fabrics Eviction (Equal # of Sessions to Nodes, No Hint Match, In-active Session) ---------");
std::vector<SessionParameters> sessionParamList = {
{ { 1, kFabric1 }, System::Clock::Timestamp(9), SecureSession::State::kActive },
{ { 1, kFabric2 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 2, kFabric1 }, System::Clock::Timestamp(3), SecureSession::State::kActive },
{ { 3, kFabric1 }, System::Clock::Timestamp(7), SecureSession::State::kActive },
{ { 3, kFabric2 }, System::Clock::Timestamp(1), SecureSession::State::kEstablishing },
{ { 4, kFabric2 }, System::Clock::Timestamp(2), SecureSession::State::kActive },
};
_this->CreateSessionTable(sessionParamList);
_this->AllocateSession(ScopedNodeId(4, kFabric3), sessionParamList, 5);
}
}
Platform::UniquePtr<TestSecureSessionTable> gTestSecureSessionTable;
} // namespace Transport
} // namespace chip
// Test Suite
namespace {
/**
* Test Suite that lists all the test functions.
*/
// clang-format off
const nlTest sTests[] =
{
NL_TEST_DEF("Validate Session Sorting (Over Minima)", chip::Transport::TestSecureSessionTable::ValidateSessionSorting),
NL_TEST_SENTINEL()
};
// clang-format on
int Initialize(void * apSuite)
{
VerifyOrReturnError(chip::Platform::MemoryInit() == CHIP_NO_ERROR, FAILURE);
chip::Transport::gTestSecureSessionTable = chip::Platform::MakeUnique<chip::Transport::TestSecureSessionTable>();
return SUCCESS;
}
int Finalize(void * aContext)
{
chip::Transport::gTestSecureSessionTable.reset();
chip::Platform::MemoryShutdown();
return SUCCESS;
}
// clang-format off
nlTestSuite sSuite =
{
"TestSecureSessionTable",
&sTests[0],
Initialize,
Finalize
};
// clang-format on
} // namespace
/**
* Main
*/
int SecureSessionTableTest()
{
// Run test suit against one context
nlTestRunner(&sSuite, &chip::Transport::gTestSecureSessionTable);
int r = (nlTestRunnerStats(&sSuite));
return r;
}
CHIP_REGISTER_TEST_SUITE(SecureSessionTableTest);