/**
 * @file libwrtd.c
 *
 * Copyright (c) 2018-2019 CERN (home.cern)
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include <stdarg.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include "mockturtle/libmockturtle.h"
#include "libwrtd.h"
#include "libwrtd-private.h"

/**
 * @defgroup Base
 * Functions to manage the basic Node and library configuration.
 * @{
 */

/**
 * Retrieve the number of detected WRTD Nodes.
 *
 * @param[out] count number of detected WRTD Nodes
 * @return #wrtd_status
 */
wrtd_status wrtd_get_node_count(uint32_t *count)
{
        int i;
        uint32_t dev_count = 0;
        struct trtl_dev *trtl;
        const struct trtl_config_rom *cfgrom;

        char **dev_list = trtl_list();

        if (!dev_list)
                return WRTD_ERROR_INTERNAL;


        for (i = 0; dev_list[i]; i++) {
                trtl = trtl_open(dev_list[i]);
                if (trtl == NULL) {
                        trtl_list_free(dev_list);
                        return WRTD_ERROR_RESOURCE_UNKNOWN;
                }
                cfgrom = trtl_config_get(trtl);
                if (wrtd_node_id_match(cfgrom->app_id))
                        dev_count++;
                trtl_close(trtl);
        }

        trtl_list_free(dev_list);

        *count = dev_count;

        return WRTD_SUCCESS;
}

/**
 * Retrieve the ID of a WRTD Node.
 *
 * Before calling this function, you should probably call #wrtd_get_node_count to know the
 * number of Nodes.
 *
 * @param[in]  index The index of the Node ("1" for the first Node, etc.)
 * @param[out] node_id The retrieved ID of the Node
 * @return #wrtd_status
 */
wrtd_status wrtd_get_node_id(uint32_t index, uint32_t *node_id)
{
        int i;
        uint32_t dev_count = 0;
        struct trtl_dev *trtl;
        const struct trtl_config_rom *cfgrom;

        char **dev_list = trtl_list();

        if (!dev_list)
                return WRTD_ERROR_INTERNAL;

        *node_id = 0;

        for (i = 0; dev_list[i]; i++) {
                trtl = trtl_open(dev_list[i]);
                if (trtl == NULL) {
                        trtl_list_free(dev_list);
                        return WRTD_ERROR_RESOURCE_UNKNOWN;
                }
                cfgrom = trtl_config_get(trtl);
                if (wrtd_node_id_match(cfgrom->app_id))
                        dev_count++;
                if (dev_count == index) {
                        char *eptr;
                        /* expecting string in the form of "trtl-xxxx, where
                         xxxx is a hex integer */
                        *node_id = strtoul(dev_list[i]+5, &eptr, 16);
                        if (*eptr != 0) {
                                trtl_list_free(dev_list);
                                trtl_close(trtl);
                                return WRTD_ERROR_INTERNAL;
                        }
                }
                trtl_close(trtl);
        }

        trtl_list_free(dev_list);

        if (*node_id == 0)
                return WRTD_ERROR_RESOURCE_UNKNOWN;

       return WRTD_SUCCESS;
}

/**
 * Initialize the WRTD Node and obtain the WRTD device token.
 *
 * @param[in]  node_id       WRTD Node ID
 * @param[in]  reset         Reserved for future use.
 * @param[in]  options_str   Reserved for future use.
 * @param[out] wrtd          Pointer to WRTD device token.
 * @return #wrtd_status
 */
wrtd_status wrtd_init(uint32_t node_id,
                      bool reset,
                      const char *options_str,
                      wrtd_dev **wrtd)
{
        int i;
        struct trtl_dev *trtl;
        const struct trtl_config_rom *cfgrom;
        struct wrtd_config_msg msg;
        wrtd_status status;

        /* In case of error...  */
        *wrtd = NULL;

        trtl = trtl_open_by_id(node_id);

       if (trtl == NULL)
                return WRTD_ERROR_RESOURCE_UNKNOWN;

        wrtd_dev *res;

        res = malloc(sizeof(wrtd_dev));
        if (res == NULL)
                return WRTD_ERROR_OUT_OF_MEMORY;
        memset(res, 0, sizeof(*res));

        res->trtl = trtl;

        cfgrom = trtl_config_get(res->trtl);

        if (cfgrom->n_cpu > WRTD_MAX_CPUS)
                return wrtd_return_error(res, WRTD_ERROR_INTERNAL,
                                         "%s: Too many CPUs detected.", __func__);

        /* Set HMQ words.  */
        for (i = 0; i < cfgrom->n_cpu; i++)
                if (cfgrom->n_hmq[i] < 0)
                        res->hmq_words[i] = TRTL_CONFIG_ROM_MQ_SIZE_PAYLOAD
                                (cfgrom->hmq[i][0].sizes);

        for (i = 0; i < cfgrom->n_cpu; i++) {
                status = wrtd_msg_get_config(res, i, &msg, __func__);
                WRTD_RETURN_IF_ERROR(status);
                res->root_addr[i] = msg.root_addr;
        }

        for (i = 0; i < cfgrom->n_cpu; i++) {
                if (trtl_fw_version(res->trtl, i, WRTD_HMQ, &res->fw_version[i]) < 0)
                        return wrtd_return_error(
                                res, WRTD_ERROR_INTERNAL,
                                "%s: Could not retrieve firmware vesion info (%s)",
                                __func__, trtl_strerror(errno));
        }

        res->nbr_cpus = cfgrom->n_cpu;

        *wrtd = res;

        return WRTD_SUCCESS;
}

/**
 * Close a WRTD Node and release all resources.
 *
 * @param[in]  wrtd        Device token.
 * @return #wrtd_status
 */
wrtd_status wrtd_close(wrtd_dev *wrtd)
{
        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        trtl_close(wrtd->trtl);

        free(wrtd->alarms);
        free(wrtd->rules);
        free(wrtd);

        return WRTD_SUCCESS;
}

/**
 * Reset a WRTD Node. This will remove all defined Alarms and Rules.
 *
 * @param[in]  wrtd        Device token.
 * @return #wrtd_status
 */
wrtd_status wrtd_reset(wrtd_dev *wrtd)
{
        wrtd_status status;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_disable_all_alarms(wrtd);
        WRTD_RETURN_IF_ERROR(status);
        status = wrtd_disable_all_rules(wrtd);
        WRTD_RETURN_IF_ERROR(status);
        status = wrtd_remove_all_alarms(wrtd);
        WRTD_RETURN_IF_ERROR(status);
        status = wrtd_remove_all_rules(wrtd);
        WRTD_RETURN_IF_ERROR(status);

        return WRTD_SUCCESS;
}

/**
 * Retrieve and clear the last error from the device.
 *
 * Modelled after the IVI-C GetError function.
 *
 * This function complies with IVI-3.2, section 3.1.2.1 (Additional Compliance Rules
 * for C Functions with ViChar Array Output Parameters), with the exception of
 * the buffer_size < 0 case, which produces an error instead of allowing a potential
 * buffer overflow.
 *
 * @param[in]  wrtd        Device token.
 * @param[out] error_code  #wrtd_status pointer to return the error code. Ignored if NULL.
 * @param[in]  error_description_buffer_size Size of pre-allocated `error_description` buffer.
 * @param[out] error_description Buffer to store the detailed error message string.
 * @return #wrtd_status. However, if the buffer size parameter is 0, then this function returns
 * instead a positive value, indicating the minimum buffer size necessary to fit the full message.
 * See also IVI-3.2, section 3.1.2.1.
 */
