blob: f5e343f1a2ea79fd384abe6599226d92b79b04d1 [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 TcpTransport implementation.
*/
#include "NetworkTestHelpers.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <utility>
#include <gtest/gtest.h>
#include <crypto/RandUtils.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPEncoding.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/UnitTestUtils.h>
#include <system/SystemLayer.h>
#include <transport/TransportMgr.h>
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
#include <transport/raw/TCP.h>
#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT
#include <transport/raw/tests/TCPBaseTestAccess.h>
using namespace chip;
using namespace chip::Test;
using namespace chip::Inet;
namespace {
constexpr size_t kMaxTcpActiveConnectionCount = 4;
constexpr size_t kMaxTcpPendingPackets = 4;
constexpr uint16_t kPacketSizeBytes = static_cast<uint16_t>(sizeof(uint16_t));
uint16_t gChipTCPPort = static_cast<uint16_t>(CHIP_PORT + chip::Crypto::GetRandU16() % 100);
chip::Transport::AppTCPConnectionCallbackCtxt gAppTCPConnCbCtxt;
chip::Transport::ActiveTCPConnectionState * gActiveTCPConnState = nullptr;
using TCPImpl = Transport::TCP<kMaxTcpActiveConnectionCount, kMaxTcpPendingPackets>;
using TestAccess = Transport::TCPBaseTestAccess<kMaxTcpActiveConnectionCount, kMaxTcpPendingPackets>;
constexpr NodeId kSourceNodeId = 123654;
constexpr NodeId kDestinationNodeId = 111222333;
constexpr uint32_t kMessageCounter = 18;
const char PAYLOAD[] = "Hello!";
class MockTransportMgrDelegate : public chip::TransportMgrDelegate
{
public:
typedef int (*MessageReceivedCallback)(const uint8_t * message, size_t length, int count, void * data);
MockTransportMgrDelegate(IOContext * inContext) : mIOContext(inContext), mCallback(nullptr), mCallbackData(nullptr) {}
~MockTransportMgrDelegate() override {}
void SetCallback(MessageReceivedCallback callback = nullptr, void * callback_data = nullptr)
{
mCallback = callback;
mCallbackData = callback_data;
}
void OnMessageReceived(const Transport::PeerAddress & source, System::PacketBufferHandle && msgBuf,
Transport::MessageTransportContext * transCtxt = nullptr) override
{
PacketHeader packetHeader;
EXPECT_EQ(packetHeader.DecodeAndConsume(msgBuf), CHIP_NO_ERROR);
if (mCallback)
{
EXPECT_EQ(mCallback(msgBuf->Start(), msgBuf->DataLength(), mReceiveHandlerCallCount, mCallbackData), 0);
}
ChipLogProgress(Inet, "Message Receive Handler called");
mReceiveHandlerCallCount++;
}
void HandleConnectionAttemptComplete(chip::Transport::ActiveTCPConnectionState * conn, CHIP_ERROR conErr) override
{
chip::Transport::AppTCPConnectionCallbackCtxt * appConnCbCtxt = nullptr;
VerifyOrReturn(conn != nullptr);
mHandleConnectionCompleteCalled = true;
appConnCbCtxt = conn->mAppState;
VerifyOrReturn(appConnCbCtxt != nullptr);
if (appConnCbCtxt->connCompleteCb != nullptr)
{
appConnCbCtxt->connCompleteCb(conn, conErr);
}
else
{
ChipLogProgress(Inet, "Connection established. App callback missing.");
}
}
void HandleConnectionClosed(chip::Transport::ActiveTCPConnectionState * conn, CHIP_ERROR conErr) override
{
chip::Transport::AppTCPConnectionCallbackCtxt * appConnCbCtxt = nullptr;
VerifyOrReturn(conn != nullptr);
mHandleConnectionCloseCalled = true;
appConnCbCtxt = conn->mAppState;
VerifyOrReturn(appConnCbCtxt != nullptr);
if (appConnCbCtxt->connClosedCb != nullptr)
{
appConnCbCtxt->connClosedCb(conn, conErr);
}
else
{
ChipLogProgress(Inet, "Connection Closed. App callback missing.");
}
}
void InitializeMessageTest(TCPImpl & tcp, const IPAddress & addr)
{
CHIP_ERROR err = tcp.Init(Transport::TcpListenParameters(mIOContext->GetTCPEndPointManager())
.SetAddressType(addr.Type())
.SetListenPort(gChipTCPPort));
// retry a few times in case the port is somehow in use.
// this is a WORKAROUND for flaky testing if we run tests very fast after each other.
// in that case, a port could be in a WAIT state.
//
// What may be happening:
// - We call InitializeMessageTest several times in this unit test
// - closing sockets takes a while (FIN-wait or similar)
// - trying InitializeMessageTest to take the same port right after may fail
//
// The tests may be run with a 0 port (to self select an active port) however I have not
// validated that this works and we need a followup for it
//
// TODO: stop using fixed ports.
for (int i = 0; (i < 50) && (err != CHIP_NO_ERROR); i++)
{
ChipLogProgress(NotSpecified, "RETRYING tcp initialization");
chip::test_utils::SleepMillis(100);
err = tcp.Init(Transport::TcpListenParameters(mIOContext->GetTCPEndPointManager())
.SetAddressType(addr.Type())
.SetListenPort(gChipTCPPort));
}
EXPECT_EQ(err, CHIP_NO_ERROR);
mTransportMgrBase.SetSessionManager(this);
mTransportMgrBase.Init(&tcp);
mReceiveHandlerCallCount = 0;
mHandleConnectionCompleteCalled = false;
mHandleConnectionCloseCalled = false;
gAppTCPConnCbCtxt.appContext = nullptr;
gAppTCPConnCbCtxt.connReceivedCb = nullptr;
gAppTCPConnCbCtxt.connCompleteCb = nullptr;
gAppTCPConnCbCtxt.connClosedCb = nullptr;
}
void SingleMessageTest(TCPImpl & tcp, const IPAddress & addr)
{
chip::System::PacketBufferHandle buffer = chip::System::PacketBufferHandle::NewWithData(PAYLOAD, sizeof(PAYLOAD));
ASSERT_FALSE(buffer.IsNull());
PacketHeader header;
header.SetSourceNodeId(kSourceNodeId).SetDestinationNodeId(kDestinationNodeId).SetMessageCounter(kMessageCounter);
SetCallback([](const uint8_t * message, size_t length, int count, void * data) { return memcmp(message, data, length); },
const_cast<void *>(static_cast<const void *>(PAYLOAD)));
CHIP_ERROR err = header.EncodeBeforeData(buffer);
EXPECT_EQ(err, CHIP_NO_ERROR);
// Should be able to send a message to itself by just calling send.
err = tcp.SendMessage(Transport::PeerAddress::TCP(addr, gChipTCPPort), std::move(buffer));
EXPECT_EQ(err, CHIP_NO_ERROR);
mIOContext->DriveIOUntil(chip::System::Clock::Seconds16(5), [this]() { return mReceiveHandlerCallCount != 0; });
EXPECT_EQ(mReceiveHandlerCallCount, 1);
SetCallback(nullptr);
}
void ConnectTest(TCPImpl & tcp, const IPAddress & addr)
{
// Connect and wait for seeing active connection
CHIP_ERROR err = tcp.TCPConnect(Transport::PeerAddress::TCP(addr, gChipTCPPort), &gAppTCPConnCbCtxt, &gActiveTCPConnState);
EXPECT_EQ(err, CHIP_NO_ERROR);
mIOContext->DriveIOUntil(chip::System::Clock::Seconds16(5), [&tcp]() { return tcp.HasActiveConnections(); });
EXPECT_EQ(tcp.HasActiveConnections(), true);
}
void HandleConnectCompleteCbCalledTest(TCPImpl & tcp, const IPAddress & addr)
{
// Connect and wait for seeing active connection and connection complete
// handler being called.
CHIP_ERROR err = tcp.TCPConnect(Transport::PeerAddress::TCP(addr, gChipTCPPort), &gAppTCPConnCbCtxt, &gActiveTCPConnState);
EXPECT_EQ(err, CHIP_NO_ERROR);
mIOContext->DriveIOUntil(chip::System::Clock::Seconds16(5), [this]() { return mHandleConnectionCompleteCalled; });
EXPECT_EQ(mHandleConnectionCompleteCalled, true);
}
void HandleConnectCloseCbCalledTest(TCPImpl & tcp, const IPAddress & addr)
{
// Connect and wait for seeing active connection and connection complete
// handler being called.
CHIP_ERROR err = tcp.TCPConnect(Transport::PeerAddress::TCP(addr, gChipTCPPort), &gAppTCPConnCbCtxt, &gActiveTCPConnState);
EXPECT_EQ(err, CHIP_NO_ERROR);
mIOContext->DriveIOUntil(chip::System::Clock::Seconds16(5), [this]() { return mHandleConnectionCompleteCalled; });
EXPECT_EQ(mHandleConnectionCompleteCalled, true);
tcp.TCPDisconnect(Transport::PeerAddress::TCP(addr, gChipTCPPort));
mIOContext->DriveIOUntil(chip::System::Clock::Seconds16(5), [&tcp]() { return !tcp.HasActiveConnections(); });
EXPECT_EQ(mHandleConnectionCloseCalled, true);
}
void DisconnectTest(TCPImpl & tcp, chip::Transport::ActiveTCPConnectionState * conn)
{
// Disconnect and wait for seeing peer close
tcp.TCPDisconnect(conn, true);
mIOContext->DriveIOUntil(chip::System::Clock::Seconds16(5), [&tcp]() { return !tcp.HasActiveConnections(); });
EXPECT_EQ(tcp.HasActiveConnections(), false);
}
void DisconnectTest(TCPImpl & tcp, const IPAddress & addr)
{
// Disconnect and wait for seeing peer close
tcp.TCPDisconnect(Transport::PeerAddress::TCP(addr, gChipTCPPort));
mIOContext->DriveIOUntil(chip::System::Clock::Seconds16(5), [&tcp]() { return !tcp.HasActiveConnections(); });
EXPECT_EQ(tcp.HasActiveConnections(), false);
}
CHIP_ERROR TCPConnect(const Transport::PeerAddress & peerAddress, Transport::AppTCPConnectionCallbackCtxt * appState,
Transport::ActiveTCPConnectionState ** peerConnState)
{
return mTransportMgrBase.TCPConnect(peerAddress, appState, peerConnState);
}
using OnTCPConnectionReceivedCallback = void (*)(void * context, chip::Transport::ActiveTCPConnectionState * conn);
using OnTCPConnectionCompleteCallback = void (*)(void * context, chip::Transport::ActiveTCPConnectionState * conn,
CHIP_ERROR conErr);
using OnTCPConnectionClosedCallback = void (*)(void * context, chip::Transport::ActiveTCPConnectionState * conn,
CHIP_ERROR conErr);
void SetConnectionCallbacks(OnTCPConnectionCompleteCallback connCompleteCb, OnTCPConnectionClosedCallback connClosedCb,
OnTCPConnectionReceivedCallback connReceivedCb)
{
mConnCompleteCb = connCompleteCb;
mConnClosedCb = connClosedCb;
mConnReceivedCb = connReceivedCb;
}
int mReceiveHandlerCallCount = 0;
bool mHandleConnectionCompleteCalled = false;
bool mHandleConnectionCloseCalled = false;
private:
IOContext * mIOContext;
MessageReceivedCallback mCallback;
void * mCallbackData;
TransportMgrBase mTransportMgrBase;
OnTCPConnectionCompleteCallback mConnCompleteCb = nullptr;
OnTCPConnectionClosedCallback mConnClosedCb = nullptr;
OnTCPConnectionReceivedCallback mConnReceivedCb = nullptr;
};
// Generates a packet buffer or a chain of packet buffers for a single message.
struct TestData
{
// `sizes[]` is a zero-terminated sequence of packet buffer sizes.
// If total length supplied is not large enough for at least the PacketHeader and length field,
// the last buffer will be made larger.
TestData() : mPayload(nullptr), mTotalLength(0), mMessageLength(0), mMessageOffset(0) {}
~TestData() { Free(); }
bool Init(const uint16_t sizes[]);
void Free();
bool IsValid() { return !mHandle.IsNull() && (mPayload != nullptr); }
chip::System::PacketBufferHandle mHandle;
uint8_t * mPayload;
size_t mTotalLength;
size_t mMessageLength;
size_t mMessageOffset;
};
bool TestData::Init(const uint16_t sizes[])
{
Free();
PacketHeader header;
header.SetSourceNodeId(kSourceNodeId).SetDestinationNodeId(kDestinationNodeId).SetMessageCounter(kMessageCounter);
const size_t headerLength = header.EncodeSizeBytes();
// Determine the total length.
mTotalLength = 0;
int bufferCount = 0;
for (; sizes[bufferCount] != 0; ++bufferCount)
{
mTotalLength += sizes[bufferCount];
}
--bufferCount;
uint16_t additionalLength = 0;
if (headerLength + kPacketSizeBytes > mTotalLength)
{
additionalLength = static_cast<uint16_t>((headerLength + kPacketSizeBytes) - mTotalLength);
mTotalLength += additionalLength;
}
if (mTotalLength > UINT16_MAX)
{
return false;
}
uint16_t messageLength = static_cast<uint16_t>(mTotalLength - kPacketSizeBytes);
// Build the test payload.
uint8_t * payload = static_cast<uint8_t *>(chip::Platform::MemoryCalloc(1, mTotalLength));
if (payload == nullptr)
{
return false;
}
chip::Encoding::LittleEndian::Put16(payload, messageLength);
uint16_t headerSize;
CHIP_ERROR err = header.Encode(payload + kPacketSizeBytes, messageLength, &headerSize);
if (err != CHIP_NO_ERROR)
{
return false;
}
mMessageLength = messageLength - headerSize;
mMessageOffset = kPacketSizeBytes + headerSize;
// Fill the rest of the payload with a recognizable pattern.
for (size_t i = mMessageOffset; i < mTotalLength; ++i)
{
payload[i] = static_cast<uint8_t>(i);
}
// When we get the message back, the header will have been removed.
// Allocate the buffer chain.
System::PacketBufferHandle head = chip::System::PacketBufferHandle::New(sizes[0], 0 /* reserve */);
for (int i = 1; i <= bufferCount; ++i)
{
uint16_t size = sizes[i];
if (i == bufferCount)
{
size = static_cast<uint16_t>(size + additionalLength);
}
chip::System::PacketBufferHandle buffer = chip::System::PacketBufferHandle::New(size, 0 /* reserve */);
if (buffer.IsNull())
{
return false;
}
head.AddToEnd(std::move(buffer));
}
// Write the test payload to the buffer chain.
System::PacketBufferHandle iterator = head.Retain();
uint8_t * writePayload = payload;
size_t writeLength = mTotalLength;
while (writeLength > 0)
{
if (iterator.IsNull())
{
return false;
}
size_t lAvailableLengthInCurrentBuf = iterator->AvailableDataLength();
size_t lToWriteToCurrentBuf = lAvailableLengthInCurrentBuf;
if (writeLength < lToWriteToCurrentBuf)
{
lToWriteToCurrentBuf = writeLength;
}
if (lToWriteToCurrentBuf != 0)
{
memcpy(iterator->Start(), writePayload, lToWriteToCurrentBuf);
iterator->SetDataLength(static_cast<uint16_t>(iterator->DataLength() + lToWriteToCurrentBuf), head);
writePayload += lToWriteToCurrentBuf;
writeLength -= lToWriteToCurrentBuf;
}
iterator.Advance();
}
mHandle = std::move(head);
mPayload = payload;
return true;
}
void TestData::Free()
{
chip::Platform::MemoryFree(mPayload);
mPayload = nullptr;
mHandle = nullptr;
mTotalLength = 0;
mMessageLength = 0;
mMessageOffset = 0;
}
class TestTCP : public ::testing::Test
{
public:
static void SetUpTestSuite()
{
if (mIOContext == nullptr)
{
mIOContext = new IOContext();
ASSERT_NE(mIOContext, nullptr);
}
ASSERT_EQ(mIOContext->Init(), CHIP_NO_ERROR);
}
static void TearDownTestSuite()
{
if (mIOContext != nullptr)
{
mIOContext->Shutdown();
delete mIOContext;
mIOContext = nullptr;
}
}
protected:
static IOContext * mIOContext;
/////////////////////////// Init test
void CheckSimpleInitTest(IPAddressType type)
{
TCPImpl tcp;
CHIP_ERROR err = tcp.Init(
Transport::TcpListenParameters(mIOContext->GetTCPEndPointManager()).SetAddressType(type).SetListenPort(gChipTCPPort));
EXPECT_EQ(err, CHIP_NO_ERROR);
}
/////////////////////////// Messaging test
void CheckMessageTest(const IPAddress & addr)
{
TCPImpl tcp;
MockTransportMgrDelegate gMockTransportMgrDelegate(mIOContext);
gMockTransportMgrDelegate.InitializeMessageTest(tcp, addr);
gMockTransportMgrDelegate.SingleMessageTest(tcp, addr);
gMockTransportMgrDelegate.DisconnectTest(tcp, addr);
}
void ConnectToSelfTest(const IPAddress & addr)
{
TCPImpl tcp;
MockTransportMgrDelegate gMockTransportMgrDelegate(mIOContext);
gMockTransportMgrDelegate.InitializeMessageTest(tcp, addr);
gMockTransportMgrDelegate.ConnectTest(tcp, addr);
gMockTransportMgrDelegate.DisconnectTest(tcp, addr);
}
void ConnectSendMessageThenCloseTest(const IPAddress & addr)
{
TCPImpl tcp;
MockTransportMgrDelegate gMockTransportMgrDelegate(mIOContext);
gMockTransportMgrDelegate.InitializeMessageTest(tcp, addr);
gMockTransportMgrDelegate.ConnectTest(tcp, addr);
gMockTransportMgrDelegate.SingleMessageTest(tcp, addr);
gMockTransportMgrDelegate.DisconnectTest(tcp, addr);
}
void HandleConnCompleteTest(const IPAddress & addr)
{
TCPImpl tcp;
MockTransportMgrDelegate gMockTransportMgrDelegate(mIOContext);
gMockTransportMgrDelegate.InitializeMessageTest(tcp, addr);
gMockTransportMgrDelegate.HandleConnectCompleteCbCalledTest(tcp, addr);
gMockTransportMgrDelegate.DisconnectTest(tcp, addr);
}
void HandleConnCloseTest(const IPAddress & addr)
{
TCPImpl tcp;
MockTransportMgrDelegate gMockTransportMgrDelegate(mIOContext);
gMockTransportMgrDelegate.InitializeMessageTest(tcp, addr);
gMockTransportMgrDelegate.HandleConnectCloseCbCalledTest(tcp, addr);
gMockTransportMgrDelegate.DisconnectTest(tcp, addr);
}
// Callback used by CheckProcessReceivedBuffer.
static int TestDataCallbackCheck(const uint8_t * message, size_t length, int count, void * data)
{
if (data == nullptr)
{
return -1;
}
TestData * currentData = static_cast<TestData *>(data) + count;
if (currentData->mPayload == nullptr)
{
return -2;
}
if (currentData->mMessageLength != length)
{
return -3;
}
if (memcmp(currentData->mPayload + currentData->mMessageOffset, message, length) != 0)
{
return -4;
}
return 0;
}
};
IOContext * TestTCP::mIOContext = nullptr;
#if INET_CONFIG_ENABLE_IPV4
TEST_F(TestTCP, CheckSimpleInitTest4)
{
CheckSimpleInitTest(IPAddressType::kIPv4);
}
TEST_F(TestTCP, CheckMessageTest4)
{
IPAddress addr;
IPAddress::FromString("127.0.0.1", addr);
CheckMessageTest(addr);
}
#endif
TEST_F(TestTCP, CheckSimpleInitTest6)
{
CheckSimpleInitTest(IPAddressType::kIPv6);
}
TEST_F(TestTCP, CheckMessageTest6)
{
IPAddress addr;
IPAddress::FromString("::1", addr);
CheckMessageTest(addr);
}
#if INET_CONFIG_ENABLE_IPV4
TEST_F(TestTCP, ConnectToSelfTest4)
{
IPAddress addr;
IPAddress::FromString("127.0.0.1", addr);
ConnectToSelfTest(addr);
}
TEST_F(TestTCP, ConnectSendMessageThenCloseTest4)
{
IPAddress addr;
IPAddress::FromString("127.0.0.1", addr);
ConnectSendMessageThenCloseTest(addr);
}
TEST_F(TestTCP, HandleConnCompleteCalledTest4)
{
IPAddress addr;
IPAddress::FromString("127.0.0.1", addr);
HandleConnCompleteTest(addr);
}
#endif // INET_CONFIG_ENABLE_IPV4
TEST_F(TestTCP, ConnectToSelfTest6)
{
IPAddress addr;
IPAddress::FromString("::1", addr);
ConnectToSelfTest(addr);
}
TEST_F(TestTCP, ConnectSendMessageThenCloseTest6)
{
IPAddress addr;
IPAddress::FromString("::1", addr);
ConnectSendMessageThenCloseTest(addr);
}
TEST_F(TestTCP, HandleConnCompleteCalledTest6)
{
IPAddress addr;
IPAddress::FromString("::1", addr);
HandleConnCompleteTest(addr);
}
TEST_F(TestTCP, HandleConnCloseCalledTest6)
{
IPAddress addr;
IPAddress::FromString("::1", addr);
HandleConnCloseTest(addr);
}
TEST_F(TestTCP, CheckProcessReceivedBuffer)
{
TCPImpl tcp;
IPAddress addr;
IPAddress::FromString("::1", addr);
MockTransportMgrDelegate gMockTransportMgrDelegate(mIOContext);
gMockTransportMgrDelegate.InitializeMessageTest(tcp, addr);
// Send a packet to get TCP going, so that we can find a TCPEndPoint to pass to ProcessReceivedBuffer.
// (The current TCPEndPoint implementation is not effectively mockable.)
gMockTransportMgrDelegate.SingleMessageTest(tcp, addr);
Transport::PeerAddress lPeerAddress = Transport::PeerAddress::TCP(addr, gChipTCPPort);
void * state = TestAccess::FindActiveConnection(tcp, lPeerAddress);
ASSERT_NE(state, nullptr);
TCPEndPoint * lEndPoint = TestAccess::GetEndpoint(state);
ASSERT_NE(lEndPoint, nullptr);
CHIP_ERROR err = CHIP_NO_ERROR;
TestData testData[2];
gMockTransportMgrDelegate.SetCallback(TestDataCallbackCheck, testData);
// Test a single packet buffer.
gMockTransportMgrDelegate.mReceiveHandlerCallCount = 0;
EXPECT_TRUE(testData[0].Init((const uint16_t[]){ 111, 0 }));
err = TestAccess::ProcessReceivedBuffer(tcp, lEndPoint, lPeerAddress, std::move(testData[0].mHandle));
EXPECT_EQ(err, CHIP_NO_ERROR);
EXPECT_EQ(gMockTransportMgrDelegate.mReceiveHandlerCallCount, 1);
// Test a message in a chain of three packet buffers. The message length is split across buffers.
gMockTransportMgrDelegate.mReceiveHandlerCallCount = 0;
EXPECT_TRUE(testData[0].Init((const uint16_t[]){ 1, 122, 123, 0 }));
err = TestAccess::ProcessReceivedBuffer(tcp, lEndPoint, lPeerAddress, std::move(testData[0].mHandle));
EXPECT_EQ(err, CHIP_NO_ERROR);
EXPECT_EQ(gMockTransportMgrDelegate.mReceiveHandlerCallCount, 1);
// Test two messages in a chain.
gMockTransportMgrDelegate.mReceiveHandlerCallCount = 0;
EXPECT_TRUE(testData[0].Init((const uint16_t[]){ 131, 0 }));
EXPECT_TRUE(testData[1].Init((const uint16_t[]){ 132, 0 }));
testData[0].mHandle->AddToEnd(std::move(testData[1].mHandle));
err = TestAccess::ProcessReceivedBuffer(tcp, lEndPoint, lPeerAddress, std::move(testData[0].mHandle));
EXPECT_EQ(err, CHIP_NO_ERROR);
EXPECT_EQ(gMockTransportMgrDelegate.mReceiveHandlerCallCount, 2);
// Test a chain of two messages, each a chain.
gMockTransportMgrDelegate.mReceiveHandlerCallCount = 0;
EXPECT_TRUE(testData[0].Init((const uint16_t[]){ 141, 142, 0 }));
EXPECT_TRUE(testData[1].Init((const uint16_t[]){ 143, 144, 0 }));
testData[0].mHandle->AddToEnd(std::move(testData[1].mHandle));
err = TestAccess::ProcessReceivedBuffer(tcp, lEndPoint, lPeerAddress, std::move(testData[0].mHandle));
EXPECT_EQ(err, CHIP_NO_ERROR);
EXPECT_EQ(gMockTransportMgrDelegate.mReceiveHandlerCallCount, 2);
// Test a message that is too large to coalesce into a single packet buffer.
gMockTransportMgrDelegate.mReceiveHandlerCallCount = 0;
gMockTransportMgrDelegate.SetCallback(TestDataCallbackCheck, &testData[1]);
EXPECT_TRUE(testData[0].Init((const uint16_t[]){ 51, System::PacketBuffer::kMaxSizeWithoutReserve, 0 }));
// Sending only the first buffer of the long chain. This should be enough to trigger the error.
System::PacketBufferHandle head = testData[0].mHandle.PopHead();
err = TestAccess::ProcessReceivedBuffer(tcp, lEndPoint, lPeerAddress, std::move(head));
EXPECT_EQ(err, CHIP_ERROR_MESSAGE_TOO_LONG);
EXPECT_EQ(gMockTransportMgrDelegate.mReceiveHandlerCallCount, 0);
gMockTransportMgrDelegate.DisconnectTest(tcp, addr);
}
} // namespace