Time synchronization via a shared library

Accurate time synchronization is essential for the reliability of test results, especially with low-level hardware tools and bus protocols. Otherwise, timeouts or other time sensitive functions may not be applied correctly. For the synchronized execution of bus and diagnostic communication with an external time source, a shared library (DLL under Windows, SO under Linux) can be provided to the bus and diagnostic ports.

The shared library must satisfy the following interface:

  • Templates/DefaultData/Utilities/SharedLibs/UserSubscribe.h Header for the shared library

Info

The shared library must be built as a 64-bit library for the target plattform.

/*******************************************************************
 *
 *    FILENAME:  UserSubcribe.h
 *
 *      AUTHOR:  tracetronic GmbH
 *
 *     CREATED:  16.01.2024
 *
 *   COPYRIGHT:  Copyright © tracetronic GmbH, Dresden
 *
 *               This file is distributed as an integral part of
 *               tracetronic's software products and may only be
 *               used in connection with and pursuant to the terms
 *               and conditions of a valid tracetronic software
 *               product license.
 *
 *  Interface that provides the current time used by ecu.test
 *
 *******************************************************************/

#pragma once

#define VERSION 2

#ifdef MODELCLOCK_IMPORT
#define MODELCLOCK_API __declspec(dllimport)
#else
#define MODELCLOCK_API __declspec(dllexport)
#endif

