/********************************************************************************
 * Copyright (c) 2025-2026 ZF Friedrichshafen AG
 *
 * This program and the accompanying materials are made available under the
 * terms of the Apache License Version 2.0 which is available at
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Contributors:
 *   Erik Verhoeven - initial API and implementation
 ********************************************************************************/

#include <gtest/gtest.h>
#include <iostream>
#include <ostream>
#include <fstream>
#include <mutex>
#include <thread>
#include <utility>
#include <atomic>
#include <chrono>

#include <support/signal_support.h>
#include <interfaces/dispatch.h>
#include <support/app_control.h>
#include <support/timer.h>
#include "../../include/sdv_test_macro.h"
#include "../../../global/process_watchdog.h"
#include "../global/ascformat/ascreader.cpp"
#include "../global/ascformat/ascwriter.cpp"
#include "../global/exec_dir_helper.h"

#define CDataLink CAscReaderDataLink
#define SObjectClassInstance_CDataLink SObjectClassInstance_CAscReaderDataLink
#define g_CDataLink g_CAscReaderDataLink
#undef CDataLink
#undef SObjectClassInstance_CDataLink
#undef g_CDataLink
#define CDataLink CDbcStructDataLink
#define SObjectClassInstance_CDataLink SObjectClassInstance_CDbcStructDataLink
#define g_CDataLink g_CSbcStructDataLink
#include "generated/dbc_struct_test/can_dl/datalink.cpp"
#undef CDataLink
#undef SObjectClassInstance_CDataLink
#undef g_CDataLink

#if defined(_WIN32) && defined(_UNICODE)
extern "C" int wmain(int argc, wchar_t* argv[])
#else
extern "C" int main(int argc, char* argv[])
#endif
{
    CProcessWatchdog watchdog;

    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

TEST(DbcUtilCanDLTest, Prerequisitives)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "asc_writer_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "asc_writer_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_asc.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    sdv::TInterfaceAccessPtr ptrDispatchService = sdv::core::GetObject("DataDispatchService");
    EXPECT_TRUE(ptrDispatchService);

    sdv::TInterfaceAccessPtr ptrCanComObj = sdv::core::GetObject("CAN_Communication_Object");
    EXPECT_TRUE(ptrCanComObj);

    appcontrol.Shutdown();
}

TEST(DbcUtilCanDLTest, SpontaneousTransmitBigEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalBE1 = dispatch.AddPublisher("TestBE.SignalBE1");
    EXPECT_TRUE(signalBE1);
    sdv::core::CSignal signalBE2 = dispatch.AddPublisher("TestBE.SignalBE2");
    EXPECT_TRUE(signalBE2);
    sdv::core::CSignal signalBE3 = dispatch.AddPublisher("TestBE.SignalBE3");
    EXPECT_TRUE(signalBE3);
    sdv::core::CSignal signalBE4 = dispatch.AddPublisher("TestBE.SignalBE4");
    EXPECT_TRUE(signalBE4);
    sdv::core::CSignal signalBE5 = dispatch.AddPublisher("TestBE.SignalBE5");
    EXPECT_TRUE(signalBE5);
    sdv::core::CSignal signalBE6 = dispatch.AddPublisher("TestBE.SignalBE6");
    EXPECT_TRUE(signalBE6);
    appcontrol.SetRunningMode();

    EXPECT_NO_THROW(signalBE1.Write(0xa));
    EXPECT_NO_THROW(signalBE2.Write(0x5d));
    EXPECT_NO_THROW(signalBE3.Write(0x31));
    EXPECT_NO_THROW(signalBE4.Write(0x2d));
    EXPECT_NO_THROW(signalBE5.Write(0x96));
    EXPECT_NO_THROW(signalBE6.Write(0x33cc55aa));

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalBE1.Reset();
    signalBE2.Reset();
    signalBE3.Reset();
    signalBE4.Reset();
    signalBE5.Reset();
    signalBE6.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0x6, {0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xDC, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xDC, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xDC, 0x6D, 0x96, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xDC, 0x6D, 0x96, 0x33, 0xCC, 0x55, 0xAA}}
    };

    // Compare with test data.
    for (const STestData& sData : rgsData)
    {
        EXPECT_FALSE(reader.IsEOF());
        std::pair<asc::SCanMessage, bool> prMsg;
        do {
            prMsg = reader.Get();
            EXPECT_TRUE(prMsg.second);
            ++reader;
        } while (!reader.IsEOF() && prMsg.first.uiId != sData.uiID);   // Skip other messages
        EXPECT_EQ(prMsg.first.uiChannel, 1ul);
        EXPECT_EQ(prMsg.first.uiId, sData.uiID);
        EXPECT_FALSE(prMsg.first.bExtended);
        EXPECT_FALSE(prMsg.first.bCanFd);
        EXPECT_EQ(prMsg.first.uiLength, 8ul);
        EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
        EXPECT_EQ(prMsg.first.rguiData[0], sData.rguiData[0]);
        EXPECT_EQ(prMsg.first.rguiData[1], sData.rguiData[1]);
        EXPECT_EQ(prMsg.first.rguiData[2], sData.rguiData[2]);
        EXPECT_EQ(prMsg.first.rguiData[3], sData.rguiData[3]);
        EXPECT_EQ(prMsg.first.rguiData[4], sData.rguiData[4]);
        EXPECT_EQ(prMsg.first.rguiData[5], sData.rguiData[5]);
        EXPECT_EQ(prMsg.first.rguiData[6], sData.rguiData[6]);
        EXPECT_EQ(prMsg.first.rguiData[7], sData.rguiData[7]);
    }
}

TEST(DbcUtilCanDLTest, SpontaneousTransmit64BitBigEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalInt64BE = dispatch.AddPublisher("TestInt64BE.SignalInt64BE");
    EXPECT_TRUE(signalInt64BE);
    sdv::core::CSignal signalUint64BE = dispatch.AddPublisher("TestUint64BE.SignalUint64BE");
    EXPECT_TRUE(signalUint64BE);
    sdv::core::CSignal signalScaleInt64BE = dispatch.AddPublisher("TestScale64BE.SignalScaleInt64BE");
    EXPECT_TRUE(signalScaleInt64BE);
    appcontrol.SetRunningMode();

    EXPECT_NO_THROW(signalInt64BE.Write(-6144092013047381999ll));   // 0xaabbccddeeff0011
    EXPECT_NO_THROW(signalUint64BE.Write(0xaabbccddeeff0011));
    EXPECT_NO_THROW(signalScaleInt64BE.Write(987.654321012));       // 0xE5F4C8F374

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalInt64BE.Reset();
    signalUint64BE.Reset();
    signalScaleInt64BE.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0xCB, {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11}},
        { 0x67, {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11}},
        { 0x1F7, {0x00, 0x00, 0x00, 0xE5, 0xF4, 0xC8, 0xF3, 0x74}}
    };

    // Compare with test data.
    for (const STestData& sData : rgsData)
    {
        EXPECT_FALSE(reader.IsEOF());
        std::pair<asc::SCanMessage, bool> prMsg;
        do {
            prMsg = reader.Get();
            EXPECT_TRUE(prMsg.second);
            ++reader;
        } while (!reader.IsEOF() && prMsg.first.uiId != sData.uiID);   // Skip other messages
        EXPECT_EQ(prMsg.first.uiChannel, 1ul);
        EXPECT_EQ(prMsg.first.uiId, sData.uiID);
        EXPECT_FALSE(prMsg.first.bExtended);
        EXPECT_FALSE(prMsg.first.bCanFd);
        EXPECT_EQ(prMsg.first.uiLength, 8ul);
        EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
        EXPECT_EQ(prMsg.first.rguiData[0], sData.rguiData[0]);
        EXPECT_EQ(prMsg.first.rguiData[1], sData.rguiData[1]);
        EXPECT_EQ(prMsg.first.rguiData[2], sData.rguiData[2]);
        EXPECT_EQ(prMsg.first.rguiData[3], sData.rguiData[3]);
        EXPECT_EQ(prMsg.first.rguiData[4], sData.rguiData[4]);
        EXPECT_EQ(prMsg.first.rguiData[5], sData.rguiData[5]);
        EXPECT_EQ(prMsg.first.rguiData[6], sData.rguiData[6]);
        EXPECT_EQ(prMsg.first.rguiData[7], sData.rguiData[7]);
    }
}

TEST(DbcUtilCanDLTest, ReceiveBigEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0x6, {0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xDC, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xDC, 0x6D, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xDC, 0x6D, 0x96, 0x00, 0x00, 0x00, 0x00}},
        { 0x6, {0xA5, 0xDC, 0x6D, 0x96, 0x33, 0xCC, 0x55, 0xAA}}
    };

    // Create ASC file with a delay of 100 (to allow the system to initialize)
    asc::CAscWriter writer;
    double dTimestamp = 0.100;
    for (const STestData& sData : rgsData)
    {
        asc::SCanMessage sMsg{};
        sMsg.dTimestamp = dTimestamp;
        dTimestamp += 0.001;
        sMsg.uiChannel = 1ul;
        sMsg.uiId = sData.uiID;
        sMsg.bExtended = false;
        sMsg.bCanFd = false;
        sMsg.uiLength = 8ul;
        sMsg.eDirection = asc::SCanMessage::EDirection::rx;
        sMsg.rguiData[0] = sData.rguiData[0];
        sMsg.rguiData[1] = sData.rguiData[1];
        sMsg.rguiData[2] = sData.rguiData[2];
        sMsg.rguiData[3] = sData.rguiData[3];
        sMsg.rguiData[4] = sData.rguiData[4];
        sMsg.rguiData[5] = sData.rguiData[5];
        sMsg.rguiData[6] = sData.rguiData[6];
        sMsg.rguiData[7] = sData.rguiData[7];
        writer.AddSample(sMsg);
    }

    // Add a dummy message with a big timegap to prevent the repetition function to jump in.
    asc::SCanMessage sMsgDummy{};
    sMsgDummy.dTimestamp = 100.0;
    sMsgDummy.uiChannel = 1ul;
    sMsgDummy.uiId = 999;
    writer.AddSample(sMsgDummy);

    // Write the ASC file
    EXPECT_TRUE(writer.Write(GetExecDirectory() / "receiver_test.asc"));
    writer.Clear();

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_rx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    uint32_t rguiVal[6] = {};
    size_t nCnt = 0;
    sdv::core::CSignal signalBE1 = dispatch.Subscribe("TestBE.SignalBE1", [&](sdv::any_t tVal) {rguiVal[0] = tVal; nCnt++; });
    EXPECT_TRUE(signalBE1);
    sdv::core::CSignal signalBE2 = dispatch.Subscribe("TestBE.SignalBE2", [&](sdv::any_t tVal) {rguiVal[1] = tVal; nCnt++; });
    EXPECT_TRUE(signalBE2);
    sdv::core::CSignal signalBE3 = dispatch.Subscribe("TestBE.SignalBE3", [&](sdv::any_t tVal) {rguiVal[2] = tVal; nCnt++; });
    EXPECT_TRUE(signalBE3);
    sdv::core::CSignal signalBE4 = dispatch.Subscribe("TestBE.SignalBE4", [&](sdv::any_t tVal) {rguiVal[3] = tVal; nCnt++; });
    EXPECT_TRUE(signalBE4);
    sdv::core::CSignal signalBE5 = dispatch.Subscribe("TestBE.SignalBE5", [&](sdv::any_t tVal) {rguiVal[4] = tVal; nCnt++; });
    EXPECT_TRUE(signalBE5);
    sdv::core::CSignal signalBE6 = dispatch.Subscribe("TestBE.SignalBE6", [&](sdv::any_t tVal) {rguiVal[5] = tVal; nCnt++; });
    EXPECT_TRUE(signalBE6);
    appcontrol.SetRunningMode();

    std::this_thread::sleep_for(std::chrono::milliseconds(250));

    EXPECT_EQ(nCnt, 36u);
    EXPECT_EQ(rguiVal[0], 0xau);
    EXPECT_EQ(rguiVal[1], 0x5du);
    EXPECT_EQ(rguiVal[2], 0x31u);
    EXPECT_EQ(rguiVal[3], 0x2du);
    EXPECT_EQ(rguiVal[4], 0x96u);
    EXPECT_EQ(rguiVal[5], 0x33cc55aau);

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalBE1.Reset();
    signalBE2.Reset();
    signalBE3.Reset();
    signalBE4.Reset();
    signalBE5.Reset();
    signalBE6.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();
}

TEST(DbcUtilCanDLTest, Receive64BitBigEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0xCB, {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11}},
        { 0x67, {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11}},
        { 0x1F7, {0x00, 0x00, 0x00, 0xE5, 0xF4, 0xC8, 0xF3, 0x74}}
    };

    // Create ASC file with a delay of 100 (to allow the system to initialize)
    asc::CAscWriter writer;
    double dTimestamp = 0.100;
    for (const STestData& sData : rgsData)
    {
        asc::SCanMessage sMsg{};
        sMsg.dTimestamp = dTimestamp;
        dTimestamp += 0.001;
        sMsg.uiChannel = 1ul;
        sMsg.uiId = sData.uiID;
        sMsg.bExtended = false;
        sMsg.bCanFd = false;
        sMsg.uiLength = 8ul;
        sMsg.eDirection = asc::SCanMessage::EDirection::rx;
        sMsg.rguiData[0] = sData.rguiData[0];
        sMsg.rguiData[1] = sData.rguiData[1];
        sMsg.rguiData[2] = sData.rguiData[2];
        sMsg.rguiData[3] = sData.rguiData[3];
        sMsg.rguiData[4] = sData.rguiData[4];
        sMsg.rguiData[5] = sData.rguiData[5];
        sMsg.rguiData[6] = sData.rguiData[6];
        sMsg.rguiData[7] = sData.rguiData[7];
        writer.AddSample(sMsg);
    }

    // Add a dummy message with a big timegap to prevent the repetition function to jump in.
    asc::SCanMessage sMsgDummy{};
    sMsgDummy.dTimestamp = 100.0;
    sMsgDummy.uiChannel = 1ul;
    sMsgDummy.uiId = 999;
    writer.AddSample(sMsgDummy);

    // Write the ASC file
    EXPECT_TRUE(writer.Write(GetExecDirectory() / "receiver_test.asc"));
    writer.Clear();

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_rx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    int64_t iInt64Val = 0;
    uint64_t uiUint64Val = 0u;
    double dScaleInt64Val = 0;
    sdv::core::CSignal signalInt64BE = dispatch.Subscribe("TestInt64BE.SignalInt64BE", [&](sdv::any_t tVal) {iInt64Val = tVal; });
    EXPECT_TRUE(signalInt64BE);
    sdv::core::CSignal signalUint64BE = dispatch.Subscribe("TestUint64BE.SignalUint64BE", [&](sdv::any_t tVal) {uiUint64Val = tVal; });
    EXPECT_TRUE(signalUint64BE);
    sdv::core::CSignal signalScaleInt64BE = dispatch.Subscribe("TestScale64BE.SignalScaleInt64BE", [&](sdv::any_t tVal) {dScaleInt64Val = tVal; });
    EXPECT_TRUE(signalScaleInt64BE);
    appcontrol.SetRunningMode();

    std::this_thread::sleep_for(std::chrono::milliseconds(250));

    EXPECT_EQ(iInt64Val, -6144092013047381999ll);
    EXPECT_EQ(uiUint64Val, 0xaabbccddeeff0011);
    EXPECT_EQ(round(dScaleInt64Val * 1e8), round(987.654321012 * 1e8));

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalInt64BE.Reset();
    signalUint64BE.Reset();
    signalScaleInt64BE.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();
}

