/**
 * @file ex_gpstorage.c
 * @author NXP Semiconductors
 * @version 1.0
 * @par License
 *
 * Copyright 2016 NXP
 * SPDX-License-Identifier: Apache-2.0
 *
 * @par Description
 * Example invocations of general purpose storage related functionality of the A71CH
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

// #include "a70cm_configuration.h"
#include "a71ch_ex.h"
#include "ax_util.h"
#include "a71_debug.h"
#include "sm_types.h"
#include "sm_apdu.h"
#include "tst_sm_util.h"
#include "tst_a71ch_util.h"
#include "nxLog_hostLib.h"

#define EX_RND_DATA         0x00 //!< Bit position 0 decides on random or incrementing data payload
#define EX_INC_DATA         0x01 //!< Bit position 0 decides on random or incrementing data payload
#define EX_ALL_PACKET_SIZES 0x02 //!< Bit position 1 decides on all or a selection of packetsizes

/******************************************************************************
* test
*****************************************************************************/
static U8 exGpStoragePacketSize(U8 initMode, U8 tstMode);
static U8 exGpStorageFreeze(U8 initMode, U8 tstMode);
static U8 exMonotonicCounter(U8 initMode);

/**
 * Demonstrate general purpose storage functionality
 * - ::exGpStoragePacketSize
 * - ::exGpStorageFreeze
 *
 * Demonstrate monotonic counter usage
 * - ::exMonotonicCounter
 */
U8 exGPStorage(U8 tstMode)
{
    U8 result = 1;
    LOG_I( "-----------Start exGPStorage(%s)------------",
        ((tstMode & EXTENDED_TEST) == EXTENDED_TEST) ? "Extended Test" : "Fast Test");

    // No channel encryption
    DEV_ClearChannelState();

    result &= exMonotonicCounter(INIT_MODE_RESET);

    result &= exGpStoragePacketSize(INIT_MODE_RESET, EX_RND_DATA);

    result &= exGpStoragePacketSize(INIT_MODE_NO_RESET, EX_INC_DATA);

    if ((tstMode & EXTENDED_TEST) == EXTENDED_TEST)
    {
        result &= exGpStoragePacketSize(INIT_MODE_NO_RESET, EX_RND_DATA|EX_ALL_PACKET_SIZES);
    }

    result &= exGpStorageFreeze(INIT_MODE_RESET, EX_INC_DATA);

    // Use channel encryption
    result &= exMonotonicCounter(INIT_MODE_RESET_DO_SCP03);
    result &= exGpStoragePacketSize(INIT_MODE_NO_RESET, EX_RND_DATA);

    LOG_I( "-----------End exGPStorage(%s), result = %s------------",
        ((tstMode & EXTENDED_TEST) == EXTENDED_TEST) ? "Extended Test" : "Fast Test",
        ((result == 1)? "OK": "FAILED"));

    return result;
}

/**
 * Demonstrate monotonic counter usage.
 *
 * @param[in] initMode Visit the documentation of ::a71chInitModule for more information on this parameter
 */
