/**
 * @file tc_orig.c
 *
 * @brief This file contains functions that helps to create
 *        the telecommand frame
 *
 * The functions provided in the file helps to create tele command
 * frame with crc and encryptions.
 *
 * @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.
 */
/*********************************************************************/
#include <tc_common.h>

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

/// s_crypt_params local instance to keep track of cryption parameters
static s_crypt_params tc_param_local;

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

    if(NULL != tc_orig)
    {
        os_memset(tc_orig, 0, sizeof(s_tc_frame));
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the start of frame passed in sof1 and sof2 value to
 * to the sof1 and sof2 members of passed in s_tc_frame type struct. Refer
 * section 3.2.1.1 in TC_TM_GS & Space Communication Protocol Design document
 * for more information
 */
int8 tc_orig_cfg_sof(s_tc_frame *tc_orig, uint8 sof1, uint8 sof2)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->sof1 = sof1;
        tc_orig->sof2 = sof2;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the tc_ctrl value to the tc_ctrl member
 * of passed isrc/tc_orig.c:608:33: error: the comparison will always evaluate as 'true' for the address of 'iv' will never be NULL [-Werror=address]
 * n s_tc_frame type struct. Refer section 3.2.1.2 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_tc_ctrl(s_tc_frame *tc_orig, uint8 tc_ctrl)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->tc_ctrl = tc_ctrl;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in seq_no_in value to the seq_no member
 * of passed in s_tc_frame type struct and also updates the global seq_no.
 * Refer section 3.2.1.4 in TC_TM_GS & Space Communication Protocol Design
 * document for more information
 */
int8 tc_orig_cfg_seq_no(s_tc_frame *tc_orig, uint16 seq_no_in)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->seq_no = seq_no_in;
        seq_no = seq_no_in;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in sat_id value to the sat_id member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.5 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_sat_id(s_tc_frame *tc_orig, uint16 sat_id)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->sat_id = sat_id;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in gs_id value to the gs_id member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.6 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_gs_id(s_tc_frame *tc_orig, uint16 gs_id)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->gs_id = gs_id;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in priority value to the qos member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.7 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_qos(s_tc_frame *tc_orig, uint8 priority)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->qos = priority;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in sa_id value to the sa_id member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.8 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_sa_id(s_tc_frame *tc_orig, uint16 sa_id)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->sa_id = sa_id;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in da_id value to the da_id member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.9 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_da_id(s_tc_frame *tc_orig, uint16 da_id)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->da_id = da_id;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in rm_id value to the rm_id member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.10 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_rm_id(s_tc_frame *tc_orig, uint8 rm_id)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->rm_id = rm_id;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in tc_id value to the tc_id member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.11 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_tc_id(s_tc_frame *tc_orig, uint16 tc_id)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->tc_id = tc_id;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in tc_len value to the tc_len member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.12 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_tc_len(s_tc_frame *tc_orig, uint16 tc_len)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->tc_len = tc_len;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in value to the tc_len member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.13 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_tc_data(s_tc_frame *tc_orig, uint8 *data)
{
    int8 status;

    if((NULL != tc_orig) && (NULL != data))
    {
        tc_orig->tc_data = data;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in crc_8 value to the crc_8 member
 * of passed in s_tc_frame type struct. Refer section 3.2.1.14 in
 * TC_TM_GS & Space Communication Protocol Design document for more information
 */
int8 tc_orig_cfg_crc_8(s_tc_frame *tc_orig, uint8 crc_8)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->crc_8 = crc_8;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

/**
 * @brief This API stores the passed in end of frame (eof) to the eof member
 * member of passed in s_tc_frame type struct. Refer section 3.2.1.16
 * in TC_TM_GS & Space Communication Protocol Design document for more
 * more information
 */
int8 tc_orig_cfg_eof(s_tc_frame *tc_orig, uint8 eof)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->eof = eof;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

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

    if((NULL != tc_orig) && (NULL != key) && (NULL != out))
    {
        status =
            compute_hmac((const uint8 *)tc_orig, key, keysize, datasize, out);
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }
    return status;
}

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

    if((NULL != tc_orig) && (NULL != hmac))
    {
        os_memcpy(tc_orig->cmac_hmac, hmac, size);
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_NULL_PTR;
    }

    return status;
}

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

/**
 * @brief This API resets the timestamp to zero
 */
int8 tc_orig_reset_time_stamp(s_tc_frame *tc_orig)
{
    int8 status;

    if(NULL != tc_orig)
    {
        tc_orig->time_stamp = 0;
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_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 tc_orig_compute_crc(const uint8 *input_str, uint8 *val, size_t size)
{

    int8 status;

    if((NULL != input_str) && (NULL != val))
    {
        *val = crc_8(input_str, size);
        status = TC_TM_LIB_OK;
    }
    else
    {
        status = TC_TM_LIB_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.
 * (Refer Table 3 in TC_TM_GS & Space Communication Protocol Design document
 * for more information) Finally it computes CRC ,hmac and embeds those value
 * to the final output buffer(dst).
 */
int32 tc_pack(uint8 *dst, s_tc_frame *tc_orig)
{
    int32 size = 0;
    int8 status = TC_TM_LIB_OK;
    size_t hmac_size = 0;

    if((NULL != dst) && (NULL != tc_orig))
    {
        os_memcpy(&dst[size], &tc_orig->sof1, sizeof(tc_orig->sof1));
        size += sizeof(tc_orig->sof1);

        os_memcpy(&dst[size], &tc_orig->sof2, sizeof(tc_orig->sof2));
        size += sizeof(tc_orig->sof2);

        os_memcpy(&dst[size], &tc_orig->tc_ctrl, sizeof(tc_orig->tc_ctrl));
        size += sizeof(tc_orig->tc_ctrl);

        tc_orig->time_stamp = get_utc_epoch();

        os_memcpy(&dst[size], &tc_orig->time_stamp,
                  sizeof(tc_orig->time_stamp));

        size += sizeof(tc_orig->time_stamp);

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

        os_memcpy(&dst[size], &tc_orig->seq_no, sizeof(tc_orig->seq_no));
        size += sizeof(tc_orig->seq_no);

        // Populate the value based on 1 or 2 byte encoding
        size += set_extnd_val_16(&tc_orig->sat_id, &dst[size],
                                 sizeof(tc_orig->sat_id));

        // Populate the value based on 1 or 2 byte encoding
        size += set_extnd_val_16(&tc_orig->gs_id, &dst[size],
                                 sizeof(tc_orig->gs_id));

        os_memcpy(&dst[size], &tc_orig->qos, sizeof(tc_orig->qos));
        size += sizeof(tc_orig->qos);

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

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

        os_memcpy(&dst[size], &tc_orig->rm_id, sizeof(tc_orig->rm_id));
        size += sizeof(tc_orig->rm_id);

        // Populate the value based on 1 or 2 byte encoding
        size += set_extnd_val_16(&tc_orig->tc_id, &dst[size],
                                 sizeof(tc_orig->tc_id));
        //tc_len is fixed as 2bytes, before it is of variable length 1 or 2 bytes
        os_memcpy(&dst[size], &tc_orig->tc_len, sizeof(tc_orig->tc_len));
        size += sizeof(tc_orig->tc_len);


        if(((tc_orig->tc_ctrl & TC_CTRL_CRYPT_BIT) >> TC_CTRL_CRYPT_POS) & 1)
        {
            /* encrypt bit is set in tc_ctrl. encrypt the data */
            status = tc_orig_encrypt_data(&tc_param_local,
                                          (const uint8 *)tc_orig->tc_data,
                                          tc_orig->tc_len, tc_orig->tc_data);
        }

        if(TC_TM_LIB_OK == status)
        {
            os_memcpy(&dst[size], tc_orig->tc_data, tc_orig->tc_len);
            size += tc_orig->tc_len;
            /**
             *Either encryption is success or encryption bit is
             *not set in tc_ctrl
             */
            if(((tc_orig->tc_ctrl & TC_CTRL_AUTH_BIT) >> TC_CTRL_AUTH_POS) & 1)
            {
                hmac_size = compute_hmac(tc_orig->tc_data,
                                         (const uint8 *)TC_KEY, strlen(TC_KEY),
                                         tc_orig->tc_len, tc_orig->cmac_hmac);
                if(0 < hmac_size)
                {
                    os_memcpy(&dst[size], tc_orig->cmac_hmac, hmac_size);
                    size += hmac_size;
                }
                else
                {
                    /**
                     *compute_hmac failed for some reason. Set the size to
                     *compute_hmac status and return
                     */
                    size = hmac_size;
                    status = TC_TM_LIB_HMAC_ERROR;
                }
            }

            if(TC_TM_LIB_OK == status)
            {
                tc_orig_compute_crc(dst, &tc_orig->crc_8, size);

                os_memcpy(&dst[size], &tc_orig->crc_8, sizeof(tc_orig->crc_8));
                size += sizeof(tc_orig->crc_8);
                os_memcpy(&dst[size], &tc_orig->eof, sizeof(tc_orig->eof));
                size += sizeof(tc_orig->eof);
                seq_no++;
            }
            else
            {
                /**
                 * HMAC bit is set but the calculation failed.
                 * Size will already have the status of compute_hmac
                 */
            }
        }
        else
        {
            /**
             *encryption failed for some reason. Set the size to encryption
             *status and return
             */
            size = status;
        }
    }
    else
    {
        size = TC_TM_LIB_NULL_PTR;
    }

    return size;
}

/**
 * @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 tc_pack() api
 */
int8 tc_orig_select_encryption(s_crypt_params *param)
{
    int8 status = TC_TM_LIB_OK;

    if((NULL != param) && (NULL != param->key))
    {
        os_memcpy(tc_param_local.iv, param->iv, 16);
        tc_param_local.key = param->key;
        tc_param_local.key_size = param->key_size;
        tc_param_local.encrypt_mode = param->encrypt_mode;
    }
    else
    {
        status = TC_TM_LIB_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 tc_orig_encrypt_data(s_crypt_params *param, const uint8 *buf,
                           int32 length, uint8 *output)
{
    int32 status;

    if((NULL != param) && (NULL != buf) && (NULL != param->key) &&
       (NULL != output))
    {
        status = aes_encrypt_data(param, buf, length, output);
    }
    else
    {
        status = TC_TM_LIB_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 tc_orig_decrypt_data(s_crypt_params *param, const uint8 *buf,
                           size_t length, uint8 *output)
{
    int32 status;

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

    return status;
}
