/*
 * Copyright (c) 2025-2026 The Johns Hopkins University Applied Physics
 * Laboratory LLC.
 *
 * This file is part of the Bundle Protocol Security Library (BSL).
 *
 * 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.
 *
 * This work was performed for the Jet Propulsion Laboratory, California
 * Institute of Technology, sponsored by the United States Government under
 * the prime contract 80NM0018D0004 between the Caltech and NASA under
 * subcontract 1700763.
 */

/** @file
 * Definitions for bundle and block decoding.
 * @ingroup mock_bpa
 */
#include "decode.h"
#include "agent.h"
#include "crc.h"
#include <BPSecLib_Public.h>
#include <BPSecLib_Private.h>

#include <qcbor/qcbor_spiffy_decode.h>

int bsl_mock_decode_eid(const BSL_Data_t *encoded_bytes, BSL_HostEID_t *eid)
{
    bsl_mock_eid_t *obj = (bsl_mock_eid_t *)eid->handle;
    // GCOV_EXCL_START
    ASSERT_ARG_NONNULL(obj);
    // GCOV_EXCL_STOP

    bsl_mock_eid_deinit(obj);
    bsl_mock_eid_init(obj);

    QCBORDecodeContext dec;
    UsefulBufC         qcbor_buf = (UsefulBufC) { .ptr = encoded_bytes->ptr, .len = encoded_bytes->len };
    QCBORDecode_Init(&dec, qcbor_buf, QCBOR_DECODE_MODE_NORMAL);

    QCBORItem decitem;
    QCBORDecode_EnterArray(&dec, NULL);

    QCBORDecode_GetUInt64(&dec, &(obj->scheme));
    if (QCBOR_SUCCESS != QCBORDecode_GetError(&dec))
    {
        return 2;
    }

    switch (obj->scheme)
    {
        case BSL_MOCK_EID_IPN:
        {
            bsl_eid_ipn_ssp_t *ipn = &(obj->ssp.as_ipn);
            ASSERT_ARG_NONNULL(ipn);
            QCBORDecode_EnterArray(&dec, &decitem);
            if (decitem.val.uCount == 2)
            {
                ipn->ncomp = 2;
                uint64_t qnode;
                QCBORDecode_GetUInt64(&dec, &qnode);
                ipn->auth_num = qnode >> 32;
                ipn->node_num = qnode & 0xFFFFFFFF;
                QCBORDecode_GetUInt64(&dec, &(ipn->svc_num));
            }
            else if (decitem.val.uCount == 3)
            {
                ipn->ncomp = 3;
                QCBORDecode_GetUInt64(&dec, &(ipn->auth_num));
                QCBORDecode_GetUInt64(&dec, &(ipn->node_num));
                QCBORDecode_GetUInt64(&dec, &(ipn->svc_num));

                if ((ipn->auth_num > UINT32_MAX) || (ipn->node_num > UINT32_MAX))
                {
                    // parts larger than allowed
                    return 4;
                }
            }
            else
            {
                return 2;
            }
            QCBORDecode_ExitArray(&dec);
            break;
        }
        default:
        {
            // skip over item and store its encoded form
            const size_t begin = QCBORDecode_Tell(&dec);
            QCBORDecode_VGetNextConsume(&dec, &decitem);
            const size_t end = QCBORDecode_Tell(&dec);

            if ((QCBOR_SUCCESS == QCBORDecode_GetError(&dec)) && (end > begin))
            {
                const UsefulBufC buf = QCBORDecode_RetrieveUndecodedInput(&dec);

                BSL_Data_t *raw = &(obj->ssp.as_raw);
                ASSERT_ARG_NONNULL(raw);
                BSL_Data_Init(raw);
                // FIXME expose this from the decoder
                BSL_Data_CopyFrom(raw, end - begin, (const uint8_t *)buf.ptr + begin);
            }
            break;
        }
    }

    QCBORDecode_ExitArray(&dec);
    return 0;
}