static U8 exMonotonicCounter(U8 initMode)
{
    U8 result = 1;
    U8 index = 0;
    U16 err;

    U32 tgtValue[] = {0x00000004, 0x00000014, 0x00000024, 0x00000034};
    U32 readValue = 0;

    LOG_I("-----------Start exMonotonicCounter(%s)------------",
        getInitModeAsString(initMode));

    // Initialize the A71CH (Debug mode restrictions may apply)
    result &= a71chInitModule(initMode);
    assert(result);

    // Check all counters default to value 0
    for (index=0; index<A71CH_COUNTER_MAX; index++)
    {
        LOG_I("A71_GetCounter(index=0x%02x)", index);
        err = A71_GetCounter(index, &readValue);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to retrieve counter value");
        if (err == SW_OK)
        {
            if (readValue != 0)
            {
                LOG_E("Failed to retrieve expected counter value (index=0x%02x): %ld != 0", index, readValue);
                result &= 0;
            }
        }
    }

    // Set all counters to a counter specific target value
    for (index=0; index<A71CH_COUNTER_MAX; index++)
    {
        LOG_I("A71_SetCounter(index=0x%02x, %ld)", index, tgtValue[index]);
        err = A71_SetCounter(index, tgtValue[index]);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to set counter value");
    }

    // Verify all counters were set to target value
    for (index=0; index<A71CH_COUNTER_MAX; index++)
    {
        LOG_I("A71_GetCounter(index=0x%02x)", index);
        err = A71_GetCounter(index, &readValue);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to retrieve counter value");
        if (err == SW_OK)
        {
            if (readValue != tgtValue[index])
            {
                LOG_E("Failed to retrieve expected counter value (index=0x%02x): %ld != %ld", index, readValue, tgtValue[index]);
                result &= 0;
            }
        }
    }

    // Increment all counters with one
    for (index=0; index<A71CH_COUNTER_MAX; index++)
    {
        LOG_I("A71_IncrementCounter(index=0x%02x)", index);
        err = A71_IncrementCounter(index);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to increment counter value");
    }

    // Verify all counters have target value
    for (index=0; index<A71CH_COUNTER_MAX; index++)
    {
        LOG_I("A71_GetCounter(index=0x%02x)", index);
        err = A71_GetCounter(index, &readValue);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to retrieve counter value");
        if (err == SW_OK)
        {
            if (readValue != (tgtValue[index]+1))
            {
                LOG_E("Failed to retrieve expected counter value (index=0x%02x): %ld != %ld", index, readValue, (tgtValue[index]+1));
                result &= 0;
            }
        }
    }

    // Now erase all counters and check whether they are back to default value 0
    for (index=0; index<A71CH_COUNTER_MAX; index++)
    {
        LOG_I("A71_DbgEraseCounter(index=0x%02x)", index);
        err = A71_DbgEraseCounter(index);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to erase counter value");
    }
    for (index=0; index<A71CH_COUNTER_MAX; index++)
    {
        LOG_I("A71_GetCounter(index=0x%02x)", index);
        err = A71_GetCounter(index, &readValue);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to retrieve counter value");
        if (err == SW_OK)
        {
            if (readValue != 0)
            {
                LOG_E("Failed to retrieve expected counter value (index=0x%02x): %ld != 0", index, readValue);
                result &= 0;
            }
        }
    }

    // Set all counters to a counter specific target value & increment the value & freeze the counter
    for (index=0; index<A71CH_COUNTER_MAX; index++)
    {
        LOG_I("A71_SetCounter(index=0x%02x, %ld)", index, tgtValue[index]);
        err = A71_SetCounter(index, tgtValue[index]);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to set counter value");
        LOG_I("A71_IncrementCounter(index=0x%02x)", index);
        err = A71_IncrementCounter(index);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to increment counter value");
    }

    // Check value just set
    for (index=0; index<A71CH_COUNTER_MAX; index++)
    {
        LOG_I("A71_GetCounter(index=0x%02x)", index);
        err = A71_GetCounter(index, &readValue);
        result &= AX_CHECK_SW(err, SW_OK, "Failed to retrieve counter value");
        if (err == SW_OK)
        {
            if (readValue != (tgtValue[index]+1))
            {
                LOG_E("Failed to retrieve expected counter value (index=0x%02x): %ld != %ld", index, readValue, (tgtValue[index]+1));
                result &= 0;
            }
        }

        // LOG_I("A71_IncrementCounter(index=0x%02x) - negative test", index);
        // err = A71_IncrementCounter(index);
        // result &= AX_CHECK_SW(err, SW_COMMAND_NOT_ALLOWED, "Increment frozen counter must fail");
    }

    LOG_I("-----------End exMonotonicCounter(%s), result = %s------------", getInitModeAsString(initMode),
        ((result == 1)? "OK": "FAILED"));

    return result;
}

/**
 * Demonstrate reading and writing to GP Storage.
 *
 * @param[in] initMode Visit the documentation of ::a71chInitModule for more information on this parameter
 * @param[in] tstMode Influence test execution through this parameter
 */