wrtd_status wrtd_get_error(wrtd_dev *wrtd,
                           wrtd_status *error_code,
                           int32_t error_description_buffer_size,
                           char *error_description)
{
        wrtd_status status;
        int ret;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        char error_message[256];
        memset(error_message, 0, 256);

        status = wrtd_error_message(wrtd, wrtd->err, error_message);
        WRTD_RETURN_IF_ERROR(status);

        int required_buffer_size =
                strlen(error_message) + strlen(wrtd->error_msg) + 2;

        /* If buffer_size is zero, just report on necessary
           buffer size. According to IVI-3.2, section 3.1.2.1. */
        if (error_description_buffer_size == 0)
                return required_buffer_size;

        if(error_description == NULL){
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_NULL_POINTER,
                         "Null pointer passed for function %s, "
                         "parameter error_description", __func__);
        }

        /* This violates IVI-3.2, section 3.1.2.1 which dictates to
           ignore overflow if buffer_size is negative. Still, it is
           the safer thing to do.*/
        if (error_description_buffer_size < 0)
                return wrtd_return_error(wrtd, WRTD_ERROR_INVALID_VALUE,
                                         "Invalid value (%d) for function %s, "
                                         "parameter error_description_buffer_size",
                                         error_description_buffer_size, __func__);

        if(error_code != NULL){
                *error_code = wrtd->err;
        }

        ret = snprintf(error_description, error_description_buffer_size,
                       "%s %s", error_message, wrtd->error_msg);
        if (ret < error_description_buffer_size)
                status = WRTD_SUCCESS;
        else
                status = ret + 1;

        wrtd->err = 0;

        memset(wrtd->error_msg, 0, sizeof(wrtd->error_msg));

        return status;
}

/**
 * Convert a #wrtd_status error code to a string.
 *
 * Modelled after the IVI-C ErrorMessage function.
 *
 * @param[in]  wrtd        Device token. Can be NULL to allow calling this function
 * even when initialisation has failed.
 * @param[in]  err_code    #wrtd_status error code to convert.
 * @param[out] err_message Buffer of at least 256 bytes to store the resulting string.
 * @return #wrtd_status
 */
wrtd_status wrtd_error_message(wrtd_dev *wrtd,
                               wrtd_status err_code,
                               char *err_message)
{

        if (err_message == NULL) {
                if (wrtd != NULL)
                        return wrtd_return_error
                                (wrtd, WRTD_ERROR_NULL_POINTER,
                                 "Null pointer passed for function %s, "
                                 "parameter err_message", __func__);

                return WRTD_ERROR_NULL_POINTER;
        }

        switch(err_code){
        case WRTD_SUCCESS:
                snprintf(err_message, 256, "WRTD_SUCCESS");
                break;
        case WRTD_ERROR_INVALID_ATTRIBUTE:
                snprintf(err_message, 256, "WRTD_ERROR_INVALID_ATTRIBUTE");
                break;
        case WRTD_ERROR_ATTR_NOT_WRITEABLE:
                snprintf(err_message, 256, "WRTD_ERROR_ATTR_NOT_WRITEABLE");
                break;
        case WRTD_ERROR_ATTR_NOT_READABLE:
                snprintf(err_message, 256, "WRTD_ERROR_ATTR_NOT_READABLE");
                break;
        case WRTD_ERROR_INVALID_VALUE:
                snprintf(err_message, 256, "WRTD_ERROR_INVALID_VALUE");
                break;
        case WRTD_ERROR_NOT_INITIALIZED:
                snprintf(err_message, 256, "WRTD_ERROR_NOT_INITIALIZED");
                break;
        case WRTD_ERROR_UNKNOWN_CHANNEL_NAME:
                snprintf(err_message, 256, "WRTD_ERROR_UNKNOWN_CHANNEL_NAME");
                break;
        case WRTD_ERROR_OUT_OF_MEMORY:
                snprintf(err_message, 256, "WRTD_ERROR_OUT_OF_MEMORY");
                break;
        case WRTD_ERROR_NULL_POINTER:
                snprintf(err_message, 256, "WRTD_ERROR_NULL_POINTER");
                break;
        case WRTD_ERROR_UNEXPECTED_RESPONSE:
                snprintf(err_message, 256, "WRTD_ERROR_UNEXPECTED_RESPONSE");
                break;
        case WRTD_ERROR_RESOURCE_UNKNOWN:
                snprintf(err_message, 256, "WRTD_ERROR_RESOURCE_UNKNOWN");
                break;
        case WRTD_ERROR_BADLY_FORMED_SELECTOR:
                snprintf(err_message, 256, "WRTD_ERROR_BADLY_FORMED_SELECTOR");
                break;
        case WRTD_ERROR_ALARM_EXISTS:
                snprintf(err_message, 256, "WRTD_ERROR_ALARM_EXISTS");
                break;
        case WRTD_ERROR_ALARM_DOES_NOT_EXIST:
                snprintf(err_message, 256, "WRTD_ERROR_ALARM_DOES_NOT_EXIST");
                break;
        case WRTD_ERROR_VERSION_MISMATCH:
                snprintf(err_message, 256, "WRTD_ERROR_VERSION_MISMATCH");
                break;
        case WRTD_ERROR_INTERNAL:
                snprintf(err_message, 256, "WRTD_ERROR_INTERNAL");
                break;
        case WRTD_ERROR_UNKNOWN_LOG_TYPE:
                snprintf(err_message, 256, "WRTD_ERROR_UNKNOWN_LOG_TYPE");
                break;

        case WRTD_ERROR_RESOURCE_ACTIVE:
                snprintf(err_message, 256, "WRTD_ERROR_RESOURCE_ACTIVE");
                break;
        case WRTD_ERROR_ATTR_GLOBAL:
                snprintf(err_message, 256, "WRTD_ERROR_ATTR_GLOBAL");
                break;
        case WRTD_ERROR_OUT_OF_RESOURCES:
                snprintf(err_message, 256, "WRTD_ERROR_OUT_OF_RESOURCES");
                break;
        case WRTD_ERROR_RULE_EXISTS:
                snprintf(err_message, 256, "WRTD_ERROR_RULE_EXISTS");
                break;
        case WRTD_ERROR_RULE_DOES_NOT_EXIST:
                snprintf(err_message, 256, "WRTD_ERROR_RULE_DOES_NOT_EXIST");
                break;

        default:
                if (wrtd != NULL)
                        return wrtd_return_error(wrtd, WRTD_ERROR_INVALID_VALUE,
                                                 "Invalid value (%d) for function %s, "
                                                 "parameter err_code",
                                                 err_code, __func__);

                return WRTD_ERROR_INVALID_VALUE;
        }

        return WRTD_SUCCESS;
}

/**
 *@} End group Base
 */

/**
 * @defgroup Attributes
 * Functions to get/set WRTD attributes.
 * @{
 */

/**
 * Set an attribute of type `bool`.
 *
 * Modelled after the IVI-C SetAttribute family of functions.
 *
 * @param[in]  wrtd       Device token.
 * @param[in]  rep_cap_id ID (string) of concerned repeated capability.
 * If it is a global attribute, use #WRTD_GLOBAL_REP_CAP_ID
 * @param[in]  id         ID (#wrtd_attr) of concerned attribute.
 * @param[in] value       Value to write to the attribute.
 * @return #wrtd_status
 */
