/*
 * 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 AbsSecBlock.c
 * @brief Concrete implementation of the Abstract Security Block defined in RFC 9172.
 * @ingroup backend_dyn
 */
#include <qcbor/qcbor_encode.h>
#include <qcbor/qcbor_spiffy_decode.h>

#include <BPSecLib_Private.h>

#include "AbsSecBlock.h"

size_t BSL_AbsSecBlock_Sizeof(void)
{
    return sizeof(BSL_AbsSecBlock_t);
}

bool BSL_AbsSecBlock_IsConsistent(const BSL_AbsSecBlock_t *self)
{
    // GCOV_EXCL_START
    // NOLINTBEGIN
    CHK_AS_BOOL(self != NULL);
    CHK_AS_BOOL(self->source_eid.handle != NULL);
    // NOLINTEND
    // GCOV_EXCL_STOP
    return true;
}

void BSL_AbsSecBlock_Print(const BSL_AbsSecBlock_t *self)
{
    BSL_LOG_INFO("ASB  context id: %" PRId64, self->sec_context_id);
    for (size_t index = 0; index < uint64_list_size(self->targets); index++)
    {
        BSL_LOG_INFO("ASB  target[%zu]: %" PRIu64, index, *uint64_list_cget(self->targets, index));
    }

    for (size_t index = 0; index < BSLB_SecParamList_size(self->params); index++)
    {
        BSL_SecParam_t *param = BSLB_SecParamList_get(self->params, index);
        if (BSL_SecParam_IsUint64(param))
        {
            BSL_LOG_INFO("ASB  Param[%zu]: id=%" PRIu64 " val=%" PRIu64, index, param->param_id, param->_uint_value);
        }
        else if (BSL_SecParam_IsBytestr(param))
        {
            size_t         blen = m_bstring_size(param->_bytes);
            const uint8_t *bptr = m_bstring_view(param->_bytes, 0, blen);
            char           hex_str[2 * blen + 1];
            BSL_Log_DumpAsHexString(hex_str, sizeof(hex_str), bptr, blen);
            BSL_LOG_INFO("ASB  Param[%zu]: id=%" PRIu64 " %s", index, param->param_id, hex_str);
        }
    }

    for (size_t index = 0; index < BSLB_SecResultList_size(self->results); index++)
    {
        BSL_SecResult_t *sec_result = BSLB_SecResultList_get(self->results, index);

        size_t         blen = m_bstring_size(sec_result->_bytes);
        const uint8_t *bptr = m_bstring_view(sec_result->_bytes, 0, blen);
        char           hex_str[2 * blen + 1];
        BSL_Log_DumpAsHexString(hex_str, sizeof(hex_str), bptr, blen);
        BSL_LOG_INFO("ASB  Result[%zu]: tgt=%" PRIu64 ", id=%" PRIu64 " %s", index, sec_result->target_block_num,
                     sec_result->result_id, hex_str);
    }
}

void BSL_AbsSecBlock_Init(BSL_AbsSecBlock_t *self)
{
    // GCOV_EXCL_START
    ASSERT_ARG_NONNULL(self);
    // GCOV_EXCL_STOP

    memset(self, 0, sizeof(*self));

    uint64_list_init(self->targets);
    self->sec_context_id = 0;
    BSL_HostEID_Init(&self->source_eid);
    BSLB_SecParamList_init(self->params);
    BSLB_SecResultList_init(self->results);

    // GCOV_EXCL_START
    ASSERT_POSTCONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP
}

void BSL_AbsSecBlock_Deinit(BSL_AbsSecBlock_t *self)
{
    // GCOV_EXCL_START
    ASSERT_PRECONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP

    BSLB_SecResultList_clear(self->results);
    BSLB_SecParamList_clear(self->params);
    BSL_HostEID_Deinit(&self->source_eid);
    uint64_list_clear(self->targets);

    memset(self, 0, sizeof(*self));
}

bool BSL_AbsSecBlock_IsEmpty(const BSL_AbsSecBlock_t *self)
{
    // GCOV_EXCL_START
    ASSERT_ARG_NONNULL(self);
    // GCOV_EXCL_STOP

    bool is_empty = (uint64_list_size(self->targets) == 0) && (BSLB_SecResultList_size(self->results) == 0);
    return is_empty;
}

