blob: ae959ba92976534316acc18c62c89c59cbb07efe [file] [log] [blame]
/*
*
* Copyright (c) 2025 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 an E2E test for the SessionManager/Transport/UnauthenticatedSession
* TCP connection.
*/
#include <errno.h>
#include <vector>
#include <pw_unit_test/framework.h>
#include <credentials/PersistentStorageOpCertStore.h>
#include <crypto/DefaultSessionKeystore.h>
#include <crypto/PersistentStorageOperationalKeystore.h>
#include <crypto/RandUtils.h>
#include <lib/core/CHIPCore.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/TestPersistentStorageDelegate.h>
#include <lib/support/UnitTestUtils.h>
#include <lib/support/tests/ExtraPwTestMacros.h>
#include <platform/CHIPDeviceLayer.h>
#include <protocols/secure_channel/MessageCounterManager.h>
#include <system/SystemLayer.h>
#include <system/SystemPacketBuffer.h>
#include <transport/SessionManager.h>
#include <transport/TransportMgr.h>
#include <transport/raw/TCP.h>
#include <transport/raw/tests/NetworkTestHelpers.h>
using namespace chip;
using namespace chip::Inet;
using namespace chip::Transport;
using namespace chip::Testing;
namespace {
// Use only 2 active connections (1 outgoing, 1 incoming) to easily detect leaks (exhaustion)
constexpr size_t kMaxTcpActiveConnectionCount = 2;
constexpr size_t kMaxTcpPendingPackets = 4;
constexpr int kMaxPortBindRetries = 100;
using TCPImpl = Transport::TCP<kMaxTcpActiveConnectionCount, kMaxTcpPendingPackets>;
uint16_t GetRandomPort()
{
return static_cast<uint16_t>(CHIP_PORT + chip::Crypto::GetRandU16() % 1000);
}
CHIP_ERROR RetryPortSetup(uint16_t & outPort, std::function<CHIP_ERROR(uint16_t)> setupFn)
{
CHIP_ERROR err;
for (int retryCount = 0; retryCount < kMaxPortBindRetries; retryCount++)
{
uint16_t port = GetRandomPort();
err = setupFn(port);
if (err == CHIP_ERROR_POSIX(EADDRINUSE))
{
continue;
}
outPort = (err == CHIP_NO_ERROR) ? port : 0;
return err;
}
return err;
}
class TestTCPConnection : public ::testing::Test, public SessionConnectionDelegate, public SessionMessageDelegate
{
public:
static void SetUpTestSuite()
{
if (mIOContext == nullptr)
{
mIOContext = new IOContext();
ASSERT_NE(mIOContext, nullptr);
}
EXPECT_SUCCESS(mIOContext->Init());
}
static void TearDownTestSuite()
{
if (mIOContext != nullptr)
{
mIOContext->Shutdown();
delete mIOContext;
mIOContext = nullptr;
}
}
void SetUp() override
{
// Ignore Init return value, we check correctness later/implicitly
(void) mTransportMgrBase.Init(&mTCP);
mTransportMgrBase.SetSessionManager(&mSessionManager);
mSessionManager.SetConnectionDelegate(this);
mSessionManager.SetMessageDelegate(this);
}
void TearDown() override
{
mSessionManager.Shutdown();
mTransportMgrBase.Close();
mTCP.Close();
mFabricTable.Shutdown();
mOpKeyStore.Finish();
mOpCertStore.Finish();
}
// SessionConnectionDelegate
void OnTCPConnectionClosed(const Transport::ActiveTCPConnectionState & conn, const SessionHandle & session,
CHIP_ERROR conErr) override
{}
bool OnTCPConnectionAttemptComplete(Transport::ActiveTCPConnectionHandle & conn, CHIP_ERROR conErr) override { return true; }
// SessionMessageDelegate
void OnMessageReceived(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, const SessionHandle & session,
DuplicateMessage isDuplicate, System::PacketBufferHandle && msgBuf) override
{}
protected:
static IOContext * mIOContext;
TCPImpl mTCP;
TransportMgrBase mTransportMgrBase;
SessionManager mSessionManager;
secure_channel::MessageCounterManager mMessageCounterManager;
chip::TestPersistentStorageDelegate mDeviceStorage;
chip::Crypto::DefaultSessionKeystore mSessionKeystore;
chip::PersistentStorageOperationalKeystore mOpKeyStore;
chip::Credentials::PersistentStorageOpCertStore mOpCertStore;
FabricTable mFabricTable;
CHIP_ERROR InitTCP(uint16_t & outPort)
{
return RetryPortSetup(outPort, [&](uint16_t port) {
return mTCP.Init(Transport::TcpListenParameters(mIOContext->GetTCPEndPointManager())
.SetAddressType(IPAddressType::kIPv6)
.SetListenPort(port)
.SetServerListenEnabled(true));
});
}
CHIP_ERROR InitSessionManager()
{
ReturnErrorOnFailure(mOpKeyStore.Init(&mDeviceStorage));
ReturnErrorOnFailure(mOpCertStore.Init(&mDeviceStorage));
chip::FabricTable::InitParams initParams;
initParams.storage = &mDeviceStorage;
initParams.operationalKeystore = &mOpKeyStore;
initParams.opCertStore = &mOpCertStore;
ReturnErrorOnFailure(mFabricTable.Init(initParams));
return mSessionManager.Init(&mIOContext->GetSystemLayer(), &mTransportMgrBase, &mMessageCounterManager, &mDeviceStorage,
&mFabricTable, mSessionKeystore);
}
};
IOContext * TestTCPConnection::mIOContext = nullptr;
TEST_F(TestTCPConnection, TestUnauthenticatedSessionReleaseOnConnectionClose)
{
uint16_t port;
EXPECT_SUCCESS(InitTCP(port));
EXPECT_SUCCESS(InitSessionManager());
IPAddress addr;
IPAddress::FromString("::1", addr);
// Connect to self (loopback)
Transport::PeerAddress peerAddr = Transport::PeerAddress::TCP(addr, port);
// 1. Establish initial connection (Leaked Candidate)
ActiveTCPConnectionHandle connHandle;
EXPECT_SUCCESS(mTCP.TCPConnect(peerAddr, nullptr, connHandle));
mIOContext->DriveIOUntil(chip::System::Clock::Seconds16(5),
[&]() { return !connHandle.IsNull() && connHandle->IsConnected(); });
ASSERT_FALSE(connHandle.IsNull());
ASSERT_TRUE(connHandle->IsConnected());
// 2. Create UnauthenticatedSession by simulating an incoming message
PayloadHeader header;
uint8_t payloadBuffer[64];
uint16_t encodeLen;
header.SetMessageType(Protocols::Id(VendorId::Common, 1221), 112).SetExchangeID(2233).SetInitiator(true);
EXPECT_SUCCESS(header.Encode(payloadBuffer, &encodeLen));
auto msgBuf = System::PacketBufferHandle::NewWithData(payloadBuffer, encodeLen);
PacketHeader packetHeader;
packetHeader.SetSourceNodeId(1234).SetMessageCounter(1);
packetHeader.SetSessionType(Header::SessionType::kUnicastSession); // Unauthenticated
EXPECT_SUCCESS(packetHeader.EncodeBeforeData(msgBuf));
{
Transport::MessageTransportContext ctxt;
ctxt.conn = connHandle;
mSessionManager.OnMessageReceived(peerAddr, std::move(msgBuf), &ctxt);
}
// Close TCP connection & release local handle
connHandle->ForceDisconnect();
connHandle.Release();
// Drive IO to process TCP closure
mIOContext->DriveIOUntil(chip::System::Clock::Seconds16(1), [&]() { return false; });
// 5. Try to connect again to make sure there's no leak
EXPECT_SUCCESS(mTCP.TCPConnect(peerAddr, nullptr, connHandle));
}
} // namespace