wrtd_status wrtd_set_attr_bool(wrtd_dev *wrtd,
                               const char *rep_cap_id,
                               wrtd_attr id,
                               bool value)
{
        wrtd_status status;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        switch(id) {
        case WRTD_ATTR_EVENT_LOG_ENABLED:
                status = wrtd_attr_global
                        (wrtd, rep_cap_id);
                WRTD_RETURN_IF_ERROR(status);
                return wrtd_attr_set_log_enable
                        (wrtd, value);
        case WRTD_ATTR_ALARM_ENABLED:
                return wrtd_attr_set_alarm_enable
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_ENABLED:
                return wrtd_attr_set_rule_enable
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_SEND_LATE:
                return wrtd_attr_set_rule_send_late
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_EVENT_LOG_EMPTY:
        case WRTD_ATTR_IS_TIME_SYNCHRONIZED:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_ATTR_NOT_WRITEABLE,
                         "Attribute %u is read only.", id);
        default:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_INVALID_ATTRIBUTE,
                         "Attribute ID %u is not recognized.", id);
        }
}

/**
 * Get an attribute of type `bool`.
 *
 * Modelled after the IVI-C GetAttribute family of functions.
 *
 * @param[in]  wrtd     Device token.
 * @param[in]  rep_cap_id ID (string) of concerned repeated capability.
 * If it is a global attribute, use #WRTD_GLOBAL_REP_CAP_ID
 * @param[in]  id         ID (#wrtd_attr) of concerned attribute.
 * @param[out] value      Retrieved attribute value.
 * @return #wrtd_status
 */
wrtd_status wrtd_get_attr_bool(wrtd_dev *wrtd,
                               const char *rep_cap_id,
                               wrtd_attr id,
                               bool *value)
{
        wrtd_status status;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        if(value == NULL){
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_NULL_POINTER,
                         "Null pointer passed for function %s, "
                         "parameter value", __func__);
        }

        switch(id) {
        case WRTD_ATTR_EVENT_LOG_ENABLED:
                status = wrtd_attr_global
                        (wrtd, rep_cap_id);
                WRTD_RETURN_IF_ERROR(status);
                return wrtd_attr_get_log_enable
                        (wrtd, value);
        case WRTD_ATTR_EVENT_LOG_EMPTY:
                status = wrtd_attr_global
                        (wrtd, rep_cap_id);
                WRTD_RETURN_IF_ERROR(status);
                return wrtd_attr_get_log_empty
                        (wrtd, value);
        case WRTD_ATTR_IS_TIME_SYNCHRONIZED:
                status = wrtd_attr_global
                        (wrtd, rep_cap_id);
                WRTD_RETURN_IF_ERROR(status);
                return wrtd_attr_get_time_sync
                        (wrtd, value);
        case WRTD_ATTR_ALARM_ENABLED:
                return wrtd_attr_get_alarm_enable
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_ENABLED:
                return wrtd_attr_get_rule_enable
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_SEND_LATE:
                return wrtd_attr_get_rule_send_late
                        (wrtd, rep_cap_id, value);
        default:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_INVALID_ATTRIBUTE,
                         "Attribute ID %u is not recognized.", id);
        }
}

/**
 * Set an attribute of type `int32`.
 *
 * Modelled after the IVI-C SetAttribute family of functions.
 *
 * @param[in]  wrtd       Device token.
 * @param[in]  rep_cap_id ID (string) of concerned repeated capability.
 * If it is a global attribute, use #WRTD_GLOBAL_REP_CAP_ID
 * @param[in]  id         ID (#wrtd_attr) of concerned attribute.
 * @param[in] value       Value to write to the attribute.
 * @return #wrtd_status
 */
wrtd_status wrtd_set_attr_int32(wrtd_dev *wrtd,
                                const char *rep_cap_id,
                                wrtd_attr id,
                                int32_t value)
{
        wrtd_status status;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        switch(id) {
        case WRTD_ATTR_ALARM_REPEAT_COUNT:
                return wrtd_attr_set_alarm_repeat_count
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_REPEAT_COUNT:
                return wrtd_attr_set_rule_repeat_count
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_RESYNC_FACTOR:
                return wrtd_attr_set_rule_resync_factor
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_FW_COUNT:
        case WRTD_ATTR_FW_MAJOR_VERSION:
        case WRTD_ATTR_FW_MINOR_VERSION:
        case WRTD_ATTR_FW_MAJOR_VERSION_REQUIRED:
        case WRTD_ATTR_FW_MINOR_VERSION_REQUIRED:
        case WRTD_ATTR_FW_MAX_RULES:
        case WRTD_ATTR_FW_MAX_ALARMS:
        case WRTD_ATTR_FW_CAPABILITIES:
        case WRTD_ATTR_FW_LOCAL_INPUTS:
        case WRTD_ATTR_FW_LOCAL_OUTPUTS:
        case WRTD_ATTR_ALARM_COUNT:
        case WRTD_ATTR_RULE_COUNT:
        case WRTD_ATTR_STAT_RULE_RX_EVENTS:
        case WRTD_ATTR_STAT_RULE_TX_EVENTS:
        case WRTD_ATTR_STAT_RULE_MISSED_LATE:
        case WRTD_ATTR_STAT_RULE_MISSED_HOLDOFF:
        case WRTD_ATTR_STAT_RULE_MISSED_NOSYNC:
        case WRTD_ATTR_STAT_RULE_MISSED_OVERFLOW:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_ATTR_NOT_WRITEABLE,
                         "Attribute %u is read only.", id);
        default:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_INVALID_ATTRIBUTE,
                         "Attribute ID %u is not recognized.", id);
        }
}

/**
 * Get an attribute of type `int32`.
 *
 * Modelled after the IVI-C GetAttribute family of functions.
 *
 * @param[in]  wrtd     Device token.
 * @param[in]  rep_cap_id ID (string) of concerned repeated capability.
 * If it is a global attribute, use #WRTD_GLOBAL_REP_CAP_ID
 * @param[in]  id         ID (#wrtd_attr) of concerned attribute.
 * @param[out] value      Retrieved attribute value.
 * @return #wrtd_status
 */
