/**
 * \file
 * \brief Unity tests for the cryptoauthlib Basic API
 *
 * \copyright (c) 2015-2020 Microchip Technology Inc. and its subsidiaries.
 *
 * \page License
 *
 * Subject to your compliance with these terms, you may use Microchip software
 * and any derivatives exclusively with Microchip products. It is your
 * responsibility to comply with third party license terms applicable to your
 * use of third party software (including open source software) that may
 * accompany Microchip software.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
 * EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
 * PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT,
 * SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE
 * OF ANY KIND WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF
 * MICROCHIP HAS BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE
 * FORESEEABLE. TO THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL
 * LIABILITY ON ALL CLAIMS IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED
 * THE AMOUNT OF FEES, IF ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR
 * THIS SOFTWARE.
 */
#include <stdlib.h>
#ifdef _WIN32
#include <time.h>
#endif
#include "atca_test.h"
#include "basic/atca_basic.h"
#include "host/atca_host.h"
#include "test/atca_tests.h"
#include "atca_execution.h"

extern const uint8_t g_aes_keys[4][16];
extern const uint8_t g_plaintext[64];

// Output ciphertext for AES-ECB mode for the g_plaintext and g_aes_keys keys
// First output is from the NIST AES-ECB test vectors
// https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_ECB.pdf
const uint8_t g_ciphertext_ecb[4][64] = {
    {
        0x3A, 0xD7, 0x7B, 0xB4, 0x0D, 0x7A, 0x36, 0x60, 0xA8, 0x9E, 0xCA, 0xF3, 0x24, 0x66, 0xEF, 0x97,
        0xF5, 0xD3, 0xD5, 0x85, 0x03, 0xB9, 0x69, 0x9D, 0xE7, 0x85, 0x89, 0x5A, 0x96, 0xFD, 0xBA, 0xAF,
        0x43, 0xB1, 0xCD, 0x7F, 0x59, 0x8E, 0xCE, 0x23, 0x88, 0x1B, 0x00, 0xE3, 0xED, 0x03, 0x06, 0x88,
        0x7B, 0x0C, 0x78, 0x5E, 0x27, 0xE8, 0xAD, 0x3F, 0x82, 0x23, 0x20, 0x71, 0x04, 0x72, 0x5D, 0xD4,
    },
    {
        0x53, 0x5A, 0x0D, 0x67, 0x77, 0x7A, 0x96, 0xAF, 0x77, 0x98, 0x30, 0x03, 0xA6, 0xDF, 0x85, 0x5A,
        0xAC, 0xFC, 0xDE, 0xD1, 0xB3, 0x36, 0x53, 0x07, 0x6C, 0x27, 0x6D, 0x15, 0xD9, 0xD1, 0xEC, 0x1B,
        0x04, 0x06, 0x65, 0x92, 0xA3, 0xFA, 0x7C, 0x4C, 0x6E, 0xC3, 0x4B, 0x91, 0x8F, 0xDB, 0x54, 0x2A,
        0x4B, 0x7E, 0xFC, 0x64, 0x53, 0x48, 0xB5, 0x80, 0xD2, 0x3C, 0x02, 0x4C, 0xE2, 0x65, 0x54, 0x01,
    },
    {
        0x28, 0x61, 0xA0, 0xFA, 0x39, 0x25, 0xE2, 0x02, 0x5E, 0x41, 0xDC, 0xDE, 0x53, 0xDF, 0x7C, 0xA7,
        0x4D, 0xEB, 0x73, 0xED, 0x68, 0x3C, 0x55, 0x56, 0x9F, 0x10, 0x29, 0x14, 0x45, 0x16, 0x90, 0x86,
        0x92, 0x12, 0x82, 0x5D, 0xC2, 0xC8, 0xB2, 0x88, 0x01, 0x26, 0x60, 0x7D, 0x74, 0x40, 0xD8, 0x6D,
        0x44, 0x31, 0x51, 0x85, 0xBD, 0x22, 0xE6, 0x9B, 0xD6, 0xA6, 0xDF, 0xE9, 0xCE, 0x1C, 0x06, 0x83,
    },
    {
        0xB1, 0x24, 0xD6, 0x3C, 0x41, 0xD8, 0x5B, 0xFD, 0x2E, 0xB4, 0xA4, 0xA8, 0xF4, 0x45, 0x02, 0xB9,
        0xED, 0x78, 0xD6, 0x95, 0xE7, 0xCA, 0x77, 0x37, 0x91, 0xFE, 0x3B, 0x80, 0x59, 0xA8, 0x4A, 0x4B,
        0xBA, 0xCF, 0x40, 0x1F, 0xD1, 0x9A, 0x34, 0x43, 0x2D, 0xDC, 0xE2, 0xC3, 0xCC, 0xFE, 0x2F, 0x69,
        0x87, 0xAE, 0x9A, 0xAE, 0x96, 0x51, 0x40, 0x19, 0xA3, 0x2E, 0xE5, 0x7B, 0x19, 0xCE, 0x6A, 0x56,
    }
};