static U8 exGpStoragePacketSize(U8 initMode, U8 tstMode)
{
    U16 testSizes[] = {60, 200, 300, 400, 500, 600, 700, 800, 900, 1000, A71CH_GP_STORAGE_SIZE};
    U8 gpStorageRef[A71CH_GP_STORAGE_SIZE];
    U8 gpStorage[A71CH_GP_STORAGE_SIZE];
    int i = 0;
    U16 packetSize = 0;
    int maxIter = sizeof(testSizes)/sizeof(U16);
    U8 result = 1;
    U16 err;

    LOG_I( "-----------Start exGpStoragePacketSize(%s)------------",
        getInitModeAsString(initMode));

    if ((tstMode & EX_INC_DATA) == EX_INC_DATA)
    {
        for (i=0; i<A71CH_GP_STORAGE_SIZE; i++)
        {
            gpStorageRef[i] = (U8)i;
        }
    }
    else
    {
        // Create reference array containing random values
        srand(0);
        for (i=0; i<A71CH_GP_STORAGE_SIZE; i++)
        {
            gpStorageRef[i] = (U8)rand();
        }
    }

    // Initialize the A71CH (Debug mode restrictions may apply)
    result &= a71chInitModule(initMode);
    assert(result);

    if ((tstMode & EX_ALL_PACKET_SIZES) == EX_ALL_PACKET_SIZES)
    {
        // Test all packetsizes in read and write mode
        for (packetSize = 1; packetSize <= A71CH_GP_STORAGE_SIZE; packetSize++)
        {
            if ((tstMode & EX_INC_DATA) == EX_RND_DATA)
            {
                // In case of random data, recreate the reference array
                srand(0);
                for (i=0; i<packetSize; i++)
                {
                    gpStorageRef[i] = (U8)rand();
                }
            }
            LOG_I( "A71_SetGpData(0, %d, ...)", packetSize);
            err = A71_SetGpData(0, gpStorageRef, packetSize);
            result &= AX_CHECK_SW(err, SW_OK, "A71_SetGpData fails");

            LOG_I( "A71_GetGpData(0, %d, ...)", packetSize);
            err = A71_GetGpData(0, gpStorage, packetSize);
            result &= AX_CHECK_SW(err, SW_OK, "A71_GetGpData fails");

            result &= AX_COMPARE_BYTE_ARRAY( "gpStorageRef", gpStorageRef, packetSize, "gpStorage", gpStorage, packetSize, AX_COLON_32);
        }
    }
    else
    {
        // Always start from offset 0
        for (i = 0; i < maxIter; i++)
        {
            LOG_I( "A71_SetGpData(0, %d, ...)", testSizes[i]);
            err = A71_SetGpData(0, gpStorageRef, testSizes[i]);
            result &= AX_CHECK_SW(err, SW_OK, "A71_SetGpData fails");

            LOG_I( "A71_GetGpData(0, %d, ...)", testSizes[i]);
            err = A71_GetGpData(0, gpStorage, testSizes[i]);
            result &= AX_CHECK_SW(err, SW_OK, "A71_GetGpData fails");

            result &= AX_COMPARE_BYTE_ARRAY("gpStorageRef", gpStorageRef, testSizes[i], "gpStorage", gpStorage, testSizes[i], AX_COLON_32);
        }
    }

    LOG_I( "-----------End exGpStoragePacketSize(%s), result = %s------------",
        getInitModeAsString(initMode), ((result == 1)? "OK": "FAILED"));

    return result;
}

/**
 * Demonstrate freezing of GP Storage data chunks.
 * GP Storage data can be frozen with a granularity of ::A71CH_GP_STORAGE_GRANULARITY  byte
 *
 * @param[in] initMode Visit the documentation of ::a71chInitModule for more information on this parameter
 * @param[in] tstMode Influence test execution through this parameter
 */