wrtd_status wrtd_get_attr_int32(wrtd_dev *wrtd,
                                const char *rep_cap_id,
                                wrtd_attr id,
                                int32_t *value)
{
        wrtd_status status;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        if(value == NULL){
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_NULL_POINTER,
                         "Null pointer passed for function %s, "
                         "parameter value", __func__);
        }

        switch(id) {
        case WRTD_ATTR_FW_COUNT:
                status = wrtd_attr_global
                        (wrtd, rep_cap_id);
                WRTD_RETURN_IF_ERROR(status);
                *value = wrtd->nbr_cpus;
                return WRTD_SUCCESS;
        case WRTD_ATTR_FW_MAJOR_VERSION:
                status = wrtd_attr_get_fw_major_version
                        (wrtd, rep_cap_id, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_FW_MINOR_VERSION:
                status = wrtd_attr_get_fw_minor_version
                        (wrtd, rep_cap_id, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_FW_MAJOR_VERSION_REQUIRED:
                status = wrtd_attr_get_fw_major_version_required
                        (wrtd, rep_cap_id, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_FW_MINOR_VERSION_REQUIRED:
                status = wrtd_attr_get_fw_minor_version_required
                        (wrtd, rep_cap_id, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_FW_MAX_RULES:
                status = wrtd_attr_get_fw_max_rules
                        (wrtd, rep_cap_id, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_FW_MAX_ALARMS:
                status = wrtd_attr_get_fw_max_alarms
                        (wrtd, rep_cap_id, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_FW_CAPABILITIES:
                status = wrtd_attr_get_fw_capabilities
                        (wrtd, rep_cap_id, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_FW_LOCAL_INPUTS:
                status = wrtd_attr_get_fw_local_inputs
                        (wrtd, rep_cap_id, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_FW_LOCAL_OUTPUTS:
                status = wrtd_attr_get_fw_local_outputs
                        (wrtd, rep_cap_id, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_ALARM_COUNT:
                status = wrtd_attr_global
                        (wrtd, rep_cap_id);
                WRTD_RETURN_IF_ERROR(status);
                status = wrtd_attr_get_alarm_count
                        (wrtd, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_ALARM_REPEAT_COUNT:
                return wrtd_attr_get_alarm_repeat_count
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_COUNT:
                status = wrtd_attr_global
                        (wrtd, rep_cap_id);
                WRTD_RETURN_IF_ERROR(status);
                status = wrtd_attr_get_rule_count
                        (wrtd, value);
                WRTD_RETURN_IF_ERROR(status);
                return WRTD_SUCCESS;
        case WRTD_ATTR_RULE_REPEAT_COUNT:
                return wrtd_attr_get_rule_repeat_count
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_RESYNC_FACTOR:
                return wrtd_attr_get_rule_resync_factor
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_RX_EVENTS:
                return wrtd_attr_get_stat_rule_rx_events
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_TX_EVENTS:
                return wrtd_attr_get_stat_rule_tx_events
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_MISSED_LATE:
                return wrtd_attr_get_stat_rule_missed_late
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_MISSED_HOLDOFF:
                return wrtd_attr_get_stat_rule_missed_holdoff
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_MISSED_NOSYNC:
                return wrtd_attr_get_stat_rule_missed_nosync
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_MISSED_OVERFLOW:
                return wrtd_attr_get_stat_rule_missed_overflow
                        (wrtd, rep_cap_id, value);
        default:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_INVALID_ATTRIBUTE,
                         "Attribute ID %u is not recognized.", id);
        }
}

/**
 * Set an attribute of type `string`.
 *
 * Modelled after the IVI-C SetAttribute family of functions.
 *
 * @param[in]  wrtd       Device token.
 * @param[in]  rep_cap_id ID (string) of concerned repeated capability.
 * If it is a global attribute, use #WRTD_GLOBAL_REP_CAP_ID
 * @param[in]  id         ID (#wrtd_attr) of concerned attribute.
 * @param[in] value       Value to write to the attribute.
 * @return #wrtd_status
 */
wrtd_status wrtd_set_attr_string(wrtd_dev *wrtd,
                                 const char *rep_cap_id,
                                 wrtd_attr id,
                                 const char *value)
{
        wrtd_status status;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        switch(id) {
        case WRTD_ATTR_RULE_SOURCE:
                return wrtd_attr_set_rule_source
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_DESTINATION:
                return wrtd_attr_set_rule_destination
                        (wrtd, rep_cap_id, value);
        default:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_INVALID_ATTRIBUTE,
                         "Attribute ID %u is not recognized.", id);
        }
}

/**
 * Get an attribute of type `string`.
 *
 * Modelled after the IVI-C GetAttribute family of functions.
 *
 * This function complies with IVI-3.2 section, 3.1.2.1 (Additional Compliance Rules
 * for C Functions with ViChar Array Output Parameters), with the exception of
 * the buffer_size < 0 case, which produces an error instead of allowing a potential
 * buffer overflow.
 *
 * @param[in]  wrtd     Device token.
 * @param[in]  rep_cap_id ID (string) of concerned repeated capability.
 * If it is a global attribute, use #WRTD_GLOBAL_REP_CAP_ID
 * @param[in]  id         ID (#wrtd_attr) of concerned attribute.
 * @param[in]  value_buffer_size Size of pre-allocated `value` buffer.
 * @param[out] value      Retrieved attribute value.
 * @return #wrtd_status. However, if the buffer size parameter is 0, then this function returns
 * instead a positive value, indicating the minimum buffer size necessary to fit the full message.
 * See also IVI-3.2, section 3.1.2.1.
 */
wrtd_status wrtd_get_attr_string(wrtd_dev *wrtd,
                                 const char *rep_cap_id,
                                 wrtd_attr id,
                                 int32_t value_buffer_size,
                                 char *value)
{
        wrtd_status status;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        if ((value_buffer_size != 0) && (value == NULL)) {
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_NULL_POINTER,
                         "Null pointer passed for function %s, "
                         "parameter value", __func__);
        }

        switch(id) {
        case WRTD_ATTR_RULE_SOURCE:
                return wrtd_attr_get_rule_source
                        (wrtd, rep_cap_id, value_buffer_size, value);
        case WRTD_ATTR_RULE_DESTINATION:
                return wrtd_attr_get_rule_destination
                        (wrtd, rep_cap_id, value_buffer_size, value);
        default:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_INVALID_ATTRIBUTE,
                         "Attribute ID %u is not recognized.", id);
        }
}

/**
 * Set an attribute of type `timestamp`.
 *
 * Modelled after the IVI-C SetAttribute family of functions.
 *
 * @param[in]  wrtd       Device token.
 * @param[in]  rep_cap_id ID (string) of concerned repeated capability.
 * If it is a global attribute, use #WRTD_GLOBAL_REP_CAP_ID
 * @param[in]  id         ID (#wrtd_attr) of concerned attribute.
 * @param[in] value       Value to write to the attribute.
 * @return #wrtd_status
 */
wrtd_status wrtd_set_attr_tstamp(wrtd_dev *wrtd,
                                 const char *rep_cap_id,
                                 wrtd_attr id,
                                 const wrtd_tstamp *value)
{
        wrtd_status status;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        if(value == NULL){
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_NULL_POINTER,
                         "Null pointer passed for function %s, "
                         "parameter value", __func__);
        }

        switch(id) {
        case WRTD_ATTR_ALARM_TIME:
                return wrtd_attr_set_alarm_time
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_ALARM_SETUP_TIME:
                return wrtd_attr_set_alarm_setup_time
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_ALARM_PERIOD:
                return wrtd_attr_set_alarm_period
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_DELAY:
                return wrtd_attr_set_rule_delay
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_HOLDOFF:
                return wrtd_attr_set_rule_holdoff
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_RESYNC_PERIOD:
                return wrtd_attr_set_rule_resync_period
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_SYS_TIME:
        case WRTD_ATTR_STAT_RULE_RX_LAST:
        case WRTD_ATTR_STAT_RULE_TX_LAST:
        case WRTD_ATTR_STAT_RULE_MISSED_LAST:
        case WRTD_ATTR_STAT_RULE_RX_LATENCY_MIN:
        case WRTD_ATTR_STAT_RULE_RX_LATENCY_MAX:
        case WRTD_ATTR_STAT_RULE_RX_LATENCY_AVG:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_ATTR_NOT_WRITEABLE,
                         "Attribute %u is read only.", id);
        default:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_INVALID_ATTRIBUTE,
                         "Attribute ID %u is not recognized.", id);
        }
}

/**
 * Get an attribute of type `timestamp`.
 *
 * Modelled after the IVI-C GetAttribute family of functions.
 *
 * @param[in]  wrtd     Device token.
 * @param[in]  rep_cap_id ID (string) of concerned repeated capability.
 * If it is a global attribute, use #WRTD_GLOBAL_REP_CAP_ID
 * @param[in]  id         ID (#wrtd_attr) of concerned attribute.
 * @param[out] value      Retrieved attribute value.
 * @return #wrtd_status
 */
wrtd_status wrtd_get_attr_tstamp(wrtd_dev *wrtd,
                                 const char *rep_cap_id,
                                 wrtd_attr id,
                                 wrtd_tstamp *value)
{
        wrtd_status status;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        if(value == NULL){
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_NULL_POINTER,
                         "Null pointer passed for function %s, "
                         "parameter value", __func__);
        }

        switch(id) {
        case WRTD_ATTR_SYS_TIME:
                status = wrtd_attr_global
                        (wrtd, rep_cap_id);
                WRTD_RETURN_IF_ERROR(status);
                return wrtd_attr_get_sys_time
                        (wrtd, value);
        case WRTD_ATTR_ALARM_TIME:
                return wrtd_attr_get_alarm_time
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_ALARM_SETUP_TIME:
                return wrtd_attr_get_alarm_setup_time
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_ALARM_PERIOD:
                return wrtd_attr_get_alarm_period
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_DELAY:
                return wrtd_attr_get_rule_delay
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_HOLDOFF:
                return wrtd_attr_get_rule_holdoff
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_RULE_RESYNC_PERIOD:
                return wrtd_attr_get_rule_resync_period
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_RX_LAST:
                return wrtd_attr_get_stat_rule_rx_last
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_TX_LAST:
                return wrtd_attr_get_stat_rule_tx_last
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_MISSED_LAST:
                return wrtd_attr_get_stat_rule_missed_last
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_RX_LATENCY_MIN:
                return wrtd_attr_get_stat_rule_rx_latency_min
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_RX_LATENCY_MAX:
                return wrtd_attr_get_stat_rule_rx_latency_max
                        (wrtd, rep_cap_id, value);
        case WRTD_ATTR_STAT_RULE_RX_LATENCY_AVG:
                return wrtd_attr_get_stat_rule_rx_latency_avg
                        (wrtd, rep_cap_id, value);
        default:
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_INVALID_ATTRIBUTE,
                         "Attribute ID %u is not recognized.", id);
        }
}