TEST(DbcUtilCanDLTest, SpontaneousTransmitLittleEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalLE1 = dispatch.AddPublisher("TestLE.SignalLE1");
    EXPECT_TRUE(signalLE1);
    sdv::core::CSignal signalLE2 = dispatch.AddPublisher("TestLE.SignalLE2");
    EXPECT_TRUE(signalLE2);
    sdv::core::CSignal signalLE3 = dispatch.AddPublisher("TestLE.SignalLE3");
    EXPECT_TRUE(signalLE3);
    sdv::core::CSignal signalLE4 = dispatch.AddPublisher("TestLE.SignalLE4");
    EXPECT_TRUE(signalLE4);
    sdv::core::CSignal signalLE5 = dispatch.AddPublisher("TestLE.SignalLE5");
    EXPECT_TRUE(signalLE5);
    sdv::core::CSignal signalLE6 = dispatch.AddPublisher("TestLE.SignalLE6");
    EXPECT_TRUE(signalLE6);
    appcontrol.SetRunningMode();

    EXPECT_NO_THROW(signalLE1.Write(0xa));
    EXPECT_NO_THROW(signalLE2.Write(0x5d));
    EXPECT_NO_THROW(signalLE3.Write(0x31));
    EXPECT_NO_THROW(signalLE4.Write(0x2d));
    EXPECT_NO_THROW(signalLE5.Write(0x96));
    EXPECT_NO_THROW(signalLE6.Write(0x33cc55aa));

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalLE1.Reset();
    signalLE2.Reset();
    signalLE3.Reset();
    signalLE4.Reset();
    signalLE5.Reset();
    signalLE6.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0x7, {0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x15, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x15, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x15, 0xB7, 0x96, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x15, 0xB7, 0x96, 0xAA, 0x55, 0xCC, 0x33}}
    };

    // Compare with test data.
    for (const STestData& sData : rgsData)
    {
        EXPECT_FALSE(reader.IsEOF());
        std::pair<asc::SCanMessage, bool> prMsg;
        do {
            prMsg = reader.Get();
            EXPECT_TRUE(prMsg.second);
            ++reader;
        } while (!reader.IsEOF() && prMsg.first.uiId != sData.uiID);   // Skip other messages
        EXPECT_EQ(prMsg.first.uiChannel, 1ul);
        EXPECT_EQ(prMsg.first.uiId, sData.uiID);
        EXPECT_FALSE(prMsg.first.bExtended);
        EXPECT_FALSE(prMsg.first.bCanFd);
        EXPECT_EQ(prMsg.first.uiLength, 8ul);
        EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
        EXPECT_EQ(prMsg.first.rguiData[0], sData.rguiData[0]);
        EXPECT_EQ(prMsg.first.rguiData[1], sData.rguiData[1]);
        EXPECT_EQ(prMsg.first.rguiData[2], sData.rguiData[2]);
        EXPECT_EQ(prMsg.first.rguiData[3], sData.rguiData[3]);
        EXPECT_EQ(prMsg.first.rguiData[4], sData.rguiData[4]);
        EXPECT_EQ(prMsg.first.rguiData[5], sData.rguiData[5]);
        EXPECT_EQ(prMsg.first.rguiData[6], sData.rguiData[6]);
        EXPECT_EQ(prMsg.first.rguiData[7], sData.rguiData[7]);
    }
}

TEST(DbcUtilCanDLTest, SpontaneousTransmit64BitLittleEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalInt64LE = dispatch.AddPublisher("TestInt64LE.SignalInt64LE");
    EXPECT_TRUE(signalInt64LE);
    sdv::core::CSignal signalUint64LE = dispatch.AddPublisher("TestUint64LE.SignalUint64LE");
    EXPECT_TRUE(signalUint64LE);
    sdv::core::CSignal signalScaleInt64LE = dispatch.AddPublisher("TestScale64LE.SignalScaleInt64LE");
    EXPECT_TRUE(signalScaleInt64LE);
    appcontrol.SetRunningMode();

    EXPECT_NO_THROW(signalInt64LE.Write(-6144092013047381999ll));   // 0xaabbccddeeff0011
    EXPECT_NO_THROW(signalUint64LE.Write(0xaabbccddeeff0011));
    EXPECT_NO_THROW(signalScaleInt64LE.Write(987.654321012));       // 0xE5F4C8F374

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalInt64LE.Reset();
    signalUint64LE.Reset();
    signalScaleInt64LE.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0xCA, {0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA}},
        { 0x66, {0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA}},
        { 0x1F6, {0x74, 0xF3, 0xC8, 0xF4, 0xE5, 0x00, 0x00, 0x00}}
    };

    // Compare with test data.
    for (const STestData& sData : rgsData)
    {
        EXPECT_FALSE(reader.IsEOF());
        std::pair<asc::SCanMessage, bool> prMsg;
        do {
            prMsg = reader.Get();
            EXPECT_TRUE(prMsg.second);
            ++reader;
        } while (!reader.IsEOF() && prMsg.first.uiId != sData.uiID);   // Skip other messages
        EXPECT_EQ(prMsg.first.uiChannel, 1ul);
        EXPECT_EQ(prMsg.first.uiId, sData.uiID);
        EXPECT_FALSE(prMsg.first.bExtended);
        EXPECT_FALSE(prMsg.first.bCanFd);
        EXPECT_EQ(prMsg.first.uiLength, 8ul);
        EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
        EXPECT_EQ(prMsg.first.rguiData[0], sData.rguiData[0]);
        EXPECT_EQ(prMsg.first.rguiData[1], sData.rguiData[1]);
        EXPECT_EQ(prMsg.first.rguiData[2], sData.rguiData[2]);
        EXPECT_EQ(prMsg.first.rguiData[3], sData.rguiData[3]);
        EXPECT_EQ(prMsg.first.rguiData[4], sData.rguiData[4]);
        EXPECT_EQ(prMsg.first.rguiData[5], sData.rguiData[5]);
        EXPECT_EQ(prMsg.first.rguiData[6], sData.rguiData[6]);
        EXPECT_EQ(prMsg.first.rguiData[7], sData.rguiData[7]);
    }
}

TEST(DbcUtilCanDLTest, ReceiveLittleEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0x7, {0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x15, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x15, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x15, 0xB7, 0x96, 0x00, 0x00, 0x00, 0x00}},
        { 0x7, {0xDA, 0x15, 0xB7, 0x96, 0xAA, 0x55, 0xCC, 0x33}}
    };

    // Create ASC file with a delay of 100 (to allow the system to initialize)
    asc::CAscWriter writer;
    double dTimestamp = 0.100;
    for (const STestData& sData : rgsData)
    {
        asc::SCanMessage sMsg{};
        sMsg.dTimestamp = dTimestamp;
        dTimestamp += 0.001;
        sMsg.uiChannel = 1ul;
        sMsg.uiId = sData.uiID;
        sMsg.bExtended = false;
        sMsg.bCanFd = false;
        sMsg.uiLength = 8ul;
        sMsg.eDirection = asc::SCanMessage::EDirection::rx;
        sMsg.rguiData[0] = sData.rguiData[0];
        sMsg.rguiData[1] = sData.rguiData[1];
        sMsg.rguiData[2] = sData.rguiData[2];
        sMsg.rguiData[3] = sData.rguiData[3];
        sMsg.rguiData[4] = sData.rguiData[4];
        sMsg.rguiData[5] = sData.rguiData[5];
        sMsg.rguiData[6] = sData.rguiData[6];
        sMsg.rguiData[7] = sData.rguiData[7];
        writer.AddSample(sMsg);
    }

    // Add a dummy message with a big timegap to prevent the repetition function to jump in.
    asc::SCanMessage sMsgDummy{};
    sMsgDummy.dTimestamp = 100.0;
    sMsgDummy.uiChannel = 1ul;
    sMsgDummy.uiId = 999;
    writer.AddSample(sMsgDummy);

    // Write the ASC file
    EXPECT_TRUE(writer.Write(GetExecDirectory() / "receiver_test.asc"));
    writer.Clear();

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_rx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    uint32_t rguiVal[6] = {};
    size_t nCnt = 0;
    sdv::core::CSignal signalLE1 = dispatch.Subscribe("TestLE.SignalLE1", [&](sdv::any_t tVal) {rguiVal[0] = tVal; nCnt++; });
    EXPECT_TRUE(signalLE1);
    sdv::core::CSignal signalLE2 = dispatch.Subscribe("TestLE.SignalLE2", [&](sdv::any_t tVal) {rguiVal[1] = tVal; nCnt++; });
    EXPECT_TRUE(signalLE2);
    sdv::core::CSignal signalLE3 = dispatch.Subscribe("TestLE.SignalLE3", [&](sdv::any_t tVal) {rguiVal[2] = tVal; nCnt++; });
    EXPECT_TRUE(signalLE3);
    sdv::core::CSignal signalLE4 = dispatch.Subscribe("TestLE.SignalLE4", [&](sdv::any_t tVal) {rguiVal[3] = tVal; nCnt++; });
    EXPECT_TRUE(signalLE4);
    sdv::core::CSignal signalLE5 = dispatch.Subscribe("TestLE.SignalLE5", [&](sdv::any_t tVal) {rguiVal[4] = tVal; nCnt++; });
    EXPECT_TRUE(signalLE5);
    sdv::core::CSignal signalLE6 = dispatch.Subscribe("TestLE.SignalLE6", [&](sdv::any_t tVal) {rguiVal[5] = tVal; nCnt++; });
    EXPECT_TRUE(signalLE6);
    appcontrol.SetRunningMode();

    std::this_thread::sleep_for(std::chrono::milliseconds(250));

    EXPECT_EQ(nCnt, 36u);
    EXPECT_EQ(rguiVal[0], 0xau);
    EXPECT_EQ(rguiVal[1], 0x5du);
    EXPECT_EQ(rguiVal[2], 0x31u);
    EXPECT_EQ(rguiVal[3], 0x2du);
    EXPECT_EQ(rguiVal[4], 0x96u);
    EXPECT_EQ(rguiVal[5], 0x33cc55aau);

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalLE1.Reset();
    signalLE2.Reset();
    signalLE3.Reset();
    signalLE4.Reset();
    signalLE5.Reset();
    signalLE6.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();
}

TEST(DbcUtilCanDLTest, Receive64BitLittleEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0xCA, {0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA}},
        { 0x66, {0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA}},
        { 0x1F6, {0x74, 0xF3, 0xC8, 0xF4, 0xE5, 0x00, 0x00, 0x00}}
    };

    // Create ASC file with a delay of 100 (to allow the system to initialize)
    asc::CAscWriter writer;
    double dTimestamp = 0.100;
    for (const STestData& sData : rgsData)
    {
        asc::SCanMessage sMsg{};
        sMsg.dTimestamp = dTimestamp;
        dTimestamp += 0.001;
        sMsg.uiChannel = 1ul;
        sMsg.uiId = sData.uiID;
        sMsg.bExtended = false;
        sMsg.bCanFd = false;
        sMsg.uiLength = 8ul;
        sMsg.eDirection = asc::SCanMessage::EDirection::rx;
        sMsg.rguiData[0] = sData.rguiData[0];
        sMsg.rguiData[1] = sData.rguiData[1];
        sMsg.rguiData[2] = sData.rguiData[2];
        sMsg.rguiData[3] = sData.rguiData[3];
        sMsg.rguiData[4] = sData.rguiData[4];
        sMsg.rguiData[5] = sData.rguiData[5];
        sMsg.rguiData[6] = sData.rguiData[6];
        sMsg.rguiData[7] = sData.rguiData[7];
        writer.AddSample(sMsg);
    }

    // Add a dummy message with a big timegap to prevent the repetition function to jump in.
    asc::SCanMessage sMsgDummy{};
    sMsgDummy.dTimestamp = 100.0;
    sMsgDummy.uiChannel = 1ul;
    sMsgDummy.uiId = 999;
    writer.AddSample(sMsgDummy);

    // Write the ASC file
    EXPECT_TRUE(writer.Write(GetExecDirectory() / "receiver_test.asc"));
    writer.Clear();

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_rx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    int64_t iInt64Val = 0;
    uint64_t uiUint64Val = 0u;
    double dScaleInt64Val = 0;
    sdv::core::CSignal signalInt64LE = dispatch.Subscribe("TestInt64LE.SignalInt64LE", [&](sdv::any_t tVal) {iInt64Val = tVal; });
    EXPECT_TRUE(signalInt64LE);
    sdv::core::CSignal signalUint64LE = dispatch.Subscribe("TestUint64LE.SignalUint64LE", [&](sdv::any_t tVal) {uiUint64Val = tVal; });
    EXPECT_TRUE(signalUint64LE);
    sdv::core::CSignal signalScaleInt64LE = dispatch.Subscribe("TestScale64LE.SignalScaleInt64LE", [&](sdv::any_t tVal) {dScaleInt64Val = tVal; });
    EXPECT_TRUE(signalScaleInt64LE);
    appcontrol.SetRunningMode();

    std::this_thread::sleep_for(std::chrono::milliseconds(250));

    EXPECT_EQ(iInt64Val, -6144092013047381999ll);
    EXPECT_EQ(uiUint64Val, 0xaabbccddeeff0011);
    EXPECT_EQ(round(dScaleInt64Val * 1e8), round(987.654321012 * 1e8));

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalInt64LE.Reset();
    signalUint64LE.Reset();
    signalScaleInt64LE.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();
}

TEST(DbcUtilCanDLTest, SpontaneousTransactionalTransmitBigEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalBE1 = dispatch.AddPublisher("TestBE.SignalBE1");
    EXPECT_TRUE(signalBE1);
    sdv::core::CSignal signalBE2 = dispatch.AddPublisher("TestBE.SignalBE2");
    EXPECT_TRUE(signalBE2);
    sdv::core::CSignal signalBE3 = dispatch.AddPublisher("TestBE.SignalBE3");
    EXPECT_TRUE(signalBE3);
    sdv::core::CSignal signalBE4 = dispatch.AddPublisher("TestBE.SignalBE4");
    EXPECT_TRUE(signalBE4);
    sdv::core::CSignal signalBE5 = dispatch.AddPublisher("TestBE.SignalBE5");
    EXPECT_TRUE(signalBE5);
    sdv::core::CSignal signalBE6 = dispatch.AddPublisher("TestBE.SignalBE6");
    EXPECT_TRUE(signalBE6);
    appcontrol.SetRunningMode();

    sdv::core::CTransaction transaction = dispatch.CreateTransaction();
    EXPECT_TRUE(transaction);
    EXPECT_NO_THROW(signalBE1.Write(0xa, transaction));
    EXPECT_NO_THROW(signalBE2.Write(0x5d, transaction));
    EXPECT_NO_THROW(signalBE3.Write(0x31, transaction));
    EXPECT_NO_THROW(signalBE4.Write(0x2d, transaction));
    EXPECT_NO_THROW(signalBE5.Write(0x96, transaction));
    EXPECT_NO_THROW(signalBE6.Write(0x33cc55aa, transaction));
    transaction.Finish();

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalBE1.Reset();
    signalBE2.Reset();
    signalBE3.Reset();
    signalBE4.Reset();
    signalBE5.Reset();
    signalBE6.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    // Get first message
    std::pair<asc::SCanMessage, bool> prMsg = reader.Get();
    EXPECT_TRUE(prMsg.second);
    ++reader;
    EXPECT_EQ(prMsg.first.uiChannel, 1ul);
    EXPECT_EQ(prMsg.first.uiId, 6ul);
    EXPECT_FALSE(prMsg.first.bExtended);
    EXPECT_FALSE(prMsg.first.bCanFd);
    EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
    EXPECT_EQ(prMsg.first.uiLength, 8ul);
    EXPECT_EQ(prMsg.first.rguiData[0], 0xa5);
    EXPECT_EQ(prMsg.first.rguiData[1], 0xdC);
    EXPECT_EQ(prMsg.first.rguiData[2], 0x6d);
    EXPECT_EQ(prMsg.first.rguiData[3], 0x96);
    EXPECT_EQ(prMsg.first.rguiData[4], 0x33);
    EXPECT_EQ(prMsg.first.rguiData[5], 0xcc);
    EXPECT_EQ(prMsg.first.rguiData[6], 0x55);
    EXPECT_EQ(prMsg.first.rguiData[7], 0xaa);
}