static U8 exGpStorageFreeze(U8 initMode, U8 tstMode)
{
#if (A71CH_GP_STORAGE_SIZE == A71CH_GP_STORAGE_SIZE_A)
    U16 testSizes[] = {60, 200, 300, 400, 500, 600, 700, 800, 900, 1000, A71CH_GP_STORAGE_SIZE};
#else
    U16 testSizes[] = {60, 200, 300, 400, 500, 600, 1000, 1500, 2000, 3000, A71CH_GP_STORAGE_SIZE};
#endif
    U8 gpStorageRef[A71CH_GP_STORAGE_SIZE];
    U8 gpStorageNew[A71CH_GP_STORAGE_SIZE];
    U8 gpStorage[A71CH_GP_STORAGE_SIZE];
    U8 gpStorageExpected[A71CH_GP_STORAGE_SIZE];
    int i = 0;
    U16 packetSize = 0;
    U16 offset = 0;
    int maxIter = sizeof(testSizes)/sizeof(U16);
    U8 result = 1;
    U16 err;

    LOG_I( "-----------Start exGpStorageFreeze(%s, A71CH_GP_STORAGE_SIZE=%d)------------",
        getInitModeAsString(initMode), A71CH_GP_STORAGE_SIZE);

    if ((tstMode & EX_INC_DATA) == EX_INC_DATA)
    {
        for (i=0; i<A71CH_GP_STORAGE_SIZE; i++)
        {
            gpStorageRef[i] = (U8)i;
        }
    }
    else
    {
        // Create reference array containing random values
        srand(0);
        for (i=0; i<A71CH_GP_STORAGE_SIZE; i++)
        {
            gpStorageRef[i] = (U8)rand();
        }
    }

    for (i=0; i<A71CH_GP_STORAGE_SIZE; i++)
    {
        gpStorageNew[i] = 0xAA;
    }

    // Initialize the A71CH (Debug mode restrictions may apply)
    result &= a71chInitModule(initMode);
    assert(result);

    if ((tstMode & EX_ALL_PACKET_SIZES) == EX_ALL_PACKET_SIZES)
    {
        // Test all packetsizes in read and write mode
        for (packetSize = 1; packetSize <= A71CH_GP_STORAGE_SIZE; packetSize++)
        {
            if ((tstMode & EX_INC_DATA) == EX_RND_DATA)
            {
                // In case of random data, recreate the reference array
                srand(0);
                for (i=0; i<packetSize; i++)
                {
                    gpStorageRef[i] = (U8)rand();
                }
            }
            LOG_I( "A71_SetGpData(0, %d, ...)", packetSize);
            err = A71_SetGpData(0, gpStorageRef, packetSize);
            result &= AX_CHECK_SW(err, SW_OK, "A71_SetGpData fails");

            LOG_I( "A71_GetGpData(0, %d, ...)", packetSize);
            err = A71_GetGpData(0, gpStorage, packetSize);
            result &= AX_CHECK_SW(err, SW_OK, "A71_GetGpData fails");

            result &= AX_COMPARE_BYTE_ARRAY("gpStorageRef", gpStorageRef, packetSize, "gpStorage", gpStorage, packetSize, AX_COLON_32);
        }
    }
    else
    {
        // Always start from offset 0
        for (i = 0; i < maxIter; i++)
        {
            LOG_I( "A71_SetGpData(0, %d, ...)", testSizes[i]);
            err = A71_SetGpData(0, gpStorageRef, testSizes[i]);
            result &= AX_CHECK_SW(err, SW_OK, "A71_SetGpData fails");

            LOG_I( "A71_GetGpData(0, %d, ...)", testSizes[i]);
            err = A71_GetGpData(0, gpStorage, testSizes[i]);
            result &= AX_CHECK_SW(err, SW_OK, "A71_GetGpData fails");

            result &= AX_COMPARE_BYTE_ARRAY("gpStorageRef", gpStorageRef, testSizes[i], "gpStorage", gpStorage, testSizes[i], AX_COLON_32);
        }
    }

    // Just fill up the full GpStorage with reference data
    packetSize = A71CH_GP_STORAGE_SIZE;
    LOG_I( "A71_SetGpData(0, %d, ...)", packetSize);
    err = A71_SetGpData(0, gpStorageRef, packetSize);
    result &= AX_CHECK_SW(err, SW_OK, "A71_SetGpData fails");

    // Check whether data was written successfully
    LOG_I( "A71_GetGpData(0, %d, ...)", packetSize);
    err = A71_GetGpData(0, gpStorage, packetSize);
    result &= AX_CHECK_SW(err, SW_OK, "A71_GetGpData fails");
    result &= AX_COMPARE_BYTE_ARRAY("gpStorageRef", gpStorageRef, packetSize,
        "gpStorage", gpStorage, packetSize, AX_COLON_32);

    // Lock the first half of GP storage
    LOG_I( "A71_FreezeGpData(offset=%d, dataLen=%d, ...)", 0, A71CH_GP_STORAGE_SIZE>>1);
    err = A71_FreezeGpData(0, A71CH_GP_STORAGE_SIZE>>1);
    result &= AX_CHECK_SW(err, SW_OK, "A71_FreezeGpData fails");

    // Attempt to write in the frozen area
    packetSize = 16;
    offset = 0;
    LOG_I( "A71_SetGpData(%d, %d, ...)", offset, packetSize);
    err = A71_SetGpData(offset, gpStorageNew, packetSize);
    result &= AX_CHECK_SW(err, SW_COMMAND_NOT_ALLOWED, "A71_SetGpData was expected to fail (because area has just been locked)");

    // Write to open area
    offset = A71CH_GP_STORAGE_SIZE>>1;
    LOG_I( "A71_SetGpData(%d, %d, ...)", offset, packetSize);
    err = A71_SetGpData(offset, gpStorageNew, packetSize);
    result &= AX_CHECK_SW(err, SW_OK, "A71_SetGpData fails");

    // Create expected data pattern
    memcpy(gpStorageExpected, gpStorageRef, A71CH_GP_STORAGE_SIZE);
    memcpy(gpStorageExpected+(A71CH_GP_STORAGE_SIZE>>1), gpStorageNew, 16);

    // Retrieve data and compare with expected data pattern
    packetSize = A71CH_GP_STORAGE_SIZE;
    LOG_I( "A71_GetGpData(0, %d, ...)", packetSize);
    err = A71_GetGpData(0, gpStorage, packetSize);
    result &= AX_CHECK_SW(err, SW_OK, "A71_GetGpData fails");
    result &= AX_COMPARE_BYTE_ARRAY("gpStorageExpected", gpStorageExpected, A71CH_GP_STORAGE_SIZE,
        "gpStorage", gpStorage, A71CH_GP_STORAGE_SIZE, AX_COLON_32);

    LOG_I( "-----------End exGpStorageFreeze(%s), result = %s------------",
        getInitModeAsString(initMode), ((result == 1)? "OK": "FAILED"));

    return result;
}