/**
 * @file
 * @brief BACnet Change-of-Value (COV) services encode and decode functions
 * @author Steve Karg <skarg@users.sourceforge.net>
 * @date 2006
 * @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/bacapp.h"
/* me! */
#include "bacnet/cov.h"

/* Change-Of-Value Services
COV Subscribe
COV Subscribe Property
COV Notification
Unconfirmed COV Notification
*/

/**
 * @brief Encode APDU for COV Notification.
 * @param apdu  Pointer to the buffer, or NULL for length
 * @param data  Pointer to the data to encode.
 * @return number of bytes encoded, or zero on error.
 */
int cov_notify_encode_apdu(uint8_t *apdu, const BACNET_COV_DATA *data)
{
    int len = 0; /* length of each encoding */
    int apdu_len = 0; /* total length of the apdu, return value */
    const BACNET_PROPERTY_VALUE *value = NULL; /* value in list */

    if (!data) {
        return 0;
    }
    /* tag 0 - subscriberProcessIdentifier */
    len = encode_context_unsigned(apdu, 0, data->subscriberProcessIdentifier);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    /* tag 1 - initiatingDeviceIdentifier */
    len = encode_context_object_id(
        apdu, 1, OBJECT_DEVICE, data->initiatingDeviceIdentifier);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    /* tag 2 - monitoredObjectIdentifier */
    len = encode_context_object_id(
        apdu, 2, data->monitoredObjectIdentifier.type,
        data->monitoredObjectIdentifier.instance);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    /* tag 3 - timeRemaining */
    len = encode_context_unsigned(apdu, 3, data->timeRemaining);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    /* tag 4 - listOfValues */
    len = encode_opening_tag(apdu, 4);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    /* the first value includes a pointer to the next value, etc */
    value = data->listOfValues;
    while (value != NULL) {
        len = bacapp_property_value_encode(apdu, value);
        apdu_len += len;
        if (apdu) {
            apdu += len;
        }
        /* is there another one to encode? */
        value = value->next;
    }
    len = encode_closing_tag(apdu, 4);
    apdu_len += len;

    return apdu_len;
}

/**
 * @brief Encode the COVNotification service request
 * @param apdu  Pointer to the buffer for encoding into
 * @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 cov_notify_service_request_encode(
    uint8_t *apdu, size_t apdu_size, const BACNET_COV_DATA *data)
{
    size_t apdu_len = 0; /* total length of the apdu, return value */

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

    return apdu_len;
}

/**
 * Encode APDU for confirmed notification.
 *
 * @param apdu  Pointer to the buffer, or NULL for length
 * @param apdu_size number of bytes available in the buffer
 * @param invoke_id  ID to invoke for notification
 * @param data  Pointer to the data to encode.
 *
 * @return bytes encoded or zero on error.
 */
int ccov_notify_encode_apdu(
    uint8_t *apdu,
    unsigned apdu_size,
    uint8_t invoke_id,
    const BACNET_COV_DATA *data)
{
    int len = 0; /* length of each encoding */
    int apdu_len = 0; /* return value */

    if (apdu && (apdu_size > 4)) {
        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_COV_NOTIFICATION;
    }
    len = 4;
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    len = cov_notify_service_request_encode(apdu, apdu_size - apdu_len, data);
    if (len > 0) {
        apdu_len += len;
    } else {
        apdu_len = 0;
    }

    return apdu_len;
}

/**
 * @brief Encode APDU for unconfirmed notification.
 * @param apdu  Pointer to the buffer for encoding into, or NULL for length
 * @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
 */
int ucov_notify_encode_apdu(
    uint8_t *apdu, unsigned apdu_size, const BACNET_COV_DATA *data)
{
    int len = 0; /* length of each encoding */
    int apdu_len = 0; /* return value */

    if (apdu && (apdu_size > 2)) {
        apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST;
        apdu[1] = SERVICE_UNCONFIRMED_COV_NOTIFICATION;
    }
    len = 2;
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    len = cov_notify_service_request_encode(apdu, apdu_size - apdu_len, data);
    if (len > 0) {
        apdu_len += len;
    } else {
        apdu_len = 0;
    }

    return apdu_len;
}