#ifdef __cplusplus
extern "C"
{
#endif

#include <stdint.h>
#include <string.h>
#include <malloc.h>

    // major, minor and patch version
    typedef struct
    {
        uint32_t major;
        uint32_t minor;
        uint32_t patch;
    } VersionInfo;

    typedef struct
    {
        int32_t errorCode;
        char *errorMessage;
    } ModelClockError;

    /**
     * Type definition for the callback to update the clients model time (in nanoseconds).
     *
     * @param clientId      The unique identifier of the client.
     * @param time_ns       The new model time in nanoseconds.
     * @param error         NULL if no error occurred. Otherwise pass a `ModelClockError` instance
     *                      to signal the client the error. If error is present the time parameter
     *                      is ignored.
     *                      `ModelClockError_FromString` can be used to create a new `ModelClockError` instance.
     */
    typedef void updateTimeFuncType(uint32_t clientId, uint64_t time_ns, ModelClockError *error);

    /**
     * Important:
     *  - This function has to be implemented by customers.
     *  - ecu.test might call exported functions from different threads,
     *      consequently they have to be implemented thread-safe.
     *
     * Initialization of the model clock for the calling client.
     * This function is called once at the start of every client instance.
     *
     * @param[out]  clientId    Implementation has to return a unique client id via this pointer
     *                          that identifies the calling client.
     *                          This client id will be used by the client for subsequent function calls.
     * @return      If an error occurred return a `ModelClockError` instance, otherwise NULL.
     *              `ModelClockError_FromString` can be used to create a new `ModelClockError` instance.
     */
    MODELCLOCK_API ModelClockError *ModelClock_Init(uint32_t *clientId);

    /**
     * Important:
     *  - This function has to be implemented by customers.
     *  - ecu.test might call exported functions from different threads,
     *      consequently they have to be implemented thread-safe.
     *
     * Deinitializes the model clock for the client instance identified by the given client id.
     * This function is called once at the end of every client instance.
     *
     * @param       clientId    Unique client id that is used to identify the current client instance
     * @return      If an error occurred return a `ModelClockError` instance, otherwise NULL.
     *              `ModelClockError_FromString` can be used to create a new `ModelClockError` instance.
     */
    MODELCLOCK_API ModelClockError *ModelClock_Close(uint32_t clientId);

    /**
     * Important:
     *  - This function has to be implemented by customers.
     *  - ecu.test might call exported functions from different threads,
     *      consequently they have to be implemented thread-safe.
     *
     * The client instance identified by the given client id subscribes the model clock.
     * The client calls this function in order to stay up to date with respect to the model time.
     * For that, the callback argument `updateTimeCallback` is passed and shall be called to
     * update the clients model time (in nanoseconds).
     * Basically, by calling `updateTimeCallback(...)`, the implementation tells the client
     * the current model time (in nanoseconds).
     *
     * Example:
     *      ...
     *      // model time has changed => update clients model time
     *      uint64_t modelTime = ...;
     *      updateTimeCallback(clientId, modelTime, NULL);
     *      ...
     *
     * In addition, the implementation can inform the client of an occurred error:
     *
     * Example:
     *      ...
     *      // error occurred => inform the client
     *      ModelClockError *error = ModelClockError_FromString(-1, "Some error occurred :/");
     *      updateTimeCallback(clientId, 0, error);
     *      ...
     *
     * @param       clientId            Unique client id that is used to identify the current client instance
     * @param[in]   updateTimeCallback  Callback for updating the clients model time
     * @return      If an error occurred return a `ModelClockError` instance, otherwise NULL.
     *              `ModelClockError_FromString` can be used to create a new `ModelClockError` instance.
     */
    MODELCLOCK_API ModelClockError *ModelClock_SubscribeTime(uint32_t clientId, updateTimeFuncType *updateTimeCallback);

    /**
     * Important:
     *  - This function has to be implemented by customers.
     *  - ecu.test might call exported functions from different threads,
     *      consequently they have to be implemented thread-safe.
     *
     * The client instance identified by the given client id unsubscribes the model clock.
     * If this function is called the implementation shall not call `updateTimeCallback()` for the given client id
     * until `ModelClock_SubscribeTime()` has been called again.
     *
     * @param       clientId    Unique client id that is used to identify the current client instance
     * @return      If an error occurred return a `ModelClockError` instance, otherwise NULL.
     *          `   ModelClockError_FromString` can be used to create a new `ModelClockError` instance.
     */
    MODELCLOCK_API ModelClockError *ModelClock_UnsubscribeTime(uint32_t clientId);

    /**
     * Important:
     *  - This function has to be implemented by customers.
     *  - ecu.test might call exported functions from different threads,
     *      consequently they have to be implemented thread-safe.
     *
     * Returns the version of the current DLL
     * This is used for the purpose of logging the version of the used DLL
     *
     * @return  The major, minor and patch version of the DLL
     */
    MODELCLOCK_API VersionInfo ModelClock_GetDllVersionInfo();

    /**
     * Important: This function shall not be modified.
     *
     * Predefined function to return the version of the header.
     *
     * @return  The major, minor and patch version of the header
     */
    MODELCLOCK_API VersionInfo ModelClock_GetHeaderVersionInfo()
    {
        VersionInfo versionInfo = {1, 0, 0};
        return versionInfo;
    }

    /**
     * Predefined utility function to create a `ModelClockError` instance used by the API.
     *
     * Example:
     *      ModelClockError *modelError = ModelClockError_FromString(-1, "Some error :/");
     *
     * @param   errorCode       Error code of the occurred error. If no special error code is associated with the error,
     *                          the default -1 is recommended.
     * @param   errorMessage    A null-terminated error message for the occurred error.
     * @return  New `ModelClockError` instance
     */
    static ModelClockError *ModelClockError_FromString(int32_t errorCode, const char *errorMessage)
    {
        size_t buffer_size = strlen(errorMessage) + 1;
        if (buffer_size > 4000)
        {
            // avoid accidentally huge allocations
            buffer_size = 4000;
        }
        char *errorMsgCopy = (char *)malloc(buffer_size);
        if (!errorMsgCopy)
        {
            // should be academical
            return NULL;
        }
        if (strcpy_s(errorMsgCopy, buffer_size, errorMessage) != 0)
        {
            // should be academical
            return NULL;
        }
        ModelClockError *modelClockError = (ModelClockError *)malloc(sizeof(ModelClockError));
        if (!modelClockError)
        {
            // should be academical
            return NULL;
        }
        *modelClockError = ModelClockError{errorCode, errorMsgCopy};
        return modelClockError;
    }
#ifdef __cplusplus
}
#endif