TEST(DbcUtilCanDLTest, SpontaneousTransactionalTransmitLittleEndian)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalLE1 = dispatch.AddPublisher("TestLE.SignalLE1");
    EXPECT_TRUE(signalLE1);
    sdv::core::CSignal signalLE2 = dispatch.AddPublisher("TestLE.SignalLE2");
    EXPECT_TRUE(signalLE2);
    sdv::core::CSignal signalLE3 = dispatch.AddPublisher("TestLE.SignalLE3");
    EXPECT_TRUE(signalLE3);
    sdv::core::CSignal signalLE4 = dispatch.AddPublisher("TestLE.SignalLE4");
    EXPECT_TRUE(signalLE4);
    sdv::core::CSignal signalLE5 = dispatch.AddPublisher("TestLE.SignalLE5");
    EXPECT_TRUE(signalLE5);
    sdv::core::CSignal signalLE6 = dispatch.AddPublisher("TestLE.SignalLE6");
    EXPECT_TRUE(signalLE6);
    appcontrol.SetRunningMode();

    sdv::core::CTransaction transaction = dispatch.CreateTransaction();
    EXPECT_TRUE(transaction);
    EXPECT_NO_THROW(signalLE1.Write(0xa, transaction));
    EXPECT_NO_THROW(signalLE2.Write(0x5d, transaction));
    EXPECT_NO_THROW(signalLE3.Write(0x31, transaction));
    EXPECT_NO_THROW(signalLE4.Write(0x2d, transaction));
    EXPECT_NO_THROW(signalLE5.Write(0x96, transaction));
    EXPECT_NO_THROW(signalLE6.Write(0x33cc55aa, transaction));
    transaction.Finish();

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalLE1.Reset();
    signalLE2.Reset();
    signalLE3.Reset();
    signalLE4.Reset();
    signalLE5.Reset();
    signalLE6.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    // Get first message
    std::pair<asc::SCanMessage, bool> prMsg = reader.Get();
    EXPECT_TRUE(prMsg.second);
    ++reader;
    EXPECT_EQ(prMsg.first.uiChannel, 1ul);
    EXPECT_EQ(prMsg.first.uiId, 7ul);
    EXPECT_FALSE(prMsg.first.bExtended);
    EXPECT_FALSE(prMsg.first.bCanFd);
    EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
    EXPECT_EQ(prMsg.first.uiLength, 8ul);
    EXPECT_EQ(prMsg.first.rguiData[0], 0xda);
    EXPECT_EQ(prMsg.first.rguiData[1], 0x15);
    EXPECT_EQ(prMsg.first.rguiData[2], 0xb7);
    EXPECT_EQ(prMsg.first.rguiData[3], 0x96);
    EXPECT_EQ(prMsg.first.rguiData[4], 0xaa);
    EXPECT_EQ(prMsg.first.rguiData[5], 0x55);
    EXPECT_EQ(prMsg.first.rguiData[6], 0xcc);
    EXPECT_EQ(prMsg.first.rguiData[7], 0x33);
}

TEST(DbcUtilCanDLTest, SpontaneousTransmitBigEndianAllDataTypes)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to integer based signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalIntBE1 = dispatch.AddPublisher("TestIntBE.SignalIntBE1");
    EXPECT_TRUE(signalIntBE1);
    sdv::core::CSignal signalIntBE2 = dispatch.AddPublisher("TestIntBE.SignalIntBE2");
    EXPECT_TRUE(signalIntBE2);
    sdv::core::CSignal signalIntBE3 = dispatch.AddPublisher("TestIntBE.SignalIntBE3");
    EXPECT_TRUE(signalIntBE3);
    sdv::core::CSignal signalIntBE4 = dispatch.AddPublisher("TestIntBE.SignalIntBE4");
    EXPECT_TRUE(signalIntBE4);
    sdv::core::CSignal signalIntBE5 = dispatch.AddPublisher("TestIntBE.SignalIntBE5");
    EXPECT_TRUE(signalIntBE5);
    sdv::core::CSignal signalIntBE6 = dispatch.AddPublisher("TestIntBE.SignalIntBE6");
    EXPECT_TRUE(signalIntBE6);

    // Subscribe to unsigned integer based signals
    sdv::core::CSignal signalUintBE1 = dispatch.AddPublisher("TestUintBE.SignalUintBE1");
    EXPECT_TRUE(signalUintBE1);
    sdv::core::CSignal signalUintBE2 = dispatch.AddPublisher("TestUintBE.SignalUintBE2");
    EXPECT_TRUE(signalUintBE2);
    sdv::core::CSignal signalUintBE3 = dispatch.AddPublisher("TestUintBE.SignalUintBE3");
    EXPECT_TRUE(signalUintBE3);
    sdv::core::CSignal signalUintBE4 = dispatch.AddPublisher("TestUintBE.SignalUintBE4");
    EXPECT_TRUE(signalUintBE4);
    sdv::core::CSignal signalUintBE5 = dispatch.AddPublisher("TestUintBE.SignalUintBE5");
    EXPECT_TRUE(signalUintBE5);
    sdv::core::CSignal signalUintBE6 = dispatch.AddPublisher("TestUintBE.SignalUintBE6");
    EXPECT_TRUE(signalUintBE6);

    // Subscribe to floating point based signals
    sdv::core::CSignal signalFloatBE1 = dispatch.AddPublisher("TestFloatBE.SignalFloatBE1");
    EXPECT_TRUE(signalFloatBE1);
    sdv::core::CSignal signalFloatBE2 = dispatch.AddPublisher("TestFloatBE.SignalFloatBE2");
    EXPECT_TRUE(signalFloatBE2);

    // Subscribe to double precision floating point based signals
    sdv::core::CSignal signalDoubleBE1 = dispatch.AddPublisher("TestDoubleBE.SignalDoubleBE1");
    EXPECT_TRUE(signalDoubleBE1);
    appcontrol.SetRunningMode();

    for (size_t n = 10; n < 20; n++)
    {
        sdv::core::CTransaction transaction = dispatch.CreateTransaction();
        signalIntBE1.Write((static_cast<int32_t>(n) & 0b1) * (n & 1 ? -1 : 1), transaction);     // 2 bits incl. sign
        signalIntBE2.Write((static_cast<int32_t>(n) & 0b1111) * (n & 1 ? -1 : 1), transaction);  // 5 bits incl. sign
        signalIntBE3.Write((static_cast<int32_t>(n) & 0b11111) * (n & 1 ? -1 : 1), transaction); // 6 bits incl. sign
        signalIntBE4.Write((static_cast<int32_t>(n) & 0b11) * (n & 1 ? -1 : 1), transaction);    // 3 bits incl. sign
        signalIntBE5.Write((static_cast<int32_t>(n)) * (n & 1 ? -1 : 1), transaction);           // 18 bits incl. sign
        signalIntBE6.Write((static_cast<int32_t>(n)) * (n & 1 ? -1 : 1), transaction);           // 30 bits incl. sign
        transaction.Finish();

        transaction = dispatch.CreateTransaction();
        signalUintBE1.Write(static_cast<uint32_t>(n) & 0b1, transaction);        // 1 bit
        signalUintBE2.Write(static_cast<uint32_t>(n) & 0b11, transaction);       // 2 bits
        signalUintBE3.Write(static_cast<uint32_t>(n) & 0b111111, transaction);   // 6 bits
        signalUintBE4.Write(static_cast<uint32_t>(n) & 0b1111111, transaction);  // 7 bits
        signalUintBE5.Write(static_cast<uint32_t>(n), transaction);              // 18 bits
        signalUintBE6.Write(static_cast<uint32_t>(n), transaction);              // 30 bits
        transaction.Finish();

        transaction = dispatch.CreateTransaction();
        signalFloatBE1.Write(static_cast<float>(n) * 1.111f, transaction);
        signalFloatBE2.Write(static_cast<float>(n) * 2.222f, transaction);
        transaction.Finish();

        transaction = dispatch.CreateTransaction();
        signalDoubleBE1.Write(static_cast<double>(n) * 3.333, transaction);
        transaction.Finish();
    }

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalIntBE1.Reset();
    signalIntBE2.Reset();
    signalIntBE3.Reset();
    signalIntBE4.Reset();
    signalIntBE5.Reset();
    signalIntBE6.Reset();

    signalUintBE1.Reset();
    signalUintBE2.Reset();
    signalUintBE3.Reset();
    signalUintBE4.Reset();
    signalUintBE5.Reset();
    signalUintBE6.Reset();

    signalFloatBE1.Reset();
    signalFloatBE2.Reset();

    signalDoubleBE1.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0xC9 , {0x14, 0x52, 0x00, 0x02, 0x80, 0x00, 0x00, 0x0A}},
        { 0x65 , {0x45, 0x0A, 0x00, 0x02, 0x80, 0x00, 0x00, 0x0A}},
        { 0x12D, {0x41, 0x31, 0xC2, 0x8F, 0x41, 0xB1, 0xC2, 0x8F}},
        { 0x191, {0x40, 0x40, 0xAA, 0x3D, 0x70, 0xA3, 0xD7, 0x0A}},
        { 0xC9 , {0xEB, 0xAD, 0xFF, 0xFD, 0x7F, 0xFF, 0xFF, 0xF5}},
        { 0x65 , {0xE5, 0x8B, 0x00, 0x02, 0xC0, 0x00, 0x00, 0x0B}},
        { 0x12D, {0x41, 0x43, 0x89, 0x37, 0x41, 0xC3, 0x89, 0x37}},
        { 0x191, {0x40, 0x42, 0x54, 0xDD, 0x2F, 0x1A, 0x9F, 0xBF}},
        { 0xC9 , {0x18, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0C}},
        { 0x65 , {0x06, 0x0C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0C}},
        { 0x12D, {0x41, 0x55, 0x4F, 0xDE, 0x41, 0xD5, 0x4F, 0xDE}},
        { 0x191, {0x40, 0x43, 0xFF, 0x7C, 0xED, 0x91, 0x68, 0x73}},
        { 0xC9 , {0xE7, 0x9F, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xF3}},
        { 0x65 , {0xA6, 0x8D, 0x00, 0x03, 0x40, 0x00, 0x00, 0x0D}},
        { 0x12D, {0x41, 0x67, 0x16, 0x86, 0x41, 0xE7, 0x16, 0x86}},
        { 0x191, {0x40, 0x45, 0xAA, 0x1C, 0xAC, 0x08, 0x31, 0x27}},
        { 0xC9 , {0x1C, 0x72, 0x00, 0x03, 0x80, 0x00, 0x00, 0x0E}},
        { 0x65 , {0x47, 0x0E, 0x00, 0x03, 0x80, 0x00, 0x00, 0x0E}},
        { 0x12D, {0x41, 0x78, 0xDD, 0x2E, 0x41, 0xF8, 0xDD, 0x2E}},
        { 0x191, {0x40, 0x47, 0x54, 0xBC, 0x6A, 0x7E, 0xF9, 0xDC}},
        { 0xC9 , {0xE3, 0x8D, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xF1}},
        { 0x65 , {0xE7, 0x8F, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x0F}},
        { 0x12D, {0x41, 0x85, 0x51, 0xEB, 0x42, 0x05, 0x51, 0xEB}},
        { 0x191, {0x40, 0x48, 0xFF, 0x5C, 0x28, 0xF5, 0xC2, 0x90}},
        { 0xC9 , {0x00, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10}},
        { 0x65 , {0x08, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10}},
        { 0x12D, {0x41, 0x8E, 0x35, 0x3F, 0x42, 0x0E, 0x35, 0x3F}},
        { 0x191, {0x40, 0x4A, 0xA9, 0xFB, 0xE7, 0x6C, 0x8B, 0x44}},
        { 0xC9 , {0xFF, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xEF}},
        { 0x65 , {0xA8, 0x91, 0x00, 0x04, 0x40, 0x00, 0x00, 0x11}},
        { 0x12D, {0x41, 0x97, 0x18, 0x93, 0x42, 0x17, 0x18, 0x93}},
        { 0x191, {0x40, 0x4C, 0x54, 0x9B, 0xA5, 0xE3, 0x53, 0xF8}},
        { 0xC9 , {0x04, 0x92, 0x00, 0x04, 0x80, 0x00, 0x00, 0x12}},
        { 0x65 , {0x49, 0x12, 0x00, 0x04, 0x80, 0x00, 0x00, 0x12}},
        { 0x12D, {0x41, 0x9F, 0xFB, 0xE7, 0x42, 0x1F, 0xFB, 0xE7}},
        { 0x191, {0x40, 0x4D, 0xFF, 0x3B, 0x64, 0x5A, 0x1C, 0xAC}},
        { 0xC9 , {0xFB, 0x6D, 0xFF, 0xFB, 0x7F, 0xFF, 0xFF, 0xED}},
        { 0x65 , {0xE9, 0x93, 0x00, 0x04, 0xC0, 0x00, 0x00, 0x13}},
        { 0x12D, {0x41, 0xA8, 0xDF, 0x3B, 0x42, 0x28, 0xDF, 0x3B}},
        { 0x191, {0x40, 0x4F, 0xA9, 0xDB, 0x22, 0xD0, 0xE5, 0x61}}
    };

    // Compare with test data.
    for (const STestData& sData : rgsData)
    {
        EXPECT_FALSE(reader.IsEOF());
        std::pair<asc::SCanMessage, bool> prMsg;
        do {
            prMsg = reader.Get();
            EXPECT_TRUE(prMsg.second);
            ++reader;
        } while (!reader.IsEOF() && prMsg.first.uiId != sData.uiID);   // Skip other messages
        EXPECT_EQ(prMsg.first.uiChannel, 1ul);
        EXPECT_EQ(prMsg.first.uiId, sData.uiID);
        EXPECT_FALSE(prMsg.first.bExtended);
        EXPECT_FALSE(prMsg.first.bCanFd);
        EXPECT_EQ(prMsg.first.uiLength, 8ul);
        EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
        EXPECT_EQ(prMsg.first.rguiData[0], sData.rguiData[0]);
        EXPECT_EQ(prMsg.first.rguiData[1], sData.rguiData[1]);
        EXPECT_EQ(prMsg.first.rguiData[2], sData.rguiData[2]);
        EXPECT_EQ(prMsg.first.rguiData[3], sData.rguiData[3]);
        EXPECT_EQ(prMsg.first.rguiData[4], sData.rguiData[4]);
        EXPECT_EQ(prMsg.first.rguiData[5], sData.rguiData[5]);
        EXPECT_EQ(prMsg.first.rguiData[6], sData.rguiData[6]);
        EXPECT_EQ(prMsg.first.rguiData[7], sData.rguiData[7]);
    }
}

