/**
 * \file
 * \brief CryptoAuthLib Basic API methods. These methods provide a simpler way
 *        to access the core crypto methods.
 *
 * \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 "atca_basic.h"
#include "atca_version.h"
#include "host/atca_host.h"

#if defined(ATCA_USE_CONSTANT_HOST_NONCE)
#if defined(_MSC_VER)
#pragma message("Warning : Using a constant host nonce with atcab_read_enc, atcab_write_enc, etcc., can allow spoofing of a device by replaying previously recorded messages")
#else
#warning "Using a constant host nonce with atcab_read_enc, atcab_write_enc, etcc., can allow spoofing of a device by replaying previously recorded messages"
#endif
#endif

const char atca_version[] = ATCA_LIBRARY_VERSION_DATE;
ATCADevice _gDevice = NULL;
#ifdef ATCA_NO_HEAP
struct atca_command g_atcab_command;
struct atca_iface g_atcab_iface;
struct atca_device g_atcab_device;
#endif
#define MAX_BUSES   4

/** \brief basic API methods are all prefixed with atcab_  (CryptoAuthLib Basic)
 *  the fundamental premise of the basic API is it is based on a single interface
 *  instance and that instance is global, so all basic API commands assume that
 *  one global device is the one to operate on.
 */

/** \brief returns a version string for the CryptoAuthLib release.
 *  The format of the version string returned is "yyyymmdd"
 * \param[out] ver_str ptr to space to receive version string
 * \return ATCA_SUCCESS on success, otherwise an error code.
 */
ATCA_STATUS atcab_version(char *ver_str)
{
    strcpy(ver_str, atca_version);
    return ATCA_SUCCESS;
}


/** \brief Creates a global ATCADevice object used by Basic API.
 *  \param[in] cfg  Logical interface configuration. Some predefined
 *                  configurations can be found in atca_cfgs.h
 *  \return ATCA_SUCCESS on success, otherwise an error code.
 */
ATCA_STATUS atcab_init(ATCAIfaceCfg *cfg)
{
    ATCA_STATUS status = ATCA_GEN_FAIL;

    // If a device has already been initialized, release it
    if (_gDevice)
    {
        atcab_release();
    }

#ifdef ATCA_NO_HEAP
    g_atcab_device.mCommands = &g_atcab_command;
    g_atcab_device.mIface = &g_atcab_iface;
    status = initATCADevice(cfg, &g_atcab_device);
    if (status != ATCA_SUCCESS)
    {
        return status;
    }
    _gDevice = &g_atcab_device;
#else
    _gDevice = newATCADevice(cfg);
    if (_gDevice == NULL)
    {
        return ATCA_GEN_FAIL;
    }
#endif

    if (cfg->devtype == ATECC608A)
    {
        if ((status = atcab_read_bytes_zone(ATCA_ZONE_CONFIG, 0, ATCA_CHIPMODE_OFFSET, &_gDevice->mCommands->clock_divider, 1)) != ATCA_SUCCESS)
        {
            return status;
        }
        _gDevice->mCommands->clock_divider &= ATCA_CHIPMODE_CLOCK_DIV_MASK;
    }

    return ATCA_SUCCESS;
}

/** \brief Initialize the global ATCADevice object to point to one of your
 *         choosing for use with all the atcab_ basic API.
 *  \param[in] ca_device  ATCADevice instance to use as the global Basic API
 *                        crypto device instance
 *  \return ATCA_SUCCESS on success, otherwise an error code.
 */
ATCA_STATUS atcab_init_device(ATCADevice ca_device)
{
    if (ca_device == NULL)
    {
        return ATCA_BAD_PARAM;
    }

    if (atGetCommands(ca_device) == NULL || atGetIFace(ca_device) == NULL)
    {
        return ATCA_GEN_FAIL;
    }

    // if there's already a device created, release it
    if (_gDevice)
    {
        atcab_release();
    }

    _gDevice = ca_device;

    return ATCA_SUCCESS;
}

/** \brief release (free) the global ATCADevice instance.
 *  This must be called in order to release or free up the interface.
 *  \return Returns ATCA_SUCCESS .
 */
ATCA_STATUS atcab_release(void)
{
#ifdef ATCA_NO_HEAP
    ATCA_STATUS status = releaseATCADevice(_gDevice);
    if (status != ATCA_SUCCESS)
    {
        return status;
    }
    _gDevice = NULL;
#else
    deleteATCADevice(&_gDevice);
#endif
    return ATCA_SUCCESS;
}

