/*
 * CANopen Safety Related Data Object protocol.
 *
 * @file        CO_SRDO.c
 * @ingroup     CO_SRDO
 * @author      Robert Grüning
 * @copyright   2020 Robert Grüning
 * @copyright   2024 temi54c1l8(at)github
 * @copyright   2024 Janez Paternoster
 *
 * This file is part of <https://github.com/CANopenNode/CANopenNode>, a CANopen Stack.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

#include "304/CO_SRDO.h"

#if ((CO_CONFIG_SRDO)&CO_CONFIG_SRDO_ENABLE) != 0

#include "301/crc16-ccitt.h"

/* verify configuration */
#if ((CO_CONFIG_CRC16)&CO_CONFIG_CRC16_ENABLE) == 0
#error CO_CONFIG_CRC16_ENABLE must be enabled.
#endif

#ifdef CO_CONFORMANCE_TEST_TOOL_ADAPTATION
#warning CO_CONFORMANCE_TEST_TOOL_ADAPTATION may be used only for conformance testing (because of CTT limitations)
#endif

/* values for informationDirection and configurationValid */
#define CO_SRDO_INVALID                 (0U)
#define CO_SRDO_TX                      (1U)
#define CO_SRDO_RX                      (2U)
#define CO_SRDO_VALID_MAGIC             (0xA5U)

/* macro for information about SRDO configuration error */
#define ERR_INFO(index, subindex, info) (((uint32_t)(index) << 16) | ((uint32_t)(subindex) << 8) | ((uint32_t)(info)))

static void
CO_SRDO_receive_normal(void* object, void* msg) {
    CO_SRDO_t* SRDO = (CO_SRDO_t*)object;
    uint8_t DLC = CO_CANrxMsg_readDLC(msg);
    const uint8_t* data = CO_CANrxMsg_readData(msg);

    if ((SRDO->informationDirection == CO_SRDO_RX) && (DLC >= SRDO->dataLength) && !CO_FLAG_READ(SRDO->CANrxNew[1])) {
        /* copy data into appropriate buffer and set 'new message' flag */
        (void)memcpy(SRDO->CANrxData[0], data, sizeof(SRDO->CANrxData[0]));
        CO_FLAG_SET(SRDO->CANrxNew[0]);

#if ((CO_CONFIG_SRDO)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
        /* Optional signal to RTOS, which can resume task, which handles SRDO. */
        if (SRDO->pFunctSignalPre != NULL) {
            SRDO->pFunctSignalPre(SRDO->functSignalObjectPre);
        }
#endif
    } else if (DLC < SRDO->dataLength) {
        SRDO->rxSrdoShort = true;
    } else { /* MISRA C 2004 14.10 */
    }
}

static void
CO_SRDO_receive_inverted(void* object, void* msg) {
    CO_SRDO_t* SRDO = (CO_SRDO_t*)object;
    uint8_t DLC = CO_CANrxMsg_readDLC(msg);
    const uint8_t* data = CO_CANrxMsg_readData(msg);

    if ((SRDO->informationDirection == CO_SRDO_RX) && (DLC >= SRDO->dataLength) && CO_FLAG_READ(SRDO->CANrxNew[0])) {
        /* copy data into appropriate buffer and set 'new message' flag */
        (void)memcpy(SRDO->CANrxData[1], data, sizeof(SRDO->CANrxData[1]));
        CO_FLAG_SET(SRDO->CANrxNew[1]);

#if ((CO_CONFIG_SRDO)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
        /* Optional signal to RTOS, which can resume task, which handles SRDO. */
        if (SRDO->pFunctSignalPre != NULL) {
            SRDO->pFunctSignalPre(SRDO->functSignalObjectPre);
        }
#endif
    } else if (DLC < SRDO->dataLength) {
        SRDO->rxSrdoShort = true;
    } else { /* MISRA C 2004 14.10 */
    }
}

/* Set OD object 13FE:00 to CO_SRDO_INVALID and clear configurationValid flag. */
static void
configurationValidUnset(CO_SRDOGuard_t* SRDOGuard) {
    if (SRDOGuard != NULL) {
        OD_IO_t* OD_IO = &SRDOGuard->OD_IO_configurationValid;
        uint8_t val = CO_SRDO_INVALID;
        OD_size_t dummy;

        SRDOGuard->configurationValid = false;

        OD_IO->write(&OD_IO->stream, &val, sizeof(val), &dummy);
    }
}

/*
 * Custom functions for reading or writing OD object.
 *
 * For more information see file CO_ODinterface.h, OD_IO_t.
 */
static ODR_t
OD_write_dummy(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
    (void)stream;
    (void)buf;
    if (countWritten != NULL) {
        *countWritten = count;
    }
    return ODR_OK;
}

static ODR_t
OD_read_dummy(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
    if ((buf == NULL) || (stream == NULL) || (countRead == NULL)) {
        return ODR_DEV_INCOMPAT;
    }

    if (count > stream->dataLength) {
        count = stream->dataLength;
    }

    (void)memset(buf, 0, count);

    *countRead = count;
    return ODR_OK;
}

#ifdef CO_CONFORMANCE_TEST_TOOL_ADAPTATION
static bool_t
OD_not_write_same_value(OD_stream_t* stream, const void* buf, OD_size_t count) {
    /* The conformance test tool does not recognize CANopen Safety and on all object
     * dictionaty tries to read and write the same value */
    OD_size_t countRead = 0;
    uint8_t bufRead[6] = {0};
    if (count > 6U) {
        return false;
    }
    ODR_t returnCode = OD_readOriginal(stream, bufRead, count, &countRead);
    if (returnCode != ODR_OK) {
        return false;
    }
    if (memcmp((const void*)(buf), (const void*)(bufRead), count) == 0) {
        return true;
    }
    return false;
}
#endif