TEST(DbcUtilCanDLTest, ReceiveBigEndianAllDataTypes)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0xC9 , {0x14, 0x52, 0x00, 0x02, 0x80, 0x00, 0x00, 0x0A}},
        { 0x65 , {0x45, 0x0A, 0x00, 0x02, 0x80, 0x00, 0x00, 0x0A}},
        { 0x12D, {0x41, 0x31, 0xC2, 0x8F, 0x41, 0xB1, 0xC2, 0x8F}},
        { 0x191, {0x40, 0x40, 0xAA, 0x3D, 0x70, 0xA3, 0xD7, 0x0A}},
        { 0xC9 , {0xEB, 0xAD, 0xFF, 0xFD, 0x7F, 0xFF, 0xFF, 0xF5}},
        { 0x65 , {0xE5, 0x8B, 0x00, 0x02, 0xC0, 0x00, 0x00, 0x0B}},
        { 0x12D, {0x41, 0x43, 0x89, 0x37, 0x41, 0xC3, 0x89, 0x37}},
        { 0x191, {0x40, 0x42, 0x54, 0xDD, 0x2F, 0x1A, 0x9F, 0xBF}},
        { 0xC9 , {0x18, 0x60, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0C}},
        { 0x65 , {0x06, 0x0C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0C}},
        { 0x12D, {0x41, 0x55, 0x4F, 0xDE, 0x41, 0xD5, 0x4F, 0xDE}},
        { 0x191, {0x40, 0x43, 0xFF, 0x7C, 0xED, 0x91, 0x68, 0x73}},
        { 0xC9 , {0xE7, 0x9F, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xF3}},
        { 0x65 , {0xA6, 0x8D, 0x00, 0x03, 0x40, 0x00, 0x00, 0x0D}},
        { 0x12D, {0x41, 0x67, 0x16, 0x86, 0x41, 0xE7, 0x16, 0x86}},
        { 0x191, {0x40, 0x45, 0xAA, 0x1C, 0xAC, 0x08, 0x31, 0x27}},
        { 0xC9 , {0x1C, 0x72, 0x00, 0x03, 0x80, 0x00, 0x00, 0x0E}},
        { 0x65 , {0x47, 0x0E, 0x00, 0x03, 0x80, 0x00, 0x00, 0x0E}},
        { 0x12D, {0x41, 0x78, 0xDD, 0x2E, 0x41, 0xF8, 0xDD, 0x2E}},
        { 0x191, {0x40, 0x47, 0x54, 0xBC, 0x6A, 0x7E, 0xF9, 0xDC}},
        { 0xC9 , {0xE3, 0x8D, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xF1}},
        { 0x65 , {0xE7, 0x8F, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x0F}},
        { 0x12D, {0x41, 0x85, 0x51, 0xEB, 0x42, 0x05, 0x51, 0xEB}},
        { 0x191, {0x40, 0x48, 0xFF, 0x5C, 0x28, 0xF5, 0xC2, 0x90}},
        { 0xC9 , {0x00, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10}},
        { 0x65 , {0x08, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10}},
        { 0x12D, {0x41, 0x8E, 0x35, 0x3F, 0x42, 0x0E, 0x35, 0x3F}},
        { 0x191, {0x40, 0x4A, 0xA9, 0xFB, 0xE7, 0x6C, 0x8B, 0x44}},
        { 0xC9 , {0xFF, 0x7F, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xEF}},
        { 0x65 , {0xA8, 0x91, 0x00, 0x04, 0x40, 0x00, 0x00, 0x11}},
        { 0x12D, {0x41, 0x97, 0x18, 0x93, 0x42, 0x17, 0x18, 0x93}},
        { 0x191, {0x40, 0x4C, 0x54, 0x9B, 0xA5, 0xE3, 0x53, 0xF8}},
        { 0xC9 , {0x04, 0x92, 0x00, 0x04, 0x80, 0x00, 0x00, 0x12}},
        { 0x65 , {0x49, 0x12, 0x00, 0x04, 0x80, 0x00, 0x00, 0x12}},
        { 0x12D, {0x41, 0x9F, 0xFB, 0xE7, 0x42, 0x1F, 0xFB, 0xE7}},
        { 0x191, {0x40, 0x4D, 0xFF, 0x3B, 0x64, 0x5A, 0x1C, 0xAC}},
        { 0xC9 , {0xFB, 0x6D, 0xFF, 0xFB, 0x7F, 0xFF, 0xFF, 0xED}},
        { 0x65 , {0xE9, 0x93, 0x00, 0x04, 0xC0, 0x00, 0x00, 0x13}},
        { 0x12D, {0x41, 0xA8, 0xDF, 0x3B, 0x42, 0x28, 0xDF, 0x3B}},
        { 0x191, {0x40, 0x4F, 0xA9, 0xDB, 0x22, 0xD0, 0xE5, 0x61}}
    };

    // Create ASC file with a delay of 100 (to allow the system to initialize)
    asc::CAscWriter writer;
    double dTimestamp = 0.100;
    for (const STestData& sData : rgsData)
    {
        asc::SCanMessage sMsg{};
        sMsg.dTimestamp = dTimestamp;
        dTimestamp += 0.001;
        sMsg.uiChannel = 1ul;
        sMsg.uiId = sData.uiID;
        sMsg.bExtended = false;
        sMsg.bCanFd = false;
        sMsg.uiLength = 8ul;
        sMsg.eDirection = asc::SCanMessage::EDirection::rx;
        sMsg.rguiData[0] = sData.rguiData[0];
        sMsg.rguiData[1] = sData.rguiData[1];
        sMsg.rguiData[2] = sData.rguiData[2];
        sMsg.rguiData[3] = sData.rguiData[3];
        sMsg.rguiData[4] = sData.rguiData[4];
        sMsg.rguiData[5] = sData.rguiData[5];
        sMsg.rguiData[6] = sData.rguiData[6];
        sMsg.rguiData[7] = sData.rguiData[7];
        writer.AddSample(sMsg);
    }

    // Add a dummy message with a big timegap to prevent the repetition function to jump in.
    asc::SCanMessage sMsgDummy{};
    sMsgDummy.dTimestamp = 100.0;
    sMsgDummy.uiChannel = 1ul;
    sMsgDummy.uiId = 999;
    writer.AddSample(sMsgDummy);

    // Write the ASC file
    EXPECT_TRUE(writer.Write(GetExecDirectory() / "receiver_test.asc"));
    writer.Clear();

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_rx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    int32_t rgiIntCnt[6] = {10, 10, 10, 10, 10, 10};
    sdv::core::CSignal signalIntBE1 = dispatch.Subscribe("TestIntBE.SignalIntBE1", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[0]++;
            int32_t iCalculated = (static_cast<int32_t>(i) & 0b1) * (i & 1 ? -1 : 1);   // 2 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntBE1);
    sdv::core::CSignal signalIntBE2 = dispatch.Subscribe("TestIntBE.SignalIntBE2", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[1]++;
            int32_t iCalculated = (static_cast<int32_t>(i) & 0b1111) * (i & 1 ? -1 : 1);    // 5 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntBE2);
    sdv::core::CSignal signalIntBE3 = dispatch.Subscribe("TestIntBE.SignalIntBE3", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[2]++;
            int32_t iCalculated = (static_cast<int32_t>(i) & 0b11111) * (i & 1 ? -1 : 1);   // 6 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntBE3);
    sdv::core::CSignal signalIntBE4 = dispatch.Subscribe("TestIntBE.SignalIntBE4", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[3]++;
            int32_t iCalculated = (static_cast<int32_t>(i) & 0b11) * (i & 1 ? -1 : 1);  // 3 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntBE4);
    sdv::core::CSignal signalIntBE5 = dispatch.Subscribe("TestIntBE.SignalIntBE5", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[4]++;
            int32_t iCalculated = (static_cast<int32_t>(i)) * (i & 1 ? -1 : 1); // 18 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntBE5);
    sdv::core::CSignal signalIntBE6 = dispatch.Subscribe("TestIntBE.SignalIntBE6", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[5]++;
            int32_t iCalculated = (static_cast<int32_t>(i)) * (i & 1 ? -1 : 1); // 30 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntBE6);

    // Subscribe to unsigned integer based signals
    uint32_t rguiUintCnt[6] = {10u, 10u, 10u, 10u, 10u, 10u};
    sdv::core::CSignal signalUintBE1 = dispatch.Subscribe("TestUintBE.SignalUintBE1", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[0]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui) & 0b1;   // 1 bit
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintBE1);
    sdv::core::CSignal signalUintBE2 = dispatch.Subscribe("TestUintBE.SignalUintBE2", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[1]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui) & 0b11;   // 2 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintBE2);
    sdv::core::CSignal signalUintBE3 = dispatch.Subscribe("TestUintBE.SignalUintBE3", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[2]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui) & 0b111111;   // 6 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintBE3);
    sdv::core::CSignal signalUintBE4 = dispatch.Subscribe("TestUintBE.SignalUintBE4", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[3]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui) & 0b1111111;   // 7 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintBE4);
    sdv::core::CSignal signalUintBE5 = dispatch.Subscribe("TestUintBE.SignalUintBE5", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[4]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui);   // 18 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintBE5);
    sdv::core::CSignal signalUintBE6 = dispatch.Subscribe("TestUintBE.SignalUintBE6", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[5]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui);   // 30 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintBE6);

    // Subscribe to floating point based signals
    uint32_t rguiFloatCnt[2] = {10u, 10u};
    sdv::core::CSignal signalFloatBE1 = dispatch.Subscribe("TestFloatBE.SignalFloatBE1", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiFloatCnt[0]++;
            float fCalculated = static_cast<float>(ui) * 1.111f;
            EXPECT_EQ(fCalculated, static_cast<float>(tVal));
        });
    EXPECT_TRUE(signalFloatBE1);
    sdv::core::CSignal signalFloatBE2 = dispatch.Subscribe("TestFloatBE.SignalFloatBE2", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiFloatCnt[1]++;
            float fCalculated = static_cast<float>(ui) * 2.222f;
            EXPECT_EQ(fCalculated, static_cast<float>(tVal));
        });
    EXPECT_TRUE(signalFloatBE2);

    // Subscribe to double precision floating point based signals
    uint32_t uiDoubleCnt = 10u;
    sdv::core::CSignal signalDoubleBE1 = dispatch.Subscribe("TestDoubleBE.SignalDoubleBE1", [&](sdv::any_t tVal)
        {
            uint32_t ui = uiDoubleCnt++;
            double dCalculated = static_cast<double>(ui) * 3.333;
            EXPECT_EQ(dCalculated, static_cast<double>(tVal));
        });
    EXPECT_TRUE(signalDoubleBE1);
    appcontrol.SetRunningMode();

    std::this_thread::sleep_for(std::chrono::milliseconds(250));

    // Check for correct counters
    for (int32_t i : rgiIntCnt)
        EXPECT_EQ(i, 20);
    for (uint32_t ui : rguiUintCnt)
        EXPECT_EQ(ui, 20u);
    for (uint32_t ui : rguiFloatCnt)
        EXPECT_EQ(ui, 20u);
    EXPECT_EQ(uiDoubleCnt, 20u);

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalIntBE1.Reset();
    signalIntBE2.Reset();
    signalIntBE3.Reset();
    signalIntBE4.Reset();
    signalIntBE5.Reset();
    signalIntBE6.Reset();

    signalUintBE1.Reset();
    signalUintBE2.Reset();
    signalUintBE3.Reset();
    signalUintBE4.Reset();
    signalUintBE5.Reset();
    signalUintBE6.Reset();

    signalFloatBE1.Reset();
    signalFloatBE2.Reset();

    signalDoubleBE1.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();
}

TEST(DbcUtilCanDLTest, SpontaneousTransmitLittleEndianAllDataTypes)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to integer based signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalIntLE1 = dispatch.AddPublisher("TestIntLE.SignalIntLE1");
    EXPECT_TRUE(signalIntLE1);
    sdv::core::CSignal signalIntLE2 = dispatch.AddPublisher("TestIntLE.SignalIntLE2");
    EXPECT_TRUE(signalIntLE2);
    sdv::core::CSignal signalIntLE3 = dispatch.AddPublisher("TestIntLE.SignalIntLE3");
    EXPECT_TRUE(signalIntLE3);
    sdv::core::CSignal signalIntLE4 = dispatch.AddPublisher("TestIntLE.SignalIntLE4");
    EXPECT_TRUE(signalIntLE4);
    sdv::core::CSignal signalIntLE5 = dispatch.AddPublisher("TestIntLE.SignalIntLE5");
    EXPECT_TRUE(signalIntLE5);
    sdv::core::CSignal signalIntLE6 = dispatch.AddPublisher("TestIntLE.SignalIntLE6");
    EXPECT_TRUE(signalIntLE6);

    // Subscribe to unsigned integer based signals
    sdv::core::CSignal signalUintLE1 = dispatch.AddPublisher("TestUintLE.SignalUintLE1");
    EXPECT_TRUE(signalUintLE1);
    sdv::core::CSignal signalUintLE2 = dispatch.AddPublisher("TestUintLE.SignalUintLE2");
    EXPECT_TRUE(signalUintLE2);
    sdv::core::CSignal signalUintLE3 = dispatch.AddPublisher("TestUintLE.SignalUintLE3");
    EXPECT_TRUE(signalUintLE3);
    sdv::core::CSignal signalUintLE4 = dispatch.AddPublisher("TestUintLE.SignalUintLE4");
    EXPECT_TRUE(signalUintLE4);
    sdv::core::CSignal signalUintLE5 = dispatch.AddPublisher("TestUintLE.SignalUintLE5");
    EXPECT_TRUE(signalUintLE5);
    sdv::core::CSignal signalUintLE6 = dispatch.AddPublisher("TestUintLE.SignalUintLE6");
    EXPECT_TRUE(signalUintLE6);

    // Subscribe to floating point based signals
    sdv::core::CSignal signalFloatLE1 = dispatch.AddPublisher("TestFloatLE.SignalFloatLE1");
    EXPECT_TRUE(signalFloatLE1);
    sdv::core::CSignal signalFloatLE2 = dispatch.AddPublisher("TestFloatLE.SignalFloatLE2");
    EXPECT_TRUE(signalFloatLE2);

    // Subscribe to double precision floating point based signals
    sdv::core::CSignal signalDoubleLE1 = dispatch.AddPublisher("TestDoubleLE.SignalDoubleLE1");
    EXPECT_TRUE(signalDoubleLE1);
    appcontrol.SetRunningMode();

    for (size_t n = 10; n < 20; n++)
    {
        sdv::core::CTransaction transaction = dispatch.CreateTransaction();
        signalIntLE1.Write((static_cast<int32_t>(n) & 0b1) * (n & 1 ? -1 : 1), transaction);     // 2 bits incl. sign
        signalIntLE2.Write((static_cast<int32_t>(n) & 0b1111) * (n & 1 ? -1 : 1), transaction);  // 5 bits incl. sign
        signalIntLE3.Write((static_cast<int32_t>(n) & 0b11111) * (n & 1 ? -1 : 1), transaction); // 6 bits incl. sign
        signalIntLE4.Write((static_cast<int32_t>(n) & 0b11) * (n & 1 ? -1 : 1), transaction);    // 3 bits incl. sign
        signalIntLE5.Write((static_cast<int32_t>(n)) * (n & 1 ? -1 : 1), transaction);           // 18 bits incl. sign
        signalIntLE6.Write((static_cast<int32_t>(n)) * (n & 1 ? -1 : 1), transaction);           // 30 bits incl. sign
        transaction.Finish();

        transaction = dispatch.CreateTransaction();
        signalUintLE1.Write(static_cast<uint32_t>(n) & 0b1, transaction);        // 1 bit
        signalUintLE2.Write(static_cast<uint32_t>(n) & 0b11, transaction);       // 2 bits
        signalUintLE3.Write(static_cast<uint32_t>(n) & 0b111111, transaction);   // 6 bits
        signalUintLE4.Write(static_cast<uint32_t>(n) & 0b1111111, transaction);  // 7 bits
        signalUintLE5.Write(static_cast<uint32_t>(n), transaction);              // 18 bits
        signalUintLE6.Write(static_cast<uint32_t>(n), transaction);              // 30 bits
        transaction.Finish();

        transaction = dispatch.CreateTransaction();
        signalFloatLE1.Write(static_cast<float>(n) * 1.111f, transaction);
        signalFloatLE2.Write(static_cast<float>(n) * 2.222f, transaction);
        transaction.Finish();

        transaction = dispatch.CreateTransaction();
        signalDoubleLE1.Write(static_cast<double>(n) * 3.333, transaction);
        transaction.Finish();
    }

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalIntLE1.Reset();
    signalIntLE2.Reset();
    signalIntLE3.Reset();
    signalIntLE4.Reset();
    signalIntLE5.Reset();
    signalIntLE6.Reset();

    signalUintLE1.Reset();
    signalUintLE2.Reset();
    signalUintLE3.Reset();
    signalUintLE4.Reset();
    signalUintLE5.Reset();
    signalUintLE6.Reset();

    signalFloatLE1.Reset();
    signalFloatLE2.Reset();

    signalDoubleLE1.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0xC8 , {0x28, 0x45, 0x0A, 0x00, 0x28, 0x00, 0x00, 0x00}},
        { 0x64 , {0x54, 0x14, 0x0A, 0x00, 0x28, 0x00, 0x00, 0x00}},
        { 0x12C, {0x8F, 0xC2, 0x31, 0x41, 0x8F, 0xC2, 0xB1, 0x41}},
        { 0x190, {0x0A, 0xD7, 0xA3, 0x70, 0x3D, 0xAA, 0x40, 0x40}},
        { 0xC8 , {0xD7, 0xBA, 0xF5, 0xFF, 0xD7, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x5F, 0x16, 0x0B, 0x00, 0x2C, 0x00, 0x00, 0x00}},
        { 0x12C, {0x37, 0x89, 0x43, 0x41, 0x37, 0x89, 0xC3, 0x41}},
        { 0x190, {0xBF, 0x9F, 0x1A, 0x2F, 0xDD, 0x54, 0x42, 0x40}},
        { 0xC8 , {0x30, 0x06, 0x0C, 0x00, 0x30, 0x00, 0x00, 0x00}},
        { 0x64 , {0x60, 0x18, 0x0C, 0x00, 0x30, 0x00, 0x00, 0x00}},
        { 0x12C, {0xDE, 0x4F, 0x55, 0x41, 0xDE, 0x4F, 0xD5, 0x41}},
        { 0x190, {0x73, 0x68, 0x91, 0xED, 0x7C, 0xFF, 0x43, 0x40}},
        { 0xC8 , {0xCF, 0xF9, 0xF3, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x6B, 0x1A, 0x0D, 0x00, 0x34, 0x00, 0x00, 0x00}},
        { 0x12C, {0x86, 0x16, 0x67, 0x41, 0x86, 0x16, 0xE7, 0x41}},
        { 0x190, {0x27, 0x31, 0x08, 0xAC, 0x1C, 0xAA, 0x45, 0x40}},
        { 0xC8 , {0x38, 0x47, 0x0E, 0x00, 0x38, 0x00, 0x00, 0x00}},
        { 0x64 , {0x74, 0x1C, 0x0E, 0x00, 0x38, 0x00, 0x00, 0x00}},
        { 0x12C, {0x2E, 0xDD, 0x78, 0x41, 0x2E, 0xDD, 0xF8, 0x41}},
        { 0x190, {0xDC, 0xF9, 0x7E, 0x6A, 0xBC, 0x54, 0x47, 0x40}},
        { 0xC8 , {0xC7, 0xB8, 0xF1, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x7F, 0x1E, 0x0F, 0x00, 0x3C, 0x00, 0x00, 0x00}},
        { 0x12C, {0xEB, 0x51, 0x85, 0x41, 0xEB, 0x51, 0x05, 0x42}},
        { 0x190, {0x90, 0xC2, 0xF5, 0x28, 0x5C, 0xFF, 0x48, 0x40}},
        { 0xC8 , {0x00, 0x08, 0x10, 0x00, 0x40, 0x00, 0x00, 0x00}},
        { 0x64 , {0x80, 0x20, 0x10, 0x00, 0x40, 0x00, 0x00, 0x00}},
        { 0x12C, {0x3F, 0x35, 0x8E, 0x41, 0x3F, 0x35, 0x0E, 0x42}},
        { 0x190, {0x44, 0x8B, 0x6C, 0xE7, 0xFB, 0xA9, 0x4A, 0x40}},
        { 0xC8 , {0xFF, 0xF7, 0xEF, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x8B, 0x22, 0x11, 0x00, 0x44, 0x00, 0x00, 0x00}},
        { 0x12C, {0x93, 0x18, 0x97, 0x41, 0x93, 0x18, 0x17, 0x42}},
        { 0x190, {0xF8, 0x53, 0xE3, 0xA5, 0x9B, 0x54, 0x4C, 0x40}},
        { 0xC8 , {0x08, 0x49, 0x12, 0x00, 0x48, 0x00, 0x00, 0x00}},
        { 0x64 , {0x94, 0x24, 0x12, 0x00, 0x48, 0x00, 0x00, 0x00}},
        { 0x12C, {0xE7, 0xFB, 0x9F, 0x41, 0xE7, 0xFB, 0x1F, 0x42}},
        { 0x190, {0xAC, 0x1C, 0x5A, 0x64, 0x3B, 0xFF, 0x4D, 0x40}},
        { 0xC8 , {0xF7, 0xB6, 0xED, 0xFF, 0xB7, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x9F, 0x26, 0x13, 0x00, 0x4C, 0x00, 0x00, 0x00}},
        { 0x12C, {0x3B, 0xDF, 0xA8, 0x41, 0x3B, 0xDF, 0x28, 0x42}},
        { 0x190, {0x61, 0xE5, 0xD0, 0x22, 0xDB, 0xA9, 0x4F, 0x40}}
    };

    // Compare with test data.
    for (const STestData& sData : rgsData)
    {
        EXPECT_FALSE(reader.IsEOF());
        std::pair<asc::SCanMessage, bool> prMsg;
        do {
            prMsg = reader.Get();
            EXPECT_TRUE(prMsg.second);
            ++reader;
        } while (!reader.IsEOF() && prMsg.first.uiId != sData.uiID);   // Skip other messages
        EXPECT_EQ(prMsg.first.uiChannel, 1ul);
        EXPECT_EQ(prMsg.first.uiId, sData.uiID);
        EXPECT_FALSE(prMsg.first.bExtended);
        EXPECT_FALSE(prMsg.first.bCanFd);
        EXPECT_EQ(prMsg.first.uiLength, 8ul);
        EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
        EXPECT_EQ(prMsg.first.rguiData[0], sData.rguiData[0]);
        EXPECT_EQ(prMsg.first.rguiData[1], sData.rguiData[1]);
        EXPECT_EQ(prMsg.first.rguiData[2], sData.rguiData[2]);
        EXPECT_EQ(prMsg.first.rguiData[3], sData.rguiData[3]);
        EXPECT_EQ(prMsg.first.rguiData[4], sData.rguiData[4]);
        EXPECT_EQ(prMsg.first.rguiData[5], sData.rguiData[5]);
        EXPECT_EQ(prMsg.first.rguiData[6], sData.rguiData[6]);
        EXPECT_EQ(prMsg.first.rguiData[7], sData.rguiData[7]);
    }
}