/** \brief Get the global device object.
 *  \return instance of global ATCADevice
 */
ATCADevice atcab_get_device(void)
{
    return _gDevice;
}

/** \brief Get the current device type.
 *  \return Device type if basic api is initialized or ATCA_DEV_UNKNOWN.
 */
ATCADeviceType atcab_get_device_type(void)
{
    if (_gDevice)
    {
        return _gDevice->mIface->mIfaceCFG->devtype;
    }
    else
    {
        return ATCA_DEV_UNKNOWN;
    }
}

/** \brief wakeup the CryptoAuth device
 *  \return ATCA_SUCCESS on success, otherwise an error code.
 */
ATCA_STATUS atcab_wakeup(void)
{
    if (_gDevice == NULL)
    {
        return ATCA_GEN_FAIL;
    }

    return atwake(_gDevice->mIface);
}

/** \brief idle the CryptoAuth device
 *  \return ATCA_SUCCESS on success, otherwise an error code.
 */
ATCA_STATUS atcab_idle(void)
{
    if (_gDevice == NULL)
    {
        return ATCA_GEN_FAIL;
    }

    return atidle(_gDevice->mIface);
}

/** \brief invoke sleep on the CryptoAuth device
 *  \return ATCA_SUCCESS on success, otherwise an error code.
 */
ATCA_STATUS atcab_sleep(void)
{
    if (_gDevice == NULL)
    {
        return ATCA_GEN_FAIL;
    }

    return atsleep(_gDevice->mIface);
}


/** \brief auto discovery of crypto auth devices
 *
 * Calls interface discovery functions and fills in cfg_array up to the maximum
 * number of configurations either found or the size of the array. The cfg_array
 * can have a mixture of interface types (ie: some I2C, some SWI or UART) depending upon
 * which interfaces you've enabled
 *
 * \param[out] cfg_array ptr to an array of interface configs
 * \param[in] max_ifaces maximum size of cfg_array
 * \return ATCA_SUCCESS on success, otherwise an error code.
 */


ATCA_STATUS atcab_cfg_discover(ATCAIfaceCfg cfg_array[], int max_ifaces)
{
    int iface_num = 0;
    int found = 0;
    int i = 0;

// this cumulatively gathers all the interfaces enabled by #defines

#ifdef ATCA_HAL_I2C
    int i2c_buses[MAX_BUSES];
    memset(i2c_buses, -1, sizeof(i2c_buses));
    hal_i2c_discover_buses(i2c_buses, MAX_BUSES);

    for (i = 0; i < MAX_BUSES && iface_num < max_ifaces; i++)
    {
        if (i2c_buses[i] != -1)
        {
            hal_i2c_discover_devices(i2c_buses[i], &cfg_array[iface_num], &found);
            iface_num += found;
        }
    }
#endif

#ifdef ATCA_HAL_SWI
    int swi_buses[MAX_BUSES];
    memset(swi_buses, -1, sizeof(swi_buses));
    hal_swi_discover_buses(swi_buses, MAX_BUSES);
    for (i = 0; i < MAX_BUSES && iface_num < max_ifaces; i++)
    {
        if (swi_buses[i] != -1)
        {
            hal_swi_discover_devices(swi_buses[i], &cfg_array[iface_num], &found);
            iface_num += found;
        }
    }

#endif

#ifdef ATCA_HAL_UART
    int uart_buses[MAX_BUSES];
    memset(uart_buses, -1, sizeof(uart_buses));
    hal_uart_discover_buses(uart_buses, MAX_BUSES);
    for (i = 0; i < MAX_BUSES && iface_num < max_ifaces; i++)
    {
        if (uart_buses[i] != -1)
        {
            hal_uart_discover_devices(uart_buses[i], &cfg_array[iface_num], &found);
            iface_num += found;
        }
    }
#endif

#ifdef ATCA_HAL_KIT_CDC
    int cdc_buses[MAX_BUSES];
    memset(cdc_buses, -1, sizeof(cdc_buses));
    hal_kit_cdc_discover_buses(cdc_buses, MAX_BUSES);
    for (i = 0; i < MAX_BUSES && iface_num < max_ifaces; i++)
    {
        if (cdc_buses[i] != -1)
        {
            hal_kit_cdc_discover_devices(cdc_buses[i], &cfg_array[iface_num++], &found);
            iface_num += found;
        }
    }
#endif

#ifdef ATCA_HAL_KIT_HID
    int hid_buses[MAX_BUSES];
    memset(hid_buses, -1, sizeof(hid_buses));
    hal_kit_hid_discover_buses(hid_buses, MAX_BUSES);
    for (i = 0; i < MAX_BUSES && iface_num < max_ifaces; i++)
    {
        if (hid_buses[i] != -1)
        {
            hal_kit_hid_discover_devices(hid_buses[i], &cfg_array[iface_num++], &found);
            iface_num += found;
        }
    }
#endif
    return ATCA_SUCCESS;
}