static ODR_t
OD_read_SRDO_communicationParam(OD_stream_t* stream, void* buf, OD_size_t count, OD_size_t* countRead) {
    ODR_t returnCode = OD_readOriginal(stream, buf, count, countRead);

    /* When reading COB_ID, add Node-Id to the read value, if necessary */
    if ((returnCode == ODR_OK) && ((stream->subIndex == 5U) || (stream->subIndex == 6U)) && (*countRead == 4U)) {
        CO_SRDO_t* SRDO = stream->object;

        uint32_t value = CO_getUint32(buf);
        uint16_t defaultCOB_ID = SRDO->defaultCOB_ID + ((uint16_t)(stream->subIndex) - 5U);

        /* If default COB ID is used, then OD entry does not contain $NodeId. Add it here. */
        if ((value == defaultCOB_ID) && (SRDO->nodeId <= 64U)) {
            value += (uint32_t)SRDO->nodeId * 2U;
        }

        (void)CO_setUint32(buf, value);
    }

    return returnCode;
}

static ODR_t
OD_write_SRDO_communicationParam(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
    if ((stream == NULL) || (buf == NULL) || (countWritten == NULL) || (count > 4U)) {
        return ODR_DEV_INCOMPAT;
    }

#ifdef CO_CONFORMANCE_TEST_TOOL_ADAPTATION
    if (OD_not_write_same_value(stream, buf, count)) {
        return ODR_OK;
    }
#endif

    CO_SRDO_t* SRDO = stream->object;
    CO_SRDOGuard_t* SRDOGuard = SRDO->SRDOGuard;
    uint8_t bufCopy[4];
    (void)memcpy((void*)(bufCopy), (const void*)(buf), count);

    /* Writing Object Dictionary variable */
    if (SRDOGuard->NMTisOperational) {
        /* Data cannot be transferred or stored to the application because of the present device state. */
        return ODR_DATA_DEV_STATE;
    }

    if (stream->subIndex == 1U) { /* Information direction */
        uint8_t value = CO_getUint8(buf);
        if (value > 2U) {
            return ODR_INVALID_VALUE;
        }
        SRDO->informationDirection = value;
    } else if (stream->subIndex == 2U) { /* SCT */
        uint16_t value = CO_getUint16(buf);
        if (value < ((CO_CONFIG_SRDO_MINIMUM_DELAY / 1000U) + 1U)) {
            return ODR_INVALID_VALUE;
        }
    } else if (stream->subIndex == 3U) { /* SRVT */
        uint8_t value = CO_getUint8(buf);
        if (value == 0U) {
            return ODR_INVALID_VALUE;
        }
    } else if (stream->subIndex == 4U) { /* Transmission_type */
        uint8_t value = CO_getUint8(buf);
        if (value != 254U) {
            return ODR_INVALID_VALUE;
        }
    } else if ((stream->subIndex == 5U) || (stream->subIndex == 6U)) { /* COB_ID */
        uint32_t value = CO_getUint32(buf);
        uint16_t index = (uint16_t)(stream->subIndex) - 5U;
        uint16_t defaultCOB_ID = SRDO->defaultCOB_ID + index;

        /* check value range, the spec does not specify if COB-ID flags are allowed */
        if ((value < 0x101U) || (value > 0x180U) || ((value & 1U) == index)) {
            return ODR_INVALID_VALUE; /* Invalid value for parameter (download only). */
        }

        /* if default COB-ID is being written, write defaultCOB_ID without nodeId */
        if ((SRDO->nodeId <= 64U) && (value == (defaultCOB_ID + ((uint32_t)SRDO->nodeId * 2U)))) {
            value = defaultCOB_ID;
            (void)CO_setUint32(bufCopy, value);
        }
    } else { /* MISRA C 2004 14.10 */
    }

    /* set OD object 13FE:00 to CO_SRDO_INVALID */
    configurationValidUnset(SRDOGuard);

    /* write value to the original location in the Object Dictionary */
    return OD_writeOriginal(stream, bufCopy, count, countWritten);
}

static ODR_t
OD_write_SRDO_mappingParam(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
    if ((stream == NULL) || (buf == NULL) || (countWritten == NULL)
        || (stream->subIndex > CO_SRDO_MAX_MAPPED_ENTRIES)) {
        return ODR_DEV_INCOMPAT;
    }

#ifdef CO_CONFORMANCE_TEST_TOOL_ADAPTATION
    if (OD_not_write_same_value(stream, buf, count)) {
        return ODR_OK;
    }
#endif

    CO_SRDO_t* SRDO = stream->object;
    CO_SRDOGuard_t* SRDOGuard = SRDO->SRDOGuard;

    /* Writing Object Dictionary variable */
    if (SRDOGuard->NMTisOperational) {
        /* Data cannot be transferred or stored to the application because of the present device state. */
        return ODR_DATA_DEV_STATE;
    }

    /* SRDO must be disabled */
    if (SRDO->informationDirection != 0U) {
        return ODR_UNSUPP_ACCESS; /* Unsupported access to an object. */
    }

    /* numberOfMappedObjects */
    if (stream->subIndex == 0U) {
        uint8_t value = CO_getUint8(buf);
        /* only odd numbers are allowed */
        if ((value > CO_SRDO_MAX_MAPPED_ENTRIES) || ((value & 1U) != 0U)) {
            return ODR_MAP_LEN; /* Number and length of object to be mapped exceeds SRDO length. */
        }
        SRDO->mappedObjectsCount = value;
    }
    /* mapping objects */
    else {
        if (SRDO->mappedObjectsCount != 0U) {
            return ODR_UNSUPP_ACCESS;
        }
        /* No other checking is implemented here. Values are validated in the configuration function. */
    }

    /* set OD object 13FE:00 to CO_SRDO_INVALID */
    configurationValidUnset(SRDOGuard);

    /* write value to the original location in the Object Dictionary */
    return OD_writeOriginal(stream, buf, count, countWritten);
}