TEST(DbcUtilCanDLTest, ReceiveLittleEndianAllDataTypes)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[8];
    } rgsData[] = {
        { 0xC8 , {0x28, 0x45, 0x0A, 0x00, 0x28, 0x00, 0x00, 0x00}},
        { 0x64 , {0x54, 0x14, 0x0A, 0x00, 0x28, 0x00, 0x00, 0x00}},
        { 0x12C, {0x8F, 0xC2, 0x31, 0x41, 0x8F, 0xC2, 0xB1, 0x41}},
        { 0x190, {0x0A, 0xD7, 0xA3, 0x70, 0x3D, 0xAA, 0x40, 0x40}},
        { 0xC8 , {0xD7, 0xBA, 0xF5, 0xFF, 0xD7, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x5F, 0x16, 0x0B, 0x00, 0x2C, 0x00, 0x00, 0x00}},
        { 0x12C, {0x37, 0x89, 0x43, 0x41, 0x37, 0x89, 0xC3, 0x41}},
        { 0x190, {0xBF, 0x9F, 0x1A, 0x2F, 0xDD, 0x54, 0x42, 0x40}},
        { 0xC8 , {0x30, 0x06, 0x0C, 0x00, 0x30, 0x00, 0x00, 0x00}},
        { 0x64 , {0x60, 0x18, 0x0C, 0x00, 0x30, 0x00, 0x00, 0x00}},
        { 0x12C, {0xDE, 0x4F, 0x55, 0x41, 0xDE, 0x4F, 0xD5, 0x41}},
        { 0x190, {0x73, 0x68, 0x91, 0xED, 0x7C, 0xFF, 0x43, 0x40}},
        { 0xC8 , {0xCF, 0xF9, 0xF3, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x6B, 0x1A, 0x0D, 0x00, 0x34, 0x00, 0x00, 0x00}},
        { 0x12C, {0x86, 0x16, 0x67, 0x41, 0x86, 0x16, 0xE7, 0x41}},
        { 0x190, {0x27, 0x31, 0x08, 0xAC, 0x1C, 0xAA, 0x45, 0x40}},
        { 0xC8 , {0x38, 0x47, 0x0E, 0x00, 0x38, 0x00, 0x00, 0x00}},
        { 0x64 , {0x74, 0x1C, 0x0E, 0x00, 0x38, 0x00, 0x00, 0x00}},
        { 0x12C, {0x2E, 0xDD, 0x78, 0x41, 0x2E, 0xDD, 0xF8, 0x41}},
        { 0x190, {0xDC, 0xF9, 0x7E, 0x6A, 0xBC, 0x54, 0x47, 0x40}},
        { 0xC8 , {0xC7, 0xB8, 0xF1, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x7F, 0x1E, 0x0F, 0x00, 0x3C, 0x00, 0x00, 0x00}},
        { 0x12C, {0xEB, 0x51, 0x85, 0x41, 0xEB, 0x51, 0x05, 0x42}},
        { 0x190, {0x90, 0xC2, 0xF5, 0x28, 0x5C, 0xFF, 0x48, 0x40}},
        { 0xC8 , {0x00, 0x08, 0x10, 0x00, 0x40, 0x00, 0x00, 0x00}},
        { 0x64 , {0x80, 0x20, 0x10, 0x00, 0x40, 0x00, 0x00, 0x00}},
        { 0x12C, {0x3F, 0x35, 0x8E, 0x41, 0x3F, 0x35, 0x0E, 0x42}},
        { 0x190, {0x44, 0x8B, 0x6C, 0xE7, 0xFB, 0xA9, 0x4A, 0x40}},
        { 0xC8 , {0xFF, 0xF7, 0xEF, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x8B, 0x22, 0x11, 0x00, 0x44, 0x00, 0x00, 0x00}},
        { 0x12C, {0x93, 0x18, 0x97, 0x41, 0x93, 0x18, 0x17, 0x42}},
        { 0x190, {0xF8, 0x53, 0xE3, 0xA5, 0x9B, 0x54, 0x4C, 0x40}},
        { 0xC8 , {0x08, 0x49, 0x12, 0x00, 0x48, 0x00, 0x00, 0x00}},
        { 0x64 , {0x94, 0x24, 0x12, 0x00, 0x48, 0x00, 0x00, 0x00}},
        { 0x12C, {0xE7, 0xFB, 0x9F, 0x41, 0xE7, 0xFB, 0x1F, 0x42}},
        { 0x190, {0xAC, 0x1C, 0x5A, 0x64, 0x3B, 0xFF, 0x4D, 0x40}},
        { 0xC8 , {0xF7, 0xB6, 0xED, 0xFF, 0xB7, 0xFF, 0xFF, 0xFF}},
        { 0x64 , {0x9F, 0x26, 0x13, 0x00, 0x4C, 0x00, 0x00, 0x00}},
        { 0x12C, {0x3B, 0xDF, 0xA8, 0x41, 0x3B, 0xDF, 0x28, 0x42}},
        { 0x190, {0x61, 0xE5, 0xD0, 0x22, 0xDB, 0xA9, 0x4F, 0x40}}
    };

    // Create ASC file with a delay of 100 (to allow the system to initialize)
    asc::CAscWriter writer;
    double dTimestamp = 0.100;
    for (const STestData& sData : rgsData)
    {
        asc::SCanMessage sMsg{};
        sMsg.dTimestamp = dTimestamp;
        dTimestamp += 0.001;
        sMsg.uiChannel = 1ul;
        sMsg.uiId = sData.uiID;
        sMsg.bExtended = false;
        sMsg.bCanFd = false;
        sMsg.uiLength = 8ul;
        sMsg.eDirection = asc::SCanMessage::EDirection::rx;
        sMsg.rguiData[0] = sData.rguiData[0];
        sMsg.rguiData[1] = sData.rguiData[1];
        sMsg.rguiData[2] = sData.rguiData[2];
        sMsg.rguiData[3] = sData.rguiData[3];
        sMsg.rguiData[4] = sData.rguiData[4];
        sMsg.rguiData[5] = sData.rguiData[5];
        sMsg.rguiData[6] = sData.rguiData[6];
        sMsg.rguiData[7] = sData.rguiData[7];
        writer.AddSample(sMsg);
    }

    // Add a dummy message with a big timegap to prevent the repetition function to jump in.
    asc::SCanMessage sMsgDummy{};
    sMsgDummy.dTimestamp = 100.0;
    sMsgDummy.uiChannel = 1ul;
    sMsgDummy.uiId = 999;
    writer.AddSample(sMsgDummy);

    // Write the ASC file
    EXPECT_TRUE(writer.Write(GetExecDirectory() / "receiver_test.asc"));
    writer.Clear();

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_rx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    int32_t rgiIntCnt[6] = {10, 10, 10, 10, 10, 10};
    sdv::core::CSignal signalIntLE1 = dispatch.Subscribe("TestIntLE.SignalIntLE1", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[0]++;
            int32_t iCalculated = (static_cast<int32_t>(i) & 0b1) * (i & 1 ? -1 : 1);   // 2 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntLE1);
    sdv::core::CSignal signalIntLE2 = dispatch.Subscribe("TestIntLE.SignalIntLE2", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[1]++;
            int32_t iCalculated = (static_cast<int32_t>(i) & 0b1111) * (i & 1 ? -1 : 1);    // 5 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntLE2);
    sdv::core::CSignal signalIntLE3 = dispatch.Subscribe("TestIntLE.SignalIntLE3", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[2]++;
            int32_t iCalculated = (static_cast<int32_t>(i) & 0b11111) * (i & 1 ? -1 : 1);   // 6 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntLE3);
    sdv::core::CSignal signalIntLE4 = dispatch.Subscribe("TestIntLE.SignalIntLE4", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[3]++;
            int32_t iCalculated = (static_cast<int32_t>(i) & 0b11) * (i & 1 ? -1 : 1);  // 3 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntLE4);
    sdv::core::CSignal signalIntLE5 = dispatch.Subscribe("TestIntLE.SignalIntLE5", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[4]++;
            int32_t iCalculated = (static_cast<int32_t>(i)) * (i & 1 ? -1 : 1); // 18 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntLE5);
    sdv::core::CSignal signalIntLE6 = dispatch.Subscribe("TestIntLE.SignalIntLE6", [&](sdv::any_t tVal)
        {
            int32_t i = rgiIntCnt[5]++;
            int32_t iCalculated = (static_cast<int32_t>(i)) * (i & 1 ? -1 : 1); // 30 bits incl. sign
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalIntLE6);

    // Subscribe to unsigned integer based signals
    uint32_t rguiUintCnt[6] = {10u, 10u, 10u, 10u, 10u, 10u};
    sdv::core::CSignal signalUintLE1 = dispatch.Subscribe("TestUintLE.SignalUintLE1", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[0]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui) & 0b1;   // 1 bit
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintLE1);
    sdv::core::CSignal signalUintLE2 = dispatch.Subscribe("TestUintLE.SignalUintLE2", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[1]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui) & 0b11;   // 2 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintLE2);
    sdv::core::CSignal signalUintLE3 = dispatch.Subscribe("TestUintLE.SignalUintLE3", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[2]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui) & 0b111111;   // 6 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintLE3);
    sdv::core::CSignal signalUintLE4 = dispatch.Subscribe("TestUintLE.SignalUintLE4", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[3]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui) & 0b1111111;   // 7 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintLE4);
    sdv::core::CSignal signalUintLE5 = dispatch.Subscribe("TestUintLE.SignalUintLE5", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[4]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui);   // 18 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintLE5);
    sdv::core::CSignal signalUintLE6 = dispatch.Subscribe("TestUintLE.SignalUintLE6", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiUintCnt[5]++;
            uint32_t uiCalculated = static_cast<uint32_t>(ui);   // 30 bits
            EXPECT_EQ(uiCalculated, static_cast<uint32_t>(tVal));
        });
    EXPECT_TRUE(signalUintLE6);

    // Subscribe to floating point based signals
    uint32_t rguiFloatCnt[2] = {10u, 10u};
    sdv::core::CSignal signalFloatLE1 = dispatch.Subscribe("TestFloatLE.SignalFloatLE1", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiFloatCnt[0]++;
            float fCalculated = static_cast<float>(ui) * 1.111f;
            EXPECT_EQ(fCalculated, static_cast<float>(tVal));
        });
    EXPECT_TRUE(signalFloatLE1);
    sdv::core::CSignal signalFloatLE2 = dispatch.Subscribe("TestFloatLE.SignalFloatLE2", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiFloatCnt[1]++;
            float fCalculated = static_cast<float>(ui) * 2.222f;
            EXPECT_EQ(fCalculated, static_cast<float>(tVal));
        });
    EXPECT_TRUE(signalFloatLE2);

    // Subscribe to double precision floating point based signals
    uint32_t uiDoubleCnt = 10u;
    sdv::core::CSignal signalDoubleLE1 = dispatch.Subscribe("TestDoubleLE.SignalDoubleLE1", [&](sdv::any_t tVal)
        {
            uint32_t ui = uiDoubleCnt++;
            double dCalculated = static_cast<double>(ui) * 3.333;
            EXPECT_EQ(dCalculated, static_cast<double>(tVal));
        });
    EXPECT_TRUE(signalDoubleLE1);
    appcontrol.SetRunningMode();

    std::this_thread::sleep_for(std::chrono::milliseconds(250));

    // Check for correct counters
    for (int32_t i : rgiIntCnt)
        EXPECT_EQ(i, 20);
    for (uint32_t ui : rguiUintCnt)
        EXPECT_EQ(ui, 20u);
    for (uint32_t ui : rguiFloatCnt)
        EXPECT_EQ(ui, 20u);
    EXPECT_EQ(uiDoubleCnt, 20u);

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalIntLE1.Reset();
    signalIntLE2.Reset();
    signalIntLE3.Reset();
    signalIntLE4.Reset();
    signalIntLE5.Reset();
    signalIntLE6.Reset();

    signalUintLE1.Reset();
    signalUintLE2.Reset();
    signalUintLE3.Reset();
    signalUintLE4.Reset();
    signalUintLE5.Reset();
    signalUintLE6.Reset();

    signalFloatLE1.Reset();
    signalFloatLE2.Reset();

    signalDoubleLE1.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();
}

TEST(DbcUtilCanDLTest, SpontaneousTransmitBigEndianScaledDataTypes)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to integer based signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalUintBE1 = dispatch.AddPublisher("TestScaleBE.SignalScaleUintBE1");
    EXPECT_TRUE(signalUintBE1);
    sdv::core::CSignal signalUintBE2 = dispatch.AddPublisher("TestScaleBE.SignalScaleUintBE2");
    EXPECT_TRUE(signalUintBE2);
    sdv::core::CSignal signalUintBE3 = dispatch.AddPublisher("TestScaleBE.SignalScaleUintBE3");
    EXPECT_TRUE(signalUintBE3);
    sdv::core::CSignal signalUintBE4 = dispatch.AddPublisher("TestScaleBE.SignalScaleUintBE4");
    EXPECT_TRUE(signalUintBE4);
    sdv::core::CSignal signalIntBE5 = dispatch.AddPublisher("TestScaleBE.SignalScaleIntBE5");
    EXPECT_TRUE(signalIntBE5);
    sdv::core::CSignal signalIntBE6 = dispatch.AddPublisher("TestScaleBE.SignalScaleIntBE6");
    EXPECT_TRUE(signalIntBE6);
    sdv::core::CSignal signalFloatBE7 = dispatch.AddPublisher("TestScaleBE.SignalScaleFloatBE7");
    EXPECT_TRUE(signalFloatBE7);
    appcontrol.SetRunningMode();

    for (size_t n = 4; n < 12; n++)
    {
        sdv::core::CTransaction transaction = dispatch.CreateTransaction();
        signalUintBE1.Write(static_cast<double>(n) / 1000.0, transaction);      // 4 bits - factor 0.001
        signalUintBE2.Write(static_cast<int32_t>(n) - 5, transaction);          // 4 bits - offset -3.5
        signalUintBE3.Write(static_cast<int32_t>(n) * -1, transaction);         // 4 bits - factor -1
        signalUintBE4.Write(static_cast<int32_t>(n) - 1, transaction);          // 4 bits - offset -2
        signalIntBE5.Write(static_cast<float>(n) / 1000.0f, transaction);       // 4 bits incl. sign - factor 0.001, offset 0.008
        signalIntBE6.Write(static_cast<int32_t>(n) - 10, transaction);          // 4 bits incl. sign - offset -3.5
        signalFloatBE7.Write(static_cast<float>(n) / 10000.0f, transaction);    // 32 bits - factor 0.001, offset 100
        transaction.Finish();
    }

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalUintBE1.Reset();
    signalUintBE2.Reset();
    signalUintBE3.Reset();
    signalUintBE4.Reset();
    signalIntBE5.Reset();
    signalIntBE6.Reset();
    signalFloatBE7.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[7];
    } rgsData[] = {
        { 0x1F5, {0x43, 0x45, 0xCD, 0xC7, 0xC3, 0x4F, 0xCD}},
        { 0x1F5, {0x54, 0x56, 0xDE, 0xC7, 0xC3, 0x4F, 0xBF}},
        { 0x1F5, {0x65, 0x67, 0xEF, 0xC7, 0xC3, 0x4F, 0xB2}},
        { 0x1F5, {0x76, 0x78, 0xF1, 0xC7, 0xC3, 0x4F, 0xA6}},
        { 0x1F5, {0x87, 0x89, 0x02, 0xC7, 0xC3, 0x4F, 0x99}},
        { 0x1F5, {0x98, 0x9A, 0x13, 0xC7, 0xC3, 0x4F, 0x8C}},
        { 0x1F5, {0xA9, 0xAB, 0x24, 0xC7, 0xC3, 0x4F, 0x7F}},
        { 0x1F5, {0xBA, 0xBC, 0x35, 0xC7, 0xC3, 0x4F, 0x73}}
    };

    // Compare with test data.
    for (const STestData& sData : rgsData)
    {
        EXPECT_FALSE(reader.IsEOF());
        std::pair<asc::SCanMessage, bool> prMsg;
        do {
            prMsg = reader.Get();
            EXPECT_TRUE(prMsg.second);
            ++reader;
        } while (!reader.IsEOF() && prMsg.first.uiId != sData.uiID);   // Skip other messages
        EXPECT_EQ(prMsg.first.uiChannel, 1ul);
        EXPECT_EQ(prMsg.first.uiId, sData.uiID);
        EXPECT_FALSE(prMsg.first.bExtended);
        EXPECT_FALSE(prMsg.first.bCanFd);
        EXPECT_EQ(prMsg.first.uiLength, 7ul);
        EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
        EXPECT_EQ(prMsg.first.rguiData[0], sData.rguiData[0]);
        EXPECT_EQ(prMsg.first.rguiData[1], sData.rguiData[1]);
        EXPECT_EQ(prMsg.first.rguiData[2], sData.rguiData[2]);
        EXPECT_EQ(prMsg.first.rguiData[3], sData.rguiData[3]);
        EXPECT_EQ(prMsg.first.rguiData[4], sData.rguiData[4]);
        EXPECT_EQ(prMsg.first.rguiData[5], sData.rguiData[5]);
        EXPECT_EQ(prMsg.first.rguiData[6], sData.rguiData[6]);
    }
}