TEST(atca_cmd_unit_test, aes)
{
    ATCA_STATUS status;
    ATCAPacket packet;
    ATCACommand ca_cmd = _gDevice->mCommands;

    // build read command
    packet.param1 = ATCA_ZONE_CONFIG;
    packet.param2 = 0x0003;

    status = atRead(ca_cmd, &packet);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
    status = atca_execute_command(&packet, _gDevice);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
    if ((packet.data[2] & AES_CONFIG_ENABLE_BIT_MASK) == 0) //packet.data[2] contains the AES enable bit
    {
        TEST_IGNORE_MESSAGE("Ignoring the test ,AES is not enabled in Configuration zone");
    }

    //build a nonce command (pass through mode) to store the aes key in tempkey
    packet.param1 = NONCE_MODE_PASSTHROUGH;
    packet.param2 = 0x0000;
    memcpy(packet.data, g_aes_keys[0], ATCA_KEY_SIZE);    // a 32-byte nonce

    status = atNonce(ca_cmd, &packet);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
    TEST_ASSERT_EQUAL_INT(NONCE_COUNT_LONG, packet.txsize);
    status = atca_execute_command(&packet, _gDevice);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
    TEST_ASSERT_EQUAL_INT(NONCE_RSP_SIZE_SHORT, packet.data[ATCA_COUNT_IDX]);

    // check for nonce response for pass through mode
    TEST_ASSERT_EQUAL_INT8(ATCA_SUCCESS, packet.data[ATCA_RSP_DATA_IDX]);

    packet.param1 = AES_MODE_ENCRYPT;                //selects encrypt mode and use first 16 byte data in tempkey as key
    packet.param2 = 0xFFFF;
    memcpy(packet.data, g_plaintext, AES_DATA_SIZE); // a 16-byte data to be encrypted

    status = atAES(ca_cmd, &packet);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
    status = atca_execute_command(&packet, _gDevice);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    TEST_ASSERT_EQUAL_MEMORY(g_ciphertext_ecb[0], &packet.data[ATCA_RSP_DATA_IDX], AES_DATA_SIZE);

    packet.param1 = AES_MODE_DECRYPT;                        //selects decrypt mode and use first 16 byte data in tempkey as key
    packet.param2 = 0xFFFF;
    memcpy(packet.data, g_ciphertext_ecb[0], AES_DATA_SIZE); // a 16-byte data to be encrypted

    status = atAES(ca_cmd, &packet);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
    status = atca_execute_command(&packet, _gDevice);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    TEST_ASSERT_EQUAL_MEMORY(g_plaintext, &packet.data[ATCA_RSP_DATA_IDX], AES_DATA_SIZE);
}



TEST(atca_cmd_basic_test, aes_encrypt_key_tempkey)
{
    ATCA_STATUS status;
    uint8_t key_block;
    size_t data_block;
    uint8_t encrypted_data_out[16];

    // Skip test if AES is not enabled
    check_config_aes_enable();

    // Load AES keys into TempKey
    status = atcab_nonce_load(NONCE_MODE_TARGET_TEMPKEY, g_aes_keys[0], 64);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    // Test encryption with the AES keys
    for (key_block = 0; key_block < 4; key_block++)
    {
        for (data_block = 0; data_block < sizeof(g_plaintext) / AES_DATA_SIZE; data_block++)
        {
            status = atcab_aes_encrypt(ATCA_TEMPKEY_KEYID, key_block, &g_plaintext[data_block * AES_DATA_SIZE], encrypted_data_out);
            TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
            TEST_ASSERT_EQUAL_MEMORY(&g_ciphertext_ecb[key_block][data_block * AES_DATA_SIZE], encrypted_data_out, AES_DATA_SIZE);
        }
    }
}

TEST(atca_cmd_basic_test, aes_decrypt_key_tempkey)
{
    ATCA_STATUS status;
    uint8_t key_block;
    size_t data_block;
    uint8_t decrypted_data_out[16];

    // Skip test if AES is not enabled
    check_config_aes_enable();

    // Load AES keys into TempKey
    status = atcab_nonce_load(NONCE_MODE_TARGET_TEMPKEY, g_aes_keys[0], 64);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    // Test decryption with the AES keys
    for (key_block = 0; key_block < 4; key_block++)
    {
        for (data_block = 0; data_block < sizeof(g_plaintext) / AES_DATA_SIZE; data_block++)
        {
            status = atcab_aes_decrypt(ATCA_TEMPKEY_KEYID, key_block, &g_ciphertext_ecb[key_block][data_block * AES_DATA_SIZE], decrypted_data_out);
            TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
            TEST_ASSERT_EQUAL_MEMORY(&g_plaintext[data_block * AES_DATA_SIZE], decrypted_data_out, AES_DATA_SIZE);
        }
    }
}