int bsl_mock_decode_eid_from_ctx(QCBORDecodeContext *dec, BSL_HostEID_t *eid)
{
    UsefulBufC all = QCBORDecode_RetrieveUndecodedInput(dec);
    QCBORItem  eid_item;

    uint32_t eid_item_start_index = QCBORDecode_Tell(dec);
    QCBORDecode_VGetNextConsume(dec, &eid_item);
    uint32_t eid_item_end_index = QCBORDecode_Tell(dec);

    // Validate indexes
    if ((QCBOR_SUCCESS != QCBORDecode_GetError(dec)) || (eid_item_end_index <= eid_item_start_index))
    {
        return 2;
    }

    UsefulBufC eid_raw =
        (UsefulBufC) { (const uint8_t *)all.ptr + eid_item_start_index, eid_item_end_index - eid_item_start_index };

    BSL_Data_t eid_cbor_data;
    BSL_Data_InitView(&eid_cbor_data, eid_raw.len, (uint8_t *)eid_raw.ptr);

    int res = bsl_mock_decode_eid(&eid_cbor_data, eid);
    BSL_Data_Deinit(&eid_cbor_data);
    if (res != 0)
    {
        return 2;
    }

    return 0;
}

int bsl_mock_decode_primary(QCBORDecodeContext *dec, MockBPA_PrimaryBlock_t *blk)
{
    // GCOV_EXCL_START
    BSL_CHKERR1(dec);
    BSL_CHKERR1(blk);
    // GCOV_EXCL_STOP

    const size_t begin = QCBORDecode_Tell(dec);
    QCBORDecode_EnterArray(dec, NULL);

    QCBORDecode_GetUInt64(dec, &(blk->version));
    if ((QCBOR_SUCCESS != QCBORDecode_GetError(dec)) || (blk->version != 7))
    {
        return 2;
    }

    QCBORDecode_GetUInt64(dec, &(blk->flags));
    QCBORDecode_GetUInt64(dec, &(blk->crc_type));

    if (0 != bsl_mock_decode_eid_from_ctx(dec, &(blk->dest_eid)))
    {
        return 2;
    }

    if (0 != bsl_mock_decode_eid_from_ctx(dec, &(blk->src_node_id)))
    {
        return 2;
    }

    if (0 != bsl_mock_decode_eid_from_ctx(dec, &(blk->report_to_eid)))
    {
        return 2;
    }

    QCBORDecode_EnterArray(dec, NULL);
    QCBORDecode_GetUInt64(dec, &(blk->timestamp.bundle_creation_time));
    QCBORDecode_GetUInt64(dec, &(blk->timestamp.seq_num));
    QCBORDecode_ExitArray(dec);

    QCBORDecode_GetUInt64(dec, &(blk->lifetime));

    if (blk->flags & BSL_BUNDLE_IS_FRAGMENT)
    {
        QCBORDecode_GetUInt64(dec, &(blk->frag_offset));
        QCBORDecode_GetUInt64(dec, &(blk->adu_length));
    }
    else
    {
        blk->frag_offset = blk->adu_length = 0;
    }

    UsefulBufC view;
    switch (blk->crc_type)
    {
        case BSL_BUNDLECRCTYPE_16:
        case BSL_BUNDLECRCTYPE_32:
            // just ignore the bytes, CRC check will use the encoded buffer
            QCBORDecode_GetByteString(dec, &view);
            break;
        default:
            // nothing
            break;
    }

    QCBORDecode_ExitArray(dec);
    if (QCBOR_SUCCESS != QCBORDecode_GetError(dec))
    {
        return 2;
    }
    const size_t end = QCBORDecode_Tell(dec);

    const UsefulBufC buf = QCBORDecode_RetrieveUndecodedInput(dec);
    if (!mock_bpa_crc_check(buf, begin, end, blk->crc_type))
    {
        return 4;
    }

    BSL_Data_Resize(&blk->encoded, end - begin);
    memcpy(blk->encoded.ptr, (const uint8_t *)buf.ptr + begin, blk->encoded.len);

    return 0;
}