/**
 * @brief Decode the COV-service request only.
 *
 * ConfirmedCOVNotification-Request ::= SEQUENCE {
 *      subscriber-process-identifier [0] Unsigned32,
 *      initiating-device-identifier [1] BACnetObjectIdentifier,
 *      monitored-object-identifier [2] BACnetObjectIdentifier,
 *      time-remaining [3] Unsigned,
 *      list-of-values [4] SEQUENCE OF BACnetPropertyValue
 *  }
 *
 * @note: COV and Unconfirmed COV are the same.
 * @param apdu  Pointer to the buffer.
 * @param apdu_size  Number of valid bytes in the buffer.
 * @param data  Pointer to the data to store the decoded values, or NULL
 *
 * @return Bytes decoded or BACNET_STATUS_ERROR on error.
 */
int cov_notify_decode_service_request(
    const uint8_t *apdu, unsigned apdu_size, BACNET_COV_DATA *data)
{
    int len = 0; /* return value */
    int value_len = 0, tag_len = 0;
    BACNET_UNSIGNED_INTEGER decoded_value = 0;
    BACNET_OBJECT_TYPE decoded_type = OBJECT_NONE;
    uint32_t decoded_instance = 0;
    BACNET_PROPERTY_VALUE *value = NULL;

    /* subscriber-process-identifier [0] Unsigned32 */
    value_len = bacnet_unsigned_context_decode(
        &apdu[len], apdu_size - len, 0, &decoded_value);
    if (value_len > 0) {
        if (data) {
            data->subscriberProcessIdentifier = decoded_value;
        }
        len += value_len;
    } else {
        return BACNET_STATUS_ERROR;
    }
    /* initiating-device-identifier [1] BACnetObjectIdentifier */
    value_len = bacnet_object_id_context_decode(
        &apdu[len], apdu_size - len, 1, &decoded_type, &decoded_instance);
    if (value_len > 0) {
        if (decoded_type != OBJECT_DEVICE) {
            return BACNET_STATUS_ERROR;
        }
        if (data) {
            data->initiatingDeviceIdentifier = decoded_instance;
        }
        len += value_len;
    } else {
        return BACNET_STATUS_ERROR;
    }
    /* monitored-object-identifier [2] BACnetObjectIdentifier */
    value_len = bacnet_object_id_context_decode(
        &apdu[len], apdu_size - len, 2, &decoded_type, &decoded_instance);
    if (value_len > 0) {
        if (data) {
            data->monitoredObjectIdentifier.type = decoded_type;
            data->monitoredObjectIdentifier.instance = decoded_instance;
        }
        len += value_len;
    } else {
        return BACNET_STATUS_ERROR;
    }
    /* time-remaining [3] Unsigned */
    value_len = bacnet_unsigned_context_decode(
        &apdu[len], apdu_size - len, 3, &decoded_value);
    if (value_len > 0) {
        if (data) {
            data->timeRemaining = decoded_value;
        }
        len += value_len;
    } else {
        return BACNET_STATUS_ERROR;
    }
    /* list-of-values [4] SEQUENCE OF BACnetPropertyValue */
    if (bacnet_is_opening_tag_number(
            &apdu[len], apdu_size - len, 4, &tag_len)) {
        if (data) {
            len += tag_len;
            /* the first value includes a pointer to the next value, etc */
            value = data->listOfValues;
            while (value != NULL) {
                value_len = bacapp_object_property_value_decode(
                    &apdu[len], apdu_size - len, value, decoded_type);
                if (value_len == BACNET_STATUS_ERROR) {
                    return BACNET_STATUS_ERROR;
                } else {
                    len += value_len;
                }
                /* end of list? */
                if (bacnet_is_closing_tag_number(
                        &apdu[len], apdu_size - len, 4, &tag_len)) {
                    len += tag_len;
                    value->next = NULL;
                    break;
                }
                /* is there another one to decode? */
                value = value->next;
                if (value == NULL) {
                    /* out of room to store next value */
                    return BACNET_STATUS_ERROR;
                }
            }
        } else {
            /* this len function needs to start at the opening tag
               to match opening/closing tags like a stack.
               However, it returns the len between the tags. */
            value_len =
                bacnet_enclosed_data_length(&apdu[len], apdu_size - len);
            len += value_len;
            /* add the opening tag length to the totals */
            len += tag_len;
        }
    }

    return len;
}