TEST(DbcUtilCanDLTest, ReceiveBigEndianScaledDataType)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[7];
    } rgsData[] = {
        { 0x1F5, {0x43, 0x45, 0xCD, 0xC7, 0xC3, 0x4F, 0xCD}},
        { 0x1F5, {0x54, 0x56, 0xDE, 0xC7, 0xC3, 0x4F, 0xBF}},
        { 0x1F5, {0x65, 0x67, 0xEF, 0xC7, 0xC3, 0x4F, 0xB2}},
        { 0x1F5, {0x76, 0x78, 0xF1, 0xC7, 0xC3, 0x4F, 0xA6}},
        { 0x1F5, {0x87, 0x89, 0x02, 0xC7, 0xC3, 0x4F, 0x99}},
        { 0x1F5, {0x98, 0x9A, 0x13, 0xC7, 0xC3, 0x4F, 0x8C}},
        { 0x1F5, {0xA9, 0xAB, 0x24, 0xC7, 0xC3, 0x4F, 0x7F}},
        { 0x1F5, {0xBA, 0xBC, 0x35, 0xC7, 0xC3, 0x4F, 0x73}}
    };

    // Create ASC file with a delay of 100 (to allow the system to initialize)
    asc::CAscWriter writer;
    double dTimestamp = 0.100;
    for (const STestData& sData : rgsData)
    {
        asc::SCanMessage sMsg{};
        sMsg.dTimestamp = dTimestamp;
        dTimestamp += 0.001;
        sMsg.uiChannel = 1ul;
        sMsg.uiId = sData.uiID;
        sMsg.bExtended = false;
        sMsg.bCanFd = false;
        sMsg.uiLength = 7ul;
        sMsg.eDirection = asc::SCanMessage::EDirection::rx;
        sMsg.rguiData[0] = sData.rguiData[0];
        sMsg.rguiData[1] = sData.rguiData[1];
        sMsg.rguiData[2] = sData.rguiData[2];
        sMsg.rguiData[3] = sData.rguiData[3];
        sMsg.rguiData[4] = sData.rguiData[4];
        sMsg.rguiData[5] = sData.rguiData[5];
        sMsg.rguiData[6] = sData.rguiData[6];
        writer.AddSample(sMsg);
    }

    // Add a dummy message with a big timegap to prevent the repetition function to jump in.
    asc::SCanMessage sMsgDummy{};
    sMsgDummy.dTimestamp = 100.0;
    sMsgDummy.uiChannel = 1ul;
    sMsgDummy.uiId = 999;
    writer.AddSample(sMsgDummy);

    // Write the ASC file
    EXPECT_TRUE(writer.Write(GetExecDirectory() / "receiver_test.asc"));
    writer.Clear();

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_rx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    uint32_t rguiCnt[7] = {4u, 4u, 4u, 4u, 4u, 4u, 4u};
    sdv::core::CSignal signalUintBE1 = dispatch.Subscribe("TestScaleBE.SignalScaleUintBE1", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[0]++;
            double dCalculated = static_cast<double>(ui) / 1000.0;   // 4 bits - factor 0.001
            EXPECT_EQ(std::round(dCalculated * 1000.0), std::round(static_cast<double>(tVal) * 1000.0));
        });
    EXPECT_TRUE(signalUintBE1);
    sdv::core::CSignal signalUintBE2 = dispatch.Subscribe("TestScaleBE.SignalScaleUintBE2", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[1]++;
            // Since the offset is a floating point value, but the data type is a unsigned integer, the value is being rounded.
            double dCalculated = static_cast<int32_t>(std::round(static_cast<int32_t>(ui) - 5.0 + 3.5)) - 3.5;   // 4 bits - offset -3.5
            EXPECT_EQ(dCalculated, static_cast<double>(tVal));
        });
    EXPECT_TRUE(signalUintBE2);
    sdv::core::CSignal signalUintBE3 = dispatch.Subscribe("TestScaleBE.SignalScaleUintBE3", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[2]++;
            int32_t iCalculated = static_cast<int32_t>(ui) * -1;   // 4 bits - factor -1
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalUintBE3);
    sdv::core::CSignal signalUintBE4 = dispatch.Subscribe("TestScaleBE.SignalScaleUintBE4", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[3]++;
            int32_t iCalculated = static_cast<int32_t>(ui) - 1;   // 4 bits - offset -2
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalUintBE4);
    sdv::core::CSignal signalIntBE5 = dispatch.Subscribe("TestScaleBE.SignalScaleIntBE5", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[4]++;
            float fCalculated = static_cast<float>(ui) / 1000.0f;   // 4 bits incl. sign - factor 0.001, offset 0.008
            EXPECT_EQ(fCalculated, static_cast<float>(tVal));
        });
    EXPECT_TRUE(signalIntBE5);
    sdv::core::CSignal signalIntBE6 = dispatch.Subscribe("TestScaleBE.SignalScaleIntBE6", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[5]++;
            // Since the offset is a floating point value, but the data type is a integer, the value is being rounded.
            double dCalculated = static_cast<int32_t>(std::round(static_cast<int32_t>(ui) - 10.0 + 3.5)) - 3.5;   // 4 bits incl. sign - offset -3.5
            EXPECT_EQ(dCalculated, static_cast<double>(tVal));
        });
    EXPECT_TRUE(signalIntBE6);
    sdv::core::CSignal signalFloatBE7 = dispatch.Subscribe("TestScaleBE.SignalScaleFloatBE7", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[6]++;
            float fCalculated = static_cast<float>(ui) / 10000.0f;   // 32 bits - factor 0.001, offset 100
            EXPECT_EQ(std::round(fCalculated * 1000.f), std::round(static_cast<float>(tVal) * 1000.f));
        });
    EXPECT_TRUE(signalFloatBE7);
    appcontrol.SetRunningMode();

    std::this_thread::sleep_for(std::chrono::milliseconds(250));

    // Check for correct counters
    for (uint32_t ui : rguiCnt)
        EXPECT_EQ(ui, 12u);

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalUintBE1.Reset();
    signalUintBE2.Reset();
    signalUintBE3.Reset();
    signalUintBE4.Reset();
    signalIntBE5.Reset();
    signalIntBE6.Reset();
    signalFloatBE7.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();
}