/**
 *@} End group Attributes
 */

/**
 * @defgroup Event Log
 * Functions to retrieve and clear entries from the event log.
 *@{
 */

/**
 * Retrieve and clear the last entry from the Event Log.
 *
 * Modelled after the IVI-C GetNextEventLogEntry function (from IVI-3.15).
 *
 * This function complies with IVI-3.2, section 3.1.2.1 (Additional Compliance Rules
 * for C Functions with ViChar Array Output Parameters), with the exception of
 * the buffer_size < 0 case, which produces an error instead of allowing a potential
 * buffer overflow.
 *
 * Event log entries use the format `id|seq|log tstamp|event tstamp|log type|log reason`, where:
 * - `id` is the Event ID.
 * - `seq` is a sequence number for the rule that generated this Event. Each Event generated by
 *    the Rule causes the sequence number to increase by one. Limited to 4 digits.
 * - `log tstamp` is the timestamp of when the event was logged.
 * - `event tstamp` is the timestamp of when the event happened.
 * - `log type` is the type of the event.
 * - `log reason` is the specific reason for this type of event.
 *
 * All event log entries have a fixed length of #WRTD_LOG_ENTRY_SIZE.
 *
 * Timestamps are represented using the format `YYYY-MM-DD,hh:mm:ss.mmm.uuu.nnn+fff`, where:
 * - `YYYY-MM-DD` is the year, month and day.
 * - `hh:mm:ss` is hours, minutes and seconds.
 * - `mmm.uuu.nnn` is milliseconds, microseconds and nanoseconds.
 * - `fff` is fractional nanoseconds. The unit is 2e-9 nanoseconds.
 *
 * Log types and reasons can be one of the following (in the form `type|reason`):
 * - `GENERATED|ALARM`   : An Event was generated because of an Alarm.
 * - `GENERATED|DEVICE_x`: An Event was generated from device x.
 * - `CONSUMED|START`    : An Event with a local channel output as destination has been
 *   forwarded and scheduled to execute. The exact meaning of this log type is
 *   application-specific.
 * - `CONSUMED|DONE`     : An Event with a local channel output as destination has
 *   finished executing. The exact meaning of this log type is application-specific.
 * - `DISCARDED|NO SYNC` : An Event was discarded because the device was not syncrhonized
      to White Rabbit. See also #WRTD_ATTR_STAT_RULE_MISSED_NOSYNC.
 * - `DISCARDED|HOLD OFF`: An Event was discarded because it violated the programmed hold-off
 *    value. See also #WRTD_ATTR_STAT_RULE_MISSED_HOLDOFF.
 * - `DISCARDED|TIME OUT`: An Event was discarded because it arrived too late. See also
 *    #WRTD_ATTR_STAT_RULE_MISSED_LATE.
 * - `DISCARDED|OVERFLOW`: An Event was discarded because of internal buffer overflows.
 *    This may happen if the Event rate is too high. See also #WRTD_ATTR_STAT_RULE_MISSED_OVERFLOW.
 * - `NETWORK|TX`        : An Event was sent over the White Rabbit network.
 * - `NETWORK|RX`        : An Event was received over the White Rabbit network.
 *
 * @param[in]  wrtd        Device token.
 * @param[in]  log_entry_buffer_size Size of pre-allocated `log_entry` buffer.
 * @param[out] log_entry Buffer to store the log message string.
 * @return #wrtd_status. However, if the buffer size parameter is 0, then this function returns
 * instead a positive value, indicating the minimum buffer size necessary to fit the full message.
 * See also IVI-3.2, section 3.1.2.1.
 */