/*
12.11.38Active_COV_Subscriptions
The Active_COV_Subscriptions property is a List of BACnetCOVSubscription,
each of which consists of a Recipient, a Monitored Property Reference,
an Issue Confirmed Notifications flag, a Time Remaining value and an
optional COV Increment. This property provides a network-visible indication
of those COV subscriptions that are active at any given time.
Whenever a COV Subscription is created with the SubscribeCOV or
SubscribeCOVProperty service, a new entry is added to
the Active_COV_Subscriptions list. Similarly, whenever a COV Subscription
is terminated, the corresponding entry shall be
removed from the Active_COV_Subscriptions list.
*/
/*
SubscribeCOV-Request ::= SEQUENCE {
        subscriberProcessIdentifier  [0] Unsigned32,
        monitoredObjectIdentifier    [1] BACnetObjectIdentifier,
        issueConfirmedNotifications  [2] BOOLEAN OPTIONAL,
        lifetime                     [3] Unsigned OPTIONAL
        }
*/

/**
 * @brief Encode APDU for SubscribeCOV-Request
 * @note COV and Unconfirmed COV are the same encodings
 * @param apdu  Pointer to the buffer, or NULL for length
 * @param data  Pointer to the data to encode.
 * @return number of bytes encoded, or zero on error.
 */
int cov_subscribe_apdu_encode(
    uint8_t *apdu, const BACNET_SUBSCRIBE_COV_DATA *data)
{
    int len = 0; /* length of each encoding */
    int apdu_len = 0; /* total length of the apdu, return value */

    if (!data) {
        return 0;
    }
    /* tag 0 - subscriberProcessIdentifier */
    len = encode_context_unsigned(apdu, 0, data->subscriberProcessIdentifier);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }

    /* tag 1 - monitoredObjectIdentifier */
    len = encode_context_object_id(
        apdu, 1, data->monitoredObjectIdentifier.type,
        data->monitoredObjectIdentifier.instance);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    /*
        If both the 'Issue Confirmed Notifications' and
        'Lifetime' parameters are absent, then this shall
        indicate a cancellation request.
        */
    if (!data->cancellationRequest) {
        /* tag 2 - issueConfirmedNotifications */
        len =
            encode_context_boolean(apdu, 2, data->issueConfirmedNotifications);
        apdu_len += len;
        if (apdu) {
            apdu += len;
        }
        /* tag 3 - lifetime */
        len = encode_context_unsigned(apdu, 3, data->lifetime);
        apdu_len += len;
    }

    return apdu_len;
}

/**
 * @brief Encode the SubscribeCOV service request
 * @note COV and Unconfirmed COV are the same encodings
 * @param apdu  Pointer to the buffer for encoding into
 * @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 cov_subscribe_service_request_encode(
    uint8_t *apdu, size_t apdu_size, const BACNET_SUBSCRIBE_COV_DATA *data)
{
    size_t apdu_len = 0; /* total length of the apdu, return value */

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

    return apdu_len;
}

/**
 * @brief Encode the COV-service request.
 * @note COV and Unconfirmed COV are the same encodings
 * @param apdu  Pointer to the buffer.
 * @param apdu_size number of bytes available in the buffer
 * @param invoke_id  Invoke ID
 * @param data  Pointer to the data to store the decoded values.
 * @return number of bytes encoded, or zero if unable to encode or too large
 */
int cov_subscribe_encode_apdu(
    uint8_t *apdu,
    unsigned apdu_size,
    uint8_t invoke_id,
    const BACNET_SUBSCRIBE_COV_DATA *data)
{
    int len = 0; /* length of each encoding */
    int apdu_len = 0; /* total length of the apdu, return value */

    if (apdu && (apdu_size > 4)) {
        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_SUBSCRIBE_COV;
    }
    len = 4;
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    len =
        cov_subscribe_service_request_encode(apdu, apdu_size - apdu_len, data);
    if (len > 0) {
        apdu_len += len;
    } else {
        apdu_len = 0;
    }

    return apdu_len;
}

/**
 * @brief Decode the subscribe-service request only.
 *
 *  SubscribeCOV-Request ::= SEQUENCE {
 *      subscriberProcessIdentifier  [0] Unsigned32,
 *      monitoredObjectIdentifier    [1] BACnetObjectIdentifier,
 *      issueConfirmedNotifications  [2] BOOLEAN OPTIONAL,
 *      lifetime                     [3] Unsigned OPTIONAL
 *  }
 *
 * @param apdu  Pointer to the buffer.
 * @param apdu_size  number of valid bytes in the buffer.
 * @param data  Pointer to the data to store the decoded values.
 *
 * @return Bytes decoded or BACNET_STATUS_ERROR on error.
 */
