/**
 * @file ipcc_orig.c
 *
 * @brief This file contains functions that helps to create
 *        the ipcc command frame
 *
 * The functions provided in the file helps to create ipcc command
 * frame with crc and encryptions.
 */
/*********************************************************************/
#include <ipcc_common.h>
#include <string.h>

///Sequence number static global variable
static uint16 seq_no = 1;

///s_ipcc_crypt_params local instance to keep track of cryption parameters
s_ipcc_crypt_params param_local;

/**
 * @brief This API initializes the s_ipcc_frame by setting all the values of it's
 *        members to zero.
 */
int8 ipcc_orig_init(s_ipcc_frame *ipcc_orig)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        memset(ipcc_orig, 0, sizeof (s_ipcc_frame));
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the start of frame passed in sof value to the sof
 *  member of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_sof(s_ipcc_frame *ipcc_orig, uint8 sof)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        ipcc_orig->sof = sof;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the ipcc_ctrl value to the ipcc_ctrl member
 * of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_ipcc_ctrl(s_ipcc_frame *ipcc_orig, uint8 ipcc_ctrl)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        ipcc_orig->ipcc_ctrl = ipcc_ctrl;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in priority value to the qos member
 * of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_qos (s_ipcc_frame* ipcc_orig, uint8 priority)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        ipcc_orig->qos = priority;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in sa_id value to the sa_id member
 * of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_sa_id (s_ipcc_frame* ipcc_orig, uint16 sa_id)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        ipcc_orig->sa_id = sa_id;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in da_id value to the da_id member
 * of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_da_id (s_ipcc_frame* ipcc_orig, uint16 da_id)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        ipcc_orig->da_id = da_id;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}
/**
 * @brief This API stores the passed in ipcc_id value to the ipcc_id member
 * of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_ipcc_id (s_ipcc_frame* ipcc_orig, uint8 ipcc_id)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        ipcc_orig->ipcc_id = ipcc_id;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in ipcc_len value to the ipcc_len member
 * of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_ipcc_len (s_ipcc_frame* ipcc_orig, uint16 ipcc_len)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        ipcc_orig->ipcc_len = ipcc_len;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in value to the ipcc_len member
 * of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_ipcc_data (s_ipcc_frame* ipcc_orig, uint8 *data)
{
    int8 status;

    if ( NULL != ipcc_orig && NULL != data)
    {
        ipcc_orig->ipcc_data = data;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in crc_32 value to the crc_32 member
 * of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_crc_32 (s_ipcc_frame* ipcc_orig, uint32 crc_32)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        ipcc_orig->crc_32 = crc_32;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API calculates hmac through the 3rd party library
 */
size_t ipcc_orig_compute_hmac (s_ipcc_frame* ipcc_orig, const uint8* key, const uint32 keysize, const uint32 datasize, void* out)
{
    size_t status;

    if ( NULL != ipcc_orig && NULL != key && NULL != out)
    {
        status = ipcc_compute_hmac ((const uint8*) ipcc_orig, key, keysize, datasize,
                                out);
    }
    else
    {
        status = IPCC_NULL_PTR;
    }
    return status;
}

/**
 * @brief This API stores the passed in hmac to the cmac_hmac member
 * of passed in s_ipcc_frame type struct
 */