TEST(DbcUtilCanDLTest, SpontaneousTransmitLittleEndianScaledDataTypes)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to integer based signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalUintLE1 = dispatch.AddPublisher("TestScaleLE.SignalScaleUintLE1");
    EXPECT_TRUE(signalUintLE1);
    sdv::core::CSignal signalUintLE2 = dispatch.AddPublisher("TestScaleLE.SignalScaleUintLE2");
    EXPECT_TRUE(signalUintLE2);
    sdv::core::CSignal signalUintLE3 = dispatch.AddPublisher("TestScaleLE.SignalScaleUintLE3");
    EXPECT_TRUE(signalUintLE3);
    sdv::core::CSignal signalUintLE4 = dispatch.AddPublisher("TestScaleLE.SignalScaleUintLE4");
    EXPECT_TRUE(signalUintLE4);
    sdv::core::CSignal signalIntLE5 = dispatch.AddPublisher("TestScaleLE.SignalScaleIntLE5");
    EXPECT_TRUE(signalIntLE5);
    sdv::core::CSignal signalIntLE6 = dispatch.AddPublisher("TestScaleLE.SignalScaleIntLE6");
    EXPECT_TRUE(signalIntLE6);
    sdv::core::CSignal signalFloatLE7 = dispatch.AddPublisher("TestScaleLE.SignalScaleFloatLE7");
    EXPECT_TRUE(signalFloatLE7);
    appcontrol.SetRunningMode();

    for (size_t n = 4; n < 12; n++)
    {
        sdv::core::CTransaction transaction = dispatch.CreateTransaction();
        signalUintLE1.Write(static_cast<double>(n) / 1000.0, transaction);      // 4 bits - factor 0.001
        signalUintLE2.Write(static_cast<int32_t>(n) - 5, transaction);          // 4 bits - offset -3.5
        signalUintLE3.Write(static_cast<int32_t>(n) * -1, transaction);         // 4 bits - factor -1
        signalUintLE4.Write(static_cast<int32_t>(n) - 1, transaction);          // 4 bits - offset -2
        signalIntLE5.Write(static_cast<float>(n) / 1000.0f, transaction);       // 4 bits incl. sign - factor 0.001, offset 0.008
        signalIntLE6.Write(static_cast<int32_t>(n) - 10, transaction);          // 4 bits incl. sign - offset -3.5
        signalFloatLE7.Write(static_cast<float>(n) / 10000.0f, transaction);    // 32 bits - factor 0.001, offset 100
        transaction.Finish();
    }

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalUintLE1.Reset();
    signalUintLE2.Reset();
    signalUintLE3.Reset();
    signalUintLE4.Reset();
    signalIntLE5.Reset();
    signalIntLE6.Reset();
    signalFloatLE7.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[7];
    } rgsData[] = {
        { 0x1F4, {0x34, 0x54, 0xDC, 0xCD, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x45, 0x65, 0xED, 0xBF, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x56, 0x76, 0xFE, 0xB2, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x67, 0x87, 0x1F, 0xA6, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x78, 0x98, 0x20, 0x99, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x89, 0xA9, 0x31, 0x8C, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x9A, 0xBA, 0x42, 0x7F, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0xAB, 0xCB, 0x53, 0x73, 0x4F, 0xC3, 0xC7}}
    };

    // Compare with test data.
    for (const STestData& sData : rgsData)
    {
        EXPECT_FALSE(reader.IsEOF());
        std::pair<asc::SCanMessage, bool> prMsg;
        do {
            prMsg = reader.Get();
            EXPECT_TRUE(prMsg.second);
            ++reader;
        } while (!reader.IsEOF() && prMsg.first.uiId != sData.uiID);   // Skip other messages
        EXPECT_EQ(prMsg.first.uiChannel, 1ul);
        EXPECT_EQ(prMsg.first.uiId, sData.uiID);
        EXPECT_FALSE(prMsg.first.bExtended);
        EXPECT_FALSE(prMsg.first.bCanFd);
        EXPECT_EQ(prMsg.first.uiLength, 7ul);
        EXPECT_EQ(prMsg.first.eDirection, asc::SCanMessage::EDirection::tx);
        EXPECT_EQ(prMsg.first.rguiData[0], sData.rguiData[0]);
        EXPECT_EQ(prMsg.first.rguiData[1], sData.rguiData[1]);
        EXPECT_EQ(prMsg.first.rguiData[2], sData.rguiData[2]);
        EXPECT_EQ(prMsg.first.rguiData[3], sData.rguiData[3]);
        EXPECT_EQ(prMsg.first.rguiData[4], sData.rguiData[4]);
        EXPECT_EQ(prMsg.first.rguiData[5], sData.rguiData[5]);
        EXPECT_EQ(prMsg.first.rguiData[6], sData.rguiData[6]);
    }
}

TEST(DbcUtilCanDLTest, ReceiveLittleEndianScaledDataType)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    struct STestData
    {
        uint32_t    uiID;
        uint8_t     rguiData[7];
    } rgsData[] = {
        { 0x1F4, {0x34, 0x54, 0xDC, 0xCD, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x45, 0x65, 0xED, 0xBF, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x56, 0x76, 0xFE, 0xB2, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x67, 0x87, 0x1F, 0xA6, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x78, 0x98, 0x20, 0x99, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x89, 0xA9, 0x31, 0x8C, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0x9A, 0xBA, 0x42, 0x7F, 0x4F, 0xC3, 0xC7}},
        { 0x1F4, {0xAB, 0xCB, 0x53, 0x73, 0x4F, 0xC3, 0xC7}}
    };

    // Create ASC file with a delay of 100 (to allow the system to initialize)
    asc::CAscWriter writer;
    double dTimestamp = 0.100;
    for (const STestData& sData : rgsData)
    {
        asc::SCanMessage sMsg{};
        sMsg.dTimestamp = dTimestamp;
        dTimestamp += 0.001;
        sMsg.uiChannel = 1ul;
        sMsg.uiId = sData.uiID;
        sMsg.bExtended = false;
        sMsg.bCanFd = false;
        sMsg.uiLength = 7ul;
        sMsg.eDirection = asc::SCanMessage::EDirection::rx;
        sMsg.rguiData[0] = sData.rguiData[0];
        sMsg.rguiData[1] = sData.rguiData[1];
        sMsg.rguiData[2] = sData.rguiData[2];
        sMsg.rguiData[3] = sData.rguiData[3];
        sMsg.rguiData[4] = sData.rguiData[4];
        sMsg.rguiData[5] = sData.rguiData[5];
        sMsg.rguiData[6] = sData.rguiData[6];
        writer.AddSample(sMsg);
    }

    // Add a dummy message with a big timegap to prevent the repetition function to jump in.
    asc::SCanMessage sMsgDummy{};
    sMsgDummy.dTimestamp = 100.0;
    sMsgDummy.uiChannel = 1ul;
    sMsgDummy.uiId = 999;
    writer.AddSample(sMsgDummy);

    // Write the ASC file
    EXPECT_TRUE(writer.Write(GetExecDirectory() / "receiver_test.asc"));
    writer.Clear();

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_rx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    uint32_t rguiCnt[7] = {4u, 4u, 4u, 4u, 4u, 4u, 4u};
    sdv::core::CSignal signalUintLE1 = dispatch.Subscribe("TestScaleLE.SignalScaleUintLE1", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[0]++;
            double dCalculated = static_cast<double>(ui) / 1000.0;   // 4 bits - factor 0.001
            EXPECT_EQ(std::round(dCalculated * 1000.0), std::round(static_cast<double>(tVal) * 1000.0));
        });
    EXPECT_TRUE(signalUintLE1);
    sdv::core::CSignal signalUintLE2 = dispatch.Subscribe("TestScaleLE.SignalScaleUintLE2", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[1]++;
            // Since the offset is a floating point value, but the data type is a unsigned integer, the value is being rounded.
            double dCalculated = static_cast<int32_t>(std::round(static_cast<int32_t>(ui) - 5.0 + 3.5)) - 3.5;   // 4 bits - offset -3.5
            EXPECT_EQ(dCalculated, static_cast<double>(tVal));
        });
    EXPECT_TRUE(signalUintLE2);
    sdv::core::CSignal signalUintLE3 = dispatch.Subscribe("TestScaleLE.SignalScaleUintLE3", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[2]++;
            int32_t iCalculated = static_cast<int32_t>(ui) * -1;   // 4 bits - factor -1
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalUintLE3);
    sdv::core::CSignal signalUintLE4 = dispatch.Subscribe("TestScaleLE.SignalScaleUintLE4", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[3]++;
            int32_t iCalculated = static_cast<int32_t>(ui) - 1;   // 4 bits - offset -2
            EXPECT_EQ(iCalculated, static_cast<int32_t>(tVal));
        });
    EXPECT_TRUE(signalUintLE4);
    sdv::core::CSignal signalIntLE5 = dispatch.Subscribe("TestScaleLE.SignalScaleIntLE5", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[4]++;
            float fCalculated = static_cast<float>(ui) / 1000.0f;   // 4 bits incl. sign - factor 0.001, offset 0.008
            EXPECT_EQ(fCalculated, static_cast<float>(tVal));
        });
    EXPECT_TRUE(signalIntLE5);
    sdv::core::CSignal signalIntLE6 = dispatch.Subscribe("TestScaleLE.SignalScaleIntLE6", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[5]++;
            // Since the offset is a floating point value, but the data type is a integer, the value is being rounded.
            double dCalculated = static_cast<int32_t>(std::round(static_cast<int32_t>(ui) - 10.0 + 3.5)) - 3.5;   // 4 bits incl. sign - offset -3.5
            EXPECT_EQ(dCalculated, static_cast<double>(tVal));
        });
    EXPECT_TRUE(signalIntLE6);
    sdv::core::CSignal signalFloatLE7 = dispatch.Subscribe("TestScaleLE.SignalScaleFloatLE7", [&](sdv::any_t tVal)
        {
            uint32_t ui = rguiCnt[6]++;
            float fCalculated = static_cast<float>(ui) / 10000.0f;   // 32 bits - factor 0.001, offset 100
            EXPECT_EQ(std::round(fCalculated * 1000.f), std::round(static_cast<float>(tVal) * 1000.f));
        });
    EXPECT_TRUE(signalFloatLE7);
    appcontrol.SetRunningMode();

    std::this_thread::sleep_for(std::chrono::milliseconds(250));

    // Check for correct counters
    for (uint32_t ui : rguiCnt)
        EXPECT_EQ(ui, 12u);

    // Shutdown
    appcontrol.SetConfigMode();
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalUintLE1.Reset();
    signalUintLE2.Reset();
    signalUintLE3.Reset();
    signalUintLE4.Reset();
    signalIntLE5.Reset();
    signalIntLE6.Reset();
    signalFloatLE7.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();
}

TEST(DbcUtilCanDLTest, CyclicTransmit)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalCounter = dispatch.AddPublisher("TestCyclic.Counter");
    EXPECT_TRUE(signalCounter);

    // The cycle time is 10 ms. Write the counter 5 times with 50ms difference
    size_t n = 0;
    std::mutex mtx;
    std::condition_variable cv;
    sdv::core::CTaskTimer timer(50, [&]()
        {
            if (n == 5)
                cv.notify_all();
            else
                signalCounter.Write(n++);
        });
    appcontrol.SetRunningMode();
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait_for(lock, std::chrono::milliseconds(1000));
    timer.Reset();
    lock.unlock();
    appcontrol.SetConfigMode();

    // Shutdown
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalCounter.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    std::vector<size_t> vecStat(5);
    std::vector<double> vecTime(5);
    int32_t iCnt = -1;
    size_t nSignalCnt = 0;
    double dLastTime = 0;
    while (!reader.IsEOF())
    {
        std::pair<asc::SCanMessage, bool> prMsg = reader.Get();
        EXPECT_TRUE(prMsg.second);
        ++reader;
        dLastTime = prMsg.first.dTimestamp;

        // Only look at the cyclic message
        if (prMsg.first.uiId != 0x8) continue;
        nSignalCnt++;

        int32_t iCntTemp = static_cast<int32_t>(prMsg.first.rguiData[0] >> 5);
        EXPECT_TRUE(iCntTemp == iCnt || iCntTemp == iCnt + 1);
        iCnt = iCntTemp;
        EXPECT_LT(iCnt, 5);
        if (iCnt < 5)
        {
            vecStat[iCnt]++;
            if (iCnt && !vecTime[iCnt - 1]) vecTime[iCnt - 1] = prMsg.first.dTimestamp;
        }
    }
    if (!vecTime[4]) vecTime[4] = dLastTime;
    if (iCnt < 4)
    {
        if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED);
        else
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED);
    }

    for (n = 0; n < 5; n++)
    {
        if (n != 0)
        {
            if (vecStat[n] > 6u)       // In the unlucky case, 6 triggers might have occurred (during startup, there might be many more...).
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_LE_WARN(vecStat[n], 6u, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_LE_WARN(vecStat[n], 6u, sdv_test::WARNING_ENABLED);
            }
            double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0;
            if (dPeriod > 0.051)   // Max 51ms
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED);
            }
            if (n == 4)
            {
                if (dPeriod < 0.019)   // Min 19ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED);
                }
            }
            else
            {
                if (dPeriod < 0.039)   // Min 39ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED);
                }
            }
        }
        if (vecStat[n] < 4u)  // At least 4 triggers.
        {
            if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                SDV_EXPECT_GE_WARN(vecStat[n], 4u, sdv_test::WARNING_REDUCED);
            else
                SDV_EXPECT_GE_WARN(vecStat[n], 4u, sdv_test::WARNING_ENABLED);
        }
    }
}