To guarantee full functionality, the methods ModelClock_Init, ModelClock_Close, ModelClock_SubscribeTime and ModelClock_UnsubscribeTime must be implemented. Optionally, the ModelClock_GetDllVersionInfo function can be updated.

Example implementation

The following example contains an implementation providing the system time. The file can be used as a starting point for a more complex usecase. The example is written in C++ - a version in another language, like Rust, C or C# is also possible.

  • Templates/DefaultData/Utilities/SharedLibs/templateSubscribe.cpp Example implementation in C++

/*******************************************************************
 *
 *    FILENAME:  templateSubscribe.cpp
 *
 *      AUTHOR:  tracetronic GmbH
 *
 *     CREATED:  16.01.2024
 *
 *   COPYRIGHT:  Copyright © tracetronic GmbH, Dresden
 *
 *               This file is distributed as an integral part of
 *               tracetronic's software products and may only be
 *               used in connection with and pursuant to the terms
 *               and conditions of a valid tracetronic software
 *               product license.
 *
 *  Example implementation that provides the system time used by ecu.test
 *
 *******************************************************************/

#include <atomic>
#include <chrono>
#include <mutex>
#include <list>
#include <random>
#include <thread>

#include "../../include/UserSubscribe.h"

typedef struct
{
    uint32_t clientId;
    updateTimeFuncType *updateTimeCallback;
} ClientSetting;

static std::mutex TIME_LOOP_LOCK;
static std::list<ClientSetting> subscribedClients = {};
static bool initializedRandomness = false;
static std::mt19937 rng;

static uint64_t get_time_ns()
{
    auto time_point = std::chrono::system_clock::now();
    return std::chrono::time_point_cast<std::chrono::nanoseconds>(time_point).time_since_epoch().count();
}

static uint32_t generate_id()
{
    if (!initializedRandomness)
    {
        std::random_device rd;
        rng.seed(rd());
        initializedRandomness = true;
    }
    return rng();
}

static void time_loop_thread(updateTimeFuncType *updateTimeCallback)
{
    while (1)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        {
            std::lock_guard<std::mutex> guard(TIME_LOOP_LOCK);
            if (subscribedClients.empty())
            {
                return;
            }
            auto time = get_time_ns();
            for (auto clientSetting : subscribedClients)
            {
                clientSetting.updateTimeCallback(clientSetting.clientId, time, NULL);
            }
        }
    }
}

/*******************************************************************
 * Public C Functions
 *******************************************************************/

ModelClockError *ModelClock_Init(uint32_t *clientId)
{
    std::lock_guard<std::mutex> guard(TIME_LOOP_LOCK);
    *clientId = generate_id();
    return NULL;
}

ModelClockError *ModelClock_Close(uint32_t clientId)
{
    return NULL;
}

ModelClockError *ModelClock_SubscribeTime(uint32_t clientId, updateTimeFuncType *updateTimeCallback)
{
    std::lock_guard<std::mutex> guard(TIME_LOOP_LOCK);
    auto initialTime = get_time_ns();
    updateTimeCallback(clientId, initialTime, NULL);
    bool startLoop = subscribedClients.empty();
    ClientSetting setting = ClientSetting{clientId, updateTimeCallback};
    subscribedClients.push_back(setting);
    if (startLoop)
    {
        std::thread(time_loop_thread, updateTimeCallback).detach();
    }
    return NULL;
}

ModelClockError *ModelClock_UnsubscribeTime(uint32_t clientId)
{
    std::lock_guard<std::mutex> guard(TIME_LOOP_LOCK);
    subscribedClients.remove_if([clientId](ClientSetting setting)
                                { return setting.clientId == clientId; });
    return NULL;
}

VersionInfo ModelClock_GetDllVersionInfo()
{
    VersionInfo versionInfo = {0, 1, 0};
    return versionInfo;
};