TEST(atca_cmd_basic_test, aes_encrypt_key_slot)
{
    ATCA_STATUS status;
    uint8_t key_block;
    size_t data_block;
    uint8_t encrypted_data_out[16];
    uint16_t key_slot = 10;
    bool persistent_latch_state;

    // Skip test if data zone isn't locked
    test_assert_data_is_locked();

    // Skip test if AES is not enabled
    check_config_aes_enable();

    // Skip test if persistent latch isn't set
    status = atcab_info_get_latch(&persistent_latch_state);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
    if (!persistent_latch_state)
    {
        TEST_IGNORE_MESSAGE("Ignoring the test, persistent latch should be set");
    }

    // Load AES keys into slot
    status = atcab_write_bytes_zone(ATCA_ZONE_DATA, key_slot, 0, g_aes_keys[0], 64);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    // Test encryption with the AES keys
    for (key_block = 0; key_block < 4; key_block++)
    {
        for (data_block = 0; data_block < sizeof(g_plaintext) / AES_DATA_SIZE; data_block++)
        {
            status = atcab_aes_encrypt(key_slot, key_block, &g_plaintext[data_block * AES_DATA_SIZE], encrypted_data_out);
            TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
            TEST_ASSERT_EQUAL_MEMORY(&g_ciphertext_ecb[key_block][data_block * AES_DATA_SIZE], encrypted_data_out, AES_DATA_SIZE);
        }
    }
}

TEST(atca_cmd_basic_test, aes_decrypt_key_slot)
{
    ATCA_STATUS status;
    uint8_t key_block;
    size_t data_block;
    uint8_t decrypted_data_out[16];
    uint16_t key_slot = 10;
    bool persistent_latch_state;

    // Skip test if data zone isn't locked
    test_assert_data_is_locked();

    // Skip test if AES is not enabled
    check_config_aes_enable();

    // Skip test if persistent latch isn't set
    status = atcab_info_get_latch(&persistent_latch_state);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
    if (!persistent_latch_state)
    {
        TEST_IGNORE_MESSAGE("Ignoring the test, persistent latch should be set");
    }

    // Load AES keys into slot
    status = atcab_write_bytes_zone(ATCA_ZONE_DATA, key_slot, 0, g_aes_keys[0], 64);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    // Test decryption with the AES keys
    for (key_block = 0; key_block < 4; key_block++)
    {
        for (data_block = 0; data_block < sizeof(g_plaintext) / AES_DATA_SIZE; data_block++)
        {
            status = atcab_aes_decrypt(key_slot, key_block, &g_ciphertext_ecb[key_block][data_block * AES_DATA_SIZE], decrypted_data_out);
            TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
            TEST_ASSERT_EQUAL_MEMORY(&g_plaintext[data_block * AES_DATA_SIZE], decrypted_data_out, AES_DATA_SIZE);
        }
    }
}

TEST(atca_cmd_basic_test, aes_gfm)
{
    ATCA_STATUS status;
    uint8_t gfm_data[16];
    const uint8_t h[] = {
        0x66, 0xE9, 0x4B, 0xD4, 0xEF, 0x8A, 0x2C, 0x3B, 0X88, 0x4C, 0xFA, 0x59, 0xCA, 0x34, 0x2B, 0x2E
    };
    const uint8_t input[] = {
        0x03, 0x88, 0xDA, 0xCE, 0x60, 0xB6, 0xA3, 0x92, 0XF3, 0x28, 0xC2, 0xB9, 0x71, 0xB2, 0xFE, 0x78
    };
    const uint8_t expected_gfm_data[16] = {
        0x5E, 0x2E, 0xC7, 0x46, 0x91, 0x70, 0x62, 0x88, 0X2C, 0x85, 0xB0, 0x68, 0x53, 0x53, 0xDE, 0xB7
    };

    //Calculating GFM for the input data data_input
    status = atcab_aes_gfm(h, input, gfm_data);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);
    TEST_ASSERT_EQUAL_MEMORY(expected_gfm_data, gfm_data, 16);
}