static ODR_t
OD_write_13FE(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
    if ((stream == NULL) || (stream->subIndex != 0U) || (buf == NULL) || (countWritten == NULL)) {
        return ODR_DEV_INCOMPAT;
    }

    CO_SRDOGuard_t* SRDOGuard = stream->object;

    if (SRDOGuard->NMTisOperational) {
        /* Data cannot be transferred or stored to the application because of the present device state. */
        return ODR_DATA_DEV_STATE;
    }

#ifdef CO_CONFORMANCE_TEST_TOOL_ADAPTATION
    if (OD_not_write_same_value(stream, buf, count)) {
        return ODR_OK;
    }
#endif

    uint8_t configurationValid = CO_getUint8(buf);
    if (configurationValid == CO_SRDO_VALID_MAGIC) {
        SRDOGuard->configurationValid = true;
    } else {
        SRDOGuard->configurationValid = false;
    }

    /* write value to the original location in the Object Dictionary */
    return OD_writeOriginal(stream, buf, count, countWritten);
}

static ODR_t
OD_write_13FF(OD_stream_t* stream, const void* buf, OD_size_t count, OD_size_t* countWritten) {
    if ((stream == NULL) || (stream->subIndex == 0U) || (buf == NULL) || (countWritten == NULL)) {
        return ODR_DEV_INCOMPAT;
    }

    CO_SRDOGuard_t* SRDOGuard = stream->object;

    if (SRDOGuard->NMTisOperational) {
        /* Data cannot be transferred or stored to the application because of the present device state. */
        return ODR_DATA_DEV_STATE;
    }

#ifdef CO_CONFORMANCE_TEST_TOOL_ADAPTATION
    if (OD_not_write_same_value(stream, buf, count)) {
        return ODR_OK;
    }
#endif

    /* set OD object 13FE:00 to CO_SRDO_INVALID */
    configurationValidUnset(SRDOGuard);

    /* write value to the original location in the Object Dictionary */
    return OD_writeOriginal(stream, buf, count, countWritten);
}

#if ((CO_CONFIG_SRDO)&CO_CONFIG_FLAG_CALLBACK_PRE) != 0
void
CO_SRDO_initCallbackPre(CO_SRDO_t* SRDO, void* object, void (*pFunctSignalPre)(void* object)) {
    if (SRDO != NULL) {
        SRDO->functSignalObjectPre = object;
        SRDO->pFunctSignalPre = pFunctSignalPre;
    }
}
#endif

CO_ReturnError_t
CO_SRDOGuard_init(CO_SRDOGuard_t* SRDOGuard, OD_entry_t* OD_13FE_configurationValid,
                  OD_entry_t* OD_13FF_safetyConfigurationSignature, uint32_t* errInfo) {
    ODR_t odRet;
    uint8_t configurationValid;

    /* verify arguments */
    if ((SRDOGuard == NULL) || (OD_13FE_configurationValid == NULL) || (OD_13FF_safetyConfigurationSignature == NULL)) {
        return CO_ERROR_ILLEGAL_ARGUMENT;
    }

    /* clear object */
    (void)memset(SRDOGuard, 0, sizeof(CO_SRDOGuard_t));

    SRDOGuard->OD_13FE_entry = OD_13FE_configurationValid;
    SRDOGuard->OD_13FF_entry = OD_13FF_safetyConfigurationSignature;

    /* Configure Object dictionary extensions */
    SRDOGuard->OD_13FE_extension.object = SRDOGuard;
    SRDOGuard->OD_13FE_extension.read = OD_readOriginal;
    SRDOGuard->OD_13FE_extension.write = OD_write_13FE;
    (void)OD_extension_init(OD_13FE_configurationValid, &SRDOGuard->OD_13FE_extension);

    SRDOGuard->OD_13FF_extension.object = SRDOGuard;
    SRDOGuard->OD_13FF_extension.read = OD_readOriginal;
    SRDOGuard->OD_13FF_extension.write = OD_write_13FF;
    (void)OD_extension_init(OD_13FF_safetyConfigurationSignature, &SRDOGuard->OD_13FF_extension);

    /* Configure SRDOGuard->OD_IO_configurationValid variable. It will be used for writing 0 to OD variable 13FE,00 */
    odRet = OD_getSub(OD_13FE_configurationValid, 0, &SRDOGuard->OD_IO_configurationValid, false);
    if ((odRet != ODR_OK) || (SRDOGuard->OD_IO_configurationValid.stream.dataLength != 1U)) {
        if (errInfo != NULL) {
            *errInfo = (((uint32_t)OD_getIndex(OD_13FE_configurationValid)) << 8) | 1U;
        }
        return CO_ERROR_OD_PARAMETERS;
    }

    if (OD_get_u8(OD_13FE_configurationValid, 0, &configurationValid, true) != ODR_OK) {
        *errInfo = (((uint32_t)OD_getIndex(OD_13FE_configurationValid)) << 8) | 1U;
        return CO_ERROR_OD_PARAMETERS;
    }
    if (configurationValid == CO_SRDO_VALID_MAGIC) {
        SRDOGuard->configurationValid = true;
    } else {
        SRDOGuard->configurationValid = false;
    }

    return CO_ERROR_NO;
}