wrtd_status wrtd_get_next_event_log_entry(wrtd_dev *wrtd,
                                          int32_t log_entry_buffer_size,
                                          char *log_entry)
{
        struct wrtd_log_entry log;
        wrtd_status status;
        struct tm *tm;
        time_t t;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        /* Do not retrieve entry from log if buffer_size
           is zero. Just report on necessary buffer size.
           According to IVI-3.2, section 3.1.2.1 */
        if (log_entry_buffer_size == 0)
                return WRTD_LOG_ENTRY_SIZE;

        if(log_entry == NULL){
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_NULL_POINTER,
                         "Null pointer passed for function %s, "
                         "parameter log_entry", __func__);
        }

        status = wrtd_log_read(wrtd, &log, __func__);
        WRTD_RETURN_IF_ERROR(status);

        memset(log_entry, 0, log_entry_buffer_size);

        /* Nothing to read */
        if (log.type == WRTD_LOG_MSG_EV_NONE)
                return WRTD_SUCCESS;

        /* This violates IVI-3.2, section 3.1.2.1 which dictates to
           ignore overflow if buffer_size is negative. Still, it is
           the safer thing to do.*/
        if (log_entry_buffer_size < 0)
                return wrtd_return_error(wrtd, WRTD_ERROR_INVALID_VALUE,
                                         "Invalid value (%d) for function %s, "
                                         "parameter log_entry_buffer_size",
                                         log_entry_buffer_size, __func__);

        int count = log_entry_buffer_size;

        char *sptr = log_entry;

        int ret;

        ret = snprintf(sptr, count, "Id:%-16s|Seq:%04d|",
                       log.event.id, log.event.seq % 10000);
        if (ret < 0)
                return wrtd_return_error(
                        wrtd, WRTD_ERROR_UNEXPECTED_RESPONSE,
                        "%s: Unexpected response from device",
                        __func__);
        if (ret > count)
                return WRTD_LOG_ENTRY_SIZE;
        count -= ret;
        sptr  += ret;

        t  = log.ts.seconds;
        tm = gmtime(&t);
        if (tm == NULL)
                return wrtd_return_error(
                        wrtd, WRTD_ERROR_UNEXPECTED_RESPONSE,
                        "%s: Unexpected response from device",
                        __func__);

        ret = strftime(sptr, count, "%F,%T.", tm);
        if (ret == 0)
                return WRTD_LOG_ENTRY_SIZE;
        count -= ret;
        sptr  += ret;

        ret = snprintf(sptr, count, "%03lu.%03lu.%03lu+%03d|",
                       (log.ts.ns / (1000L * 1000)),
                       (log.ts.ns / 1000L) % 1000,
                       log.ts.ns % 1000UL,
                       log.ts.frac >> (32 - 9));
        if (ret < 0)
                return wrtd_return_error(
                        wrtd, WRTD_ERROR_UNEXPECTED_RESPONSE,
                        "%s: Unexpected response from device",
                        __func__);
        if (ret > count)
                return WRTD_LOG_ENTRY_SIZE;
        count -= ret;
        sptr  += ret;

        t  = log.event.ts.seconds;
        tm = gmtime(&t);
        if (tm == NULL)
                return wrtd_return_error(
                        wrtd, WRTD_ERROR_UNEXPECTED_RESPONSE,
                        "%s: Unexpected response from device",
                        __func__);

        ret = strftime(sptr, count, "%F,%T.", tm);
        if (ret == 0)
                return WRTD_LOG_ENTRY_SIZE;
        count -= ret;
        sptr  += ret;

        ret = snprintf(sptr, count, "%03lu.%03lu.%03lu+%03d|",
                       (log.event.ts.ns / (1000L * 1000)),
                       (log.event.ts.ns / 1000L) % 1000,
                       log.event.ts.ns % 1000UL,
                       log.event.ts.frac >> (32 - 9));
        if (ret < 0)
                return wrtd_return_error(
                        wrtd, WRTD_ERROR_UNEXPECTED_RESPONSE,
                        "%s: Unexpected response from device",
                        __func__);
        if (ret > count)
                return WRTD_LOG_ENTRY_SIZE;
        count -= ret;
        sptr  += ret;

        switch(log.type) {
        case WRTD_LOG_MSG_EV_GENERATED:
                if (log.reason == WRTD_LOG_GENERATED_ALARM)
                        ret = snprintf(sptr, count, "GENERATED|ALARM   ");
                else if ((log.reason >= WRTD_LOG_GENERATED_DEVICE_0) &&
                         (log.reason <= WRTD_LOG_GENERATED_DEVICE_7) &&
                         (log.reason % 8 == 0))
                        ret = snprintf(sptr, count, "GENERATED|DEVICE_%d",
                                       (log.reason - 8) / 8);
                else
                        return wrtd_return_error(
                                wrtd, WRTD_ERROR_UNKNOWN_LOG_TYPE,
                                "%s: Unknown log reason (%#x)",
                                __func__, log.reason);
                break;
        case WRTD_LOG_MSG_EV_CONSUMED:
                if (log.reason == WRTD_LOG_CONSUMED_START)
                        ret = snprintf(sptr, count, "CONSUMED |START   ");
                else if (log.reason == WRTD_LOG_CONSUMED_DONE)
                        ret = snprintf(sptr, count, "CONSUMED |DONE    ");
                else
                        return wrtd_return_error(
                                wrtd, WRTD_ERROR_UNKNOWN_LOG_TYPE,
                                "%s: Unknown log reason (%#x)",
                                __func__, log.reason);
                break;
        case WRTD_LOG_MSG_EV_DISCARDED:
                if (log.reason == WRTD_LOG_DISCARD_NO_SYNC)
                        ret = snprintf(sptr, count, "DISCARDED|NO SYNC ");
                else if (log.reason == WRTD_LOG_DISCARD_HOLDOFF)
                        ret = snprintf(sptr, count, "DISCARDED|HOLD OFF");
                else if (log.reason == WRTD_LOG_DISCARD_TIMEOUT)
                        ret = snprintf(sptr, count, "DISCARDED|TIME OUT");
                else if (log.reason == WRTD_LOG_DISCARD_OVERFLOW)
                        ret = snprintf(sptr, count, "DISCARDED|OVERFLOW");
                else
                        return wrtd_return_error(
                                wrtd, WRTD_ERROR_UNKNOWN_LOG_TYPE,
                                "%s: Unknown log reason (%#x)",
                                __func__, log.reason);
                break;
        case WRTD_LOG_MSG_EV_NETWORK:
                if (log.reason == WRTD_LOG_NETWORK_TX)
                        ret = snprintf(sptr, count, "NETWORK  |TX      ");
                else if (log.reason == WRTD_LOG_NETWORK_RX)
                        ret = snprintf(sptr, count, "NETWORK  |RX      ");
                else
                        return wrtd_return_error(
                                wrtd, WRTD_ERROR_UNKNOWN_LOG_TYPE,
                                "%s: Unknown log reason (%#x)",
                                __func__, log.reason);
                break;
        default:
                return wrtd_return_error(
                        wrtd, WRTD_ERROR_UNKNOWN_LOG_TYPE,
                        "%s: Unknown log type (%#x)",
                        __func__, log.type);
                return WRTD_ERROR_UNKNOWN_LOG_TYPE;
        }

        if (ret < 0)
                return wrtd_return_error(
                        wrtd, WRTD_ERROR_UNEXPECTED_RESPONSE,
                        "%s: Unexpected response from device",
                        __func__);
        if (ret > count)
                return WRTD_LOG_ENTRY_SIZE;

        return WRTD_SUCCESS;
}

/**
 * Clear all entries from the Event Log.
 *
 * Modelled after the IVI-C ClearEventLog function (from IVI-3.15).
 *
 * @param[in]  wrtd        Device token.
 * @return #wrtd_status.
 */
wrtd_status wrtd_clear_event_log_entries(wrtd_dev *wrtd)
{
        int ret, i;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        for (i = 0; i < wrtd->nbr_cpus; ++i) {
                ret = trtl_hmq_flush(wrtd->trtl, i, WRTD_HMQ);
                if (ret < 0)
                        return wrtd_return_error
                                (wrtd, WRTD_ERROR_INTERNAL,
                                 "%s: %s", __func__, trtl_strerror(errno));
        }

        return WRTD_SUCCESS;
}

/**
 *@} End group Event Log
 */

/**
 * @defgroup Alarms
 * Functions to add/remove and access WRTD Alarms.
 * @{
 */

/**
 * Create a new Alarm.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * @param[in]  wrtd       Device token.
 * @param[in]  rep_cap_id Name for the new Alarm. Must begin with
   'alarm' or 'ALARM'.
 * @return #wrtd_status
 */