int8 ipcc_orig_cfg_hmac (s_ipcc_frame* ipcc_orig, uint8 *hmac, size_t size)
{
    int8 status;

    if ( NULL != ipcc_orig && NULL != hmac)
    {
        memcpy(ipcc_orig->cmac_hmac, hmac, size);
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API resets the sequence number to 1
 */
void ipcc_orig_reset_seq_no (void)
{
   seq_no = 1;

}

/**
 * @brief This API resets the timestamp to zero
 */
int8 ipcc_orig_reset_time_stamp (s_ipcc_frame* ipcc_orig)
{
    int8 status;

    if ( NULL != ipcc_orig)
    {
        ipcc_orig->time_stamp = 0;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API calculates the crc for the passed in input_str
 *         for the input size and stores the crc value to the val parameter
 *         equal to size input value.
 */
int8 ipcc_orig_compute_crc (const uint8 *input_str, uint32* val, size_t size)
{

    int8 status;

    if ( NULL != input_str && NULL != val)
    {
        *val = crc_32(input_str, size);
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API copies the structure data to the output buffer(dst). While
 * copying it takes care of the extension bit for the required members.
 * Finally it computes CRC ,hmac and embeds those value to the final 
 * output buffer(dst).
 */
int32 ipcc_pack(uint8 *dst, s_ipcc_frame *ipcc_orig)
{
    int32 i = 0;
    int8 status = IPCC_OK;
#if 0
    size_t hmac_size = 0;
#endif

    printf("EXO IPCC Frame encoder\n");

    if ( NULL != dst && NULL != ipcc_orig)
    {
        memcpy(&dst[i], &ipcc_orig->sof, sizeof (ipcc_orig->sof));
        i += sizeof (ipcc_orig->sof);

        memcpy(&dst[i], &ipcc_orig->ipcc_ctrl, sizeof ipcc_orig->ipcc_ctrl);
        i += sizeof ipcc_orig->ipcc_ctrl;

        ipcc_orig->time_stamp = ipcc_get_utc_epoch();

        memcpy(&dst[i], &ipcc_orig->time_stamp, sizeof ipcc_orig->time_stamp);
        i += sizeof ipcc_orig->time_stamp;

        // populate the sequence number to the structure
        memcpy(&ipcc_orig->seq_no, &seq_no, sizeof seq_no);

        memcpy(&dst[i], &ipcc_orig->seq_no, sizeof ipcc_orig->seq_no);
        i += sizeof ipcc_orig->seq_no;

        memcpy(&dst[i], &ipcc_orig->qos, sizeof ipcc_orig->qos);
        i += sizeof ipcc_orig->qos;

        // Populate the value based on 1 or 2 byte encoding
        i += ipcc_set_extnd_val_16(&ipcc_orig->sa_id, &dst[i], sizeof ipcc_orig->sa_id);

        // Populate the value based on 1 or 2 byte encoding
        i += ipcc_set_extnd_val_16(&ipcc_orig->da_id, &dst[i], sizeof ipcc_orig->da_id);

        memcpy(&dst[i], &ipcc_orig->ipcc_id, sizeof ipcc_orig->ipcc_id);
        i += sizeof ipcc_orig->ipcc_id;

        if(((ipcc_orig->ipcc_ctrl & IPCC_CTRL_CRYPT_BIT) >> IPCC_CTRL_CRYPT_POS) & 1)
        {
            /* encrypt bit is set in ipcc_ctrl. encrypt the data */
            status = ipcc_orig_encrypt_data (&param_local,
                                           (const uint8 *)ipcc_orig->ipcc_data,
                                            ipcc_orig->ipcc_len,
                                            ipcc_orig->ipcc_data);
        }

        // Populate the value based on 1 or 2 byte encoding
        i += ipcc_set_extnd_val_16(&ipcc_orig->ipcc_len,
                              &dst[i],
                              sizeof ipcc_orig->ipcc_len);

        if(IPCC_OK == status)
        {
            /**
             *Either encryption is success or encryption bit is
             *not set in ipcc_ctrl
             */
            memcpy(&dst[i], ipcc_orig->ipcc_data, ipcc_orig->ipcc_len);
            i += ipcc_orig->ipcc_len;

            memcpy(&dst[i], &ipcc_orig->keys, sizeof ipcc_orig->keys);
            i += sizeof ipcc_orig->keys;

            memcpy(&dst[i], &ipcc_orig->sec_cfg, sizeof ipcc_orig->sec_cfg);
            i += sizeof ipcc_orig->sec_cfg;

            memcpy(&dst[i], &ipcc_orig->mac_cfg, sizeof ipcc_orig->mac_cfg);
            i += sizeof ipcc_orig->mac_cfg;

            ipcc_orig_compute_crc(dst, &ipcc_orig->crc_32, i);

            memcpy(&dst[i], &ipcc_orig->crc_32, sizeof ipcc_orig->crc_32);
            i += sizeof ipcc_orig->crc_32;

#if 0
            hmac_size = ipcc_compute_hmac(dst,
                                     (const uint8*)KEY,
                                     strlen(KEY), i,
                                     ipcc_orig->cmac_hmac);
            if(0 < hmac_size)
            {
                memcpy(&dst[i], ipcc_orig->cmac_hmac, hmac_size);
                i += hmac_size;
                seq_no++;
            }
            else
            {
                /**
                 *compute_hmac failed for some reason. Set the i to
                 *compute_hmac status and return
                 */
                i = hmac_size;
            }
#else
                seq_no++;
#endif
        }
        else
        {
            /**
             *encryption failed for some reason. Set the i to encryption
             *status and return
             */
            i = status;
        }
    }
    else
    {
        i =  IPCC_NULL_PTR;
    }

    return i;
}

/**
 * @brief This API copies the passed in encryption related parameters to the
 *        local structure. The parameters are utilised from the local structure
 *        during the encryption operation in ipcc_pack() api
 */
int8 ipcc_orig_select_encryption(s_ipcc_crypt_params *param)
{
    int8 status = IPCC_OK;

    if( NULL != param && NULL != param->iv && NULL != param->key)
    {
        memcpy(param_local.iv, param->iv, 16);
        param_local.key = param->key;
        param_local.key_size = param->key_size;
        param_local.encrypt_mode = param->encrypt_mode;
    }
    else
    {
        status = IPCC_NULL_PTR;
    }
    return status;
}

/**
 * @brief This API encrypts the passed in buffer of size "length" based on the
 *        input encryption "param" and stores the encrypted buffer to the
 *        "output" parameter
 */
int32 ipcc_orig_encrypt_data (s_ipcc_crypt_params *param, const uint8 *buf, int32 length, uint8 *output)
{
    int32 status;

    if( NULL != param && NULL != buf && NULL != param->key && NULL != output)
    {
        status = ipcc_aes_encrypt_data(param, buf, length, output);
    }
    else
    {
        status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API decrypts the passed in buffer of size "length" based on the
 *        input decryption "param" and stores the decrypted buffer to the
 *        "output" parameter
 */
int32 ipcc_orig_decrypt_data (s_ipcc_crypt_params *param, const uint8 *buf, size_t length, uint8 *output)
{
    int32 status;

    if( NULL != param && NULL != buf && NULL != param->key && NULL != output)
    {
        status = ipcc_aes_decrypt_data(param, buf, length, output);

    }
    else
    {
        status = IPCC_NULL_PTR;
    }

    return status;
}
