blob: 96cfce4d3572087d29d7ae9fdc7826cff51bfef2 [file] [log] [blame]
#include <string.h>
#include <pw_unit_test/framework.h>
#include <functional>
#include <optional>
#include <protocols/bdx/TransferFacilitator.h>
#include <system/SystemClock.h>
#include <system/SystemTimer.h>
using namespace ::chip;
using namespace ::chip::bdx;
using namespace ::chip::Protocols;
using namespace ::chip::System::Clock::Literals;
using TransferSessionOutputHandler = std::function<void(TransferSession::OutputEvent & event)>;
namespace chip {
namespace System {
using StartTimerHook = std::function<void(Clock::Timeout aDelay, TimerCompleteCallback aComplete, void * aAppState)>;
class SystemLayerWithMockClock : public Clock::Internal::MockClock, public Layer
{
public:
// System Layer overrides
CriticalFailure Init() override { return CHIP_NO_ERROR; }
void Shutdown() override { Clear(); }
void Clear()
{
mTimerList.Clear();
mTimerNodes.ReleaseAll();
}
bool IsInitialized() const override { return true; }
CriticalFailure StartTimer(Clock::Timeout aDelay, TimerCompleteCallback aComplete, void * aAppState) override
{
Clock::Timestamp awakenTime = GetMonotonicMilliseconds64() + std::chrono::duration_cast<Clock::Milliseconds64>(aDelay);
TimerList::Node * node = mTimerNodes.Create(*this, awakenTime, aComplete, aAppState);
mTimerList.Add(node);
if (mStartTimerHook.has_value())
{
mStartTimerHook.value()(aDelay, aComplete, aAppState);
}
return CHIP_NO_ERROR;
}
void CancelTimer(TimerCompleteCallback aComplete, void * aAppState) override
{
TimerList::Node * cancelled = mTimerList.Remove(aComplete, aAppState);
if (cancelled != nullptr)
{
mTimerNodes.Release(cancelled);
}
}
CHIP_ERROR ExtendTimerTo(Clock::Timeout aDelay, TimerCompleteCallback aComplete, void * aAppState) override
{
return CHIP_ERROR_NOT_IMPLEMENTED;
}
bool IsTimerActive(TimerCompleteCallback onComplete, void * appState) override
{
return mTimerList.GetRemainingTime(onComplete, appState) != Clock::Timeout(0);
}
Clock::Timeout GetRemainingTime(TimerCompleteCallback onComplete, void * appState) override
{
return mTimerList.GetRemainingTime(onComplete, appState);
}
CriticalFailure ScheduleWork(TimerCompleteCallback aComplete, void * aAppState) override { return CHIP_ERROR_NOT_IMPLEMENTED; }
// Clock overrides
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
void SetMonotonic(Clock::Milliseconds64 timestamp)
{
MockClock::SetMonotonic(timestamp);
// Find all the timers that fired at this time or before and invoke the callbacks
TimerList::Node * node;
while ((node = mTimerList.Earliest()) != nullptr && node->AwakenTime() <= timestamp)
{
mTimerList.PopEarliest();
// Invoke auto-releases
mTimerNodes.Invoke(node);
}
}
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
void AdvanceMonotonic(Clock::Milliseconds64 increment) { SetMonotonic(GetMonotonicMilliseconds64() + increment); }
std::optional<StartTimerHook> mStartTimerHook{ std::nullopt };
private:
TimerPool<> mTimerNodes;
TimerList mTimerList;
};
} // namespace System
} // namespace chip
// These are globals because SetUpTestSuite is static which requires static variables
System::SystemLayerWithMockClock gSystemLayerAndClock = System::SystemLayerWithMockClock();
System::Clock::ClockBase * gSavedClock = nullptr;
class TestTransferFacilitator : public ::testing::Test
{
public:
static void SetUpTestSuite()
{
EXPECT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR);
gSavedClock = &System::SystemClock();
System::Clock::Internal::SetSystemClockForTesting(&gSystemLayerAndClock);
}
static void TearDownTestSuite()
{
gSystemLayerAndClock.Shutdown();
System::Clock::Internal::SetSystemClockForTesting(gSavedClock);
Platform::MemoryShutdown();
}
};
class TestInitiator : public Initiator
{
private:
void HandleTransferSessionOutput(TransferSession::OutputEvent & event) override
{
if (mTransferSessionOutputHandler.has_value())
{
mTransferSessionOutputHandler.value()(event);
}
}
public:
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
void PollForOutput() { Initiator::PollForOutput(); }
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
void ScheduleImmediatePoll() { Initiator::ScheduleImmediatePoll(); }
std::optional<TransferSessionOutputHandler> mTransferSessionOutputHandler{ std::nullopt };
};
TEST_F(TestTransferFacilitator, InitiatesTransfer)
{
TestInitiator initiator;
auto r = initiator.InitiateTransfer(&gSystemLayerAndClock, TransferRole::kSender, TransferSession::TransferInitData(),
System::Clock::Seconds16(60), System::Clock::Milliseconds32(500));
EXPECT_EQ(r, CHIP_NO_ERROR); // Placeholder for actual test logic
}
TEST_F(TestTransferFacilitator, PollsForOutputAfterTimeoutExpires)
{
TestInitiator initiator;
auto initData = TransferSession::TransferInitData();
initData.TransferCtlFlags = TransferControlFlags::kSenderDrive;
initData.MaxBlockSize = 1024;
initData.StartOffset = 0;
initData.Length = 10000; // 10 KB transfer
initData.FileDesignator = reinterpret_cast<const uint8_t *>("test_file.txt");
initData.FileDesLength = static_cast<uint16_t>(strlen(reinterpret_cast<const char *>(initData.FileDesignator)));
initData.Metadata = nullptr; // No metadata for this test
initData.MetadataLength = 0;
// hook a callback to the output handler
auto outputEvent = TransferSession::OutputEvent(TransferSession::OutputEventType::kNone);
initiator.mTransferSessionOutputHandler = [&outputEvent](TransferSession::OutputEvent & event) {
outputEvent.EventType = event.EventType; // Capture the output event type
};
gSystemLayerAndClock.SetMonotonic(0_ms);
auto r = initiator.InitiateTransfer(&gSystemLayerAndClock, TransferRole::kSender, initData, System::Clock::Seconds16(10),
System::Clock::Milliseconds32(2000));
// advancing clock to 5s expires poll frequency timer and triggers a call to PollForOutput
// which sets initial time for starting to count timeout set in InitiateTransfer
gSystemLayerAndClock.SetMonotonic(5000_ms); // Set the clock to 5 seconds
// advancing clock to 15s expires the timeout set in InitiateTransfer since 15000_ms - 5000_ms = 10000_ms
// which triggers a call to PollForOutput that returns a TransferTimeout event type
gSystemLayerAndClock.SetMonotonic(15000_ms);
EXPECT_EQ(r, CHIP_NO_ERROR);
EXPECT_EQ(outputEvent.EventType,
TransferSession::OutputEventType::kTransferTimeout); // Check if the output event is a timeout event
}
TEST_F(TestTransferFacilitator, PollsForOutput)
{
TestInitiator initiator;
// hook a callback to the StartTimerHook
bool timerStarted = false;
gSystemLayerAndClock.mStartTimerHook = [&timerStarted](auto, auto, void *) { timerStarted = true; };
auto r = initiator.InitiateTransfer(&gSystemLayerAndClock, TransferRole::kSender, TransferSession::TransferInitData(),
System::Clock::Seconds16(60), System::Clock::Milliseconds32(500));
EXPECT_EQ(r, CHIP_NO_ERROR); // Placeholder for actual test logic
// hook a callback to the output handler
bool outputHandled = false;
initiator.mTransferSessionOutputHandler = [&outputHandled](TransferSession::OutputEvent & event) { outputHandled = true; };
// Simulate a poll
initiator.PollForOutput();
// Check if the poll was handled correctly
EXPECT_TRUE(outputHandled);
// Check if the timer was started
EXPECT_TRUE(timerStarted);
}