/*
 * Copyright (c) 2022, Shenzhen CVA Innovation CO.,LTD
 * All rights reserved.
 *
 * Shenzhen CVA Innovation CO.,LTD (CVA chip) is supplying this file for use
 * exclusively with CVA's microcontroller products. This file can be freely
 * distributed within development tools that are supporting such microcontroller
 * products.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS".  NO WARRANTIES, WHETHER EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE.
 * CVA SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL,
 * OR CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
 */

/*******************************************************************************
 * the includes
 ******************************************************************************/

#include <string.h>
#include "uds_service27.h"

/*******************************************************************************
 * the defines
 ******************************************************************************/

#define SUPPORT_LEVEL_NUM   4 /* supported security level number */

#define MAX_ALLOW_ERROR_NUM 5 /* the max allow number of key error */

#define STARTUP_DELAY_MS    2000  /* the service allow access time delay after UDS start */
#define ERR_EXCEED_DELAY_MS 10000 /* the service allow access time delay after error number exceed */

/*******************************************************************************
 * the typedefs
 ******************************************************************************/

typedef struct _SecurityLevel_Param_
{
    uint8_t level;
    uint8_t keyErrorCnt;
    uint8_t maxErrNum;
    int64_t MaxErrTimeStamp;
} SecurityLevel_Param;

/*******************************************************************************
 * the globals
 ******************************************************************************/

SecurityLevel_Param scrtParam[SUPPORT_LEVEL_NUM] = {
    {UDS_SA_LEVEL_1,  0, MAX_ALLOW_ERROR_NUM, 0},
    {UDS_SA_LEVEL_3,  0, MAX_ALLOW_ERROR_NUM, 0},
    {UDS_SA_LEVEL_11, 0, MAX_ALLOW_ERROR_NUM, 0},
    {UDS_SA_LEVEL_63, 0, MAX_ALLOW_ERROR_NUM, 0},
};

/*******************************************************************************
 * the constants
 ******************************************************************************/

/*******************************************************************************
 * the functions
 ******************************************************************************/

static void UdsSecurityAccess_GenerateSeed(UdsType *obj, uint8_t newLevel, uint8_t *outData, uint8_t outBufsize, uint16_t *outLen)
{
    /* seed is defined by user, It's usually a random value */
    uint8_t seed[4] = {1, 2, 3, 4};

    if(outBufsize < sizeof(seed))
    {
        return;
    }

    *outLen = sizeof(seed);
    if(newLevel == obj->securityLevel)
    {
        memset(outData, 0, sizeof(seed));
    }
    else
    {
        memcpy(outData, seed, sizeof(seed));
    }
}

static bool UdsSecurityAccess_ValidateKey(uint8_t seedLevel, const uint8_t *seed, uint16_t seedLen, const uint8_t *key, uint16_t keyLen)
{
    /* The encryption algorithm is provided by the vehicle manufacturer */

    return true;
}

void UdsService27_SecurityAccess(UdsType *obj, const uint8_t msgBuf[], uint16_t msgLen)
{
    static uint8_t  seedLevel    = 0;
    static uint8_t  seedValue[8] = {0};
    static uint16_t seedLen      = 0;
    uint8_t         subFunction;
    uint8_t         udx          = 0;
    uint8_t         rspBuffer[8] = {0};
    uint16_t        dataLen = 0, remainSize = 0;
    bool            ret = false;

    if(msgLen < obj->seviceTable[obj->curServiceIdx].minLen)
    {
        Uds_NegativeResponse(obj, 0x27, NRC_INVALID_MESSAGE_LENGTH_OR_FORMAT);
        return;
    }

    if(obj->session == UDS_SESSION_DEFAULT)
    {
        Uds_NegativeResponse(obj, 0x27, NRC_CONDITIONS_NOT_CORRECT);
        return;
    }

    subFunction  = UDS_GET_SUB_FUNCTION(msgBuf[1]);
    rspBuffer[0] = UDS_GET_POSITIVE_RSP(0x27);
    rspBuffer[1] = subFunction;
    if(subFunction % 2)
    {
        seedLevel = 0;
        for(udx = 0; udx < SUPPORT_LEVEL_NUM; udx++)
        {
            if(subFunction == scrtParam[udx].level)
            {
                if(scrtParam[udx].keyErrorCnt > scrtParam[udx].maxErrNum)
                {
                    if(obj->timeStamp_ms - scrtParam[udx].MaxErrTimeStamp < ERR_EXCEED_DELAY_MS)
                    {
                        Uds_NegativeResponse(obj, 0x27, NRC_EXCEEDED_NUMBER_OF_ATTEMPTS);
                        return;
                    }
                    else
                    {
                        scrtParam[udx].keyErrorCnt = 0;
                    }
                }
                break;
            }
        }

        if(udx >= SUPPORT_LEVEL_NUM)
        {
            Uds_NegativeResponse(obj, 0x27, NRC_REQUEST_OUT_OF_RANGE);
            return;
        }

        remainSize = sizeof(rspBuffer) - 2;
        UdsSecurityAccess_GenerateSeed(obj, subFunction, &rspBuffer[2], remainSize, &dataLen);
        if(dataLen > 0)
        {
            Uds_PositiveResponse(obj, rspBuffer, dataLen + 2);

            seedLevel = subFunction;
            seedLen   = dataLen;
            memcpy(seedValue, &rspBuffer[2], dataLen);
        }
        else
        {
            Uds_NegativeResponse(obj, 0x27, NRC_REQUEST_OUT_OF_RANGE);
        }
    }
    else
    {
        if(subFunction != seedLevel + 1)
        {
            Uds_NegativeResponse(obj, 0x27, NRC_REQUEST_SEQUENCE_ERROR);
            return;
        }

        /* polyspace-begin DEFECT:DEAD_CODE [No action planned:Low] "Still reserve though it's maybe unreachable" */
        ret = UdsSecurityAccess_ValidateKey(seedLevel, seedValue, seedLen, &msgBuf[2], msgLen - 2);
        if(ret == true)
        {
            Uds_PositiveResponse(obj, rspBuffer, 2);

            seedLevel          = 0;
            obj->securityLevel = subFunction - 1;

            for(udx = 0; udx < SUPPORT_LEVEL_NUM; udx++)
            {
                if(subFunction == scrtParam[udx].level)
                {
                    scrtParam[udx].keyErrorCnt = 0;
                }
            }
        }
        else
        {
            Uds_NegativeResponse(obj, 0x27, NRC_INVALID_KEY);

            for(udx = 0; udx < SUPPORT_LEVEL_NUM; udx++)
            {
                if(seedLevel == scrtParam[udx].level)
                {
                    scrtParam[udx].keyErrorCnt++;
                    if(scrtParam[udx].keyErrorCnt > scrtParam[udx].maxErrNum)
                    {
                        scrtParam[udx].MaxErrTimeStamp = obj->timeStamp_ms;
                    }
                }
            }
        }
        /* polyspace-end DEFECT:DEAD_CODE [No action planned:Low] "Still reserve though it's maybe unreachable" */
    }
}
