1449 lines
53 KiB
C++
1449 lines
53 KiB
C++
/**
|
|
* @file UDPStreamerClientTest.cpp
|
|
* @brief Source file for class UDPStreamerClientTest
|
|
* @date 01/07/2026
|
|
* @author Martino Ferrari
|
|
*
|
|
* @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and
|
|
* the Development of Fusion Energy ('Fusion for Energy').
|
|
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved
|
|
* by the European Commission - subsequent versions of the EUPL (the "Licence")
|
|
* You may not use this work except in compliance with the Licence.
|
|
* You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
|
|
*
|
|
* @warning Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the Licence is distributed on an "AS IS"
|
|
* basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
* or implied. See the Licence permissions and limitations under the Licence.
|
|
*
|
|
* @details This source file contains the definition of all the methods for
|
|
* the class UDPStreamerClientTest (public, protected, and private).
|
|
*/
|
|
|
|
#define DLL_API
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Standard header includes */
|
|
/*---------------------------------------------------------------------------*/
|
|
#include <string.h>
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Project header includes */
|
|
/*---------------------------------------------------------------------------*/
|
|
#include "AdvancedErrorManagement.h"
|
|
#include "BasicUDPSocket.h"
|
|
#include "ConfigurationDatabase.h"
|
|
#include "GAM.h"
|
|
#include "MemoryOperationsHelper.h"
|
|
#include "ObjectRegistryDatabase.h"
|
|
#include "RealTimeApplication.h"
|
|
#include "Sleep.h"
|
|
#include "StandardParser.h"
|
|
#include "Threads.h"
|
|
#include "UDPSProtocol.h"
|
|
#include "UDPStreamerClient.h"
|
|
#include "UDPStreamerClientTest.h"
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Static definitions */
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* @brief Minimal pass-through GAM used instead of IOGAM, so the test does
|
|
* not depend on IOGAM.so being dlopen()-able / on the loader path.
|
|
* Copies each input signal to the matching output signal.
|
|
*/
|
|
class UDPStreamerClientTestGAM : public MARTe::GAM {
|
|
public:
|
|
CLASS_REGISTER_DECLARATION()
|
|
|
|
UDPStreamerClientTestGAM() :
|
|
GAM() {
|
|
}
|
|
|
|
~UDPStreamerClientTestGAM() {
|
|
}
|
|
|
|
bool Execute() {
|
|
for (MARTe::uint32 i = 0u; i < GetNumberOfOutputSignals(); i++) {
|
|
void *outMem = GetOutputSignalMemory(i);
|
|
void *inMem = GetInputSignalMemory(i);
|
|
if ((outMem != NULL_PTR(void *)) && (inMem != NULL_PTR(void *))) {
|
|
MARTe::uint32 sz = 0u;
|
|
GetSignalByteSize(MARTe::OutputSignals, i, sz);
|
|
(void) MARTe::MemoryOperationsHelper::Copy(outMem, inMem, sz);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Setup() {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
CLASS_REGISTER(UDPStreamerClientTestGAM, "1.0")
|
|
|
|
/**
|
|
* @brief Shared state between the test thread and a background
|
|
* UDPStreamerClient::Synchronise() call (see StartSynchroniseThread).
|
|
*/
|
|
struct SynchroniseThreadArgs {
|
|
MARTe::ReferenceT<MARTe::UDPStreamerClient> ds;
|
|
bool result;
|
|
bool done;
|
|
};
|
|
|
|
static void SynchroniseThreadEntry(const void *const parameters) {
|
|
SynchroniseThreadArgs *args = const_cast<SynchroniseThreadArgs *>(
|
|
reinterpret_cast<const SynchroniseThreadArgs *>(parameters));
|
|
args->result = args->ds->Synchronise();
|
|
args->done = true;
|
|
}
|
|
|
|
/**
|
|
* @brief UDPStreamerClient::Synchronise() calls dataSem.ResetWait(), which
|
|
* first lowers (Reset()s) the semaphore barrier and only then waits
|
|
* (with an internal ~10 ms timeout) for it to be raised again.
|
|
* Because of this, a Post() (triggered by OnUDPSData()) that happens
|
|
* BEFORE Synchronise() is called is wiped out by that leading Reset()
|
|
* and lost. In production this is never an issue because the RT
|
|
* thread's Synchronise() call is already blocked in Wait() when the
|
|
* receiver thread posts new data.
|
|
*
|
|
* To exercise that same interleaving from a single-threaded test, run
|
|
* Synchronise() on a background thread and give it a very brief head
|
|
* start (shorter than the internal timeout) so it has a chance to
|
|
* pass through Reset() and be parked in Wait() before the caller
|
|
* triggers the data delivery. Thread-scheduling latency means this is
|
|
* still a race, not a guarantee, so callers should retry the
|
|
* deliver+StartSynchroniseThread+JoinSynchroniseThread sequence a few
|
|
* times until the expected value shows up (see e.g.
|
|
* TestOnUDPSConfig_BasicAccept for the pattern).
|
|
*/
|
|
static SynchroniseThreadArgs *StartSynchroniseThread(MARTe::ReferenceT<MARTe::UDPStreamerClient> ds) {
|
|
using namespace MARTe;
|
|
SynchroniseThreadArgs *args = new SynchroniseThreadArgs();
|
|
args->ds = ds;
|
|
args->result = false;
|
|
args->done = false;
|
|
(void) Threads::BeginThread(&SynchroniseThreadEntry, args, THREADS_DEFAULT_STACKSIZE, "SyncTest");
|
|
/* Give the thread a chance to start running and reach Reset()+Wait()
|
|
* before the caller delivers data. Must stay well below Synchronise()'s
|
|
* internal ~10 ms timeout, or the background call will already have
|
|
* timed out and returned by the time we come back here. */
|
|
Sleep::MSec(1u);
|
|
return args;
|
|
}
|
|
|
|
/**
|
|
* @brief Waits for the background Synchronise() started by
|
|
* StartSynchroniseThread() to complete, returns its result and frees
|
|
* the shared state.
|
|
*/
|
|
static bool JoinSynchroniseThread(SynchroniseThreadArgs *args) {
|
|
using namespace MARTe;
|
|
uint32 waited = 0u;
|
|
while ((!args->done) && (waited < 500u)) {
|
|
Sleep::MSec(2u);
|
|
waited += 2u;
|
|
}
|
|
bool result = args->done && args->result;
|
|
delete args;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief Helper: build a RealTimeApplication from an MARTe2 config string.
|
|
* Returns the application reference (invalid if parsing failed).
|
|
*/
|
|
static MARTe::ReferenceT<MARTe::RealTimeApplication> LoadApplication(
|
|
const MARTe::char8 *const config) {
|
|
using namespace MARTe;
|
|
|
|
ConfigurationDatabase cdb;
|
|
StreamString cfgStr = config;
|
|
(void) cfgStr.Seek(0LLU);
|
|
StandardParser parser(cfgStr, cdb);
|
|
if (!parser.Parse()) {
|
|
return ReferenceT<RealTimeApplication>();
|
|
}
|
|
|
|
ObjectRegistryDatabase *god = ObjectRegistryDatabase::Instance();
|
|
god->Purge();
|
|
if (!god->Initialise(cdb)) {
|
|
return ReferenceT<RealTimeApplication>();
|
|
}
|
|
|
|
ReferenceT<RealTimeApplication> app = god->Find("Test");
|
|
if (!app.IsValid()) {
|
|
return ReferenceT<RealTimeApplication>();
|
|
}
|
|
if (!app->ConfigureApplication()) {
|
|
return ReferenceT<RealTimeApplication>();
|
|
}
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* @brief Minimal single-scalar-signal config template.
|
|
* %d placeholders: Port.
|
|
*/
|
|
static MARTe::StreamString BuildBasicConfig(MARTe::uint16 port) {
|
|
using namespace MARTe;
|
|
StreamString cfg;
|
|
cfg.Printf(
|
|
"+Test = {\n"
|
|
" Class = RealTimeApplication\n"
|
|
" +Functions = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Reader = {\n"
|
|
" Class = UDPStreamerClientTestGAM\n"
|
|
" InputSignals = {\n"
|
|
" Counter = {\n"
|
|
" DataSource = ClientDS\n"
|
|
" Type = uint32\n"
|
|
" }\n"
|
|
" }\n"
|
|
" OutputSignals = {\n"
|
|
" Counter = {\n"
|
|
" DataSource = DDB\n"
|
|
" Type = uint32\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Data = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" DefaultDataSource = DDB\n"
|
|
" +DDB = {\n"
|
|
" Class = GAMDataSource\n"
|
|
" }\n"
|
|
" +ClientDS = {\n"
|
|
" Class = UDPStreamerClient\n"
|
|
" ServerAddress = \"127.0.0.1\"\n"
|
|
" Port = %d\n"
|
|
" MaxPayloadSize = 1400\n"
|
|
" Signals = {\n"
|
|
" Counter = {\n"
|
|
" Type = uint32\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Timings = {\n"
|
|
" Class = TimingDataSource\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +States = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +State1 = {\n"
|
|
" Class = RealTimeState\n"
|
|
" +Threads = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Thread1 = {\n"
|
|
" Class = RealTimeThread\n"
|
|
" Functions = { Reader }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Scheduler = {\n"
|
|
" Class = GAMScheduler\n"
|
|
" TimingDataSource = Timings\n"
|
|
" }\n"
|
|
"}\n", port);
|
|
return cfg;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Method definitions */
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
bool UDPStreamerClientTest::TestInitialise_Valid() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44700u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestInitialise_DefaultServerAddress() {
|
|
using namespace MARTe;
|
|
static const char8 *const cfg =
|
|
"+Test = {\n"
|
|
" Class = RealTimeApplication\n"
|
|
" +Functions = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Reader = {\n"
|
|
" Class = UDPStreamerClientTestGAM\n"
|
|
" InputSignals = { Counter = { DataSource = ClientDS Type = uint32 } }\n"
|
|
" OutputSignals = { Counter = { DataSource = DDB Type = uint32 } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Data = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" DefaultDataSource = DDB\n"
|
|
" +DDB = { Class = GAMDataSource }\n"
|
|
" +ClientDS = {\n"
|
|
" Class = UDPStreamerClient\n"
|
|
" Port = 44701\n"
|
|
" Signals = { Counter = { Type = uint32 } }\n"
|
|
" }\n"
|
|
" +Timings = { Class = TimingDataSource }\n"
|
|
" }\n"
|
|
" +States = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +State1 = {\n"
|
|
" Class = RealTimeState\n"
|
|
" +Threads = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Thread1 = { Class = RealTimeThread Functions = { Reader } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Scheduler = { Class = GAMScheduler TimingDataSource = Timings }\n"
|
|
"}\n";
|
|
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg);
|
|
bool ok = app.IsValid();
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestInitialise_DefaultPort() {
|
|
using namespace MARTe;
|
|
static const char8 *const cfg =
|
|
"+Test = {\n"
|
|
" Class = RealTimeApplication\n"
|
|
" +Functions = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Reader = {\n"
|
|
" Class = UDPStreamerClientTestGAM\n"
|
|
" InputSignals = { Counter = { DataSource = ClientDS Type = uint32 } }\n"
|
|
" OutputSignals = { Counter = { DataSource = DDB Type = uint32 } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Data = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" DefaultDataSource = DDB\n"
|
|
" +DDB = { Class = GAMDataSource }\n"
|
|
" +ClientDS = {\n"
|
|
" Class = UDPStreamerClient\n"
|
|
" ServerAddress = \"127.0.0.1\"\n"
|
|
" Signals = { Counter = { Type = uint32 } }\n"
|
|
" }\n"
|
|
" +Timings = { Class = TimingDataSource }\n"
|
|
" }\n"
|
|
" +States = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +State1 = {\n"
|
|
" Class = RealTimeState\n"
|
|
" +Threads = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Thread1 = { Class = RealTimeThread Functions = { Reader } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Scheduler = { Class = GAMScheduler TimingDataSource = Timings }\n"
|
|
"}\n";
|
|
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg);
|
|
bool ok = app.IsValid();
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestInitialise_MulticastMode_Valid() {
|
|
using namespace MARTe;
|
|
static const char8 *const cfg =
|
|
"+Test = {\n"
|
|
" Class = RealTimeApplication\n"
|
|
" +Functions = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Reader = {\n"
|
|
" Class = UDPStreamerClientTestGAM\n"
|
|
" InputSignals = { Counter = { DataSource = ClientDS Type = uint32 } }\n"
|
|
" OutputSignals = { Counter = { DataSource = DDB Type = uint32 } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Data = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" DefaultDataSource = DDB\n"
|
|
" +DDB = { Class = GAMDataSource }\n"
|
|
" +ClientDS = {\n"
|
|
" Class = UDPStreamerClient\n"
|
|
" ServerAddress = \"127.0.0.1\"\n"
|
|
" Port = 44702\n"
|
|
" MulticastGroup = \"239.0.0.5\"\n"
|
|
" DataPort = 44703\n"
|
|
" Signals = { Counter = { Type = uint32 } }\n"
|
|
" }\n"
|
|
" +Timings = { Class = TimingDataSource }\n"
|
|
" }\n"
|
|
" +States = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +State1 = {\n"
|
|
" Class = RealTimeState\n"
|
|
" +Threads = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Thread1 = { Class = RealTimeThread Functions = { Reader } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Scheduler = { Class = GAMScheduler TimingDataSource = Timings }\n"
|
|
"}\n";
|
|
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg);
|
|
bool ok = app.IsValid();
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestInitialise_MulticastMode_DefaultDataPort() {
|
|
using namespace MARTe;
|
|
static const char8 *const cfg =
|
|
"+Test = {\n"
|
|
" Class = RealTimeApplication\n"
|
|
" +Functions = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Reader = {\n"
|
|
" Class = UDPStreamerClientTestGAM\n"
|
|
" InputSignals = { Counter = { DataSource = ClientDS Type = uint32 } }\n"
|
|
" OutputSignals = { Counter = { DataSource = DDB Type = uint32 } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Data = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" DefaultDataSource = DDB\n"
|
|
" +DDB = { Class = GAMDataSource }\n"
|
|
" +ClientDS = {\n"
|
|
" Class = UDPStreamerClient\n"
|
|
" ServerAddress = \"127.0.0.1\"\n"
|
|
" Port = 44704\n"
|
|
" MulticastGroup = \"239.0.0.6\"\n"
|
|
" Signals = { Counter = { Type = uint32 } }\n"
|
|
" }\n"
|
|
" +Timings = { Class = TimingDataSource }\n"
|
|
" }\n"
|
|
" +States = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +State1 = {\n"
|
|
" Class = RealTimeState\n"
|
|
" +Threads = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Thread1 = { Class = RealTimeThread Functions = { Reader } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Scheduler = { Class = GAMScheduler TimingDataSource = Timings }\n"
|
|
"}\n";
|
|
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg);
|
|
bool ok = app.IsValid();
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestSetConfiguredDatabase_MultipleSignals() {
|
|
using namespace MARTe;
|
|
static const char8 *const cfg =
|
|
"+Test = {\n"
|
|
" Class = RealTimeApplication\n"
|
|
" +Functions = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Reader = {\n"
|
|
" Class = UDPStreamerClientTestGAM\n"
|
|
" InputSignals = {\n"
|
|
" Counter = { DataSource = ClientDS Type = uint32 }\n"
|
|
" Samples = { DataSource = ClientDS Type = float32 NumberOfDimensions = 1 NumberOfElements = 4 }\n"
|
|
" }\n"
|
|
" OutputSignals = {\n"
|
|
" Counter = { DataSource = DDB Type = uint32 }\n"
|
|
" Samples = { DataSource = DDB Type = float32 NumberOfDimensions = 1 NumberOfElements = 4 }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Data = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" DefaultDataSource = DDB\n"
|
|
" +DDB = { Class = GAMDataSource }\n"
|
|
" +ClientDS = {\n"
|
|
" Class = UDPStreamerClient\n"
|
|
" ServerAddress = \"127.0.0.1\"\n"
|
|
" Port = 44705\n"
|
|
" Signals = {\n"
|
|
" Counter = { Type = uint32 }\n"
|
|
" Samples = { Type = float32 NumberOfDimensions = 1 NumberOfElements = 4 }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Timings = { Class = TimingDataSource }\n"
|
|
" }\n"
|
|
" +States = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +State1 = {\n"
|
|
" Class = RealTimeState\n"
|
|
" +Threads = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Thread1 = { Class = RealTimeThread Functions = { Reader } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Scheduler = { Class = GAMScheduler TimingDataSource = Timings }\n"
|
|
"}\n";
|
|
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg);
|
|
bool ok = app.IsValid();
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestPrepareNextState_StartsReceiver() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44706u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
if (ok) {
|
|
ok = (app->PrepareNextState("State1") == MARTe::ErrorManagement::NoError);
|
|
}
|
|
Sleep::MSec(50u);
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestSynchronise_NoData() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44707u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
if (ok) {
|
|
/* No CONFIG/DATA ever delivered: Synchronise must be a safe no-op
|
|
* and still return true. */
|
|
ok = ds->Synchronise();
|
|
}
|
|
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ok) {
|
|
ok = ds->GetSignalMemoryBuffer(0u, 0u, sigMem);
|
|
}
|
|
if (ok) {
|
|
uint32 value = 0xFFFFFFFFu;
|
|
(void) memcpy(&value, sigMem, sizeof(uint32));
|
|
/* Memory was zero-initialised by AllocateMemory and never touched. */
|
|
ok = (value == 0u);
|
|
}
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSConfig_BasicAccept() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44708u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
/* Build a CONFIG payload declaring one scalar "Counter" uint32 signal. */
|
|
if (ok) {
|
|
uint8 payload[4u + UDPS_SIGNAL_DESC_SIZE + 1u];
|
|
uint32 numSigs = 1u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Counter", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_UINT32;
|
|
desc.quantType = UDPS_QUANT_NONE;
|
|
desc.numDimensions = 0u;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
desc.rangeMin = 0.0;
|
|
desc.rangeMax = 1.0;
|
|
desc.timeMode = UDPS_TIMEMODE_PACKET;
|
|
desc.samplingRate = 0.0;
|
|
desc.timeSignalIdx = UDPS_NO_TIME_SIGNAL;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
payload[4u + UDPS_SIGNAL_DESC_SIZE] = UDPS_PUBLISH_STRICT;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
/* Deliver one DATA payload: 8-byte timestamp + uint32 value = 12345.
|
|
* The head start given to the background Synchronise() in
|
|
* StartSynchroniseThread() is a race against thread-scheduling latency,
|
|
* so retry the deliver+sync sequence until it lands. */
|
|
bool gotValue = false;
|
|
for (uint32 attempt = 0u; ok && (!gotValue) && (attempt < 30u); attempt++) {
|
|
SynchroniseThreadArgs *syncArgs = StartSynchroniseThread(ds);
|
|
uint8 data[8u + 4u];
|
|
(void) memset(data, 0, 8u);
|
|
uint32 value = 12345u;
|
|
(void) memcpy(data + 8u, &value, 4u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
if (JoinSynchroniseThread(syncArgs)) {
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ds->GetSignalMemoryBuffer(0u, 0u, sigMem)) {
|
|
uint32 decoded = 0u;
|
|
(void) memcpy(&decoded, sigMem, sizeof(uint32));
|
|
gotValue = (decoded == 12345u);
|
|
}
|
|
}
|
|
}
|
|
ok = ok && gotValue;
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSConfig_SignalCountMismatch() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44709u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
/* CONFIG declares 2 signals while the client only has 1. */
|
|
if (ok) {
|
|
uint8 payload[4u + (2u * UDPS_SIGNAL_DESC_SIZE) + 1u];
|
|
uint32 numSigs = 2u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Counter", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_UINT32;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
(void) strncpy(desc.name, "Extra", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
(void) memcpy(payload + 4u + UDPS_SIGNAL_DESC_SIZE, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
payload[4u + (2u * UDPS_SIGNAL_DESC_SIZE)] = UDPS_PUBLISH_STRICT;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
/* A DATA payload must be silently ignored since CONFIG was rejected. */
|
|
if (ok) {
|
|
uint8 data[8u + 4u];
|
|
(void) memset(data, 0, 8u);
|
|
uint32 value = 999u;
|
|
(void) memcpy(data + 8u, &value, 4u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
ok = ds->Synchronise();
|
|
}
|
|
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ok) {
|
|
ok = ds->GetSignalMemoryBuffer(0u, 0u, sigMem);
|
|
}
|
|
if (ok) {
|
|
uint32 value = 0xFFFFFFFFu;
|
|
(void) memcpy(&value, sigMem, sizeof(uint32));
|
|
ok = (value == 0u);
|
|
}
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSConfig_NameMismatch() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44710u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 payload[4u + UDPS_SIGNAL_DESC_SIZE + 1u];
|
|
uint32 numSigs = 1u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "WrongName", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_UINT32;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
payload[4u + UDPS_SIGNAL_DESC_SIZE] = UDPS_PUBLISH_STRICT;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 data[8u + 4u];
|
|
(void) memset(data, 0, 8u);
|
|
uint32 value = 999u;
|
|
(void) memcpy(data + 8u, &value, 4u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
ok = ds->Synchronise();
|
|
}
|
|
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ok) {
|
|
ok = ds->GetSignalMemoryBuffer(0u, 0u, sigMem);
|
|
}
|
|
if (ok) {
|
|
uint32 value = 0xFFFFFFFFu;
|
|
(void) memcpy(&value, sigMem, sizeof(uint32));
|
|
ok = (value == 0u);
|
|
}
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSConfig_ElementCountMismatch() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44711u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 payload[4u + UDPS_SIGNAL_DESC_SIZE + 1u];
|
|
uint32 numSigs = 1u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Counter", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_UINT32;
|
|
desc.numRows = 3u; /* client declares a scalar (1 element) */
|
|
desc.numCols = 1u;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
payload[4u + UDPS_SIGNAL_DESC_SIZE] = UDPS_PUBLISH_STRICT;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 data[8u + 4u];
|
|
(void) memset(data, 0, 8u);
|
|
uint32 value = 999u;
|
|
(void) memcpy(data + 8u, &value, 4u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
ok = ds->Synchronise();
|
|
}
|
|
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ok) {
|
|
ok = ds->GetSignalMemoryBuffer(0u, 0u, sigMem);
|
|
}
|
|
if (ok) {
|
|
uint32 value = 0xFFFFFFFFu;
|
|
(void) memcpy(&value, sigMem, sizeof(uint32));
|
|
ok = (value == 0u);
|
|
}
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSConfig_TooSmallPayload() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44712u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
/* Fewer than 4 bytes: cannot even hold numSigs. Must not crash. */
|
|
uint8 payload[2u] = { 0u, 0u };
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
ok = ds->Synchronise();
|
|
}
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* @brief Config with two signals: scalar "Counter" (uint32) and a
|
|
* two-element "Pair" (float64[2]) — used for ordering/accumulate tests.
|
|
*/
|
|
static MARTe::StreamString BuildTwoSignalConfig(MARTe::uint16 port) {
|
|
using namespace MARTe;
|
|
StreamString cfg;
|
|
cfg.Printf(
|
|
"+Test = {\n"
|
|
" Class = RealTimeApplication\n"
|
|
" +Functions = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Reader = {\n"
|
|
" Class = UDPStreamerClientTestGAM\n"
|
|
" InputSignals = {\n"
|
|
" Counter = { DataSource = ClientDS Type = uint32 }\n"
|
|
" Pair = { DataSource = ClientDS Type = float64 NumberOfDimensions = 1 NumberOfElements = 2 }\n"
|
|
" }\n"
|
|
" OutputSignals = {\n"
|
|
" Counter = { DataSource = DDB Type = uint32 }\n"
|
|
" Pair = { DataSource = DDB Type = float64 NumberOfDimensions = 1 NumberOfElements = 2 }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Data = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" DefaultDataSource = DDB\n"
|
|
" +DDB = { Class = GAMDataSource }\n"
|
|
" +ClientDS = {\n"
|
|
" Class = UDPStreamerClient\n"
|
|
" ServerAddress = \"127.0.0.1\"\n"
|
|
" Port = %d\n"
|
|
" Signals = {\n"
|
|
" Counter = { Type = uint32 }\n"
|
|
" Pair = { Type = float64 NumberOfDimensions = 1 NumberOfElements = 2 }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Timings = { Class = TimingDataSource }\n"
|
|
" }\n"
|
|
" +States = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +State1 = {\n"
|
|
" Class = RealTimeState\n"
|
|
" +Threads = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Thread1 = { Class = RealTimeThread Functions = { Reader } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Scheduler = { Class = GAMScheduler TimingDataSource = Timings }\n"
|
|
"}\n", port);
|
|
return cfg;
|
|
}
|
|
|
|
/**
|
|
* @brief Config with a single Float32Bit scalar signal "Value" — used for
|
|
* the UINT16 quantisation test.
|
|
*/
|
|
static MARTe::StreamString BuildFloat32Config(MARTe::uint16 port) {
|
|
using namespace MARTe;
|
|
StreamString cfg;
|
|
cfg.Printf(
|
|
"+Test = {\n"
|
|
" Class = RealTimeApplication\n"
|
|
" +Functions = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Reader = {\n"
|
|
" Class = UDPStreamerClientTestGAM\n"
|
|
" InputSignals = { Value = { DataSource = ClientDS Type = float32 } }\n"
|
|
" OutputSignals = { Value = { DataSource = DDB Type = float32 } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Data = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" DefaultDataSource = DDB\n"
|
|
" +DDB = { Class = GAMDataSource }\n"
|
|
" +ClientDS = {\n"
|
|
" Class = UDPStreamerClient\n"
|
|
" ServerAddress = \"127.0.0.1\"\n"
|
|
" Port = %d\n"
|
|
" Signals = { Value = { Type = float32 } }\n"
|
|
" }\n"
|
|
" +Timings = { Class = TimingDataSource }\n"
|
|
" }\n"
|
|
" +States = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +State1 = {\n"
|
|
" Class = RealTimeState\n"
|
|
" +Threads = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Thread1 = { Class = RealTimeThread Functions = { Reader } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Scheduler = { Class = GAMScheduler TimingDataSource = Timings }\n"
|
|
"}\n", port);
|
|
return cfg;
|
|
}
|
|
|
|
/**
|
|
* @brief Config with a single Float64Bit scalar signal "Value2" — used for
|
|
* the INT8 quantisation test.
|
|
*/
|
|
static MARTe::StreamString BuildFloat64Config(MARTe::uint16 port) {
|
|
using namespace MARTe;
|
|
StreamString cfg;
|
|
cfg.Printf(
|
|
"+Test = {\n"
|
|
" Class = RealTimeApplication\n"
|
|
" +Functions = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Reader = {\n"
|
|
" Class = UDPStreamerClientTestGAM\n"
|
|
" InputSignals = { Value2 = { DataSource = ClientDS Type = float64 } }\n"
|
|
" OutputSignals = { Value2 = { DataSource = DDB Type = float64 } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Data = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" DefaultDataSource = DDB\n"
|
|
" +DDB = { Class = GAMDataSource }\n"
|
|
" +ClientDS = {\n"
|
|
" Class = UDPStreamerClient\n"
|
|
" ServerAddress = \"127.0.0.1\"\n"
|
|
" Port = %d\n"
|
|
" Signals = { Value2 = { Type = float64 } }\n"
|
|
" }\n"
|
|
" +Timings = { Class = TimingDataSource }\n"
|
|
" }\n"
|
|
" +States = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +State1 = {\n"
|
|
" Class = RealTimeState\n"
|
|
" +Threads = {\n"
|
|
" Class = ReferenceContainer\n"
|
|
" +Thread1 = { Class = RealTimeThread Functions = { Reader } }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" }\n"
|
|
" +Scheduler = { Class = GAMScheduler TimingDataSource = Timings }\n"
|
|
"}\n", port);
|
|
return cfg;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSData_QuantizedUint16() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildFloat32Config(44713u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 payload[4u + UDPS_SIGNAL_DESC_SIZE + 1u];
|
|
uint32 numSigs = 1u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Value", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_FLOAT32;
|
|
desc.quantType = UDPS_QUANT_UINT16;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
desc.rangeMin = -10.0;
|
|
desc.rangeMax = 10.0;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
payload[4u + UDPS_SIGNAL_DESC_SIZE] = UDPS_PUBLISH_STRICT;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
/* raw=65535 (max uint16) -> rangeMin + (65535/65535)*range = rangeMax = 10.0 exactly. */
|
|
bool gotValue = false;
|
|
for (uint32 attempt = 0u; ok && (!gotValue) && (attempt < 30u); attempt++) {
|
|
SynchroniseThreadArgs *syncArgs = StartSynchroniseThread(ds);
|
|
uint8 data[8u + 2u];
|
|
(void) memset(data, 0, 8u);
|
|
uint16 raw = 65535u;
|
|
(void) memcpy(data + 8u, &raw, 2u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
if (JoinSynchroniseThread(syncArgs)) {
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ds->GetSignalMemoryBuffer(0u, 0u, sigMem)) {
|
|
float32 decoded = 0.0f;
|
|
(void) memcpy(&decoded, sigMem, sizeof(float32));
|
|
gotValue = (decoded == 10.0f);
|
|
}
|
|
}
|
|
}
|
|
ok = ok && gotValue;
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSData_QuantizedInt8() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildFloat64Config(44714u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 payload[4u + UDPS_SIGNAL_DESC_SIZE + 1u];
|
|
uint32 numSigs = 1u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Value2", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_FLOAT64;
|
|
desc.quantType = UDPS_QUANT_INT8;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
desc.rangeMin = 0.0;
|
|
desc.rangeMax = 100.0;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
payload[4u + UDPS_SIGNAL_DESC_SIZE] = UDPS_PUBLISH_STRICT;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
/* raw=127 (max int8) -> rangeMin + ((127+127)/254)*range = rangeMax = 100.0 exactly. */
|
|
bool gotValue = false;
|
|
for (uint32 attempt = 0u; ok && (!gotValue) && (attempt < 30u); attempt++) {
|
|
SynchroniseThreadArgs *syncArgs = StartSynchroniseThread(ds);
|
|
uint8 data[8u + 1u];
|
|
(void) memset(data, 0, 8u);
|
|
int8 raw = 127;
|
|
(void) memcpy(data + 8u, &raw, 1u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
if (JoinSynchroniseThread(syncArgs)) {
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ds->GetSignalMemoryBuffer(0u, 0u, sigMem)) {
|
|
float64 decoded = 0.0;
|
|
(void) memcpy(&decoded, sigMem, sizeof(float64));
|
|
gotValue = (decoded == 100.0);
|
|
}
|
|
}
|
|
}
|
|
ok = ok && gotValue;
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSData_MultipleSignalsOrder() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildTwoSignalConfig(44715u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 payload[4u + (2u * UDPS_SIGNAL_DESC_SIZE) + 1u];
|
|
uint32 numSigs = 2u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Counter", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_UINT32;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Pair", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_FLOAT64;
|
|
desc.numRows = 2u;
|
|
desc.numCols = 1u;
|
|
(void) memcpy(payload + 4u + UDPS_SIGNAL_DESC_SIZE, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
|
|
payload[4u + (2u * UDPS_SIGNAL_DESC_SIZE)] = UDPS_PUBLISH_STRICT;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
bool gotValue = false;
|
|
for (uint32 attempt = 0u; ok && (!gotValue) && (attempt < 30u); attempt++) {
|
|
SynchroniseThreadArgs *syncArgs = StartSynchroniseThread(ds);
|
|
uint8 data[8u + 4u + 16u];
|
|
(void) memset(data, 0, sizeof(data));
|
|
uint32 counterVal = 777u;
|
|
(void) memcpy(data + 8u, &counterVal, 4u);
|
|
float64 pairVal[2] = { 1.5, 2.5 };
|
|
(void) memcpy(data + 12u, pairVal, 16u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
if (JoinSynchroniseThread(syncArgs)) {
|
|
void *counterMem = NULL_PTR(void *);
|
|
void *pairMem = NULL_PTR(void *);
|
|
if (ds->GetSignalMemoryBuffer(0u, 0u, counterMem) &&
|
|
ds->GetSignalMemoryBuffer(1u, 0u, pairMem)) {
|
|
uint32 decodedCounter = 0u;
|
|
(void) memcpy(&decodedCounter, counterMem, 4u);
|
|
float64 decodedPair[2] = { 0.0, 0.0 };
|
|
(void) memcpy(decodedPair, pairMem, 16u);
|
|
gotValue = (decodedCounter == 777u) && (decodedPair[0] == 1.5) && (decodedPair[1] == 2.5);
|
|
}
|
|
}
|
|
}
|
|
ok = ok && gotValue;
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSData_AccumulateMode() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildTwoSignalConfig(44716u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 payload[4u + (2u * UDPS_SIGNAL_DESC_SIZE) + 1u];
|
|
uint32 numSigs = 2u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Counter", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_UINT32;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Pair", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_FLOAT64;
|
|
desc.numRows = 2u;
|
|
desc.numCols = 1u;
|
|
(void) memcpy(payload + 4u + UDPS_SIGNAL_DESC_SIZE, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
|
|
payload[4u + (2u * UDPS_SIGNAL_DESC_SIZE)] = UDPS_PUBLISH_ACCUMULATE;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
/* [8-byte ts][4-byte numSamples=3][3x uint32 Counter samples][2x float64 Pair, once] */
|
|
bool gotValue = false;
|
|
for (uint32 attempt = 0u; ok && (!gotValue) && (attempt < 30u); attempt++) {
|
|
SynchroniseThreadArgs *syncArgs = StartSynchroniseThread(ds);
|
|
uint8 data[8u + 4u + 12u + 16u];
|
|
(void) memset(data, 0, sizeof(data));
|
|
uint32 numSamples = 3u;
|
|
(void) memcpy(data + 8u, &numSamples, 4u);
|
|
uint32 samples[3] = { 10u, 20u, 30u };
|
|
(void) memcpy(data + 12u, samples, 12u);
|
|
float64 pairVal[2] = { 1.5, 2.5 };
|
|
(void) memcpy(data + 24u, pairVal, 16u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
if (JoinSynchroniseThread(syncArgs)) {
|
|
void *counterMem = NULL_PTR(void *);
|
|
void *pairMem = NULL_PTR(void *);
|
|
if (ds->GetSignalMemoryBuffer(0u, 0u, counterMem) &&
|
|
ds->GetSignalMemoryBuffer(1u, 0u, pairMem)) {
|
|
uint32 decodedCounter = 0u;
|
|
(void) memcpy(&decodedCounter, counterMem, 4u);
|
|
float64 decodedPair[2] = { 0.0, 0.0 };
|
|
(void) memcpy(decodedPair, pairMem, 16u);
|
|
/* Scalar publishes only the most recent (last) accumulated sample. */
|
|
gotValue = (decodedCounter == 30u) && (decodedPair[0] == 1.5) && (decodedPair[1] == 2.5);
|
|
}
|
|
}
|
|
}
|
|
ok = ok && gotValue;
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSData_TooSmallPayload() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44717u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 payload[4u + UDPS_SIGNAL_DESC_SIZE + 1u];
|
|
uint32 numSigs = 1u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Counter", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_UINT32;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
payload[4u + UDPS_SIGNAL_DESC_SIZE] = UDPS_PUBLISH_STRICT;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
if (ok) {
|
|
/* Fewer than 8 bytes: cannot even hold the packet timestamp. */
|
|
uint8 data[4u] = { 1u, 2u, 3u, 4u };
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
ok = ds->Synchronise();
|
|
}
|
|
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ok) {
|
|
ok = ds->GetSignalMemoryBuffer(0u, 0u, sigMem);
|
|
}
|
|
if (ok) {
|
|
uint32 value = 0xFFFFFFFFu;
|
|
(void) memcpy(&value, sigMem, sizeof(uint32));
|
|
ok = (value == 0u);
|
|
}
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSData_BeforeConfig() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44718u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
/* No OnUDPSConfig was ever delivered: configValidated is false. */
|
|
uint8 data[8u + 4u];
|
|
(void) memset(data, 0, 8u);
|
|
uint32 value = 555u;
|
|
(void) memcpy(data + 8u, &value, 4u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
ok = ds->Synchronise();
|
|
}
|
|
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ok) {
|
|
ok = ds->GetSignalMemoryBuffer(0u, 0u, sigMem);
|
|
}
|
|
if (ok) {
|
|
uint32 value = 0xFFFFFFFFu;
|
|
(void) memcpy(&value, sigMem, sizeof(uint32));
|
|
ok = (value == 0u);
|
|
}
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestOnUDPSDisconnected_InvalidatesConfig() {
|
|
using namespace MARTe;
|
|
StreamString cfg = BuildBasicConfig(44719u);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
if (ok) {
|
|
uint8 payload[4u + UDPS_SIGNAL_DESC_SIZE + 1u];
|
|
uint32 numSigs = 1u;
|
|
(void) memcpy(payload, &numSigs, 4u);
|
|
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Counter", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_UINT32;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
(void) memcpy(payload + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
payload[4u + UDPS_SIGNAL_DESC_SIZE] = UDPS_PUBLISH_STRICT;
|
|
|
|
ds->OnUDPSConfig(payload, static_cast<uint32>(sizeof(payload)));
|
|
}
|
|
|
|
void *sigMem = NULL_PTR(void *);
|
|
bool gotValue = false;
|
|
for (uint32 attempt = 0u; ok && (!gotValue) && (attempt < 30u); attempt++) {
|
|
SynchroniseThreadArgs *syncArgs = StartSynchroniseThread(ds);
|
|
uint8 data[8u + 4u];
|
|
(void) memset(data, 0, 8u);
|
|
uint32 value = 111u;
|
|
(void) memcpy(data + 8u, &value, 4u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
if (JoinSynchroniseThread(syncArgs)) {
|
|
if (ds->GetSignalMemoryBuffer(0u, 0u, sigMem)) {
|
|
uint32 decoded = 0u;
|
|
(void) memcpy(&decoded, sigMem, sizeof(uint32));
|
|
gotValue = (decoded == 111u);
|
|
}
|
|
}
|
|
}
|
|
ok = ok && gotValue;
|
|
|
|
if (ok) {
|
|
ds->OnUDPSDisconnected();
|
|
uint8 data[8u + 4u];
|
|
(void) memset(data, 0, 8u);
|
|
uint32 value = 222u;
|
|
(void) memcpy(data + 8u, &value, 4u);
|
|
ds->OnUDPSData(data, static_cast<uint32>(sizeof(data)));
|
|
/* No semaphore post occurred (OnUDPSData returned early), so
|
|
* Synchronise() must time out and leave the previous value in place. */
|
|
ok = ds->Synchronise();
|
|
}
|
|
|
|
if (ok) {
|
|
uint32 value = 0u;
|
|
(void) memcpy(&value, sigMem, sizeof(uint32));
|
|
ok = (value == 111u);
|
|
}
|
|
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|
|
|
|
bool UDPStreamerClientTest::TestExecute_ConnectConfigDataEndToEnd() {
|
|
using namespace MARTe;
|
|
|
|
static const uint16 serverPort = 44720u;
|
|
StreamString cfg = BuildBasicConfig(serverPort);
|
|
ReferenceT<RealTimeApplication> app = LoadApplication(cfg.Buffer());
|
|
bool ok = app.IsValid();
|
|
|
|
/* Bind the mock server socket BEFORE starting the receiver, so the
|
|
* CONNECT datagram is never missed. */
|
|
BasicUDPSocket serverSock;
|
|
if (ok) {
|
|
ok = serverSock.Open() && serverSock.Listen(serverPort);
|
|
}
|
|
|
|
if (ok) {
|
|
ok = (app->PrepareNextState("State1") == MARTe::ErrorManagement::NoError);
|
|
}
|
|
|
|
/* Receive the CONNECT and learn the client's ephemeral source address. */
|
|
if (ok) {
|
|
uint8 recvBuf[64u];
|
|
uint32 recvSize = static_cast<uint32>(sizeof(recvBuf));
|
|
TimeoutType timeout(1000u);
|
|
bool received = serverSock.Read(reinterpret_cast<char8 *>(recvBuf), recvSize, timeout);
|
|
ok = received && (recvSize >= UDPS_HEADER_SIZE);
|
|
if (ok) {
|
|
const UDPSPacketHeader *hdr = reinterpret_cast<const UDPSPacketHeader *>(recvBuf);
|
|
ok = (hdr->magic == UDPS_MAGIC) && (hdr->type == UDPS_TYPE_CONNECT);
|
|
}
|
|
if (ok) {
|
|
InternetHost clientHost = serverSock.GetSource();
|
|
serverSock.SetDestination(clientHost);
|
|
}
|
|
}
|
|
|
|
/* Send CONFIG: one scalar "Counter" uint32 signal, unquantised. */
|
|
if (ok) {
|
|
UDPSSignalDescriptor desc;
|
|
(void) memset(&desc, 0, sizeof(desc));
|
|
(void) strncpy(desc.name, "Counter", UDPS_MAX_SIGNAL_NAME - 1u);
|
|
desc.typeCode = UDPS_TYPECODE_UINT32;
|
|
desc.numRows = 1u;
|
|
desc.numCols = 1u;
|
|
|
|
uint8 buf[UDPS_HEADER_SIZE + 4u + UDPS_SIGNAL_DESC_SIZE + 1u];
|
|
const uint32 configPayloadBytes = 4u + UDPS_SIGNAL_DESC_SIZE + 1u;
|
|
UDPSBuildHeader(buf, UDPS_TYPE_CONFIG, 1u, 0u, 1u, configPayloadBytes);
|
|
uint32 numSigs = 1u;
|
|
(void) memcpy(buf + UDPS_HEADER_SIZE, &numSigs, 4u);
|
|
(void) memcpy(buf + UDPS_HEADER_SIZE + 4u, &desc, UDPS_SIGNAL_DESC_SIZE);
|
|
buf[UDPS_HEADER_SIZE + 4u + UDPS_SIGNAL_DESC_SIZE] = UDPS_PUBLISH_STRICT;
|
|
|
|
uint32 sendSize = static_cast<uint32>(sizeof(buf));
|
|
ok = serverSock.Write(reinterpret_cast<const char8 *>(buf), sendSize);
|
|
}
|
|
|
|
Sleep::MSec(100u);
|
|
|
|
/* Config processing does not Post() the data semaphore, so it is safe to
|
|
* look the DataSource up and start Synchronise() on a background thread
|
|
* now: by the time the DATA datagram is received (on the DataSource's
|
|
* own receiver thread) and OnUDPSData() Post()s, this thread's
|
|
* ResetWait() will already be blocked in Wait(), so the two race
|
|
* correctly instead of the Post() being silently discarded. */
|
|
ReferenceT<UDPStreamerClient> ds;
|
|
if (ok) {
|
|
ds = ObjectRegistryDatabase::Instance()->Find("Test.Data.ClientDS");
|
|
ok = ds.IsValid();
|
|
}
|
|
|
|
/* Send DATA: 8-byte HRT timestamp + uint32 value = 424242. Each attempt
|
|
* uses a fresh packet counter, since UDPSClient's fragment-reassembly
|
|
* layer would otherwise silently drop an exact retransmission of a
|
|
* counter it has already fully reassembled. */
|
|
bool gotValue = false;
|
|
for (uint32 attempt = 0u; ok && (!gotValue) && (attempt < 30u); attempt++) {
|
|
SynchroniseThreadArgs *syncArgs = StartSynchroniseThread(ds);
|
|
|
|
uint8 buf[UDPS_HEADER_SIZE + 8u + 4u];
|
|
const uint32 dataPayloadBytes = 8u + 4u;
|
|
UDPSBuildHeader(buf, UDPS_TYPE_DATA, 2u + attempt, 0u, 1u, dataPayloadBytes);
|
|
(void) memset(buf + UDPS_HEADER_SIZE, 0, 8u);
|
|
uint32 value = 424242u;
|
|
(void) memcpy(buf + UDPS_HEADER_SIZE + 8u, &value, 4u);
|
|
|
|
uint32 sendSize = static_cast<uint32>(sizeof(buf));
|
|
ok = serverSock.Write(reinterpret_cast<const char8 *>(buf), sendSize);
|
|
|
|
if (ok && JoinSynchroniseThread(syncArgs)) {
|
|
void *sigMem = NULL_PTR(void *);
|
|
if (ds->GetSignalMemoryBuffer(0u, 0u, sigMem)) {
|
|
uint32 decoded = 0u;
|
|
(void) memcpy(&decoded, sigMem, sizeof(uint32));
|
|
gotValue = (decoded == 424242u);
|
|
}
|
|
}
|
|
else if (!ok) {
|
|
(void) JoinSynchroniseThread(syncArgs);
|
|
}
|
|
}
|
|
ok = ok && gotValue;
|
|
|
|
(void) serverSock.Close();
|
|
Sleep::MSec(50u);
|
|
ObjectRegistryDatabase::Instance()->Purge();
|
|
return ok;
|
|
}
|