int64_t BSL_AbsSecBlock_GetContextID(const BSL_AbsSecBlock_t *self)
{
    // GCOV_EXCL_START
    ASSERT_ARG_NONNULL(self);
    // GCOV_EXCL_STOP

    return self->sec_context_id;
}

bool BSL_AbsSecBlock_ContainsTarget(const BSL_AbsSecBlock_t *self, uint64_t target_block_num)
{
    // GCOV_EXCL_START
    ASSERT_PRECONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP

    for
        M_EACH(target_num, self->targets, M_ARRAY_OPLIST(uint64_list))
        {
            if (*target_num == target_block_num)
            {
                return true;
            }
        }
    return false;
}

void BSL_AbsSecBlock_AddTarget(BSL_AbsSecBlock_t *self, uint64_t target_block_id)
{
    // GCOV_EXCL_START
    ASSERT_PRECONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP

    uint64_list_push_back(self->targets, target_block_id);

    // GCOV_EXCL_START
    ASSERT_POSTCONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP
}

void BSL_AbsSecBlock_AddParam(BSL_AbsSecBlock_t *self, const BSL_SecParam_t *param)
{
    // GCOV_EXCL_START
    ASSERT_ARG_NONNULL(param);
    ASSERT_PRECONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP

    BSLB_SecParamList_push_back(self->params, *param);

    // GCOV_EXCL_START
    ASSERT_POSTCONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP
}

void BSL_AbsSecBlock_AddResult(BSL_AbsSecBlock_t *self, const BSL_SecResult_t *result)
{
    // GCOV_EXCL_START
    ASSERT_ARG_NONNULL(result);
    ASSERT_PRECONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP

    BSLB_SecResultList_push_back(self->results, *result);

    // GCOV_EXCL_START
    ASSERT_POSTCONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP
}

static size_t BSL_AbsSecBlock_GetResultCnt(const BSL_AbsSecBlock_t *self, uint64_t target_block_id)
{
    // GCOV_EXCL_START
    ASSERT_PRECONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP

    size_t match_count = 0;
    for (size_t index = 0; index < BSLB_SecResultList_size(self->results); index++)
    {
        BSL_SecResult_t *result = BSLB_SecResultList_get(self->results, index);
        if (result->target_block_num == target_block_id)
        {
            match_count++;
        }
    }
    return match_count;
}

int BSL_AbsSecBlock_StripResults(BSL_AbsSecBlock_t *self, uint64_t target_block_num)
{
    // GCOV_EXCL_START
    CHK_PRECONDITION(BSL_AbsSecBlock_IsConsistent(self));
    // GCOV_EXCL_STOP

    size_t things_removed = 0;

    // Remove target uint64 from target list
    // TODO - The m-list is not ideal. We should just use an array.
    uint64_list_it_t target_iter;
    uint64_list_it(target_iter, self->targets);
    for (size_t i = 0; i < uint64_list_size(self->targets); i++)
    {
        uint64_t *curr_target = uint64_list_ref(target_iter);
        if (*curr_target == target_block_num)
        {
            break;
        }
        uint64_list_next(target_iter);
    }
    ASSERT_PROPERTY(!uint64_list_end_p(target_iter));

    uint64_list_remove(self->targets, target_iter);
    things_removed++;

    while (BSL_AbsSecBlock_GetResultCnt(self, target_block_num) > 0)
    {
        BSLB_SecResultList_it_t result_iter;
        BSLB_SecResultList_it(result_iter, self->results);
        size_t index = 0;
        for (index = 0; index < BSLB_SecResultList_size(self->results); index++)
        {
            BSL_SecResult_t *sec_result = BSLB_SecResultList_ref(result_iter);
            if (sec_result->target_block_num == target_block_num)
            {
                break;
            }
            BSLB_SecResultList_next(result_iter);
        }

        if (!BSLB_SecResultList_end_p(result_iter) && index < BSLB_SecResultList_size(self->results))
        {
            BSLB_SecResultList_remove(self->results, result_iter);
            things_removed++;
        }
        else
        {
            // It should have always found one.
            BSL_LOG_ERR("Expected to have result to remove");
            return BSL_ERR_PROPERTY_CHECK_FAILED;
        }
    }

    CHK_POSTCONDITION(BSL_AbsSecBlock_IsConsistent(self));
    return (int)things_removed;
}

