diff --git a/src/messaging/tests/BUILD.gn b/src/messaging/tests/BUILD.gn index 1cb910b415b1e9..e206a45119d532 100644 --- a/src/messaging/tests/BUILD.gn +++ b/src/messaging/tests/BUILD.gn @@ -52,6 +52,7 @@ chip_test_suite("tests") { "TestAbortExchangesForFabric.cpp", "TestExchangeHolder.cpp", "TestExchangeMgr.cpp", + "TestMessagingLayer.cpp", "TestReliableMessageProtocol.cpp", ] } diff --git a/src/messaging/tests/MessagingContext.h b/src/messaging/tests/MessagingContext.h index d60f057463806d..a7b3e06c681ca3 100644 --- a/src/messaging/tests/MessagingContext.h +++ b/src/messaging/tests/MessagingContext.h @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -231,6 +232,47 @@ class LoopbackMessagingContext : public LoopbackTransportManager, public Messagi using LoopbackTransportManager::GetSystemLayer; }; +// UDPMessagingContext enriches MessagingContext with an UDP transport +class UDPMessagingContext : public UDPTransportManager, public MessagingContext +{ +public: + virtual ~UDPMessagingContext() {} + + /// Initialize the underlying layers. + virtual CHIP_ERROR Init() + { + ReturnErrorOnFailure(chip::Platform::MemoryInit()); + ReturnErrorOnFailure(UDPTransportManager::Init()); + ReturnErrorOnFailure(MessagingContext::Init(&GetTransportMgr(), &GetIOContext())); + return CHIP_NO_ERROR; + } + + // Shutdown all layers, finalize operations + virtual void Shutdown() + { + MessagingContext::Shutdown(); + UDPTransportManager::Shutdown(); + chip::Platform::MemoryShutdown(); + } + + // Init/Shutdown Helpers that can be used directly as the nlTestSuite + // initialize/finalize function. + static int Initialize(void * context) + { + auto * ctx = static_cast(context); + return ctx->Init() == CHIP_NO_ERROR ? SUCCESS : FAILURE; + } + + static int Finalize(void * context) + { + auto * ctx = static_cast(context); + ctx->Shutdown(); + return SUCCESS; + } + + using UDPTransportManager::GetSystemLayer; +}; + // Class that can be used to capture decrypted message traffic in tests using // MessagingContext. class MessageCapturer : public SessionMessageDelegate diff --git a/src/messaging/tests/TestMessagingLayer.cpp b/src/messaging/tests/TestMessagingLayer.cpp new file mode 100644 index 00000000000000..7e0285d6f27170 --- /dev/null +++ b/src/messaging/tests/TestMessagingLayer.cpp @@ -0,0 +1,180 @@ +/* + * + * 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 the ExchangeManager implementation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace { + +using namespace chip; +using namespace chip::Inet; +using namespace chip::Transport; +using namespace chip::Messaging; +using namespace chip::Protocols; +using namespace chip::System::Clock::Literals; + +using TestContext = Test::UDPMessagingContext; + +// The message timeout value in milliseconds. +constexpr System::Clock::Timeout kMessageTimeout = System::Clock::Milliseconds32(100); + +class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegate +{ +public: + CHIP_ERROR OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader, ExchangeDelegate *& newDelegate) override + { + newDelegate = this; + return CHIP_NO_ERROR; + } + + CHIP_ERROR OnMessageReceived(ExchangeContext * ec, const PayloadHeader & payloadHeader, + System::PacketBufferHandle && buffer) override + { + IsOnMessageReceivedCalled = true; + return CHIP_NO_ERROR; + } + + void OnResponseTimeout(ExchangeContext * ec) override { IsOnResponseTimeoutCalled = true; } + + bool IsOnMessageReceivedCalled = false; + bool IsOnResponseTimeoutCalled = false; +}; + +/** + * Tests sending exchange message with Success: + * + * DUT = sender, PEER = remote device + * + * 1) DUT sends message w/o MRP to PEER + * - Confirm the message is sent successfully + * - Observe DUT response timeout with no response + */ +void CheckExchangeOutgoingMessagesSuccess(nlTestSuite * inSuite, void * inContext) +{ + TestContext & ctx = *reinterpret_cast(inContext); + + // create solicited exchange + MockAppDelegate mockSolicitedAppDelegate; + ExchangeContext * ec = ctx.NewExchangeToAlice(&mockSolicitedAppDelegate); + + NL_TEST_ASSERT(inSuite, ec != nullptr); + ec->SetResponseTimeout(kMessageTimeout); + + CHIP_ERROR err = ec->SendMessage(Echo::MsgType::EchoRequest, System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize), + SendFlags(SendMessageFlags::kExpectResponse).Set(SendMessageFlags::kNoAutoRequestAck)); + + // Wait for the initial message to fail (should take 330-413ms) + ctx.GetIOContext().DriveIOUntil(500_ms32, [&] { return mockSolicitedAppDelegate.IsOnMessageReceivedCalled; }); + + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, mockSolicitedAppDelegate.IsOnResponseTimeoutCalled); +} + +/** + * Tests sending exchange message with Failure: + * + * DUT = sender, PEER = remote device + * + * 1) DUT configured to drop the outgoing UDP packet + * 2) DUT sends message w/o MRP to PEER + * - Confirm the message is sent with failure + * - Confirm the DUT response timeout timer is cancelled + */ +void CheckExchangeOutgoingMessagesFail(nlTestSuite * inSuite, void * inContext) +{ + TestContext & ctx = *reinterpret_cast(inContext); + + // create solicited exchange + MockAppDelegate mockSolicitedAppDelegate; + ExchangeContext * ec = ctx.NewExchangeToAlice(&mockSolicitedAppDelegate); + + NL_TEST_ASSERT(inSuite, ec != nullptr); + ec->SetResponseTimeout(kMessageTimeout); + + chip::FaultInjection::GetManager().FailAtFault(chip::FaultInjection::kFault_DropOutgoingUDPMsg, 0, 1); + + CHIP_ERROR err = ec->SendMessage(Echo::MsgType::EchoRequest, System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize), + SendFlags(SendMessageFlags::kExpectResponse).Set(SendMessageFlags::kNoAutoRequestAck)); + + // Wait for the initial message to fail (should take 330-413ms) + ctx.GetIOContext().DriveIOUntil(500_ms32, [&] { return mockSolicitedAppDelegate.IsOnMessageReceivedCalled; }); + + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !mockSolicitedAppDelegate.IsOnResponseTimeoutCalled); + ec->Close(); +} + +// Test Suite + +/** + * Test Suite that lists all the test functions. + */ +// clang-format off +const nlTest sTests[] = +{ + NL_TEST_DEF("Test MessagingLayer::ExchangeOutgoingMessagesSuccess", CheckExchangeOutgoingMessagesSuccess), + NL_TEST_DEF("Test MessagingLayer::ExchangeOutgoingMessagesFail", CheckExchangeOutgoingMessagesFail), + + NL_TEST_SENTINEL() +}; +// clang-format on + +// clang-format off +nlTestSuite sSuite = +{ + "Test-CHIP-MessagingLayer", + &sTests[0], + TestContext::Initialize, + TestContext::Finalize +}; +// clang-format on + +} // namespace + +/** + * Main + */ +int TestMessagingLayer() +{ + return chip::ExecuteTestsWithContext(&sSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestMessagingLayer); diff --git a/src/transport/raw/UDP.cpp b/src/transport/raw/UDP.cpp index 57b1d3c615bffc..3113e9797bfc72 100644 --- a/src/transport/raw/UDP.cpp +++ b/src/transport/raw/UDP.cpp @@ -23,6 +23,7 @@ */ #include +#include #include #include #include @@ -110,6 +111,9 @@ CHIP_ERROR UDP::SendMessage(const Transport::PeerAddress & address, System::Pack addrInfo.DestPort = address.GetPort(); addrInfo.Interface = address.GetInterface(); + // Drop the message and return. Free the buffer. + CHIP_FAULT_INJECT(FaultInjection::kFault_DropOutgoingUDPMsg, msgBuf = nullptr; return CHIP_ERROR_CONNECTION_ABORTED;); + return mUDPEndPoint->SendMsg(&addrInfo, std::move(msgBuf)); } @@ -119,6 +123,8 @@ void UDP::OnUdpReceive(Inet::UDPEndPoint * endPoint, System::PacketBufferHandle UDP * udp = reinterpret_cast(endPoint->mAppState); PeerAddress peerAddress = PeerAddress::UDP(pktInfo->SrcAddress, pktInfo->SrcPort, pktInfo->Interface); + CHIP_FAULT_INJECT(FaultInjection::kFault_DropIncomingUDPMsg, buffer = nullptr; return;); + udp->HandleMessageReceived(peerAddress, std::move(buffer)); if (err != CHIP_NO_ERROR) diff --git a/src/transport/tests/UDPTransportManager.h b/src/transport/tests/UDPTransportManager.h new file mode 100644 index 00000000000000..2a940d3264e834 --- /dev/null +++ b/src/transport/tests/UDPTransportManager.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * + * 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. + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace Test { + +class UDPTransportManager +{ +public: + /// Initialize the underlying layers. + CHIP_ERROR Init() + { + ReturnErrorOnFailure(mIOContext.Init()); + ReturnErrorOnFailure(DeviceLayer::PlatformMgr().InitChipStack()); + ReturnErrorOnFailure(mTransportManager.Init(Transport::UdpListenParameters(DeviceLayer::UDPEndPointManager()) + .SetAddressType(Inet::IPAddressType::kIPv6) + .SetListenPort(CHIP_PORT + 1))); + + return CHIP_NO_ERROR; + } + + // Shutdown all layers, finalize operations + void Shutdown() + { + mTransportManager.Close(); + DeviceLayer::PlatformMgr().Shutdown(); + mIOContext.Shutdown(); + } + + System::Layer & GetSystemLayer() { return mIOContext.GetSystemLayer(); } + chip::Transport::UDP & GetUDP() { return mTransportManager.GetTransport().template GetImplAtIndex<0>(); } + TransportMgrBase & GetTransportMgr() { return mTransportManager; } + IOContext & GetIOContext() { return mIOContext; } + +private: + Test::IOContext mIOContext; + TransportMgr mTransportManager; +}; + +} // namespace Test +} // namespace chip