/**
 * @file
 * @brief BACnet AtomicReadFile service structures, codecs, and handlers.
 * @author Steve Karg <skarg@users.sourceforge.net>
 * @date 2005
 * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
 */
#include <stdint.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/bacdcode.h"
#include "bacnet/arf.h"

/**
 * @brief Encode the AtomicReadFile service request
 *
 *  AtomicReadFile-Request ::= SEQUENCE {
 *      file-identifier BACnetObjectIdentifier,
 *      access-method CHOICE {
 *          stream-access [0] SEQUENCE {
 *              file-start-position INTEGER,
 *              requested-octet-count Unsigned
 *          },
 *          record-access [1] SEQUENCE {
 *              file-start-record INTEGER,
 *              requested-record-count Unsigned
 *          }
 *      }
 *  }
 *
 * @param apdu  Pointer to the buffer for encoded values
 * @param data  Pointer to the service data used for encoding values
 * @return number of bytes encoded
 */
int arf_service_encode_apdu(
    uint8_t *apdu, const BACNET_ATOMIC_READ_FILE_DATA *data)
{
    int apdu_len = 0; /* total length of the apdu, return value */
    int len = 0;

    len = encode_application_object_id(
        apdu, data->object_type, data->object_instance);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    switch (data->access) {
        case FILE_STREAM_ACCESS:
            len = encode_opening_tag(apdu, 0);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_application_signed(
                apdu, data->type.stream.fileStartPosition);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_application_unsigned(
                apdu, data->type.stream.requestedOctetCount);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_closing_tag(apdu, 0);
            apdu_len += len;
            break;
        case FILE_RECORD_ACCESS:
            len = encode_opening_tag(apdu, 1);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_application_signed(
                apdu, data->type.record.fileStartRecord);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_application_unsigned(
                apdu, data->type.record.RecordCount);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_closing_tag(apdu, 1);
            apdu_len += len;
            break;
        default:
            break;
    }

    return apdu_len;
}

/**
 * @brief Encode the AtomicReadFile service request
 * @param apdu Pointer to the buffer for encoded values
 * @param apdu_size number of bytes available in the buffer
 * @param data  Pointer to the service data used for encoding values
 * @return number of bytes encoded, or zero if unable to encode or too large
 */
size_t atomicreadfile_service_request_encode(
    uint8_t *apdu, size_t apdu_size, const BACNET_ATOMIC_READ_FILE_DATA *data)
{
    size_t apdu_len = 0; /* total length of the apdu, return value */

    apdu_len = arf_service_encode_apdu(NULL, data);
    if (apdu_len > apdu_size) {
        apdu_len = 0;
    } else {
        apdu_len = arf_service_encode_apdu(apdu, data);
    }

    return apdu_len;
}

/**
 * @brief Encode the AtomicReadFile service request
 * @param apdu  Pointer to the buffer for decoding.
 * @param invoke_id original invoke id for request
 * @param data  Pointer to the property decoded data to be stored
 * @return number of bytes encoded
 */
int arf_encode_apdu(
    uint8_t *apdu, uint8_t invoke_id, const BACNET_ATOMIC_READ_FILE_DATA *data)
{
    int apdu_len = 0; /* total length of the apdu, return value */
    int len = 0;

    if (apdu) {
        apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
        apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU);
        apdu[2] = invoke_id;
        apdu[3] = SERVICE_CONFIRMED_ATOMIC_READ_FILE; /* service choice */
    }
    len = 4;
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    len = arf_service_encode_apdu(apdu, data);
    apdu_len += len;

    return apdu_len;
}

/**
 * @brief Decode the AtomicReadFile service request
 *
 *  AtomicReadFile-Request ::= SEQUENCE {
 *      file-identifier BACnetObjectIdentifier,
 *      access-method CHOICE {
 *          stream-access [0] SEQUENCE {
 *              file-start-position INTEGER,
 *              requested-octet-count Unsigned
 *          },
 *          record-access [1] SEQUENCE {
 *              file-start-record INTEGER,
 *              requested-record-count Unsigned
 *          }
 *      }
 *  }
 *
 * @param apdu  Pointer to the buffer for decoding.
 * @param apdu_size  Count of valid bytes in the buffer.
 * @param data  Pointer to the property decoded data to be stored
 *  or NULL for length
 *
 * @return number of bytes decoded or BACNET_STATUS_ERROR on error.
 */