CO_ReturnError_t
CO_SRDO_config(CO_SRDO_t* SRDO, uint8_t SRDO_Index, CO_SRDOGuard_t* SRDOGuard, uint32_t* errInfo) {
    CO_ReturnError_t ret = CO_ERROR_NO;
    uint32_t err = 0;
    bool_t configurationInProgress = false;

    /* variables will be retrieved from Object Dictionary */
    uint8_t cp_highestSubindexSupported = 0;
    uint8_t informationDirection = 0;
    uint16_t safetyCycleTime = 0;
    uint8_t safetyRelatedValidationTime = 0;
    uint8_t transmissionType = 0;
    uint32_t COB_ID1_normal = 0;
    uint32_t COB_ID2_inverted = 0;
    uint8_t configurationValid = 0;
    uint16_t crcSignatureFromOD = 0;
    uint8_t mappedObjectsCount = 0;
    uint32_t mapping[CO_SRDO_MAX_MAPPED_ENTRIES];

    /* Get variables from object Dictionary and verify it's structure. */
    if (err == 0U) {
        if (OD_get_u8(SRDOGuard->OD_13FE_entry, 0, &configurationValid, true) != ODR_OK) {
            err = ERR_INFO(0x13FEUL, 0, 1);
        } else if (OD_get_u16(SRDOGuard->OD_13FF_entry, SRDO_Index + 1U, &crcSignatureFromOD, true) != ODR_OK) {
            err = ERR_INFO(0x13FFUL, SRDO_Index + 1UL, 1);
        } else if (OD_get_u8(SRDO->OD_communicationParam_entry, 0, &cp_highestSubindexSupported, true) != ODR_OK) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 0, 1);
        } else if (OD_get_u8(SRDO->OD_communicationParam_entry, 1, &informationDirection, true) != ODR_OK) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 1, 1);
        } else if (OD_get_u16(SRDO->OD_communicationParam_entry, 2, &safetyCycleTime, true) != ODR_OK) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 2, 1);
        } else if (OD_get_u8(SRDO->OD_communicationParam_entry, 3, &safetyRelatedValidationTime, true) != ODR_OK) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 3, 1);
        } else if (OD_get_u8(SRDO->OD_communicationParam_entry, 4, &transmissionType, true) != ODR_OK) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 4, 1);
        } else if (OD_get_u32(SRDO->OD_communicationParam_entry, 5, &COB_ID1_normal, true) != ODR_OK) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 5, 1);
        } else if (OD_get_u32(SRDO->OD_communicationParam_entry, 6, &COB_ID2_inverted, true) != ODR_OK) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 6, 1);
        } else if (OD_get_u8(SRDO->OD_mappingParam_entry, 0, &mappedObjectsCount, true) != ODR_OK) {
            err = ERR_INFO(0x1381UL + SRDO_Index, 0, 1);
        } else {
            for (uint8_t i = 0; i < mappedObjectsCount; i++) {
                if (OD_get_u32(SRDO->OD_mappingParam_entry, i + 1U, &mapping[i], true) != ODR_OK) {
                    err = ERR_INFO(0x1381UL + SRDO_Index, i + 1UL, 1);
                    break;
                }
            }
        }

        /* if OD contains default COB_IDs, add node-id */
        if ((COB_ID1_normal == SRDO->defaultCOB_ID) && (COB_ID2_inverted == ((uint32_t)SRDO->defaultCOB_ID + 1UL))
            && (SRDO->nodeId <= 64U)) {
            uint32_t add = (uint32_t)SRDO->nodeId * 2U;
            COB_ID1_normal += add;
            COB_ID2_inverted += add;
        }

        /* If this fails, something is wrong with the Object Dictionary. Device have to be reprogrammed. */
        if (err != 0U) {
            ret = CO_ERROR_OD_PARAMETERS;
        }
    }

    /* If configurationValid is set and SRDO is valid, continue with further configuration */
    if ((err == 0U) && (configurationValid == CO_SRDO_VALID_MAGIC) && (informationDirection != CO_SRDO_INVALID)) {
        configurationInProgress = true;
    }

    /* Verify parameters from OD */
    if ((err == 0U) && configurationInProgress) {
        if (cp_highestSubindexSupported != 6U) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 0, 2);
        } else if (informationDirection > 3U) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 1, 2);
        } else if (safetyCycleTime < ((CO_CONFIG_SRDO_MINIMUM_DELAY / 1000U) + 1U)) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 2, 2);
        } else if (safetyRelatedValidationTime < 1U) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 3, 2);
        } else if (transmissionType != 254U) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 4, 2);
        } else if ((COB_ID1_normal < 0x101U) || ((COB_ID1_normal & 1U) == 0U)) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 5, 2);
        } else if (((COB_ID1_normal + 1U) != COB_ID2_inverted) || (COB_ID2_inverted > 0x180U)) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 6, 2);
        } else if ((mappedObjectsCount > CO_SRDO_MAX_MAPPED_ENTRIES) || ((mappedObjectsCount & 1U) != 0U)) {
            err = ERR_INFO(0x1381UL + SRDO_Index, 0, 2);
        } else {
            /* MISRA C 2004 14.10 */
        }
    }

    /* Verify CRC */
    if ((err == 0U) && configurationInProgress) {
        uint16_t crcResult = 0x0000;
        uint16_t tmp_u16;
        uint32_t tmp_u32;

        crcResult = crc16_ccitt(&informationDirection, 1, crcResult);
        tmp_u16 = CO_SWAP_16(safetyCycleTime);
        crcResult = crc16_ccitt((uint8_t*)&tmp_u16, 2, crcResult);
        crcResult = crc16_ccitt(&safetyRelatedValidationTime, 1, crcResult);
        tmp_u32 = CO_SWAP_32(COB_ID1_normal);
        crcResult = crc16_ccitt((uint8_t*)&tmp_u32, 4, crcResult);
        tmp_u32 = CO_SWAP_32(COB_ID2_inverted);
        crcResult = crc16_ccitt((uint8_t*)&tmp_u32, 4, crcResult);
        crcResult = crc16_ccitt(&mappedObjectsCount, 1, crcResult);
        for (uint8_t i = 0; i < mappedObjectsCount; i++) {
            uint8_t crcsubindex = i + 1U;
            crcResult = crc16_ccitt(&crcsubindex, 1, crcResult);
            tmp_u32 = CO_SWAP_32(mapping[i]);
            crcResult = crc16_ccitt((uint8_t*)&tmp_u32, 4, crcResult);
        }

        if (crcResult != crcSignatureFromOD) {
            err = ERR_INFO(0x13FFUL, SRDO_Index + 1UL, 3);
        }
    }

    /* Configure mappings */
    if ((err == 0U) && configurationInProgress) {
        CO_SRDO_size_t srdoDataLength[2] = {0, 0};

        for (uint8_t i = 0; i < mappedObjectsCount; i++) {
            uint8_t plain_inverted = i % 2U;
            uint32_t map = mapping[i];
            uint16_t index = (uint16_t)(map >> 16);
            uint8_t subIndex = (uint8_t)(map >> 8);
            uint8_t mappedLengthBits = (uint8_t)map;
            uint8_t mappedLength = mappedLengthBits >> 3;
            OD_IO_t* OD_IO = &SRDO->OD_IO[i];

            /* total SRDO length can not be more than CO_SRDO_MAX_SIZE bytes */
            if (mappedLength > CO_SRDO_MAX_SIZE) {
                err = ERR_INFO(0x1381UL + SRDO_Index, i + 1UL, 4);
            }
            /* is there a reference to the dummy entry */
            else if ((index < 0x20U) && (subIndex == 0U)) {
                OD_stream_t* stream = &OD_IO->stream;
                (void)memset(stream, 0, sizeof(OD_stream_t));
                stream->dataLength = mappedLength;
                stream->dataOffset = mappedLength;
                OD_IO->read = OD_read_dummy;
                OD_IO->write = OD_write_dummy;
            }
            /* find entry in the Object Dictionary */
            else {
                OD_IO_t OD_IOcopy;
                OD_entry_t* entry = OD_find(SRDO->OD, index);
                ODR_t odRet = OD_getSub(entry, subIndex, &OD_IOcopy, false);
                if (odRet != ODR_OK) {
                    err = ERR_INFO(0x1381UL + SRDO_Index, i + 1UL, 5);
                } else {
                    /* verify access attributes, byte alignment and length */
                    OD_attr_t testAttribute = (informationDirection == CO_SRDO_RX) ? (OD_attr_t)(ODA_RSRDO)
                                                                                   : (OD_attr_t)(ODA_TSRDO);
                    if (((OD_IOcopy.stream.attribute & testAttribute) == 0U) || ((mappedLengthBits & 0x07U) != 0U)
                        || (OD_IOcopy.stream.dataLength < mappedLength)) {
                        err = ERR_INFO(0x1381UL + SRDO_Index, i + 1UL, 6);
                    }

                    /* Copy values and store mappedLength temporary. */
                    *OD_IO = OD_IOcopy;
                    OD_IO->stream.dataOffset = mappedLength;
                    srdoDataLength[plain_inverted] += mappedLength;
                }
            }
            if (err != 0U) {
                break;
            }
        } /* for (uint8_t i = 0; i < mappedObjectsCount; i++) */

        if (err == 0U) {
            if (srdoDataLength[0] != srdoDataLength[1]) {
                err = ERR_INFO(0x1381UL + SRDO_Index, 0, 7);
            } else if ((srdoDataLength[0] == 0U) || (srdoDataLength[0] > CO_SRDO_MAX_SIZE)) {
                err = ERR_INFO(0x1381UL + SRDO_Index, 0, 8);
            } else {
                SRDO->dataLength = srdoDataLength[0];
                SRDO->mappedObjectsCount = mappedObjectsCount;
            }
        }
    }

    /* Configure CAN tx buffers */
    if ((err == 0U) && configurationInProgress && (informationDirection == CO_SRDO_TX)) {
        /* Normal Configuration */
        SRDO->CANtxBuff[0] = CO_CANtxBufferInit(SRDO->CANdevTx[0], SRDO->CANdevTxIdx[0], (uint16_t)COB_ID1_normal,
                                                false, SRDO->dataLength, false);

        if (SRDO->CANtxBuff[0] == NULL) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 5, 10);
        }

        /* Inverted Configuration */
        SRDO->CANtxBuff[1] = CO_CANtxBufferInit(SRDO->CANdevTx[1], SRDO->CANdevTxIdx[1], (uint16_t)COB_ID2_inverted,
                                                false, SRDO->dataLength, false);

        if (SRDO->CANtxBuff[1] == NULL) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 6, 10);
        }
    }

    /* Configure CAN rx buffers */
    if ((err == 0U) && configurationInProgress && (informationDirection == CO_SRDO_RX)) {
        /* Normal Configuration */
        ret = CO_CANrxBufferInit(SRDO->CANdevRx[0], SRDO->CANdevRxIdx[0], (uint16_t)COB_ID1_normal, 0x7FF, false,
                                 (void*)SRDO, CO_SRDO_receive_normal);

        if (ret != CO_ERROR_NO) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 5, 11);
        }

        /* Inverted Configuration */
        ret = CO_CANrxBufferInit(SRDO->CANdevRx[1], SRDO->CANdevRxIdx[1], (uint16_t)COB_ID2_inverted, 0x7FF, false,
                                 (void*)SRDO, CO_SRDO_receive_inverted);

        if (ret != CO_ERROR_NO) {
            err = ERR_INFO(0x1301UL + SRDO_Index, 6, 11);
        }
    }

    /* Configure remaining variables */
    if (err == 0U) {
        SRDO->informationDirection = informationDirection;
        SRDO->cycleTime_us = (uint32_t)safetyCycleTime * 1000U;
        SRDO->validationTime_us = (uint32_t)safetyRelatedValidationTime * 1000U;
    } else {
        if (ret == CO_ERROR_NO) {
            CO_errorReport(SRDO->em, CO_EM_SRDO_CONFIGURATION, CO_EMC_DATA_SET, err);
            configurationValidUnset(SRDO->SRDOGuard);
        }
    }

    if (errInfo != NULL) {
        *errInfo = err;
    }

    return ret;
}