wrtd_status wrtd_add_alarm(wrtd_dev *wrtd,
                           const char *rep_cap_id)
{
        wrtd_status status;
        unsigned int i;
        int idx;
        struct wrtd_alarm *alarm;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        /* Validate rep_cap_id.  */
        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        /* Check that the name begins with alarm|ALARM */
        if ((strncmp(rep_cap_id, "alarm", 5) != 0) &&
            (strncmp(rep_cap_id, "ALARM", 5) != 0))
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_BADLY_FORMED_SELECTOR,
                         "%s: Badly-formed selector", __func__);

        /* Load all alarms.  */
        status = wrtd_fill_alarms(wrtd, __func__);
        WRTD_RETURN_IF_ERROR(status);

        /* Find a free slot, check not already used.  */
        idx = -1;
        for (i = 0; i < wrtd->nbr_alarms; i++) {
                if (wrtd_id_eq(wrtd->alarms[i].alarm.event.id, rep_cap_id)) {
                        return wrtd_return_error(wrtd, WRTD_ERROR_ALARM_EXISTS,
                                                 "%s: The alarm already exists",
                                                 __func__);
                }
                if (idx < 0 && wrtd_id_null(wrtd->alarms[i].alarm.event.id))
                        idx = i;
        }
        if (idx == -1)
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_OUT_OF_RESOURCES,
                         "%s: The device has no more resources to allocate.",
                         __func__);

        /* Initialize with default values.  */
        alarm = &wrtd->alarms[idx].alarm;
        /* That should be the default, but... */
        memset(alarm, 0, sizeof(struct wrtd_alarm));
        wrtd_id_copy(alarm->event.id, rep_cap_id);

        /* Write-back.  */
        status = wrtd_write_alarm(wrtd, idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        return WRTD_SUCCESS;
}

/**
 * Disable all defined Alarms.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * See also, #WRTD_ATTR_ALARM_ENABLED and #wrtd_set_attr_bool
 * for disabling a single Alarm.
 *
 * @param[in]  wrtd       Device token.
 * @return #wrtd_status
 */
wrtd_status wrtd_disable_all_alarms(wrtd_dev *wrtd)
{
        unsigned idx;
        wrtd_status status;
        struct wrtd_alarm *alarm;
        int changed = 0;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_fill_alarms(wrtd, __func__);
        WRTD_RETURN_IF_ERROR(status);

        for (idx = 0; idx < wrtd->nbr_alarms; idx++) {
                alarm = &wrtd->alarms[idx].alarm;
                if(alarm->enabled != 0) {
                        alarm->enabled = 0;
                        changed = 1;
                }
        }

        if (changed) {
                status = wrtd_reconfigure(wrtd, __func__);
                WRTD_RETURN_IF_ERROR(status);
        }
        return WRTD_SUCCESS;
}

/**
 * Remove an Alarm. The Alarm must not be enabled.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * @param[in]  wrtd       Device token.
 * @param[in]  rep_cap_id Name of the Alarm to remove.
 * @return #wrtd_status
 */