TEST(atca_cmd_basic_test, volatile_key_permit)
{
    ATCA_STATUS status = ATCA_GEN_FAIL;
    uint16_t key_id = 0x0004;
    uint8_t response[MAC_SIZE];
    uint8_t other_data[CHECKMAC_OTHER_DATA_SIZE];
    atca_temp_key_t temp_key;
    uint8_t num_in[NONCE_NUMIN_SIZE];
    uint8_t rand_out[RANDOM_NUM_SIZE];
    atca_nonce_in_out_t nonce_params;
    uint8_t sn[ATCA_SERIAL_NUM_SIZE];
    atca_check_mac_in_out_t checkmac_params;
    size_t i;
    uint8_t encrypted_data_out[16];
    uint8_t key_slot = 10;
    bool persistent_latch_state;

    uint8_t data_in[16] = {
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0X08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
    };

    test_assert_data_is_locked();

    status = atcab_info_get_latch(&persistent_latch_state); //Get the State of the persistent latch
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    if (persistent_latch_state)
    {
        TEST_IGNORE_MESSAGE("Ignoring the test, persistent latch already set");
    }

    check_config_aes_enable();                   //Checking the AES enable bit in configuration zone,if not set it skips the test

    //Loading AES key to slot 10
    status = atcab_write_bytes_zone(ATCA_ZONE_DATA, key_slot, 0, g_aes_keys[0], 32);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    status = atcab_aes_encrypt(key_slot, 0, data_in, encrypted_data_out); //Encrypting data with first 16 bytes in slot 10 as key

    TEST_ASSERT_EQUAL(ATCA_EXECUTION_ERROR, status);                      //Encryption fails as the persistent latch is not set


    // Read SN
    status = atcab_read_serial_number(sn);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    // Perform random nonce
    memset(&temp_key, 0, sizeof(temp_key));
    memset(num_in, 0, sizeof(num_in));
    memset(&nonce_params, 0, sizeof(nonce_params));
    nonce_params.mode = NONCE_MODE_SEED_UPDATE;
    nonce_params.zero = 0;
    nonce_params.num_in = num_in;
    nonce_params.rand_out = rand_out;
    nonce_params.temp_key = &temp_key;
    status = atcab_nonce_rand(nonce_params.num_in, rand_out);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    // Calculate nonce value
    status = atcah_nonce(&nonce_params);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    // Calculate response
    for (i = 0; i < sizeof(other_data); i++)
    {
        other_data[i] = (uint8_t)(i + 0xF0);
    }
    checkmac_params.mode = CHECKMAC_MODE_BLOCK2_TEMPKEY;
    checkmac_params.key_id = key_id;
    checkmac_params.client_chal = NULL;
    checkmac_params.client_resp = response;
    checkmac_params.other_data = other_data;
    checkmac_params.sn = sn;
    checkmac_params.otp = NULL;
    checkmac_params.slot_key = g_slot4_key;
    checkmac_params.target_key = NULL;
    checkmac_params.temp_key = &temp_key;
    status = atcah_check_mac(&checkmac_params);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    // Perform CheckMac
    status = atcab_checkmac(
        checkmac_params.mode,
        checkmac_params.key_id,
        checkmac_params.client_chal,
        checkmac_params.client_resp,
        checkmac_params.other_data);
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);

    status = atcab_info_set_latch(true); //persistent latch is set
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);



    status = atcab_aes_encrypt(key_slot, 0, data_in, encrypted_data_out); //Encrypting data with first 16 bytes in slot 10 as key
    TEST_ASSERT_EQUAL(ATCA_SUCCESS, status);                              //Encryption should pass as persistent latch is set

}

// *INDENT-OFF* - Preserve formatting
t_test_case_info aes_basic_test_info[] =
{
    { REGISTER_TEST_CASE(atca_cmd_basic_test, volatile_key_permit),              DEVICE_MASK(ATECC608A) },
    { REGISTER_TEST_CASE(atca_cmd_basic_test, aes_encrypt_key_tempkey),          DEVICE_MASK(ATECC608A) },
    { REGISTER_TEST_CASE(atca_cmd_basic_test, aes_decrypt_key_tempkey),          DEVICE_MASK(ATECC608A) },
    { REGISTER_TEST_CASE(atca_cmd_basic_test, aes_encrypt_key_slot),             DEVICE_MASK(ATECC608A) },
    { REGISTER_TEST_CASE(atca_cmd_basic_test, aes_decrypt_key_slot),             DEVICE_MASK(ATECC608A) },
    { REGISTER_TEST_CASE(atca_cmd_basic_test, aes_gfm),                          DEVICE_MASK(ATECC608A) },
    { (fp_test_case)NULL,                     (uint8_t)0 },             /* Array Termination element*/
};

t_test_case_info aes_unit_test_info[] =
{
    { REGISTER_TEST_CASE(atca_cmd_unit_test, aes), DEVICE_MASK(ATECC608A)  },
    { (fp_test_case)NULL,                    (uint8_t)0 },/* Array Termination element*/
};
// *INDENT-ON*
