/**
 * @file ipcc_terminate.c
 *
 * @brief This file contains functions to extract info from ipcccommand frame
 *
 * The functions provided in the file helps to extract the required information
 * from the ipcc command frame with crc and encryptions. It also provides the
 * functionalities to unpack buffer and store it in the s_ipcc_frame structure.
 *
 * @copyright Copyright 2023 Antaris, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *        usage of TC TM Library APIs
 */
/*********************************************************************/
#include <ipcc_common.h>
#include "exo_common.h"
#include "exo_common.h"

/**
* local instance of s_ipcc_terminator structure to store ipcc_id and trigger
* function pointer
*/

static s_ipcc_terminator ipcc_term_local;

/// Utility buffer
uint8_t out[SHA256_HASH_SIZE];

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

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

    return status;
}

/**
 * @brief This API stores the start of frame (sof) from the sof member of
 * passed in s_ipcc_frame type struct to passed in sof value
 */
int8 ipcc_term_extract_ipcc_ctrl(s_ipcc_frame *ipcc_term, s_ipcc_ctrl *ipcc_ctrl)
{
    int8 status;

    if ( NULL != ipcc_term && NULL != ipcc_ctrl )
    {
        s_ipcc_ctrl_util info;
        info.byte = ipcc_term->ipcc_ctrl;
        ipcc_ctrl->source_dest = info.ctrl.source_dest;
        ipcc_ctrl->tx_mode     = info.ctrl.tx_mode;
        ipcc_ctrl->comm_mode   = info.ctrl.comm_mode;
        ipcc_ctrl->auth        = info.ctrl.auth;
        ipcc_ctrl->encrypt     = info.ctrl.encrypt;
        ipcc_ctrl->reserved_1  = info.ctrl.reserved_1;
        ipcc_ctrl->reserved_2  = info.ctrl.reserved_2;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

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

    if ( NULL != ipcc_term && NULL != seq_no )
    {
        *seq_no = ipcc_term->seq_no;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the qos of passed
 * in s_ipcc_frame type struct to passed in qos value
 */
int8 ipcc_term_extract_qos(s_ipcc_frame *ipcc_term, uint16 *qos)
{
    int8 status;

    if ( NULL != ipcc_term && NULL != qos )
    {
        *qos = ipcc_term->qos;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

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

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

    return status;
}

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

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

    return status;
}

/**
 * @brief This API stores the ipcc_id of passed
 * in s_ipcc_frame type struct to passed in ipcc_id value
 */
int8 ipcc_term_extract_ipcc_id(s_ipcc_frame *ipcc_term, uint8 *ipcc_id)
{
    int8 status;

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

    return status;
}

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

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

    return status;
}

/**
 * @brief This API adds the ipcc_id and ipcc_func in the corresponding index of
 * ipcc_id[] and ipcc_func[] member of type s_ipcc_terminator
 */
int8 ipcc_term_add_ipcc_func(s_ipcc_terminator* ipcc_term, uint8 ipcc_id, Operation ipcc_func)
{
    int8 status;

    if ( NULL != ipcc_term && NULL != ipcc_func )
    {
        ipcc_term->ipcc_id = ipcc_id;
        ipcc_term->ipcc_func[ipcc_id] = ipcc_func;
        ipcc_term_local.ipcc_id= ipcc_id;
        ipcc_term_local.ipcc_func[ipcc_id] = ipcc_func;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API removes the ipcc_id and ipcc_func in the corresponding index of
 * type s_ipcc_terminator
 */
int8 ipcc_term_remove_ipcc_func(s_ipcc_terminator* ipcc_term, uint8 ipcc_id)
{
    int8 status;

    if ( NULL != ipcc_term)
    {
        ipcc_term->ipcc_id = -1;
        ipcc_term->ipcc_func[ipcc_id] = NULL;
        ipcc_term_local.ipcc_id = -1;
        ipcc_term_local.ipcc_func[ipcc_id] = NULL;
        status = IPCC_OK;
    }
    else
    {
       status = IPCC_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API copies the structure data to the input buffer(dst) to the
 *        input structure. It also computes crc and hmac for the input buffer
 *        and compares it with the embedded values.
 */
int32 ipcc_unpack(const uint8 *dst, s_ipcc_frame *ipcc_orig, uint32 length)
{
    int32 i = 0;
    //size_t hmac_size = 0;
    int32 rslt = -1;
    uint32 crc_32 = 0;
    uint32 rem_len = 0;

    DEBUG_CPRINT(("EXO IPCC Frame decoder\n"));

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

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

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

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

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

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

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

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

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

            if(IPCC_LENGTH_MAX > ipcc_orig->ipcc_len)
            {
                // Calculation current frame size with header
                rem_len = i + ipcc_orig->ipcc_len + sizeof(uint64_t) + sizeof(ipcc_orig->sec_cfg) + sizeof(ipcc_orig->mac_cfg) + sizeof(ipcc_orig->crc_32);
                if(length >= rem_len)
                {
                    os_memcpy(ipcc_orig->ipcc_data, &dst[i], ipcc_orig->ipcc_len);
                    i += ipcc_orig->ipcc_len;

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

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

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

                    ipcc_orig_compute_crc (dst, &crc_32, i);

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

                    if( crc_32 != ipcc_orig->crc_32)
                    {
                        rslt = IPCC_CRC_ERROR; 
                    }
                    else
                    {
                        rslt = i;
                    }
                }
                else
                {
                    rslt = IPCC_FULL_FRAME_NOT_RCVD;
                }
            }
            else
            {
                rslt = IPCC_LENGTH_OF_ERROR;
            }
        }
        else
        {
            rslt = IPCC_SOF_MISMATCH_ERROR;
        }
#if 0
        hmac_size = ipcc_compute_hmac (dst, (const uint8*)KEY, strlen(KEY), i, out);

        os_memcpy(ipcc_orig->cmac_hmac, &dst[i], hmac_size);
        i += hmac_size;

        if( crc_32 == ipcc_orig->crc_32)
        {
            rslt = memcmp(out, ipcc_orig->cmac_hmac, hmac_size);

            if(0 != rslt)
            {
                rslt = IPCC_HMAC_ERROR;
            }
            else
            {
                rslt = i;
            }
        }
        else
        {
            rslt = IPCC_CRC_ERROR;
        }
#endif
    }
    else
    {
        rslt = IPCC_NULL_PTR;
    }

    return rslt;

}

/**
 * @brief This API copies the structure data to the input buffer(dst) to the
 *        input structure. It also computes crc and hmac for the input buffer
 *        and compares it with the embedded values. It also triggers the user
 *        function if the user function is mapped with the received
 *        ipcc_orig->ipcc_id with ipcc_term_add_ipcc_func() api
 */
int32 ipcc_unpack_trigger(const uint8 *dst, s_ipcc_frame *ipcc_orig, uint32 length)
{
    int32 rslt = -1;
    int32 size = 0;
    rslt = ipcc_unpack(dst, ipcc_orig, length);

    if(0 < rslt)
    {
    	size = rslt;
        rslt = ipcc_trgr_func(ipcc_orig);
        /**  - Check fot trigger success */
        if(IPCC_NULL_PTR != rslt)
                {
                    /**   - Update the length of IPCC received */
                    rslt = size;
              }

    }
    else
    {
        DEBUG_CPRINT(("IPCC Unpack failure Error code:- %d\n",rslt));
        /* error in unpacking. Pass the rslt as return value */
    }

    return rslt;
}

/**
 * @brief This API triggers the user function if the user function is
 *        mapped with the received ipcc_orig->ipcc_id with 
 *        ipcc_term_add_ipcc_func() api
 */
int8 ipcc_trgr_func (s_ipcc_frame *ipcc_orig)
{
    int8 status;
    if(NULL != ipcc_orig && NULL != ipcc_term_local.ipcc_func[ipcc_orig->ipcc_id])
    {
        status = ipcc_term_local.ipcc_func[ipcc_orig->ipcc_id](ipcc_orig);
    }
    else
    {
        status = IPCC_NULL_PTR;
    }
    return status;
}