ssize_t BSL_AbsSecBlock_EncodeToCBOR(const BSL_AbsSecBlock_t *self, BSL_Data_t *buf)
{
    CHK_PRECONDITION(BSL_AbsSecBlock_IsConsistent(self));

    QCBOREncodeContext encoder;

    UsefulBuf qcbor_buf = buf->ptr ? (UsefulBuf) { .ptr = buf->ptr, .len = buf->len } : SizeCalculateUsefulBuf;
    QCBOREncode_Init(&encoder, qcbor_buf);

    {
        QCBOREncode_OpenArray(&encoder);
        uint64_list_it_t it;
        for (uint64_list_it(it, self->targets); !uint64_list_end_p(it); uint64_list_next(it))
        {
            QCBOREncode_AddUInt64(&encoder, *uint64_list_cref(it));
        }
        QCBOREncode_CloseArray(&encoder);
    }

    QCBOREncode_AddInt64(&encoder, self->sec_context_id);

    {
        uint64_t flags = 0;
        if (!BSLB_SecParamList_empty_p(self->params))
        {
            flags |= 0x1;
        }
        QCBOREncode_AddUInt64(&encoder, flags);
    }

    ssize_t encode_result = BSL_HostEID_EncodeToCBOR(&self->source_eid, NULL);
    if (encode_result <= 0)
    {
        BSL_LOG_ERR("Failed to calculate EID size");
        return BSL_ERR_ENCODING;
    }

    BSL_Data_t eid_data;
    BSL_Data_InitBuffer(&eid_data, (size_t)encode_result);
    encode_result = BSL_HostEID_EncodeToCBOR(&self->source_eid, &eid_data);
    if (encode_result <= BSL_SUCCESS)
    {
        BSL_LOG_ERR("Failed to encode EID");
        BSL_Data_Deinit(&eid_data);
        return BSL_ERR_ENCODING;
    }

    UsefulBufC eid_buf = (UsefulBufC) { .ptr = eid_data.ptr, .len = eid_data.len };
    QCBOREncode_AddEncoded(&encoder, eid_buf);
    BSL_Data_Deinit(&eid_data);

    {
        QCBOREncode_OpenArray(&encoder);

        BSLB_SecParamList_it_t pit;
        for (BSLB_SecParamList_it(pit, self->params); !BSLB_SecParamList_end_p(pit); BSLB_SecParamList_next(pit))
        {
            const BSL_SecParam_t *param = BSLB_SecParamList_cref(pit);
            QCBOREncode_OpenArray(&encoder);
            QCBOREncode_AddUInt64(&encoder, param->param_id);
            if (BSL_SecParam_IsUint64(param))
            {
                QCBOREncode_AddUInt64(&encoder, BSL_SecParam_GetAsUint64(param));
            }
            else if (BSL_SecParam_IsBytestr(param))
            {
                BSL_Data_t bytestr;
                BSL_SecParam_GetAsBytestr(param, &bytestr);
                UsefulBufC bytestr_buf = { .ptr = bytestr.ptr, .len = bytestr.len };
                QCBOREncode_AddBytes(&encoder, bytestr_buf);
            }
            else
            {
                BSL_LOG_ERR("Unhandled parameter type for ID %" PRIu64, BSL_SecParam_GetId(param));
                QCBOREncode_AddUndef(&encoder);
            }
            QCBOREncode_CloseArray(&encoder);
        }
        QCBOREncode_CloseArray(&encoder);
    }

    {
        // Encode results for each target.
        QCBOREncode_OpenArray(&encoder);
        for (size_t target_index = 0; target_index < uint64_list_size(self->targets); target_index++)
        {
            QCBOREncode_OpenArray(&encoder);
            const uint64_t *target_block_num = uint64_list_cget(self->targets, target_index);

            BSLB_SecResultList_it_t rit;
            for (BSLB_SecResultList_it(rit, self->results); !BSLB_SecResultList_end_p(rit);
                 BSLB_SecResultList_next(rit))
            {
                const BSL_SecResult_t *sec_result = BSLB_SecResultList_cref(rit);
                // TODO better internal structure to organize results by target
                if (sec_result->target_block_num != *target_block_num)
                {
                    continue;
                }
                QCBOREncode_OpenArray(&encoder);
                QCBOREncode_AddUInt64(&encoder, sec_result->result_id);
                {
                    BSL_Data_t bytestr;
                    BSL_SecResult_GetAsBytestr(sec_result, &bytestr);
                    UsefulBufC result_buf = { .ptr = bytestr.ptr, .len = bytestr.len };
                    QCBOREncode_AddBytes(&encoder, result_buf);
                }
                QCBOREncode_CloseArray(&encoder);
            }
            QCBOREncode_CloseArray(&encoder);
        }

        QCBOREncode_CloseArray(&encoder);
    }

    size_t     encode_sz;
    QCBORError qcbor_err = QCBOREncode_FinishGetSize(&encoder, &encode_sz);
    if (qcbor_err != QCBOR_SUCCESS)
    {
        BSL_LOG_ERR("Encoding ASB into BTSD failed: %s", qcbor_err_to_str(qcbor_err));
        return BSL_ERR_ENCODING;
    }
    BSL_LOG_DEBUG("QCBOR ENCODE SIZE: %zu", encode_sz);
    return (ssize_t)encode_sz;
}