int arf_decode_service_request(
    const uint8_t *apdu, unsigned apdu_size, BACNET_ATOMIC_READ_FILE_DATA *data)
{
    int tag_len = 0;
    int apdu_len = 0;
    BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
    uint32_t object_instance = 0;
    int32_t signed_integer;
    BACNET_UNSIGNED_INTEGER unsigned_integer;

    tag_len = bacnet_object_id_application_decode(
        &apdu[apdu_len], apdu_size - apdu_len, &object_type, &object_instance);
    if (tag_len <= 0) {
        return BACNET_STATUS_ERROR;
    }
    if (data) {
        data->object_type = (BACNET_OBJECT_TYPE)object_type;
        data->object_instance = object_instance;
    }
    apdu_len += tag_len;
    if (bacnet_is_opening_tag_number(
            &apdu[apdu_len], apdu_size - apdu_len, 0, &tag_len)) {
        if (data) {
            data->access = FILE_STREAM_ACCESS;
        }
        apdu_len += tag_len;
        /* fileStartPosition */
        tag_len = bacnet_signed_application_decode(
            &apdu[apdu_len], apdu_size - apdu_len, &signed_integer);
        if (tag_len <= 0) {
            return BACNET_STATUS_ERROR;
        }
        if (data) {
            data->type.stream.fileStartPosition = signed_integer;
        }
        apdu_len += tag_len;
        /* requestedOctetCount */
        tag_len = bacnet_unsigned_application_decode(
            &apdu[apdu_len], apdu_size - apdu_len, &unsigned_integer);
        if (tag_len <= 0) {
            return BACNET_STATUS_ERROR;
        }
        if (data) {
            data->type.stream.requestedOctetCount = unsigned_integer;
        }
        apdu_len += tag_len;
        /* closing tag */
        if (!bacnet_is_closing_tag_number(
                &apdu[apdu_len], apdu_size - apdu_len, 0, &tag_len)) {
            return BACNET_STATUS_ERROR;
        }
        apdu_len += tag_len;
    } else if (bacnet_is_opening_tag_number(
                   &apdu[apdu_len], apdu_size - apdu_len, 1, &tag_len)) {
        if (data) {
            data->access = FILE_RECORD_ACCESS;
        }
        apdu_len += tag_len;
        /* fileStartRecord */
        tag_len = bacnet_signed_application_decode(
            &apdu[apdu_len], apdu_size - apdu_len, &signed_integer);
        if (tag_len <= 0) {
            return BACNET_STATUS_ERROR;
        }
        if (data) {
            data->type.record.fileStartRecord = signed_integer;
        }
        apdu_len += tag_len;
        /* RecordCount */
        tag_len = bacnet_unsigned_application_decode(
            &apdu[apdu_len], apdu_size - apdu_len, &unsigned_integer);
        if (tag_len <= 0) {
            return BACNET_STATUS_ERROR;
        }
        if (data) {
            data->type.record.RecordCount = unsigned_integer;
        }
        apdu_len += tag_len;
        if (!bacnet_is_closing_tag_number(
                &apdu[apdu_len], apdu_size - apdu_len, 1, &tag_len)) {
            return BACNET_STATUS_ERROR;
        }
        apdu_len += tag_len;
    } else {
        return BACNET_STATUS_ERROR;
    }

    return apdu_len;
}

/**
 * @brief Decoding for AtomicReadFile APDU service data
 * @param apdu  Pointer to the buffer for decoding.
 * @param apdu_size  size of the buffer for decoding.
 * @param invoke_id [in] Invoked service ID.
 * @param data  Pointer to the property data values to be stored,
 *  or NULL for length
 * @return number of bytes decoded, or BACNET_STATUS_ERROR on error
 */
int arf_decode_apdu(
    const uint8_t *apdu,
    unsigned apdu_size,
    uint8_t *invoke_id,
    BACNET_ATOMIC_READ_FILE_DATA *data)
{
    int apdu_len = 0, len = 0;

    if (!apdu) {
        return BACNET_STATUS_ERROR;
    }
    if (apdu_size < 4) {
        return BACNET_STATUS_ERROR;
    }
    if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) {
        return BACNET_STATUS_ERROR;
    }
    /*  apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU); */
    if (invoke_id) {
        *invoke_id = apdu[2]; /* invoke id - filled in by net layer */
    }
    if (apdu[3] != SERVICE_CONFIRMED_ATOMIC_READ_FILE) {
        return BACNET_STATUS_ERROR;
    }
    len = 4;
    apdu_len += len;
    len =
        arf_decode_service_request(&apdu[apdu_len], apdu_size - apdu_len, data);
    if (len <= 0) {
        return BACNET_STATUS_ERROR;
    }
    apdu_len += len;

    return apdu_len;
}