int bsl_mock_decode_canonical(QCBORDecodeContext *dec, MockBPA_CanonicalBlock_t *blk)
{
    // GCOV_EXCL_START
    BSL_CHKERR1(dec);
    BSL_CHKERR1(blk);
    // GCOV_EXCL_STOP

    const size_t begin = QCBORDecode_Tell(dec);
    QCBORDecode_EnterArray(dec, NULL);

    QCBORDecode_GetUInt64(dec, &(blk->blk_type));
    QCBORDecode_GetUInt64(dec, &(blk->blk_num));
    QCBORDecode_GetUInt64(dec, &(blk->flags));
    QCBORDecode_GetUInt64(dec, &(blk->crc_type));

    UsefulBufC view;
    QCBORDecode_GetByteString(dec, &view);
    if (QCBOR_SUCCESS == QCBORDecode_GetError(dec))
    {
        if (blk->btsd)
        {
            BSL_free(blk->btsd);
            blk->btsd = NULL;
        }

        blk->btsd_len = view.len;
        if (blk->btsd_len > 0)
        {
            blk->btsd = BSL_malloc(view.len);
            // GCOV_EXCL_START
            ASSERT_ARG_NONNULL(blk->btsd);
            // GCOV_EXCL_STOP
            memcpy(blk->btsd, view.ptr, view.len);
        }
    }

    switch (blk->crc_type)
    {
        case BSL_BUNDLECRCTYPE_16:
        case BSL_BUNDLECRCTYPE_32:
            // just ignore the bytes
            QCBORDecode_GetByteString(dec, &view);
            break;
        default:
            // nothing
            break;
    }

    QCBORDecode_ExitArray(dec);
    if (QCBOR_SUCCESS != QCBORDecode_GetError(dec))
    {
        return 2;
    }
    const size_t end = QCBORDecode_Tell(dec);

    if (!mock_bpa_crc_check(QCBORDecode_RetrieveUndecodedInput(dec), begin, end, blk->crc_type))
    {
        return 4;
    }

    return 0;
}

int bsl_mock_decode_bundle(QCBORDecodeContext *dec, MockBPA_Bundle_t *bundle)
{
    // GCOV_EXCL_START
    BSL_CHKERR1(dec);
    BSL_CHKERR1(bundle);
    // GCOV_EXCL_STOP

    QCBORItem decitem;
    QCBORDecode_EnterArray(dec, &decitem);
    if (QCBOR_SUCCESS != QCBORDecode_GetError(dec))
    {
        return 2;
    }
    if (decitem.val.uCount != QCBOR_COUNT_INDICATES_INDEFINITE_LENGTH)
    {
        BSL_LOG_WARNING("Bundle starts with definite length array");
    }

    BSL_LOG_DEBUG("decoding primary block...");
    int res = bsl_mock_decode_primary(dec, &(bundle->primary_block));
    if (res || (QCBOR_SUCCESS != QCBORDecode_GetError(dec)))
    {
        BSL_LOG_ERR("failed decoding primary block");
        return 2;
    }

    // iterate until failure of CBOR, not block decoder
    while (QCBOR_SUCCESS == QCBORDecode_PeekNext(dec, &decitem))
    {
        BSL_LOG_DEBUG("decoding canonical block (at %zd)...", QCBORDecode_Tell(dec));

        MockBPA_CanonicalBlock_t *blk = MockBPA_BlockList_push_back_new(bundle->blocks);

        res = bsl_mock_decode_canonical(dec, blk);
        if (res || (QCBOR_SUCCESS != QCBORDecode_GetError(dec)))
        {
            // block instance is already part of the bundle, so handle cleanup there
            BSL_LOG_ERR("failed decoding canonical block");
            return 3;
        }

        if (blk->blk_type == 0)
        {
            BSL_LOG_ERR("Invalid block type 0 on block number %" PRIu64, blk->blk_num);
            return 3;
        }
        if (blk->blk_num == 0)
        {
            BSL_LOG_ERR("Invalid block number 0 with block type %" PRIu64, blk->blk_type);
            return 3;
        }
        if (MockBPA_BlockByNum_cget(bundle->blocks_num, blk->blk_num))
        {
            BSL_LOG_ERR("Duplicate block number %" PRIu64 " present with block type %" PRIu64, blk->blk_num,
                        blk->blk_type);
            return 3;
        }

        MockBPA_BlockByNum_set_at(bundle->blocks_num, blk->blk_num, blk);
    }

    if (MockBPA_BlockList_empty_p(bundle->blocks))
    {
        BSL_LOG_ERR("No canonical blocks present, at least a payload block must be present to be valid");
        return 3;
    }
    const MockBPA_CanonicalBlock_t *last = MockBPA_BlockList_back(bundle->blocks);
    if (last->blk_type != 1)
    {
        BSL_LOG_ERR("The payload block must be the last block to be valid, last block type is %" PRIu64,
                    last->blk_type);
        return 3;
    }
    if (last->blk_num != 1)
    {
        BSL_LOG_ERR("The payload block must have block number 1, block number is %" PRIu64, last->blk_num);
        return 3;
    }

    BSL_LOG_DEBUG("exiting array (at %zd)", QCBORDecode_Tell(dec));
    QCBORDecode_ExitArray(dec);
    return 0;
}