int cov_subscribe_decode_service_request(
    const uint8_t *apdu, unsigned apdu_size, BACNET_SUBSCRIBE_COV_DATA *data)
{
    int len = 0; /* return value */
    int value_len = 0;
    BACNET_UNSIGNED_INTEGER decoded_value = 0;
    BACNET_OBJECT_TYPE decoded_type = OBJECT_NONE;
    uint32_t decoded_instance = 0;
    bool decoded_boolean = false;

    if (!apdu) {
        return BACNET_STATUS_REJECT;
    }
    if (apdu_size == 0) {
        return BACNET_STATUS_REJECT;
    }
    /* subscriberProcessIdentifier [0] Unsigned32 */
    value_len = bacnet_unsigned_context_decode(
        &apdu[len], apdu_size - len, 0, &decoded_value);
    if (value_len > 0) {
        if (data) {
            data->subscriberProcessIdentifier = decoded_value;
        }
        len += value_len;
    } else {
        if (data) {
            data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
        }
        return BACNET_STATUS_ERROR;
    }
    /* monitoredObjectIdentifier [1] BACnetObjectIdentifier */
    value_len = bacnet_object_id_context_decode(
        &apdu[len], apdu_size - len, 1, &decoded_type, &decoded_instance);
    if (value_len > 0) {
        if (data) {
            data->monitoredObjectIdentifier.type = decoded_type;
            data->monitoredObjectIdentifier.instance = decoded_instance;
        }
        len += value_len;
    } else {
        if (data) {
            data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
        }
        return BACNET_STATUS_ERROR;
    }
    if ((unsigned)len < apdu_size) {
        if (data) {
            /* does not indicate a cancellation request */
            data->cancellationRequest = false;
        }
        /* issueConfirmedNotifications [2] BOOLEAN OPTIONAL */
        value_len = bacnet_boolean_context_decode(
            &apdu[len], apdu_size - len, 2, &decoded_boolean);
        if (value_len > 0) {
            if (data) {
                data->issueConfirmedNotifications = decoded_boolean;
            }
            len += value_len;
        } else if (value_len == 0) {
            /* invalid tag */
            if (data) {
                data->issueConfirmedNotifications = false;
            }
        } else {
            if (data) {
                data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
            }
            return BACNET_STATUS_ERROR;
        }
    } else {
        /* If both the 'Issue Confirmed Notifications' and
           'Lifetime' parameters are absent, then this shall
           indicate a cancellation request. */
        if (data) {
            data->cancellationRequest = true;
        }
    }
    if ((unsigned)len < apdu_size) {
        /* lifetime [3] Unsigned OPTIONAL */
        value_len = bacnet_unsigned_context_decode(
            &apdu[len], apdu_size - len, 3, &decoded_value);
        if (value_len > 0) {
            if (data) {
                data->lifetime = decoded_value;
            }
            len += value_len;
        } else {
            if (data) {
                data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
            }
            return BACNET_STATUS_ERROR;
        }
    } else {
        if (data) {
            data->lifetime = 0;
        }
    }

    return len;
}

/*
SubscribeCOVProperty-Request ::= SEQUENCE {
        subscriberProcessIdentifier  [0] Unsigned32,
        monitoredObjectIdentifier    [1] BACnetObjectIdentifier,
        issueConfirmedNotifications  [2] BOOLEAN OPTIONAL,
        lifetime                     [3] Unsigned OPTIONAL,
        monitoredPropertyIdentifier  [4] BACnetPropertyReference,
        covIncrement                 [5] REAL OPTIONAL
        }

BACnetPropertyReference ::= SEQUENCE {
      propertyIdentifier      [0] BACnetPropertyIdentifier,
      propertyArrayIndex      [1] Unsigned OPTIONAL
      -- used only with array datatype
      -- if omitted with an array the entire array is referenced
      }

*/

/**
 * @brief Encode APDU for SubscribeCOVProperty request
 * @param apdu  Pointer to the buffer, or NULL for length
 * @param data  Pointer to the data to encode.
 * @return bytes encoded or zero on error.
 */
