/** * @file * @brief BACnet ReadProperty-Request and ReadProperty-ACK encode and decode * helper functions * @author Steve Karg * @date 2005 * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 */ #include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" /* BACnet Stack API */ #include "bacnet/bacdcode.h" #include "bacnet/proplist.h" #include "bacnet/rp.h" #if BACNET_SVC_RP_A /** * @brief Encode APDU for ReadProperty-Request * * ReadProperty-Request ::= SEQUENCE { * object-identifier [0] BACnetObjectIdentifier, * property-identifier [1] BACnetPropertyIdentifier, * property-array-index [2] Unsigned OPTIONAL * -- used only with array datatype * -- if omitted with an array the entire array is referenced * } * * @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 read_property_request_encode( uint8_t *apdu, const BACNET_READ_PROPERTY_DATA *data) { int len = 0; /* length of each encoding */ int apdu_len = 0; /* total length of the apdu, return value */ if (!data) { return 0; } /* object-identifier [0] BACnetObjectIdentifier */ if (data->object_type <= BACNET_MAX_OBJECT) { len = encode_context_object_id( apdu, 0, data->object_type, data->object_instance); apdu_len += len; if (apdu) { apdu += len; } } /* property-identifier [1] BACnetPropertyIdentifier */ if (data->object_property <= MAX_BACNET_PROPERTY_ID) { len = encode_context_enumerated(apdu, 1, data->object_property); apdu_len += len; if (apdu) { apdu += len; } } /* property-array-index [2] Unsigned OPTIONAL */ if (data->array_index != BACNET_ARRAY_ALL) { len = encode_context_unsigned(apdu, 2, data->array_index); apdu_len += len; } return apdu_len; } /** * @brief Encode the ReadProperty-Request service * @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 read_property_request_service_encode( uint8_t *apdu, size_t apdu_size, const BACNET_READ_PROPERTY_DATA *data) { size_t apdu_len = 0; /* total length of the apdu, return value */ apdu_len = read_property_request_encode(NULL, data); if (apdu_len > apdu_size) { apdu_len = 0; } else { apdu_len = read_property_request_encode(apdu, data); } return apdu_len; } /** Encode the service * * @param apdu Pointer to the buffer for encoding. * @param invoke_id Invoke ID * @param rpdata Pointer to the property data to be encoded. * * @return Bytes encoded or zero on error. */ int rp_encode_apdu( uint8_t *apdu, uint8_t invoke_id, const BACNET_READ_PROPERTY_DATA *data) { int len = 0; /* length of each encoding */ int apdu_len = 0; /* total length of the apdu, return value */ 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_READ_PROPERTY; /* service choice */ } len = 4; apdu_len += len; if (apdu) { apdu += len; } len = read_property_request_encode(apdu, data); apdu_len += len; return apdu_len; } #endif /** Decode the service request only * * @param apdu Pointer to the buffer for encoding. * @param apdu_size Count of valid bytes in the buffer. * @param rpdata Pointer to the property data to be encoded. * * @return number of bytes decoded, or #BACNET_STATUS_REJECT */ int rp_decode_service_request( const uint8_t *apdu, unsigned apdu_size, BACNET_READ_PROPERTY_DATA *data) { int len = 0; int apdu_len = 0; uint32_t instance = 0; BACNET_OBJECT_TYPE type = OBJECT_NONE; /* for decoding */ uint32_t property = 0; /* for decoding */ BACNET_UNSIGNED_INTEGER unsigned_value = 0; /* for decoding */ /* check for value pointers */ if (!apdu) { if (data) { data->error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; } return BACNET_STATUS_REJECT; } /* object-identifier [0] BACnetObjectIdentifier */ len = bacnet_object_id_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 0, &type, &instance); if (len > 0) { if (instance > BACNET_MAX_INSTANCE) { if (data) { data->error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; } return BACNET_STATUS_REJECT; } apdu_len += len; if (data) { data->object_type = type; data->object_instance = instance; } } else { if (data) { data->error_code = ERROR_CODE_REJECT_INVALID_TAG; } return BACNET_STATUS_REJECT; } /* property-identifier [1] BACnetPropertyIdentifier */ len = bacnet_enumerated_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 1, &property); if (len > 0) { apdu_len += len; if (data) { data->object_property = (BACNET_PROPERTY_ID)property; } } else { if (data) { data->error_code = ERROR_CODE_REJECT_INVALID_TAG; } return BACNET_STATUS_REJECT; } /* property-array-index [2] Unsigned OPTIONAL */ len = bacnet_unsigned_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 2, &unsigned_value); if (len > 0) { apdu_len += len; if (data) { data->array_index = unsigned_value; } } else { /* wrong tag - skip apdu_len increment and go to next field */ if (data) { data->array_index = BACNET_ARRAY_ALL; } } if (apdu_len < apdu_size) { /* If something left over now, we have an invalid request */ if (data) { data->error_code = ERROR_CODE_REJECT_TOO_MANY_ARGUMENTS; } return BACNET_STATUS_REJECT; } return apdu_len; } /** * @brief Encode APDU for ReadProperty-ACK * * ReadProperty-ACK ::= SEQUENCE { * object-identifier [0] BACnetObjectIdentifier, * property-identifier [1] BACnetPropertyIdentifier, * property-array-index [2] Unsigned OPTIONAL, * -- used only with array datatype * -- if omitted with an array the entire array is referenced * property-value [3] * } * * @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 read_property_ack_encode( uint8_t *apdu, const BACNET_READ_PROPERTY_DATA *data) { int len = 0; /* length of each encoding */ int apdu_len = 0; /* total length of the apdu, return value */ if (!data) { return 0; } /* object-identifier [0] BACnetObjectIdentifier */ len = encode_context_object_id( apdu, 0, data->object_type, data->object_instance); apdu_len += len; if (apdu) { apdu += len; } /* property-identifier [1] BACnetPropertyIdentifier */ len = encode_context_enumerated(apdu, 1, data->object_property); apdu_len += len; if (apdu) { apdu += len; } /* property-array-index [2] Unsigned OPTIONAL */ if (data->array_index != BACNET_ARRAY_ALL) { len = encode_context_unsigned(apdu, 2, data->array_index); apdu_len += len; if (apdu) { apdu += len; } } /* property-value [3] */ len = encode_opening_tag(apdu, 3); apdu_len += len; if (apdu) { apdu += len; } if (apdu) { for (len = 0; len < data->application_data_len; len++) { apdu[len] = data->application_data[len]; } apdu += data->application_data_len; } apdu_len += data->application_data_len; len = encode_closing_tag(apdu, 3); 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 read_property_ack_service_encode( uint8_t *apdu, size_t apdu_size, const BACNET_READ_PROPERTY_DATA *data) { size_t apdu_len = 0; /* total length of the apdu, return value */ apdu_len = read_property_ack_encode(NULL, data); if (apdu_len > apdu_size) { apdu_len = 0; } else { apdu_len = read_property_ack_encode(apdu, data); } return apdu_len; } /** Alternate method to encode the ack without extra buffer. * * @param apdu Pointer to the buffer for encoding. * @param invoke_id Invoke Id * @param rpdata Pointer to the property data to be encoded. * * @return Bytes decoded or zero on error. */ int rp_ack_encode_apdu_init( uint8_t *apdu, uint8_t invoke_id, const BACNET_READ_PROPERTY_DATA *rpdata) { int len = 0; /* length of each encoding */ int apdu_len = 0; /* total length of the apdu, return value */ if (apdu) { apdu[0] = PDU_TYPE_COMPLEX_ACK; /* complex ACK service */ apdu[1] = invoke_id; /* original invoke id from request */ apdu[2] = SERVICE_CONFIRMED_READ_PROPERTY; /* service choice */ } len = 3; apdu_len += len; if (apdu) { apdu += len; } /* service ack follows */ len = encode_context_object_id( apdu, 0, rpdata->object_type, rpdata->object_instance); apdu_len += len; if (apdu) { apdu += len; } len = encode_context_enumerated(apdu, 1, rpdata->object_property); apdu_len += len; if (apdu) { apdu += len; } /* context 2 array index is optional */ if (rpdata->array_index != BACNET_ARRAY_ALL) { len = encode_context_unsigned(apdu, 2, rpdata->array_index); apdu_len += len; if (apdu) { apdu += len; } } len = encode_opening_tag(apdu, 3); apdu_len += len; return apdu_len; } /** Encode the closing tag for the object property. * Note: Encode the application tagged data yourself. * * @param apdu Pointer to the buffer for encoding. * @param invoke_id Invoke Id * @param rpdata Pointer to the property data to be encoded. * * @return Bytes encoded or zero on error. */ int rp_ack_encode_apdu_object_property_end(uint8_t *apdu) { int apdu_len = 0; /* total length of the apdu, return value */ if (apdu) { apdu_len = encode_closing_tag(&apdu[0], 3); } return apdu_len; } /** * @brief Validate the array indices for the given property * @param data - ReadProperty data, including requested data and * data for the reply, or error response. * @return true if the property is an array and the array indices are used. */ bool read_property_bacnet_array_valid(BACNET_READ_PROPERTY_DATA *data) { bool is_array; /* only array properties can have array options */ is_array = property_list_bacnet_array_member( data->object_type, data->object_property); if ((!is_array) && (data->array_index != BACNET_ARRAY_ALL)) { data->error_class = ERROR_CLASS_PROPERTY; data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; return false; } return true; } /** Encode the acknowledge. * * @param apdu Pointer to the buffer for encoding, or NULL for length * @param invoke_id Invoke Id * @param rpdata Pointer to the property data to be encoded. * * @return Bytes encoded or zero on error. */ int rp_ack_encode_apdu( uint8_t *apdu, uint8_t invoke_id, const BACNET_READ_PROPERTY_DATA *rpdata) { int imax = 0; int len = 0; /* length of each encoding */ int apdu_len = 0; /* total length of the apdu, return value */ if (rpdata) { /* Do the initial encoding */ len = rp_ack_encode_apdu_init(apdu, invoke_id, rpdata); apdu_len += len; if (apdu) { apdu += len; } imax = rpdata->application_data_len; for (len = 0; len < imax; len++) { if (apdu) { apdu[len] = rpdata->application_data[len]; } } apdu_len += len; if (apdu) { apdu += len; } len = encode_closing_tag(apdu, 3); apdu_len += len; } return apdu_len; } #if BACNET_SVC_RP_A /** Decode the ReadProperty reply and store the result for one Property in a * BACNET_READ_PROPERTY_DATA structure. * This leaves the value(s) in the application_data buffer to be decoded later; * the application_data field points into the apdu buffer (is not allocated). * * @param apdu [in] The apdu portion of the ACK reply. * @param apdu_size [in] The total length of the apdu. * @param rpdata [out] The structure holding the partially decoded result. * @return Number of decoded bytes (could be less than apdu_len), * or -1 on decoding error. */ int rp_ack_decode_service_request( uint8_t *apdu, int apdu_size, BACNET_READ_PROPERTY_DATA *data) { int apdu_len = 0; /* return value */ int len = 0; uint32_t instance = 0; BACNET_OBJECT_TYPE type = OBJECT_NONE; /* for decoding */ uint32_t property = 0; /* for decoding */ BACNET_UNSIGNED_INTEGER unsigned_value = 0; /* for decoding */ int data_len = 0; if (!apdu) { return -BACNET_STATUS_ERROR; } /* object-identifier [0] BACnetObjectIdentifier */ len = bacnet_object_id_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 0, &type, &instance); if (len > 0) { if (instance > BACNET_MAX_INSTANCE) { return BACNET_STATUS_ERROR; } apdu_len += len; if (data) { data->object_type = type; data->object_instance = instance; } } else { return BACNET_STATUS_ERROR; } /* property-identifier [1] BACnetPropertyIdentifier */ len = bacnet_enumerated_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 1, &property); if (len > 0) { apdu_len += len; if (data) { data->object_property = (BACNET_PROPERTY_ID)property; } } else { return BACNET_STATUS_ERROR; } /* property-array-index [2] Unsigned OPTIONAL */ len = bacnet_unsigned_context_decode( &apdu[apdu_len], apdu_size - apdu_len, 2, &unsigned_value); if (len > 0) { apdu_len += len; if (data) { data->array_index = unsigned_value; } } else { /* wrong tag - skip apdu_len increment and go to next field */ if (data) { data->array_index = BACNET_ARRAY_ALL; } } /* property-value [3] ABSTRACT-SYNTAX.&Type */ if (!bacnet_is_opening_tag_number( &apdu[apdu_len], apdu_size - apdu_len, 3, &len)) { return BACNET_STATUS_ERROR; } /* determine the length of the data blob */ data_len = bacnet_enclosed_data_length(&apdu[apdu_len], apdu_size - apdu_len); if (data_len == BACNET_STATUS_ERROR) { return BACNET_STATUS_ERROR; } /* count the opening tag number length */ apdu_len += len; if (data_len > MAX_APDU) { /* not enough size in application_data to store the data chunk */ return BACNET_STATUS_ERROR; } else if (data) { /* don't decode the application tag number or its data here */ data->application_data = &apdu[apdu_len]; data->application_data_len = data_len; } apdu_len += data_len; if (!bacnet_is_closing_tag_number( &apdu[apdu_len], apdu_size - apdu_len, 3, &len)) { return BACNET_STATUS_ERROR; } /* count the closing tag number length */ apdu_len += len; return apdu_len; } #endif