int BSL_AbsSecBlock_DecodeFromCBOR(BSL_AbsSecBlock_t *self, const BSL_Data_t *buf)
{
    CHK_ARG_NONNULL(self);
    CHK_ARG_EXPR(buf->len > 0);
    CHK_ARG_EXPR(buf->ptr != NULL);

    QCBORDecodeContext asbdec;

    UsefulBufC qcbor_buf = { .ptr = buf->ptr, .len = buf->len };
    QCBORDecode_Init(&asbdec, qcbor_buf, QCBOR_DECODE_MODE_NORMAL);
    QCBORItem asbitem;

    QCBORDecode_EnterArray(&asbdec, NULL);

    // Make sure actually entered an array - otherwise, the following while loop could be infinite
    QCBORError tgt_array_err = QCBORDecode_GetError(&asbdec);
    if (QCBOR_SUCCESS != tgt_array_err)
    {
        BSL_LOG_ERR("ASB decoding: Failed to enter target array ; error %d (%s)", tgt_array_err,
                    qcbor_err_to_str(tgt_array_err));
        return BSL_ERR_DECODING;
    }

    while (QCBOR_SUCCESS == QCBORDecode_PeekNext(&asbdec, &asbitem))
    {
        uint64_t tgt_num = 0;
        QCBORDecode_GetUInt64(&asbdec, &tgt_num);
        if (QCBOR_SUCCESS != QCBORDecode_GetError(&asbdec))
        {
            BSL_LOG_ERR("Failed processing a security target");
            return BSL_ERR_DECODING;
        }

        BSL_LOG_DEBUG("got tgt %" PRIu64, tgt_num);
        uint64_list_push_back(self->targets, tgt_num);
    }
    QCBORDecode_ExitArray(&asbdec);
    const size_t targets_size = uint64_list_size(self->targets);

    {
        int64_t ctx_id = 0;
        QCBORDecode_GetInt64(&asbdec, &ctx_id);
        if ((ctx_id < INT16_MIN) || (ctx_id > INT16_MAX))
        {
            BSL_LOG_ERR("Invalid context id: %" PRId64, ctx_id);
        }
        else
        {
            self->sec_context_id = ctx_id;
        }
        BSL_LOG_DEBUG("got ctx_id %" PRId64, ctx_id);
    }

    uint64_t flags = 0;
    QCBORDecode_GetUInt64(&asbdec, &flags);
    BSL_LOG_DEBUG("got flags %" PRId64, flags);

    // Host-specific parsing of EID

    UsefulBufC all = QCBORDecode_RetrieveUndecodedInput(&asbdec);
    QCBORItem  eid_item;

    // Get size of next CBOR item
    uint32_t eid_item_start_index = QCBORDecode_Tell(&asbdec);
    QCBORDecode_VGetNextConsume(&asbdec, &eid_item);
    uint32_t eid_item_end_index = QCBORDecode_Tell(&asbdec);

    // Validate indexes
    if ((QCBOR_SUCCESS != QCBORDecode_GetError(&asbdec)) || (eid_item_end_index <= eid_item_start_index))
    {
        BSL_LOG_ERR("BSL DECODE FAIL");
        return BSL_ERR_DECODING;
    }

    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_HostEID_DecodeFromCBOR(&eid_cbor_data, &self->source_eid);
    BSL_Data_Deinit(&eid_cbor_data);
    if (res != BSL_SUCCESS)
    {
        BSL_LOG_ERR("BSL HOST EID DECODE FAIL");
        return BSL_ERR_DECODING;
    }

    // A zero value for flags means there are NO paramers, a value of 1 indicates there are parameters to parse.
    if (flags & 0x01)
    {
        // variable length array of parameters
        QCBORDecode_EnterArray(&asbdec, NULL);
        while (QCBOR_SUCCESS == QCBORDecode_PeekNext(&asbdec, &asbitem))
        {
            // each parameter is a 2-item array
            QCBORDecode_EnterArray(&asbdec, NULL);

            uint64_t item_id = 0;
            QCBORDecode_GetUInt64(&asbdec, &item_id);
            if (QCBOR_SUCCESS != QCBORDecode_GetError(&asbdec))
            {
                BSL_LOG_ERR("Failed getting a parameter type ID");
                return BSL_ERR_DECODING;
            }

            const size_t item_begin = QCBORDecode_Tell(&asbdec);
            QCBORDecode_PeekNext(&asbdec, &asbitem);
            switch (asbitem.uDataType)
            {
                case QCBOR_TYPE_INT64:
                case QCBOR_TYPE_UINT64:
                {
                    uint64_t dec_value = 0;
                    QCBORDecode_GetUInt64(&asbdec, &dec_value);
                    if (QCBOR_SUCCESS != QCBORDecode_GetError(&asbdec))
                    {
                        BSL_LOG_ERR("Invalid integer parameter value for ID %" PRIu64, item_id);
                        break;
                    }
                    BSL_LOG_DEBUG("ASB: Parsed Param[%" PRIu64 "] = %" PRIu64, item_id, dec_value);
                    BSL_SecParam_t param;
                    BSL_SecParam_InitUint64(&param, item_id, dec_value);
                    BSLB_SecParamList_push_back(self->params, param);
                    BSL_SecParam_Deinit(&param);
                    break;
                }
                case QCBOR_TYPE_BYTE_STRING:
                {
                    UsefulBufC target_buf = NULLUsefulBufC;
                    QCBORDecode_GetByteString(&asbdec, &target_buf);
                    if (QCBOR_SUCCESS != QCBORDecode_GetError(&asbdec))
                    {
                        BSL_LOG_ERR("Invalid bytestring parameter value for ID %" PRIu64, item_id);
                        break;
                    }
                    BSL_LOG_DEBUG("ASB: Parsed Param[%" PRIu64 "] (ByteStr) = %zu bytes at %p", item_id, target_buf.len,
                                  target_buf.ptr);
                    BSL_Data_t data_view;
                    BSL_Data_InitView(&data_view, target_buf.len, (BSL_DataPtr_t)target_buf.ptr);
                    BSL_SecParam_t param;
                    BSL_SecParam_InitBytestr(&param, item_id, data_view);
                    BSLB_SecParamList_push_back(self->params, param);
                    BSL_SecParam_Deinit(&param);
                    break;
                }
                default:
                    BSL_LOG_CRIT("ASB ignoring non-bytestring item with QCBOR type %u", asbitem.uDataType);
                    // skip over entire item (recursively)
                    QCBORDecode_VGetNextConsume(&asbdec, &asbitem);
                    // NOLINTNEXTLINE
                    return BSL_ERR_DECODING;
            }
            const size_t item_end = QCBORDecode_Tell(&asbdec);

            QCBORDecode_ExitArray(&asbdec);
            if (QCBOR_SUCCESS != QCBORDecode_GetError(&asbdec))
            {
                BSL_LOG_ERR("Failed processing a security parameter");
                return BSL_ERR_DECODING;
            }
            BSL_LOG_DEBUG("param %" PRIu64 " between %zu and %zu", item_id, item_begin, item_end);
        }
        QCBORDecode_ExitArray(&asbdec);
    }

    QCBORDecode_EnterArray(&asbdec, NULL);
    size_t target_index = 0;
    while (QCBOR_SUCCESS == QCBORDecode_PeekNext(&asbdec, &asbitem))
    {
        if (target_index >= targets_size)
        {
            BSL_LOG_ERR("ASB result array has too many items, expected %zu got %zu", targets_size, target_index);
            return BSL_ERR_DECODING;
        }
        // Each set of results correlates to the same ordinal number of the target list
        uint64_t target_id = *uint64_list_cget(self->targets, target_index);
        BSL_LOG_DEBUG("Parsing ASB results for target[index=%zu, block#=%zu]", target_index, target_id);
        target_index++;

        // variable length array of result pairs
        QCBORDecode_EnterArray(&asbdec, NULL);
        while (QCBOR_SUCCESS == QCBORDecode_PeekNext(&asbdec, &asbitem))
        {
            // each parameter is a 2-item array
            QCBORDecode_EnterArray(&asbdec, NULL);

            uint64_t item_id = 0;
            QCBORDecode_GetUInt64(&asbdec, &item_id);
            if (QCBOR_SUCCESS != QCBORDecode_GetError(&asbdec))
            {
                BSL_LOG_ERR("Failed getting a result type ID");
                return BSL_ERR_DECODING;
            }

            const size_t item_begin = QCBORDecode_Tell(&asbdec);

            QCBORError is_ok = QCBORDecode_PeekNext(&asbdec, &asbitem);
            CHK_PROPERTY(is_ok == QCBOR_SUCCESS);

            if (asbitem.uDataType == QCBOR_TYPE_BYTE_STRING)
            {
                UsefulBufC bstr_qcbor_buf;
                QCBORDecode_GetByteString(&asbdec, &bstr_qcbor_buf);
                BSL_Data_t bstr_buf;
                BSL_Data_InitView(&bstr_buf, bstr_qcbor_buf.len, (BSL_DataPtr_t)bstr_qcbor_buf.ptr);

                BSL_SecResult_t result;
                int result_code = BSL_SecResult_InitFull(&result, item_id, self->sec_context_id, target_id, &bstr_buf);
                ASSERT_PROPERTY(result_code == 0);
                BSL_LOG_DEBUG("ASB: Parsed Result (target_block=%" PRIu64 ", len=%zu)", result.target_block_num,
                              bstr_buf.len);
                BSLB_SecResultList_push_back(self->results, result);
                BSL_SecResult_Deinit(&result);
            }
            else
            {
                BSL_LOG_ERR("ASB ignoring non-bytestring item with QCBOR type %u", asbitem.uDataType);
                // skip over entire item (recursively)
                QCBORDecode_VGetNextConsume(&asbdec, &asbitem);
                // NOLINTNEXTLINE
                return BSL_ERR_DECODING;
            }
            const size_t item_end = QCBORDecode_Tell(&asbdec);

            QCBORDecode_ExitArray(&asbdec);
            if (QCBOR_SUCCESS != QCBORDecode_GetError(&asbdec))
            {
                BSL_LOG_ERR("Failed processing a security result");
                return BSL_ERR_DECODING;
            }
            BSL_LOG_DEBUG("result %" PRIu64 " between %zu and %zu", item_id, item_begin, item_end);
        }
        QCBORDecode_ExitArray(&asbdec);
    }
    QCBORDecode_ExitArray(&asbdec);

    QCBORError err = QCBORDecode_Finish(&asbdec);
    if (err != QCBOR_SUCCESS)
    {
        BSL_LOG_WARNING("ASB decoding error %d (%s)", err, qcbor_err_to_str(err));
        return BSL_ERR_DECODING;
    }

    if (target_index < targets_size)
    {
        BSL_LOG_ERR("ASB result array has too few items, expected %zu got %zu", targets_size, target_index);
        return BSL_ERR_DECODING;
    }

    ASSERT_POSTCONDITION(BSL_AbsSecBlock_IsConsistent(self));
    return BSL_SUCCESS;
}