CO_ReturnError_t
CO_SRDO_init(CO_SRDO_t* SRDO, uint8_t SRDO_Index, CO_SRDOGuard_t* SRDOGuard, OD_t* OD, CO_EM_t* em, uint8_t nodeId,
             uint16_t defaultCOB_ID, OD_entry_t* OD_130x_SRDOCommPar, OD_entry_t* OD_138x_SRDOMapPar,
             CO_CANmodule_t* CANdevRxNormal, CO_CANmodule_t* CANdevRxInverted, uint16_t CANdevRxIdxNormal,
             uint16_t CANdevRxIdxInverted, CO_CANmodule_t* CANdevTxNormal, CO_CANmodule_t* CANdevTxInverted,
             uint16_t CANdevTxIdxNormal, uint16_t CANdevTxIdxInverted, uint32_t* errInfo) {

    CO_ReturnError_t ret = CO_ERROR_NO;

    /* verify arguments */
    if ((SRDO == NULL) || (SRDOGuard == NULL) || (OD == NULL) || (em == NULL) || (OD_130x_SRDOCommPar == NULL)
        || (OD_138x_SRDOMapPar == NULL) || (CANdevRxNormal == NULL) || (CANdevRxInverted == NULL)
        || (CANdevTxNormal == NULL) || (CANdevTxInverted == NULL)) {
        ret = CO_ERROR_ILLEGAL_ARGUMENT;
    }
    /* clear object and configure some object variables */
    else {
        (void)memset(SRDO, 0, sizeof(CO_SRDO_t));

        SRDO->SRDOGuard = SRDOGuard;
        SRDO->OD = OD;
        SRDO->em = em;
        SRDO->defaultCOB_ID = defaultCOB_ID;
        SRDO->nodeId = nodeId;
        SRDO->CANdevTx[0] = CANdevTxNormal;
        SRDO->CANdevTx[1] = CANdevTxInverted;
        SRDO->CANdevRx[0] = CANdevRxNormal;
        SRDO->CANdevRx[1] = CANdevRxInverted;

        SRDO->CANdevTxIdx[0] = CANdevTxIdxNormal;
        SRDO->CANdevTxIdx[1] = CANdevTxIdxInverted;
        SRDO->CANdevRxIdx[0] = CANdevRxIdxNormal;
        SRDO->CANdevRxIdx[1] = CANdevRxIdxInverted;

        SRDO->OD_communicationParam_entry = OD_130x_SRDOCommPar;
        SRDO->OD_mappingParam_entry = OD_138x_SRDOMapPar;

        /* Configure Object dictionary entry at index 0x1301+ */
        SRDO->OD_communicationParam_ext.object = SRDO;
        SRDO->OD_communicationParam_ext.read = OD_read_SRDO_communicationParam;
        SRDO->OD_communicationParam_ext.write = OD_write_SRDO_communicationParam;
        (void)OD_extension_init(OD_130x_SRDOCommPar, &SRDO->OD_communicationParam_ext);

        /* Configure Object dictionary entry at index 0x1381+ */
        SRDO->OD_mappingParam_extension.object = SRDO;
        SRDO->OD_mappingParam_extension.read = OD_readOriginal;
        SRDO->OD_mappingParam_extension.write = OD_write_SRDO_mappingParam;
        (void)OD_extension_init(OD_138x_SRDOMapPar, &SRDO->OD_mappingParam_extension);

        ret = CO_SRDO_config(SRDO, SRDO_Index, SRDOGuard, errInfo);
    }

    return ret;
}