TEST(DbcUtilCanDLTest, CyclicIfActiveTransmit)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalCounter = dispatch.AddPublisher("TestCyclicIfActive.Counter");
    EXPECT_TRUE(signalCounter);

    // The counter value 2 is the default value and therefore determines the active state.
    // The cycle time is 10 ms. Write the counter 5 times with 50ms difference
    size_t n = 0;
    std::mutex mtx;
    std::condition_variable cv;
    sdv::core::CTaskTimer timer(50, [&]()
        {
            if (n == 5)
                cv.notify_all();
            else
                signalCounter.Write(n++);
        });
    appcontrol.SetRunningMode();
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait_for(lock, std::chrono::milliseconds(1000));
    appcontrol.SetConfigMode();
    timer.Reset();

    // Shutdown
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalCounter.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    std::vector<size_t> vecStat(5);
    std::vector<double> vecTime(5);
    int32_t iCnt = -1;
    size_t nSignalCnt = 0;
    double dLastTime = 0;
    bool bInit = false;
    while (!reader.IsEOF())
    {
        std::pair<asc::SCanMessage, bool> prMsg = reader.Get();
        EXPECT_TRUE(prMsg.second);
        ++reader;
        dLastTime = prMsg.first.dTimestamp;

        // Only look at the cyclic message
        if (prMsg.first.uiId != 0xa) continue;
        nSignalCnt++;

        iCnt = static_cast<int32_t>(prMsg.first.rguiData[0] >> 5);
        EXPECT_LT(iCnt, 5);
        if (!bInit)
        {
            // Expected iCnt to be the default value...
            if (iCnt != 0 && iCnt != 2)
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_EQ_WARN(iCnt, 0, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_EQ_WARN(iCnt, 0, sdv_test::WARNING_ENABLED);
            }

            bInit = true;
        }
        else if (iCnt < 5)
        {
            vecStat[iCnt]++;
            if (iCnt && !vecTime[iCnt - 1]) vecTime[iCnt - 1] = prMsg.first.dTimestamp;
        }
    }
    if (!vecTime[4]) vecTime[4] = dLastTime;
    if (iCnt < 4)
    {
        if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED);
        else
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED);
    }

    for (n = 0; n < 5; n++)
    {
        if (n != 0)
        {
            if (n == 2)
            {
                if (vecStat[n] != 1u)       // One trigger should have occurred due to default value
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_EQ_WARN(vecStat[n], 1u, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_EQ_WARN(vecStat[n], 1u, sdv_test::WARNING_ENABLED);
                }
            }
            else
            {
                if (vecStat[n] > 6u)       // In the unlucky case, 6 triggers might have occurred (during startup, there might be many more...).
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_LE_WARN(vecStat[n], 6u, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_LE_WARN(vecStat[n], 6u, sdv_test::WARNING_ENABLED);
                }
            }
            double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0;
            if (dPeriod > 0.051)   // Max 51ms
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED);
            }
            if (n == 4)
            {
                if (dPeriod < 0.019)   // Min 19ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED);
                }
            }
            else
            {
                if (dPeriod < 0.039)   // Min 39ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED);
                }
            }
        }
    }
}

TEST(DbcUtilCanDLTest, CyclicAndSpontaneousTransmit)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalCounter = dispatch.AddPublisher("TestCyclicAndSpontaneous.Counter");
    EXPECT_TRUE(signalCounter);

    // The cycle time is 50 ms. Write a value every 50ms. This should result in a spontaneous and a cyclic trigger.
    size_t n = 0;
    std::mutex mtx;
    std::condition_variable cv;
    sdv::core::CTaskTimer timer(50, [&]()
        {
            if (n == 5)
                cv.notify_all();
            else
                signalCounter.Write(n++);
        });
    appcontrol.SetRunningMode();
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait_for(lock, std::chrono::milliseconds(1000));
    appcontrol.SetConfigMode();
    timer.Reset();

    // Shutdown
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalCounter.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    std::vector<size_t> vecStat(5);
    std::vector<double> vecTime(5);
    int32_t iCnt = -1;
    size_t nSignalCnt = 0;
    double dLastTime = 0;
    while (!reader.IsEOF())
    {
        std::pair<asc::SCanMessage, bool> prMsg = reader.Get();
        EXPECT_TRUE(prMsg.second);
        ++reader;
        dLastTime = prMsg.first.dTimestamp;

        // Only look at the cyclic message
        if (prMsg.first.uiId != 0x9) continue;
        nSignalCnt++;

        int32_t iCntTemp = static_cast<int32_t>(prMsg.first.rguiData[0] >> 5);
        EXPECT_TRUE(iCntTemp == iCnt || iCntTemp == iCnt + 1);
        iCnt = iCntTemp;
        EXPECT_LT(iCnt, 5);
        if (iCnt < 5)
        {
            vecStat[iCnt]++;
            if (iCnt && !vecTime[iCnt - 1]) vecTime[iCnt - 1] = prMsg.first.dTimestamp;
        }
    }
    if (!vecTime[4]) vecTime[4] = dLastTime;
    if (iCnt < 4)
    {
        if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED);
        else
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED);
    }

    for (n = 0; n < 5; n++)
    {
        if (n != 0)
        {
            if (vecStat[n] != 2u)       // One trigger and one cycle.
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_ENABLED);
            }
            double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0;
            if (dPeriod > 0.051)   // Max 51ms
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED);
            }
            if (n == 4)
            {
                if (dPeriod < 0.019)   // Min 19ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED);
                }
            }
            else
            {
                if (dPeriod < 0.039)   // Min 39ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED);
                }
            }
        }
    }
}

TEST(DbcUtilCanDLTest, SpontaneousDelayTransmit)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalCounter = dispatch.AddPublisher("TestSpontaneousDelay.Counter");
    EXPECT_TRUE(signalCounter);

    // The delay is 20ms. Write the counter 5 times every 10ms.
    size_t n = 0;
    std::mutex mtx;
    std::condition_variable cv;
    size_t nCycle = 0;
    sdv::core::CTaskTimer timer(10, [&]()
        {
            if (n < 5) signalCounter.Write(n);
            if (nCycle++ == 5)
            {
                nCycle = 0;
                if (n == 5) cv.notify_all();
                n++;
            }
        });
    appcontrol.SetRunningMode();
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait_for(lock, std::chrono::milliseconds(1000));
    appcontrol.SetConfigMode();
    timer.Reset();

    // Shutdown
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalCounter.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    std::vector<size_t> vecStat(5);
    std::vector<double> vecFirstTime(5);
    std::vector<double> vecLastTime(5);
    int32_t iCnt = -1;
    size_t nSignalCnt = 0;
    while (!reader.IsEOF())
    {
        std::pair<asc::SCanMessage, bool> prMsg = reader.Get();
        EXPECT_TRUE(prMsg.second);
        ++reader;

        // Only look at the cyclic message
        if (prMsg.first.uiId != 0xc) continue;
        nSignalCnt++;

        int32_t iCntTemp = static_cast<int32_t>(prMsg.first.rguiData[0] >> 5);
        EXPECT_TRUE(iCntTemp == iCnt || iCntTemp == iCnt + 1);
        iCnt = iCntTemp;
        EXPECT_LT(iCnt, 5);
        if (iCnt < 5)
        {
            vecStat[iCnt]++;
            if (!vecFirstTime[iCnt]) vecFirstTime[iCnt] = prMsg.first.dTimestamp;
            vecLastTime[iCnt] = prMsg.first.dTimestamp;
        }
    }
    if (iCnt < 4)
    {
        if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED);
        else
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED);
    }

    for (n = 0; n < 5; n++)
    {
        if (n != 0)
        {
            if (vecStat[n] > 4u)       // Max 4 times
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_LE_WARN(vecStat[n], 4u, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_LE_WARN(vecStat[n], 4u, sdv_test::WARNING_ENABLED);
            }
            if (vecStat[n] < 3u)       // Min 3 times
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_GE_WARN(vecStat[n], 3u, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_GE_WARN(vecStat[n], 3u, sdv_test::WARNING_ENABLED);
            }
            double dPeriod = std::round((vecLastTime[n] - vecFirstTime[n]) * 1000.0) / 1000.0;
            if (dPeriod > 0.061)   // Max 61ms
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_LE_WARN(dPeriod, 0.061, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_LE_WARN(dPeriod, 0.061, sdv_test::WARNING_ENABLED);
            }
            if (dPeriod < 0.039)   // Min 39ms
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED);
            }
        }
    }
}

TEST(DbcUtilCanDLTest, CyclicAndSpontaneousDelayTransmit)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalCounter = dispatch.AddPublisher("TestCyclicAndSpontaneousDelay.Counter");
    EXPECT_TRUE(signalCounter);

    // The cycle time is 50ms. The delay is 25ms. Write the counter 2 times every 25ms.
    size_t n = 0;
    std::mutex mtx;
    std::condition_variable cv;
    sdv::core::CTaskTimer timer(25, [&]()
        {
            if (n == 10)
                cv.notify_all();
            else
            {
                signalCounter.Write(n / 2);
                n++;
            }
        });
    appcontrol.SetRunningMode();
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait_for(lock, std::chrono::milliseconds(5000));
    appcontrol.SetConfigMode();
    timer.Reset();

    // Shutdown
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalCounter.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    std::vector<size_t> vecStat(5);
    std::vector<double> vecTime(5);
    int32_t iCnt = -1;
    size_t nSignalCnt = 0;
    double dLastTime = 0;
    while (!reader.IsEOF())
    {
        std::pair<asc::SCanMessage, bool> prMsg = reader.Get();
        EXPECT_TRUE(prMsg.second);
        ++reader;
        dLastTime = prMsg.first.dTimestamp;

        // Only look at the cyclic message
        if (prMsg.first.uiId != 0xd) continue;
        nSignalCnt++;

        int32_t iCntTemp = static_cast<int32_t>(prMsg.first.rguiData[0] >> 5);
        EXPECT_TRUE(iCntTemp == iCnt || iCntTemp == iCnt + 1);
        iCnt = iCntTemp;
        EXPECT_LT(iCnt, 5);
        if (iCnt < 5)
        {
            vecStat[iCnt]++;
            if (iCnt && !vecTime[iCnt - 1]) vecTime[iCnt - 1] = prMsg.first.dTimestamp;
        }
    }
    if (!vecTime[4]) vecTime[4] = dLastTime;
    if (iCnt < 4)
        {
            if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED);
            else
                SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED);
        }

    for (n = 0; n < 5; n++)
    {
        if (n != 0)
        {
            if (vecStat[n] != 2u)       // Two trigger and/or cycle.
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_ENABLED);
            }
            double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0;
            if (dPeriod > 0.051)   // Max 51ms
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                     SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED);
                else
                     SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED);
            }
            if (n == 4)
            {
                if (dPeriod < 0.019)   // Min 19ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED);
                }
            }
            else
            {
                if (dPeriod < 0.049)   // Min 49ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_GE_WARN(dPeriod, 0.049, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_GE_WARN(dPeriod, 0.049, sdv_test::WARNING_ENABLED);
                }
            }
        }
    }
}

TEST(DbcUtilCanDLTest, CyclicIfActiveAndSpontaneousTransmit)
{
    try
    {
        std::filesystem::remove(GetExecDirectory() / "receiver_test.asc");
        std::filesystem::remove(GetExecDirectory() / "transmitter_test.asc");
    } catch (const std::filesystem::filesystem_error&)
    {}
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "receiver_test.asc"));
    EXPECT_FALSE(std::filesystem::exists(GetExecDirectory() / "transmitter_test.asc"));

    std::filesystem::current_path(GetExecDirectory());

    sdv::app::CAppControl appcontrol;
    bool bResult = appcontrol.Startup("");
    EXPECT_TRUE(bResult);
    appcontrol.SetConfigMode();
    sdv::core::EConfigProcessResult eResult = appcontrol.LoadConfig("test_dbc_util_config_tx.toml");
    EXPECT_EQ(eResult, sdv::core::EConfigProcessResult::successful);

    // Start the data link
    CDbcStructDataLink dl;
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialization_pending);
    dl.Initialize(sdv::SObjectInfo());
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::initialized);
    dl.SetOperationMode(sdv::EOperationMode::running);
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::running);

    // Subscribe to several signals
    sdv::core::CDispatchService dispatch;
    sdv::core::CSignal signalCounter = dispatch.AddPublisher("TestCyclicIfActiveAndSpontaneous.Counter");
    EXPECT_TRUE(signalCounter);

    // The cycle time is 50 ms. Write a value every 50ms. This should result in a spontaneous and a cyclic trigger.
    size_t n = 0;
    std::mutex mtx;
    std::condition_variable cv;
    sdv::core::CTaskTimer timer(50, [&]()
        {
            if (n == 5)
                cv.notify_all();
            else
                signalCounter.Write(n++);
        });
    appcontrol.SetRunningMode();
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait_for(lock, std::chrono::milliseconds(1000));
    appcontrol.SetConfigMode();
    timer.Reset();

    // Shutdown
    dl.Shutdown();
    EXPECT_EQ(dl.GetObjectState(), sdv::EObjectState::destruction_pending);

    signalCounter.Reset();

    // Shutdown. This will close the CAN recording.
    appcontrol.Shutdown();

    // Read the CAN recording.
    asc::CAscReader reader;
    EXPECT_TRUE(reader.Read(GetExecDirectory() / "transmitter_test.asc"));

    std::vector<size_t> vecStat(5);
    std::vector<double> vecTime(5);
    int32_t iCnt = -1;
    size_t nSignalCnt = 0;
    double dLastTime = 0;
    bool bInit = false;
    while (!reader.IsEOF())
    {
        std::pair<asc::SCanMessage, bool> prMsg = reader.Get();
        EXPECT_TRUE(prMsg.second);
        ++reader;
        dLastTime = prMsg.first.dTimestamp;

        // Only look at the cyclic message
        if (prMsg.first.uiId != 0xb) continue;
        nSignalCnt++;

        iCnt = static_cast<int32_t>(prMsg.first.rguiData[0] >> 5);
        EXPECT_LT(iCnt, 5);
        if (!bInit)
        {
            // Expected iCnt to be the default value...
            if (iCnt != 0 && iCnt != 2)
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_EQ_WARN(iCnt, 0, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_EQ_WARN(iCnt, 0, sdv_test::WARNING_ENABLED);
            }

            bInit = true;
        }
        else if (iCnt < 5)
        {
            vecStat[iCnt]++;
            if (iCnt && !vecTime[iCnt - 1]) vecTime[iCnt - 1] = prMsg.first.dTimestamp;
        }
    }
    if (!vecTime[4]) vecTime[4] = dLastTime;
    if (iCnt < 4)
    {
        if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_REDUCED);
        else
            SDV_EXPECT_GE_WARN(iCnt, 4, sdv_test::WARNING_ENABLED);
    }
    for (n = 0; n < 5; n++)
    {
        if (n != 0)
        {
            if (n == 2)
            {
                if (vecStat[n] != 1u)       // One trigger no cycle.
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_EQ_WARN(vecStat[n], 1u, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_EQ_WARN(vecStat[n], 1u, sdv_test::WARNING_ENABLED);
                }
            }
            else
            {
                if (vecStat[n] != 2u)       // One trigger and one cycle.
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_EQ_WARN(vecStat[n], 2u, sdv_test::WARNING_ENABLED);
                }
            }
            double dPeriod = std::round((vecTime[n] - vecTime[n - 1]) * 1000.0) / 1000.0;
            if (dPeriod > 0.051)   // Max 51ms
            {
                if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                    SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_REDUCED);
                else
                    SDV_EXPECT_LE_WARN(dPeriod, 0.051, sdv_test::WARNING_ENABLED);
            }
            if (n == 4)
            {
                if (dPeriod < 0.019)   // Min 19ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_REDUCED);
                    else
                        SDV_EXPECT_GE_WARN(dPeriod, 0.019, sdv_test::WARNING_ENABLED);
                }
            }
            else
            {
                if (dPeriod < 0.039)   // Min 39ms
                {
                    if(SDV_IS_RUNNING_TESTS_WITH_CMAKE_BUILD)
                         SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_REDUCED);
                    else
                         SDV_EXPECT_GE_WARN(dPeriod, 0.039, sdv_test::WARNING_ENABLED);
                }
            }
        }
    }
}