int cov_subscribe_property_apdu_encode(
    uint8_t *apdu, const BACNET_SUBSCRIBE_COV_DATA *data)
{
    int len = 0; /* length of each encoding */
    int apdu_len = 0; /* total length of the apdu, return value */

    if (!data) {
        return 0;
    }
    /* tag 0 - subscriberProcessIdentifier */
    len = encode_context_unsigned(apdu, 0, data->subscriberProcessIdentifier);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    /* tag 1 - monitoredObjectIdentifier */
    len = encode_context_object_id(
        apdu, 1, data->monitoredObjectIdentifier.type,
        data->monitoredObjectIdentifier.instance);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    if (!data->cancellationRequest) {
        /* tag 2 - issueConfirmedNotifications */
        len =
            encode_context_boolean(apdu, 2, data->issueConfirmedNotifications);
        apdu_len += len;
        if (apdu) {
            apdu += len;
        }
        /* tag 3 - lifetime */
        len = encode_context_unsigned(apdu, 3, data->lifetime);
        apdu_len += len;
        if (apdu) {
            apdu += len;
        }
    }
    /* tag 4 - monitoredPropertyIdentifier */
    len = bacnet_property_reference_context_encode(
        apdu, 4, &data->monitoredProperty);
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    /* tag 5 - covIncrement */
    if (data->covIncrementPresent) {
        len = encode_context_real(apdu, 5, data->covIncrement);
        apdu_len += len;
    }

    return apdu_len;
}

/**
 * @brief Encode the SubscribeCOVProperty service request
 * @param apdu  Pointer to the buffer for encoding into
 * @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 cov_subscribe_property_service_request_encode(
    uint8_t *apdu, size_t apdu_size, const BACNET_SUBSCRIBE_COV_DATA *data)
{
    size_t apdu_len = 0; /* total length of the apdu, return value */

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

    return apdu_len;
}

/**
 * @brief Encode SubscribeCOVProperty request
 * @param apdu  Pointer to the buffer.
 * @param apdu_size number of bytes available in the buffer
 * @param invoke_id  Invoke Id.
 * @param data  Pointer to the data to encode.
 * @return number of bytes encoded, or zero on error.
 */
int cov_subscribe_property_encode_apdu(
    uint8_t *apdu,
    unsigned apdu_size,
    uint8_t invoke_id,
    const BACNET_SUBSCRIBE_COV_DATA *data)
{
    int len = 0; /* length of each encoding */
    int apdu_len = 0; /* total length of the apdu, return value */

    if (!data) {
        return 0;
    }
    if (apdu && (apdu_size > 4)) {
        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_SUBSCRIBE_COV_PROPERTY;
    }
    len = 4;
    apdu_len += len;
    if (apdu) {
        apdu += len;
    }
    len = cov_subscribe_property_service_request_encode(
        apdu, apdu_size - apdu_len, data);
    if (len > 0) {
        apdu_len += len;
    } else {
        apdu_len = 0;
    }

    return apdu_len;
}

/**
 * Decode the COV-service property request only.
 *
 * SubscribeCOVProperty-Request ::= SEQUENCE {
 *       subscriberProcessIdentifier  [0] Unsigned32,
 *       monitoredObjectIdentifier    [1] BACnetObjectIdentifier,
 *       issueConfirmedNotifications  [2] BOOLEAN OPTIONAL,
 *       lifetime                     [3] Unsigned OPTIONAL,
 *       monitoredPropertyIdentifier  [4] BACnetPropertyReference,
 *       covIncrement                 [5] REAL OPTIONAL
 *       }
 *
 * BACnetPropertyReference ::= SEQUENCE {
 *     propertyIdentifier      [0] BACnetPropertyIdentifier,
 *     propertyArrayIndex      [1] Unsigned OPTIONAL
 *     -- used only with array datatype
 *     -- if omitted with an array the entire array is referenced
 *     }
 *
 * @param apdu  Pointer to the buffer.
 * @param apdu_size  Count of valid bytes in the buffer.
 * @param data  Pointer to the data to store the decoded values.
 *
 * @return Bytes decoded or BACNET_STATUS_REJECT on error.
 */