/**
 * @brief Encode the AtomicReadFile-ACK service request
 *
 *  AtomicReadFile-ACK ::= SEQUENCE {
 *      end-of-file BOOLEAN,
 *      access-method CHOICE {
 *          stream-access [0] SEQUENCE {
 *             file-start-position INTEGER,
 *              file-data OCTET STRING
 *          },
 *          record-access [1] SEQUENCE {
 *              file-start-record INTEGER,
 *              returned-record-count Unsigned,
 *              file-record-data SEQUENCE OF OCTET STRING
 *          }
 *      }
 *  }
 *
 * @param apdu  Pointer to the buffer for encoding, or NULL for length
 * @param data  Pointer to the data to be encoded
 * @return number of bytes encoded
 */
int arf_ack_service_encode_apdu(
    uint8_t *apdu, const BACNET_ATOMIC_READ_FILE_DATA *data)
{
    int apdu_len = 0; /* total length of the apdu, return value */
    int len = 0;
    uint32_t i = 0;

    /* endOfFile */
    len = encode_application_boolean(apdu, data->endOfFile);
    if (apdu) {
        apdu += len;
    }
    apdu_len += len;
    switch (data->access) {
        case FILE_STREAM_ACCESS:
            len = encode_opening_tag(apdu, 0);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_application_signed(
                apdu, data->type.stream.fileStartPosition);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_application_octet_string(apdu, &data->fileData[0]);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_closing_tag(apdu, 0);
            apdu_len += len;
            break;
        case FILE_RECORD_ACCESS:
            len = encode_opening_tag(apdu, 1);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_application_signed(
                apdu, data->type.record.fileStartRecord);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            len = encode_application_unsigned(
                apdu, data->type.record.RecordCount);
            apdu_len += len;
            if (apdu) {
                apdu += len;
            }
            for (i = 0; i < data->type.record.RecordCount; i++) {
                len = encode_application_octet_string(apdu, &data->fileData[i]);
                apdu_len += len;
                if (apdu) {
                    apdu += len;
                }
            }
            len = encode_closing_tag(apdu, 1);
            apdu_len += len;
            break;
        default:
            break;
    }

    return apdu_len;
}

/**
 * @brief Encode the AtomicReadFile-ACK service request
 * @param apdu  Pointer to the buffer for decoding.
 * @param invoke_id original invoke id for request
 * @param data  Pointer to the property decoded data to be stored
 * @return number of bytes encoded
 */
int arf_ack_encode_apdu(
    uint8_t *apdu, uint8_t invoke_id, const BACNET_ATOMIC_READ_FILE_DATA *data)
{
    int apdu_len = 0; /* total length of the apdu, return value */
    int len = 0;

    if (apdu) {
        apdu[0] = PDU_TYPE_COMPLEX_ACK;
        apdu[1] = invoke_id;
        apdu[2] = SERVICE_CONFIRMED_ATOMIC_READ_FILE; /* service choice */
    }
    len = 3;
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    len = arf_ack_service_encode_apdu(apdu, data);
    apdu_len += len;

    return apdu_len;
}

/**
 * @brief Decoding for AtomicReadFile-ACK APDU service data
 *
 *  AtomicReadFile-ACK ::= SEQUENCE {
 *      end-of-file BOOLEAN,
 *      access-method CHOICE {
 *          stream-access [0] SEQUENCE {
 *             file-start-position INTEGER,
 *              file-data OCTET STRING
 *          },
 *          record-access [1] SEQUENCE {
 *              file-start-record INTEGER,
 *              returned-record-count Unsigned,
 *              file-record-data SEQUENCE OF OCTET STRING
 *          }
 *      }
 *  }
 *
 * @param apdu  Pointer to the buffer for decoding.
 * @param apdu_size  size of the buffer for decoding.
 * @param data  Pointer to the property data to be encoded,
 *  or NULL for length
 * @return Bytes encoded or BACNET_STATUS_ERROR on error.
 */