/** \brief common cleanup code which idles the device after any operation
 *  \return ATCA_SUCCESS on success, otherwise an error code.
 */
ATCA_STATUS _atcab_exit(void)
{
    return atcab_idle();
}

/** \brief Compute the address given the zone, slot, block, and offset
 *  \param[in] zone   Zone to get address from. Config(0), OTP(1), or
 *                    Data(2) which requires a slot.
 *  \param[in] slot   Slot Id number for data zone and zero for other zones.
 *  \param[in] block  Block number within the data or configuration or OTP zone .
 *  \param[in] offset Offset Number within the block of data or configuration or OTP zone.
 *  \param[out] addr  Pointer to the address of data or configuration or OTP zone.
 *  \return ATCA_SUCCESS on success, otherwise an error code.
 */
ATCA_STATUS atcab_get_addr(uint8_t zone, uint16_t slot, uint8_t block, uint8_t offset, uint16_t* addr)
{
    ATCA_STATUS status = ATCA_SUCCESS;
    uint8_t mem_zone = zone & 0x03;

    if (addr == NULL)
    {
        return ATCA_BAD_PARAM;
    }
    if ((mem_zone != ATCA_ZONE_CONFIG) && (mem_zone != ATCA_ZONE_DATA) && (mem_zone != ATCA_ZONE_OTP))
    {
        return ATCA_BAD_PARAM;
    }
    do
    {
        // Initialize the addr to 00
        *addr = 0;
        // Mask the offset
        offset = offset & (uint8_t)0x07;
        if ((mem_zone == ATCA_ZONE_CONFIG) || (mem_zone == ATCA_ZONE_OTP))
        {
            *addr = block << 3;
            *addr |= offset;
        }
        else     // ATCA_ZONE_DATA
        {
            *addr = slot << 3;
            *addr  |= offset;
            *addr |= block << 8;
        }
    }
    while (0);

    return status;
}

/** \brief Gets the size of the specified zone in bytes.
 *
 * \param[in]  zone  Zone to get size information from. Config(0), OTP(1), or
 *                   Data(2) which requires a slot.
 * \param[in]  slot  If zone is Data(2), the slot to query for size.
 * \param[out] size  Zone size is returned here.
 *
 * \return ATCA_SUCCESS on success, otherwise an error code.
 */
ATCA_STATUS atcab_get_zone_size(uint8_t zone, uint16_t slot, size_t* size)
{
    ATCA_STATUS status = ATCA_SUCCESS;

    if (size == NULL)
    {
        return ATCA_BAD_PARAM;
    }

    if (_gDevice->mIface->mIfaceCFG->devtype == ATSHA204A)
    {
        switch (zone)
        {
        case ATCA_ZONE_CONFIG: *size = 88; break;
        case ATCA_ZONE_OTP:    *size = 64; break;
        case ATCA_ZONE_DATA:   *size = 32; break;
        default: status = ATCA_BAD_PARAM; break;
        }
    }
    else if (_gDevice->mIface->mIfaceCFG->devtype == ATSHA206A)
    {
        switch (zone)
        {
        case ATCA_ZONE_CONFIG: *size = 88; break;
        case ATCA_ZONE_OTP:    *size = 0; break;
        case ATCA_ZONE_DATA:   *size = 32; break;
        default: status = ATCA_BAD_PARAM; break;
        }
    }
    else
    {
        switch (zone)
        {
        case ATCA_ZONE_CONFIG: *size = 128; break;
        case ATCA_ZONE_OTP:    *size = 64; break;
        case ATCA_ZONE_DATA:
            if (slot < 8)
            {
                *size = 36;
            }
            else if (slot == 8)
            {
                *size = 416;
            }
            else if (slot < 16)
            {
                *size = 72;
            }
            else
            {
                status = ATCA_BAD_PARAM;
            }
            break;
        default: status = ATCA_BAD_PARAM; break;
        }
    }

    return status;
}