int cov_subscribe_property_decode_service_request(
    const uint8_t *apdu, unsigned apdu_size, BACNET_SUBSCRIBE_COV_DATA *data)
{
    int len = 0, apdu_len = 0;
    BACNET_UNSIGNED_INTEGER decoded_unsigned = 0; /* for decoding */
    BACNET_OBJECT_TYPE decoded_type = OBJECT_NONE; /* for decoding */
    uint32_t decoded_instance = 0; /* for decoding */
    bool decoded_boolean = false; /* for decoding */
    struct BACnetPropertyReference decoded_reference = { 0 };
    float decoded_real = 0.0f; /* for decoding */

    if (!apdu) {
        return BACNET_STATUS_REJECT;
    }
    if (apdu_size == 0) {
        return BACNET_STATUS_REJECT;
    }
    /* subscriberProcessIdentifier [0] Unsigned32 */
    len = bacnet_unsigned_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 0, &decoded_unsigned);
    if (len > 0) {
        if (decoded_unsigned > UINT32_MAX) {
            data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
            return BACNET_STATUS_REJECT;
        }
        if (data) {
            data->subscriberProcessIdentifier = decoded_unsigned;
        }
        apdu_len += len;
    } else {
        data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
        return BACNET_STATUS_REJECT;
    }
    /* monitoredObjectIdentifier [1] BACnetObjectIdentifier */
    len = bacnet_object_id_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 1, &decoded_type,
        &decoded_instance);
    if (len > 0) {
        if (data) {
            data->monitoredObjectIdentifier.type = decoded_type;
            data->monitoredObjectIdentifier.instance = decoded_instance;
        }
        apdu_len += len;
    } else {
        if (data) {
            data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
        }
        return BACNET_STATUS_REJECT;
    }
    /* issueConfirmedNotifications  [2] BOOLEAN OPTIONAL */
    len = bacnet_boolean_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 2, &decoded_boolean);
    if (len > 0) {
        if (data) {
            data->cancellationRequest = false;
            data->issueConfirmedNotifications = decoded_boolean;
        }
        apdu_len += len;
    } else {
        /* skip - optional */
        if (data) {
            data->issueConfirmedNotifications = false;
            data->cancellationRequest = true;
        }
    }
    /* lifetime [3] Unsigned OPTIONAL */
    len = bacnet_unsigned_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 3, &decoded_unsigned);
    if (len > 0) {
        if (data) {
            data->lifetime = decoded_unsigned;
        }
        apdu_len += len;
    } else {
        /* skip - optional */
        if (data) {
            data->lifetime = 0;
        }
    }
    /* monitoredPropertyIdentifier [4] BACnetPropertyReference */
    len = bacnet_property_reference_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 4, &decoded_reference);
    if (len > 0) {
        if (data) {
            bacnet_property_reference_copy(
                &data->monitoredProperty, &decoded_reference);
        }
        apdu_len += len;
    } else {
        if (data) {
            data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
        }
        return BACNET_STATUS_REJECT;
    }
    /* covIncrement [5] REAL OPTIONAL */
    len = bacnet_real_context_decode(
        &apdu[apdu_len], apdu_size - apdu_len, 5, &decoded_real);
    if (len > 0) {
        if (data) {
            data->covIncrement = decoded_real;
            data->covIncrementPresent = true;
        }
        apdu_len += len;
    } else {
        /* skip - optional */
        if (data) {
            data->covIncrement = 0.0f;
            data->covIncrementPresent = false;
        }
    }

    return apdu_len;
}

/**
 * @brief Link an array or buffer of BACNET_PROPERTY_VALUE elements
 * @param value_list - One or more BACNET_PROPERTY_VALUE elements in
 * a buffer or array.
 * @param count - number of BACNET_PROPERTY_VALUE elements
 */
void cov_property_value_list_link(
    BACNET_PROPERTY_VALUE *value_list, size_t count)
{
    BACNET_PROPERTY_VALUE *current_value_list = NULL;

    if (value_list) {
        while (count) {
            if (count > 1) {
                current_value_list = value_list;
                value_list++;
                current_value_list->next = value_list;
            } else {
                value_list->next = NULL;
            }
            count--;
        }
    }
}

/** Link an array or buffer of BACNET_PROPERTY_VALUE elements and add them
 * to the BACNET_COV_DATA structure.  It is used prior to encoding or
 * decoding the APDU data into the structure.
 *
 * @param data - The BACNET_COV_DATA structure that holds the data to
 * be encoded or decoded.
 * @param value_list - One or more BACNET_PROPERTY_VALUE elements in
 * a buffer or array.
 * @param count - number of BACNET_PROPERTY_VALUE elements
 */