int arf_ack_decode_service_request(
    const uint8_t *apdu, unsigned apdu_size, BACNET_ATOMIC_READ_FILE_DATA *data)
{
    int apdu_len = 0;
    int len = 0;
    bool endOfFile;
    int32_t signed_integer;
    BACNET_UNSIGNED_INTEGER record_count, i;
    BACNET_OCTET_STRING *octet_string = NULL;

    len = bacnet_boolean_application_decode(apdu, apdu_size, &endOfFile);
    if (len <= 0) {
        return BACNET_STATUS_ERROR;
    }
    if (data) {
        data->endOfFile = endOfFile;
    }
    apdu_len += len;
    if (bacnet_is_opening_tag_number(
            &apdu[apdu_len], apdu_size - apdu_len, 0, &len)) {
        if (data) {
            data->access = FILE_STREAM_ACCESS;
        }
        apdu_len += len;
        /* fileStartPosition */
        len = bacnet_signed_application_decode(
            &apdu[apdu_len], apdu_size - apdu_len, &signed_integer);
        if (len <= 0) {
            return BACNET_STATUS_ERROR;
        }
        if (data) {
            data->type.stream.fileStartPosition = signed_integer;
        }
        apdu_len += len;
        /* fileData */
        if (data) {
            octet_string = &data->fileData[0];
        }
        len = bacnet_octet_string_application_decode(
            &apdu[apdu_len], apdu_size - apdu_len, octet_string);
        if (len <= 0) {
            return BACNET_STATUS_ERROR;
        }
        apdu_len += len;
        if (!bacnet_is_closing_tag_number(
                &apdu[apdu_len], apdu_size - apdu_len, 0, &len)) {
            return BACNET_STATUS_ERROR;
        }
        apdu_len += len;
    } else if (bacnet_is_opening_tag_number(
                   &apdu[apdu_len], apdu_size - apdu_len, 1, &len)) {
        if (data) {
            data->access = FILE_RECORD_ACCESS;
        }
        apdu_len += len;
        /* fileStartRecord */
        len = bacnet_signed_application_decode(
            &apdu[apdu_len], apdu_size - apdu_len, &signed_integer);
        if (len <= 0) {
            return BACNET_STATUS_ERROR;
        }
        if (data) {
            data->type.record.fileStartRecord = signed_integer;
        }
        apdu_len += len;
        /* returnedRecordCount */
        len = bacnet_unsigned_application_decode(
            &apdu[apdu_len], apdu_size - apdu_len, &record_count);
        if (len <= 0) {
            return BACNET_STATUS_ERROR;
        }
        if (data) {
            data->type.record.RecordCount = record_count;
        }
        apdu_len += len;
        for (i = 0; i < record_count; i++) {
            /* fileData */
            if (i >= BACNET_READ_FILE_RECORD_COUNT) {
                octet_string = NULL;
            } else if (data) {
                octet_string = &data->fileData[i];
            } else {
                octet_string = NULL;
            }
            len = bacnet_octet_string_application_decode(
                &apdu[apdu_len], apdu_size - apdu_len, octet_string);
            if (len <= 0) {
                return BACNET_STATUS_ERROR;
            }
            apdu_len += len;
        }
        if (!bacnet_is_closing_tag_number(
                &apdu[apdu_len], apdu_size - apdu_len, 1, &len)) {
            return BACNET_STATUS_ERROR;
        }
        apdu_len += len;
    } else {
        return BACNET_STATUS_ERROR;
    }

    return apdu_len;
}

/**
 * @brief Decoding for AtomicReadFile-ACK APDU service data
 * @param apdu  Pointer to the buffer for decoding.
 * @param apdu_size  size of the buffer for decoding.
 * @param invoke_id [in] Invoked service ID.
 * @param data  Pointer to the property data values to be stored,
 *  or NULL for length
 * @return number of bytes decoded, or BACNET_STATUS_ERROR on error
 */
int arf_ack_decode_apdu(
    const uint8_t *apdu,
    unsigned apdu_size,
    uint8_t *invoke_id,
    BACNET_ATOMIC_READ_FILE_DATA *data)
{
    int apdu_len = 0, len = 0;

    if (!apdu) {
        return BACNET_STATUS_ERROR;
    }
    if (apdu_size < 3) {
        return BACNET_STATUS_ERROR;
    }
    if (apdu[0] != PDU_TYPE_COMPLEX_ACK) {
        return BACNET_STATUS_ERROR;
    }
    if (invoke_id) {
        *invoke_id = apdu[1]; /* invoke id - filled in by net layer */
    }
    if (apdu[2] != SERVICE_CONFIRMED_ATOMIC_READ_FILE) {
        return BACNET_STATUS_ERROR;
    }
    len = 3;
    apdu_len += len;
    len = arf_ack_decode_service_request(
        &apdu[apdu_len], apdu_size - apdu_len, data);
    if (len <= 0) {
        return BACNET_STATUS_ERROR;
    }
    apdu_len += len;

    return apdu_len;
}