CO_ReturnError_t
CO_SRDO_requestSend(CO_SRDO_t* SRDO) {
    CO_ReturnError_t ret;

    if (SRDO->SRDOGuard->NMTisOperational == false) {
        ret = CO_ERROR_WRONG_NMT_STATE;
    } else if (SRDO->SRDOGuard->configurationValid == false) {
        ret = CO_ERROR_OD_PARAMETERS;
    } else if (SRDO->informationDirection != CO_SRDO_TX) {
        ret = CO_ERROR_TX_UNCONFIGURED;
    } else if (SRDO->nextIsNormal == false) {
        ret = CO_ERROR_TX_BUSY;
    } else {
        SRDO->cycleTimer = 0;
        ret = CO_ERROR_NO;
    }

    return ret;
}

CO_SRDO_state_t
CO_SRDO_process(CO_SRDO_t* SRDO, uint32_t timeDifference_us, uint32_t* timerNext_us, bool_t NMTisOperational) {
    (void)timerNext_us; /* may be unused */

    if (NMTisOperational && (SRDO->informationDirection != CO_SRDO_INVALID) && SRDO->SRDOGuard->configurationValid
        && (SRDO->internalState >= CO_SRDO_state_unknown)) {
        SRDO->cycleTimer = (SRDO->cycleTimer > timeDifference_us) ? (SRDO->cycleTimer - timeDifference_us) : 0U;
        SRDO->invertedDelay = (SRDO->invertedDelay > timeDifference_us) ? (SRDO->invertedDelay - timeDifference_us)
                                                                        : 0U;
        SRDO->validationTimer = (SRDO->validationTimer > timeDifference_us)
                                    ? (SRDO->validationTimer - timeDifference_us)
                                    : 0U;

        /* Detect transition to NMT operational */
        if (!SRDO->NMTisOperationalPrevious) {
            SRDO->cycleTimer = (SRDO->informationDirection == CO_SRDO_TX)
                                   ? ((uint32_t)SRDO->nodeId * 500U) /* 0.5ms * node-ID delay */
                                   : SRDO->cycleTime_us;
            SRDO->validationTimer = SRDO->cycleTime_us;
            SRDO->internalState = CO_SRDO_state_initializing;
            SRDO->nextIsNormal = true;
        }

        if (SRDO->internalState <= CO_SRDO_state_unknown) {
            SRDO->internalState = CO_SRDO_state_error_internal; /* should not happen */
        } else if (SRDO->informationDirection == CO_SRDO_TX) {
            if (SRDO->nextIsNormal) {
                if (SRDO->cycleTimer == 0U) {
                    uint8_t* dataSRDO[2] = {&SRDO->CANtxBuff[0]->data[0], &SRDO->CANtxBuff[1]->data[0]};
                    size_t verifyLength[2] = {0, 0};

                    /* copy mapped data from Object Dictionary into CAN buffers */
                    for (uint8_t i = 0; i < SRDO->mappedObjectsCount; i++) {
                        uint8_t plain_inverted = i % 2U;
                        OD_IO_t* OD_IO = &SRDO->OD_IO[i];
                        OD_stream_t* stream = &OD_IO->stream;

                        /* get mappedLength from temporary storage */
                        uint8_t mappedLength = (uint8_t)stream->dataOffset;

                        /* additional safety check */
                        verifyLength[plain_inverted] += mappedLength;
                        if (verifyLength[plain_inverted] > CO_SRDO_MAX_SIZE) {
                            break;
                        }

                        /* length of OD variable may be larger than mappedLength */
                        OD_size_t ODdataLength = stream->dataLength;
                        if (ODdataLength > CO_SRDO_MAX_SIZE) {
                            ODdataLength = CO_SRDO_MAX_SIZE;
                        }
                        /* If mappedLength is smaller than ODdataLength, use auxiliary buffer */
                        uint8_t buf[CO_SRDO_MAX_SIZE];
                        uint8_t* dataSRDOCopy;
                        if (ODdataLength > mappedLength) {
                            (void)memset(buf, 0, sizeof(buf));
                            dataSRDOCopy = buf;
                        } else {
                            dataSRDOCopy = dataSRDO[plain_inverted];
                        }

                        /* Set stream.dataOffset to zero, perform OD_IO.read()
                         * and store mappedLength back to stream.dataOffset */
                        stream->dataOffset = 0;
                        OD_size_t countRd;
                        OD_IO->read(stream, dataSRDOCopy, ODdataLength, &countRd);
                        stream->dataOffset = mappedLength;

                        /* swap multibyte data if big-endian */
#ifdef CO_BIG_ENDIAN
                        if ((stream->attribute & ODA_MB) != 0) {
                            uint8_t* lo = dataSRDOCopy;
                            uint8_t* hi = dataSRDOCopy + ODdataLength - 1;
                            while (lo < hi) {
                                uint8_t swap = *lo;
                                *lo++ = *hi;
                                *hi-- = swap;
                            }
                        }
#endif

                        /* If auxiliary buffer, copy it to the SRDO */
                        if (ODdataLength > mappedLength) {
                            (void)memcpy(dataSRDO[plain_inverted], buf, mappedLength);
                        }

                        dataSRDO[plain_inverted] += mappedLength;
                    }

                    if ((verifyLength[0] != verifyLength[1]) || (verifyLength[0] > CO_SRDO_MAX_SIZE)
                        || (verifyLength[0] != SRDO->dataLength)) {
                        SRDO->internalState = CO_SRDO_state_error_internal; /* should not happen */
                    } else {
                        bool_t data_ok = true;
#if ((CO_CONFIG_SRDO)&CO_CONFIG_SRDO_CHECK_TX) != 0
                        /* check data before sending (optional) */
                        for (uint8_t i = 0; i < SRDO->dataLength; i++) {
                            if ((uint8_t)(~SRDO->CANtxBuff[0]->data[i]) != SRDO->CANtxBuff[1]->data[i]) {
                                SRDO->internalState = CO_SRDO_state_error_txNotInverted;
                                data_ok = false;
                                break;
                            }
                        }
#endif
                        if (data_ok) {
                            if (CO_CANsend(SRDO->CANdevTx[0], SRDO->CANtxBuff[0]) == CO_ERROR_NO) {
                                SRDO->cycleTimer =
                                    SRDO->cycleTime_us; /* cycleTime_us is verified, result can't be <0 */
                                SRDO->invertedDelay = CO_CONFIG_SRDO_MINIMUM_DELAY;
                                SRDO->nextIsNormal = false;
                                SRDO->internalState = CO_SRDO_state_communicationEstablished;
                            } else {
                                SRDO->internalState = CO_SRDO_state_error_txFail;
                            }
                        }
                    }
                }
            } else {
                if (SRDO->invertedDelay == 0U) {
                    if (CO_CANsend(SRDO->CANdevTx[1], SRDO->CANtxBuff[1]) == CO_ERROR_NO) {
                        SRDO->nextIsNormal = true;
                    } else {
                        SRDO->internalState = CO_SRDO_state_error_txFail;
                    }
                }
            }

#if ((CO_CONFIG_SRDO)&CO_CONFIG_FLAG_TIMERNEXT) != 0
            if (timerNext_us != NULL) {
                if (*timerNext_us > SRDO->cycleTimer) {
                    *timerNext_us = SRDO->cycleTimer; /* Schedule for the next message timer */
                }
            }
#endif
        } else { /* CO_SRDO_RX */
            /* verify error from receive function */
            if (SRDO->rxSrdoShort) {
                CO_errorReport(SRDO->em, CO_EM_RPDO_WRONG_LENGTH, CO_EMC_PDO_LENGTH, 0);
                SRDO->internalState = CO_SRDO_state_error_rxShort;
            } else if (CO_FLAG_READ(SRDO->CANrxNew[SRDO->nextIsNormal ? 0 : 1])) {
                /* normal message received ? */
                if (SRDO->nextIsNormal) {
                    SRDO->validationTimer = SRDO->validationTime_us;
                    SRDO->nextIsNormal = false;
                }
                /* inverted message received */
                else {
                    SRDO->cycleTimer = SRDO->cycleTime_us;
                    SRDO->validationTimer = SRDO->cycleTime_us;
                    SRDO->nextIsNormal = true;

                    uint8_t* dataSRDO[2] = {&SRDO->CANrxData[0][0], &SRDO->CANrxData[1][0]};
                    bool_t data_ok = true;

                    /* Verify, if normal and inverted data matches properly */
                    for (uint8_t i = 0; i < SRDO->dataLength; i++) {
                        if ((uint8_t)(~dataSRDO[0][i]) != dataSRDO[1][i]) {
                            data_ok = false;
                            SRDO->internalState = CO_SRDO_state_error_rxNotInverted;
                            break;
                        }
                    }

                    /* copy data from CAN messages into mapped data from Object Dictionary */
                    if (data_ok) {
                        size_t verifyLength[2] = {0, 0};
                        for (uint8_t i = 0; i < SRDO->mappedObjectsCount; i++) {
                            uint8_t plain_inverted = i % 2U;
                            OD_IO_t* OD_IO = &SRDO->OD_IO[i];
                            OD_stream_t* stream = &OD_IO->stream;

                            /* get mappedLength from temporary storage */
                            OD_size_t* dataOffset = &stream->dataOffset;
                            uint8_t mappedLength = (uint8_t)(*dataOffset);

                            /* additional safety check */
                            verifyLength[plain_inverted] += mappedLength;
                            if (verifyLength[plain_inverted] > CO_SRDO_MAX_SIZE) {
                                break;
                            }

                            /* length of OD variable may be larger than mappedLength */
                            OD_size_t ODdataLength = stream->dataLength;
                            if (ODdataLength > CO_SRDO_MAX_SIZE) {
                                ODdataLength = CO_SRDO_MAX_SIZE;
                            }
                            /* Prepare data for writing into OD variable. If mappedLength
                             * is smaller than ODdataLength, then use auxiliary buffer */
                            uint8_t buf[CO_SRDO_MAX_SIZE];
                            uint8_t* dataOD;
                            if (ODdataLength > mappedLength) {
                                (void)memset(buf, 0, sizeof(buf));
                                (void)memcpy(buf, dataSRDO[plain_inverted], mappedLength);
                                dataOD = buf;
                            } else {
                                dataOD = dataSRDO[plain_inverted];
                            }

                            /* swap multibyte data if big-endian */
#ifdef CO_BIG_ENDIAN
                            if ((stream->attribute & ODA_MB) != 0) {
                                uint8_t* lo = dataOD;
                                uint8_t* hi = dataOD + ODdataLength - 1;
                                while (lo < hi) {
                                    uint8_t swap = *lo;
                                    *lo++ = *hi;
                                    *hi-- = swap;
                                }
                            }
#endif

                            /* Set stream.dataOffset to zero, perform OD_IO.write()
                             * and store mappedLength back to stream.dataOffset */
                            *dataOffset = 0;
                            OD_size_t countWritten;
                            OD_IO->write(&OD_IO->stream, dataOD, ODdataLength, &countWritten);
                            *dataOffset = mappedLength;

                            dataSRDO[plain_inverted] += mappedLength;
                        } /* for (uint8_t i = 0; i < SRDO->mappedObjectsCount; i++) */

                        /* safety check, this should not happen */
                        if ((verifyLength[0] != verifyLength[1]) || (verifyLength[0] > CO_SRDO_MAX_SIZE)
                            || (verifyLength[0] != SRDO->dataLength)) {
                            SRDO->internalState = CO_SRDO_state_error_internal;
                        } else {
                            SRDO->internalState = CO_SRDO_state_communicationEstablished;
                        }
                    } /* if (data_ok) { */

                    CO_FLAG_CLEAR(SRDO->CANrxNew[0]);
                    CO_FLAG_CLEAR(SRDO->CANrxNew[1]);
                }    /* inverted message received */
            } else { /* MISRA C 2004 14.10 */
            }

            /* verify timeouts */
            if (SRDO->cycleTimer == 0U) {
                SRDO->internalState = CO_SRDO_state_error_rxTimeoutSCT;
            } else if (SRDO->validationTimer == 0U) {
                SRDO->internalState = CO_SRDO_state_error_rxTimeoutSRVT;
            } else {
                /* MISRA C 2004 14.10 */
            }
#if ((CO_CONFIG_SRDO)&CO_CONFIG_FLAG_TIMERNEXT) != 0
            if (timerNext_us != NULL) {
                if (*timerNext_us > SRDO->cycleTimer) {
                    *timerNext_us = SRDO->cycleTimer; /* Schedule for the next message timer */
                }
                if (*timerNext_us > SRDO->validationTimer) {
                    *timerNext_us = SRDO->validationTimer; /* Schedule for the next message timer */
                }
            }
#endif
        } /* CO_SRDO_RX */
    }     /* if (NMTisOperational && ... */
    else {
        CO_FLAG_CLEAR(SRDO->CANrxNew[0]);
        CO_FLAG_CLEAR(SRDO->CANrxNew[1]);
        if (!SRDO->SRDOGuard->configurationValid) {
            SRDO->internalState = CO_SRDO_state_error_configuration;
        } else if (!NMTisOperational) {
            SRDO->internalState = CO_SRDO_state_nmtNotOperational;
        } else if (SRDO->informationDirection == CO_SRDO_INVALID) {
            SRDO->internalState = CO_SRDO_state_deleted;
        } else {
            /* keep internalState unchanged */
            /* MISRA C 2004 14.10 */
        }
    }

    SRDO->NMTisOperationalPrevious = NMTisOperational;
    SRDO->SRDOGuard->NMTisOperational = NMTisOperational;

    return SRDO->internalState;
}

#endif /* (CO_CONFIG_SRDO) & CO_CONFIG_SRDO_ENABLE */