void cov_data_value_list_link(
    BACNET_COV_DATA *data, BACNET_PROPERTY_VALUE *value_list, size_t count)
{
    if (data && value_list) {
        data->listOfValues = value_list;
        cov_property_value_list_link(value_list, count);
    }
}

/**
 * @brief Encode the Value List for REAL Present-Value and Status-Flags
 * @param value_list - #BACNET_PROPERTY_VALUE with at least 2 entries
 * @param value - REAL present-value
 * @param in_alarm - value of in-alarm status-flags
 * @param fault - value of fault status-flags
 * @param overridden - value of overridden status-flags
 * @param out_of_service - value of out-of-service status-flags
 *
 * @return true if values were encoded
 */
bool cov_value_list_encode_real(
    BACNET_PROPERTY_VALUE *value_list,
    float value,
    bool in_alarm,
    bool fault,
    bool overridden,
    bool out_of_service)
{
    bool status = false;

    if (value_list) {
        value_list->propertyIdentifier = PROP_PRESENT_VALUE;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_REAL;
        value_list->value.type.Real = value;
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list = value_list->next;
    }
    if (value_list) {
        value_list->propertyIdentifier = PROP_STATUS_FLAGS;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING;
        bitstring_init(&value_list->value.type.Bit_String);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_IN_ALARM, in_alarm);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_FAULT, fault);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OVERRIDDEN,
            overridden);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OUT_OF_SERVICE,
            out_of_service);
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list->next = NULL;
        status = true;
    }

    return status;
}

/**
 * @brief Encode the Value List for ENUMERATED Present-Value and Status-Flags
 * @param value_list - #BACNET_PROPERTY_VALUE with at least 2 entries
 * @param value - ENUMERATED present-value
 * @param in_alarm - value of in-alarm status-flags
 * @param fault - value of in-alarm status-flags
 * @param overridden - value of overridden status-flags
 * @param out_of_service - value of out-of-service status-flags
 *
 * @return true if values were encoded
 */
bool cov_value_list_encode_enumerated(
    BACNET_PROPERTY_VALUE *value_list,
    uint32_t value,
    bool in_alarm,
    bool fault,
    bool overridden,
    bool out_of_service)
{
    bool status = false;

    if (value_list) {
        value_list->propertyIdentifier = PROP_PRESENT_VALUE;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_ENUMERATED;
        value_list->value.type.Enumerated = value;
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list = value_list->next;
    }
    if (value_list) {
        value_list->propertyIdentifier = PROP_STATUS_FLAGS;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING;
        bitstring_init(&value_list->value.type.Bit_String);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_IN_ALARM, in_alarm);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_FAULT, fault);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OVERRIDDEN,
            overridden);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OUT_OF_SERVICE,
            out_of_service);
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list->next = NULL;
        status = true;
    }

    return status;
}

/**
 * @brief Encode the Value List for UNSIGNED INT Present-Value and Status-Flags
 * @param value_list - #BACNET_PROPERTY_VALUE with at least 2 entries
 * @param value - UNSIGNED INT present-value
 * @param in_alarm - value of in-alarm status-flags
 * @param fault - value of in-alarm status-flags
 * @param overridden - value of overridden status-flags
 * @param out_of_service - value of out-of-service status-flags
 *
 * @return true if values were encoded
 */
bool cov_value_list_encode_unsigned(
    BACNET_PROPERTY_VALUE *value_list,
    uint32_t value,
    bool in_alarm,
    bool fault,
    bool overridden,
    bool out_of_service)
{
    bool status = false;

    if (value_list) {
        value_list->propertyIdentifier = PROP_PRESENT_VALUE;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
        value_list->value.type.Unsigned_Int = value;
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list = value_list->next;
    }
    if (value_list) {
        value_list->propertyIdentifier = PROP_STATUS_FLAGS;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING;
        bitstring_init(&value_list->value.type.Bit_String);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_IN_ALARM, in_alarm);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_FAULT, fault);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OVERRIDDEN,
            overridden);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OUT_OF_SERVICE,
            out_of_service);
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list->next = NULL;
        status = true;
    }

    return status;
}