wrtd_status wrtd_remove_alarm(wrtd_dev *wrtd,
                              const char *rep_cap_id)
{
        wrtd_status status;
        unsigned idx;
        struct wrtd_alarm *alarm;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_find_alarm(wrtd, rep_cap_id, &idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        status = wrtd_alarm_check_disabled(wrtd, idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        /* Clear it.  */
        alarm = &wrtd->alarms[idx].alarm;
        memset(alarm, 0, sizeof(struct wrtd_alarm));

        /* Write-back.  */
        status = wrtd_write_alarm(wrtd, idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        return WRTD_SUCCESS;
}

/**
 * Remove all defined Alarms. The Alarms must not be enabled.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * @param[in]  wrtd       Device token.
 * @return #wrtd_status
 */
extern wrtd_status wrtd_remove_all_alarms(wrtd_dev *wrtd)
{
        wrtd_status status;
        unsigned idx;
        struct wrtd_alarm *alarm;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_fill_alarms(wrtd, __func__);
        WRTD_RETURN_IF_ERROR(status);

        for (idx = 0; idx < wrtd->nbr_alarms; idx++) {
                status = wrtd_alarm_check_disabled(wrtd, idx, __func__);
                WRTD_RETURN_IF_ERROR(status);
        }

        for (idx = 0; idx < wrtd->nbr_alarms; idx++) {
                /* Clear it.  */
                alarm = &wrtd->alarms[idx].alarm;
                memset(alarm, 0, sizeof(struct wrtd_alarm));

                /* Write-back.  */
                status = wrtd_write_alarm(wrtd, idx, __func__);
                WRTD_RETURN_IF_ERROR(status);
        }

        return WRTD_SUCCESS;
}

/**
 * Retrieve the name of an Alarm, based on the provided index.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * This function complies with IVI-3.2, section 3.1.2.1 (Additional Compliance Rules
 * for C Functions with ViChar Array Output Parameters), with the exception of
 * the buffer_size < 0 case, which produces an error instead of allowing a potential
 * buffer overflow.
 *
 * @param[in]  wrtd             Device token.
 * @param[in]  index            index of the Alarm.
 * @param[in]  name_buffer_size Size of pre-allocated `name` buffer.
 * @param[out] name             Buffer to store the retrieved Alarm name.
 * @return #wrtd_status. See also IVI-3.2, section 3.1.2.1.
 */
wrtd_status wrtd_get_alarm_name(wrtd_dev *wrtd,
                                int32_t index,
                                int32_t name_buffer_size,
                                char *name)
{
        wrtd_status status;
        unsigned i;
        unsigned n;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        /* Load all alarms.  */
        status = wrtd_fill_alarms(wrtd, __func__);
        WRTD_RETURN_IF_ERROR(status);

        /* Search it.  */
        for (n = 0, i = 0; i < wrtd->nbr_alarms; i++) {
                const char *aid = wrtd->alarms[i].alarm.event.id;
                if (wrtd_id_null(aid))
                        continue;
                if (n == index) {
                        status = wrtd_id_copy_buf
                                (wrtd, name, name_buffer_size, aid, __func__);
                        WRTD_RETURN_IF_ERROR(status);
                        return WRTD_SUCCESS;
                }
                n++;
        }

        return wrtd_return_error(wrtd, WRTD_ERROR_INVALID_VALUE,
                                 "Invalid value (%d) for function %s, "
                                 "parameter index.", index, __func__);
}

/**
 *@} End group Alarms
 */

/**
 * @defgroup Rules
 * Functions to add/remove and access WRTD Rules.
 * @{
 */

/**
 * Create a new Rule.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * @param[in]  wrtd       Device token.
 * @param[in]  rep_cap_id Name for the new Rule.
 * @return #wrtd_status
 */
wrtd_status wrtd_add_rule(wrtd_dev *wrtd,
                          const char *rep_cap_id)
{
        wrtd_status status;
        unsigned int i;
        int idx;
        struct wrtd_rule *rule;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        /* 1. Validate rep_cap_id.  */
        status = wrtd_validate_id(wrtd, rep_cap_id, __func__);
        WRTD_RETURN_IF_ERROR(status);

        /* 2. Load all rules.  */
        status = wrtd_fill_rules(wrtd, __func__);
        WRTD_RETURN_IF_ERROR(status);

        /* 3. Find a free slot, check not already used.  */
        idx = -1;
        for (i = 0; i < wrtd->nbr_rules; i++) {
                if (wrtd_id_eq(wrtd->rules[i].rule.conf.id, rep_cap_id)) {
                        return wrtd_return_error(wrtd, WRTD_ERROR_RULE_EXISTS,
                                                 "%s: The rule already exists",
                                                 __func__);
                }
                if (idx < 0
                    && wrtd_id_null(wrtd->rules[i].rule.conf.id))
                        idx = i;
        }
        if (idx == -1)
                return wrtd_return_error
                        (wrtd, WRTD_ERROR_OUT_OF_RESOURCES,
                         "%s: The device has no more resources to allocate.",
                         __func__);

        /* 4. Initialize with default values.  */
        rule = &wrtd->rules[idx].rule;
        /* That should be the default, but... */
        memset(rule, 0, sizeof(struct wrtd_rule));
        wrtd_id_copy(rule->conf.id, rep_cap_id);
        rule->conf.send_late = true;

        /* 5. Write-back.  */
        status = wrtd_write_rule(wrtd, idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        return WRTD_SUCCESS;
}

/**
 * Disable all defined Rules.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * See also, #WRTD_ATTR_RULE_ENABLED and #wrtd_set_attr_bool
 * for disabling a single Rule.
 *
 * @param[in]  wrtd       Device token.
 * @return #wrtd_status
 */
wrtd_status wrtd_disable_all_rules(wrtd_dev *wrtd)
{
        unsigned idx;
        wrtd_status status;
        struct wrtd_lib_rule *r;
        int changed = 0;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_fill_rules(wrtd, __func__);
        WRTD_RETURN_IF_ERROR(status);

        for (idx = 0; idx < wrtd->nbr_rules; idx++) {
                r = &wrtd->rules[idx];
                if (r->rule.conf.enabled != 0) {
                        r->rule.conf.enabled = 0;
                        changed = 1;
                }
        }
        if (changed) {
                status = wrtd_reconfigure(wrtd, __func__);
                WRTD_RETURN_IF_ERROR(status);
        }
        return WRTD_SUCCESS;
}

/**
 * Remove a Rule. The Rule must not be enabled.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * @param[in]  wrtd       Device token.
 * @param[in]  rep_cap_id Name of the Rule to remove.
 * @return #wrtd_status
 */
wrtd_status wrtd_remove_rule(wrtd_dev *wrtd,
                             const char *rep_cap_id)
{
        wrtd_status status;
        unsigned idx;
        struct wrtd_rule *rule;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_find_rule(wrtd, rep_cap_id, &idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        status = wrtd_rule_check_disabled(wrtd, idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        /* Clear it.  */
        rule = &wrtd->rules[idx].rule;
        memset(rule, 0, sizeof(struct wrtd_rule));

        /* Write-back.  */
        status = wrtd_write_rule(wrtd, idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        return WRTD_SUCCESS;
}

/**
 * Remove all defined Rules. The Rules must not be enabled.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * @param[in]  wrtd       Device token.
 * @return #wrtd_status
 */
wrtd_status wrtd_remove_all_rules(wrtd_dev *wrtd)
{
        wrtd_status status;
        unsigned idx;
        struct wrtd_rule *rule;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_fill_rules(wrtd, __func__);
        WRTD_RETURN_IF_ERROR(status);

        for (idx = 0; idx < wrtd->nbr_rules; idx++) {
                status = wrtd_rule_check_disabled(wrtd, idx, __func__);
                WRTD_RETURN_IF_ERROR(status);
        }

        for (idx = 0; idx < wrtd->nbr_rules; idx++) {
                rule = &wrtd->rules[idx].rule;

                /* Clear it.  */
                memset(rule, 0, sizeof(struct wrtd_rule));

                /* Write-back.  */
                status = wrtd_write_rule(wrtd, idx, __func__);
                WRTD_RETURN_IF_ERROR(status);

        }

        return WRTD_SUCCESS;
}

/**
 * Retrieve the name of a Rule, based on the provided index.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * This function complies with IVI-3.2, section 3.1.2.1 (Additional Compliance Rules
 * for C Functions with ViChar Array Output Parameters), with the exception of
 * the buffer_size < 0 case, which produces an error instead of allowing a potential
 * buffer overflow.
 *
 * @param[in]  wrtd             Device token.
 * @param[in]  index            index of the Rule.
 * @param[in]  name_buffer_size Size of pre-allocated `name` buffer.
 * @param[out] name             Buffer to store the retrieved Rule name.
 * @return #wrtd_status. See also IVI-3.2, section 3.1.2.1.
 */
wrtd_status wrtd_get_rule_name(wrtd_dev *wrtd,
                               int32_t index,
                               int32_t name_buffer_size,
                               char *name)
{
        wrtd_status status;
        unsigned i;
        unsigned n;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        /* Load all rules.  */
        status = wrtd_fill_rules(wrtd, __func__);
        WRTD_RETURN_IF_ERROR(status);

        /* Search it.  */
        for (n = 0, i = 0; i < wrtd->nbr_rules; i++) {
                const char *aid = wrtd->rules[i].rule.conf.id;
                if (wrtd_id_null(aid))
                        continue;
                if (n == index) {
                        status = wrtd_id_copy_buf
                                (wrtd, name, name_buffer_size, aid, __func__);
                        WRTD_RETURN_IF_ERROR(status);
                        return WRTD_SUCCESS;
                }
                n++;
        }

        return wrtd_return_error(wrtd, WRTD_ERROR_INVALID_VALUE,
                                 "Invalid value (%d) for function %s, "
                                 "parameter index.", index, __func__);
}

/**
 * Reset all statistics for the given rule. The Rule must not be enabled.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * @param[in]  wrtd       Device token.
 * @param[in]  rep_cap_id Name of the Rule to reset its statistics.
 * @return #wrtd_status
 */
wrtd_status wrtd_reset_rule_stats(wrtd_dev *wrtd,
                                  const char *rep_cap_id)
{
        wrtd_status status;
        unsigned idx;
        struct wrtd_rule *rule;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        status = wrtd_find_rule(wrtd, rep_cap_id, &idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        status = wrtd_rule_check_disabled(wrtd, idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        rule = &wrtd->rules[idx].rule;

        /* Clear stats.  */
        memset(&rule->stat, 0, sizeof(struct wrtd_rule_stats));

        /* Write-back.  */
        status = wrtd_write_rule(wrtd, idx, __func__);
        WRTD_RETURN_IF_ERROR(status);

        return WRTD_SUCCESS;
}

/**
 *@} End group Rules
 */

/**
 * @defgroup Rules
 * Functions to add/remove and access WRTD Rules.
 * @{
 */

/**
 * Retrieve the name of a firmware application, based on the provided index.
 *
 * Complies with the IVI "Parameter style"
 * for repeated capabilities (see IVI-3.4, section 12).
 *
 * This function complies with IVI-3.2, section 3.1.2.1 (Additional Compliance Rules
 * for C Functions with ViChar Array Output Parameters), with the exception of
 * the buffer_size < 0 case, which produces an error instead of allowing a potential
 * buffer overflow.
 *
 * @param[in]  wrtd             Device token.
 * @param[in]  index            index of the firmware application.
 * @param[in]  name_buffer_size Size of pre-allocated `name` buffer.
 * @param[out] name             Buffer to store the retrieved application name.
 * @return #wrtd_status. See also IVI-3.2, section 3.1.2.1.
 */
wrtd_status wrtd_get_fw_name(wrtd_dev *wrtd,
                             int32_t index,
                             int32_t name_buffer_size,
                             char *name)
{
        wrtd_status status;
        unsigned i;

        if(wrtd == NULL){
                return WRTD_ERROR_NOT_INITIALIZED;
        }

        /* Load all roots.  */
        status = wrtd_fill_roots(wrtd, __func__);
        WRTD_RETURN_IF_ERROR(status);

        /* Search it.  */
        for (i = 0; i < wrtd->nbr_cpus; i++) {
                if (i == index) {
                        status = wrtd_id_copy_buf
                                (wrtd, name, name_buffer_size,
                                 wrtd->roots[i].fw_name, __func__);
                        WRTD_RETURN_IF_ERROR(status);
                        return WRTD_SUCCESS;
                }
        }

        return wrtd_return_error(wrtd, WRTD_ERROR_INVALID_VALUE,
                                 "Invalid value (%d) for function %s, "
                                 "parameter index.", index, __func__);
}

/**
 *@} End group Firmware
 */