/**
 * @brief Encode the Value List for INT Present-Value and Status-Flags
 * @param value_list - #BACNET_PROPERTY_VALUE with at least 2 entries
 * @param value - INT present-value
 * @param in_alarm - value of in-alarm status-flags
 * @param fault - value of in-alarm status-flags
 * @param overridden - value of overridden status-flags
 * @param out_of_service - value of out-of-service status-flags
 *
 * @return true if values were encoded
 */
bool cov_value_list_encode_signed_int(
    BACNET_PROPERTY_VALUE *value_list,
    int32_t value,
    bool in_alarm,
    bool fault,
    bool overridden,
    bool out_of_service)
{
    bool status = false;

    if (value_list) {
        value_list->propertyIdentifier = PROP_PRESENT_VALUE;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_SIGNED_INT;
        value_list->value.type.Signed_Int = value;
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list = value_list->next;
    }
    if (value_list) {
        value_list->propertyIdentifier = PROP_STATUS_FLAGS;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING;
        bitstring_init(&value_list->value.type.Bit_String);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_IN_ALARM, in_alarm);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_FAULT, fault);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OVERRIDDEN,
            overridden);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OUT_OF_SERVICE,
            out_of_service);
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list->next = NULL;
        status = true;
    }

    return status;
}

#if defined(BACAPP_CHARACTER_STRING)
/**
 * @brief Encode the Value List for CHARACTER_STRING Present-Value and
 * Status-Flags
 * @param value_list - #BACNET_PROPERTY_VALUE with at least 2 entries
 * @param value - CHARACTER_STRING present-value
 * @param in_alarm - value of in-alarm status-flags
 * @param fault - value of in-alarm status-flags
 * @param overridden - value of overridden status-flags
 * @param out_of_service - value of out-of-service status-flags
 *
 * @return true if values were encoded
 */
bool cov_value_list_encode_character_string(
    BACNET_PROPERTY_VALUE *value_list,
    const BACNET_CHARACTER_STRING *value,
    bool in_alarm,
    bool fault,
    bool overridden,
    bool out_of_service)
{
    bool status = false;

    if (value_list) {
        value_list->propertyIdentifier = PROP_PRESENT_VALUE;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_CHARACTER_STRING;
        characterstring_copy(&value_list->value.type.Character_String, value);
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list = value_list->next;
    }
    if (value_list) {
        value_list->propertyIdentifier = PROP_STATUS_FLAGS;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING;
        bitstring_init(&value_list->value.type.Bit_String);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_IN_ALARM, in_alarm);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_FAULT, fault);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OVERRIDDEN,
            overridden);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OUT_OF_SERVICE,
            out_of_service);
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list->next = NULL;
        status = true;
    }

    return status;
}
#endif

#if defined(BACAPP_BIT_STRING)
/**
 * @brief Encode the Value List for CHARACTER_STRING Present-Value and
 * Status-Flags
 * @param value_list - #BACNET_PROPERTY_VALUE with at least 2 entries
 * @param value - CHARACTER_STRING present-value
 * @param in_alarm - value of in-alarm status-flags
 * @param fault - value of in-alarm status-flags
 * @param overridden - value of overridden status-flags
 * @param out_of_service - value of out-of-service status-flags
 *
 * @return true if values were encoded
 */
bool cov_value_list_encode_bit_string(
    BACNET_PROPERTY_VALUE *value_list,
    const BACNET_BIT_STRING *value,
    bool in_alarm,
    bool fault,
    bool overridden,
    bool out_of_service)
{
    bool status = false;

    if (value_list) {
        value_list->propertyIdentifier = PROP_PRESENT_VALUE;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING;
        bitstring_copy(&value_list->value.type.Bit_String, value);
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list = value_list->next;
    }
    if (value_list) {
        value_list->propertyIdentifier = PROP_STATUS_FLAGS;
        value_list->propertyArrayIndex = BACNET_ARRAY_ALL;
        value_list->value.context_specific = false;
        value_list->value.tag = BACNET_APPLICATION_TAG_BIT_STRING;
        bitstring_init(&value_list->value.type.Bit_String);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_IN_ALARM, in_alarm);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_FAULT, fault);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OVERRIDDEN,
            overridden);
        bitstring_set_bit(
            &value_list->value.type.Bit_String, STATUS_FLAG_OUT_OF_SERVICE,
            out_of_service);
        value_list->value.next = NULL;
        value_list->priority = BACNET_NO_PRIORITY;
        value_list->next = NULL;
        status = true;
    }

    return status;
}
#endif
