From 4cd7acebcdc6b7fdbd6e92e9b7860a522bea1b05 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Tue, 21 May 2024 08:28:50 -0500 Subject: [PATCH] Secure ReadPropertyMultiple code, and improve unit test coverage. (#650) --- src/bacnet/basic/service/h_rpm.c | 136 ++++------ src/bacnet/rpm.c | 440 ++++++++++++++++--------------- src/bacnet/rpm.h | 35 +-- test/bacnet/rpm/CMakeLists.txt | 6 +- test/bacnet/rpm/src/main.c | 16 +- 5 files changed, 302 insertions(+), 331 deletions(-) diff --git a/src/bacnet/basic/service/h_rpm.c b/src/bacnet/basic/service/h_rpm.c index f838c9c4..ea9c31ac 100644 --- a/src/bacnet/basic/service/h_rpm.c +++ b/src/bacnet/basic/service/h_rpm.c @@ -1,29 +1,13 @@ -/************************************************************************** - * - * Copyright (C) 2007 Steve Karg - * Inspired by John Stachler - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - *********************************************************************/ - +/** + * @file + * @brief BACnet ReadPropertyMultiple-Request handler + * @author Steve Karg + * @author John Stachler + * @author Peter McShane + * @author Roy Schneider + * @date 2007 + * @copyright SPDX-License-Identifier: MIT + */ #include #include #include @@ -47,12 +31,21 @@ #endif #include "bacnet/basic/tsm/tsm.h" #include "bacnet/basic/services.h" +#include "bacnet/basic/sys/debug.h" #include "bacnet/datalink/datalink.h" -/** @file h_rpm.c Handles Read Property Multiple requests. */ - static uint8_t Temp_Buf[MAX_APDU] = { 0 }; +/** + * @brief Fetches the lists of properties (array of BACNET_PROPERTY_ID's) for + * this object type and the special properties ALL or REQUIRED or OPTIONAL. + * @param pPropertyList reference for the list of ALL, REQUIRED, and OPTIONAL + * properties. + * @param special_property The special property ALL, REQUIRED, or OPTIONAL + * to fetch. + * @param index The index of the property to fetch. + * @return The property ID or -1 if not found. + */ static BACNET_PROPERTY_ID RPM_Object_Property( struct special_property_list_t *pPropertyList, BACNET_PROPERTY_ID special_property, @@ -87,6 +80,16 @@ static BACNET_PROPERTY_ID RPM_Object_Property( return (BACNET_PROPERTY_ID)property; } +/** + * @brief Fetches the number of properties (array of BACNET_PROPERTY_ID's) for + * this object type belonging to the special properties ALL or REQUIRED or + * OPTIONAL. + * @param pPropertyList reference for the list of ALL, REQUIRED, and OPTIONAL + * properties. + * @param special_property The special property ALL, REQUIRED, or OPTIONAL + * to fetch. + * @return The number of properties. + */ static unsigned RPM_Object_Property_Count( struct special_property_list_t *pPropertyList, BACNET_PROPERTY_ID special_property) @@ -105,8 +108,16 @@ static unsigned RPM_Object_Property_Count( return count; } -/** Encode the RPM property returning the length of the encoding, - or 0 if there is no room to fit the encoding. */ +/** + * @brief Encode the RPM property returning the length of the encoding, + * or 0 if there is no room to fit the encoding. + * @param apdu [out] The buffer to encode the property into. + * @param offset [in] The offset into the buffer to start encoding. + * @param max_apdu [in] The maximum length of the buffer. + * @param rpmdata [in] The RPM data to encode. + * @return The length of the encoding, or 0 if there is no room to fit the + * encoding. + */ static int RPM_Encode_Property( uint8_t *apdu, uint16_t offset, uint16_t max_apdu, BACNET_RPM_DATA *rpmdata) { @@ -221,9 +232,7 @@ void handler_read_property_multiple(uint8_t *service_request, if (service_data->segmented_message) { rpmdata.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; error = BACNET_STATUS_ABORT; -#if PRINT_ENABLED - fprintf(stderr, "RPM: Segmented message. Sending Abort!\r\n"); -#endif + debug_fprintf(stderr, "RPM: Segmented message. Sending Abort!\r\n"); } else { /* decode apdu request & encode apdu reply encode complex ack, invoke id, service choice */ @@ -239,9 +248,7 @@ void handler_read_property_multiple(uint8_t *service_request, decode_len += len; } else { /* bad encoding - skip to error/reject/abort handling */ -#if PRINT_ENABLED - fprintf(stderr, "RPM: Bad Encoding.\n"); -#endif + debug_fprintf(stderr, "RPM: Bad Encoding.\n"); error = len; berror = true; break; @@ -273,9 +280,7 @@ void handler_read_property_multiple(uint8_t *service_request, copy_len = memcopy(&Handler_Transmit_Buffer[npdu_len], &Temp_Buf[0], apdu_len, len, MAX_APDU); if (copy_len == 0) { -#if PRINT_ENABLED - fprintf(stderr, "RPM: Response too big!\r\n"); -#endif + debug_fprintf(stderr, "RPM: Response too big!\r\n"); rpmdata.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; error = BACNET_STATUS_ABORT; @@ -292,9 +297,7 @@ void handler_read_property_multiple(uint8_t *service_request, service_len - decode_len, &rpmdata); if (len < 0) { /* bad encoding - skip to error/reject/abort handling */ -#if PRINT_ENABLED - fprintf(stderr, "RPM: Bad Encoding.\n"); -#endif + debug_fprintf(stderr, "RPM: Bad Encoding.\n"); error = len; berror = true; break; /* The berror flag ensures that both loops will @@ -319,10 +322,8 @@ void handler_read_property_multiple(uint8_t *service_request, if (len > 0) { apdu_len += len; } else { -#if PRINT_ENABLED - fprintf(stderr, + debug_fprintf(stderr, "RPM: Too full for property!\r\n"); -#endif error = len; /* The berror flag ensures that both loops will be broken! */ @@ -342,10 +343,8 @@ void handler_read_property_multiple(uint8_t *service_request, &Temp_Buf[0], apdu_len, len, MAX_APDU); if (copy_len == 0) { -#if PRINT_ENABLED - fprintf(stderr, + debug_fprintf(stderr, "RPM: Too full to encode property!\r\n"); -#endif rpmdata.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; error = BACNET_STATUS_ABORT; @@ -364,10 +363,8 @@ void handler_read_property_multiple(uint8_t *service_request, &Temp_Buf[0], apdu_len, len, MAX_APDU); if (copy_len == 0) { -#if PRINT_ENABLED - fprintf(stderr, + debug_fprintf(stderr, "RPM: Too full to encode error!\r\n"); -#endif rpmdata.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; error = BACNET_STATUS_ABORT; @@ -400,10 +397,8 @@ void handler_read_property_multiple(uint8_t *service_request, if (len > 0) { apdu_len += len; } else { -#if PRINT_ENABLED - fprintf(stderr, + debug_fprintf(stderr, "RPM: Too full for property!\r\n"); -#endif error = len; /* The berror flag ensures that both loops will be broken! */ @@ -423,10 +418,8 @@ void handler_read_property_multiple(uint8_t *service_request, if (len > 0) { apdu_len += len; } else { -#if PRINT_ENABLED - fprintf(stderr, + debug_fprintf(stderr, "RPM: Too full for property!\r\n"); -#endif error = len; berror = true; break; /* The berror flag ensures that @@ -444,10 +437,8 @@ void handler_read_property_multiple(uint8_t *service_request, if (len > 0) { apdu_len += len; } else { -#if PRINT_ENABLED - fprintf(stderr, + debug_fprintf(stderr, "RPM: Too full for individual property!\r\n"); -#endif error = len; berror = true; break; /* The berror flag ensures that both loops */ @@ -464,10 +455,8 @@ void handler_read_property_multiple(uint8_t *service_request, copy_len = memcopy(&Handler_Transmit_Buffer[npdu_len], &Temp_Buf[0], apdu_len, len, MAX_APDU); if (copy_len == 0) { -#if PRINT_ENABLED - fprintf(stderr, + debug_fprintf(stderr, "RPM: Too full to encode object end!\r\n"); -#endif rpmdata.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; error = BACNET_STATUS_ABORT; @@ -496,10 +485,8 @@ void handler_read_property_multiple(uint8_t *service_request, rpmdata.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; error = BACNET_STATUS_ABORT; -#if PRINT_ENABLED - fprintf( + debug_fprintf( stderr, "RPM: Message too large. Sending Abort!\n"); -#endif } } } @@ -510,24 +497,18 @@ void handler_read_property_multiple(uint8_t *service_request, apdu_len = abort_encode_apdu(&Handler_Transmit_Buffer[npdu_len], service_data->invoke_id, abort_convert_error_code(rpmdata.error_code), true); -#if PRINT_ENABLED - fprintf(stderr, "RPM: Sending Abort!\n"); -#endif + debug_fprintf(stderr, "RPM: Sending Abort!\n"); } else if (error == BACNET_STATUS_ERROR) { apdu_len = bacerror_encode_apdu( &Handler_Transmit_Buffer[npdu_len], service_data->invoke_id, SERVICE_CONFIRMED_READ_PROP_MULTIPLE, rpmdata.error_class, rpmdata.error_code); -#if PRINT_ENABLED - fprintf(stderr, "RPM: Sending Error!\n"); -#endif + debug_fprintf(stderr, "RPM: Sending Error!\n"); } else if (error == BACNET_STATUS_REJECT) { apdu_len = reject_encode_apdu( &Handler_Transmit_Buffer[npdu_len], service_data->invoke_id, reject_convert_error_code(rpmdata.error_code)); -#if PRINT_ENABLED - fprintf(stderr, "RPM: Sending Reject!\n"); -#endif + debug_fprintf(stderr, "RPM: Sending Reject!\n"); } } @@ -535,9 +516,8 @@ void handler_read_property_multiple(uint8_t *service_request, bytes_sent = datalink_send_pdu( src, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); if (bytes_sent <= 0) { -#if PRINT_ENABLED - fprintf(stderr, "RPM: Failed to send PDU (%s)!\n", strerror(errno)); -#endif + debug_fprintf(stderr, "RPM: Failed to send PDU (errno=%d)!\n", + errno); } } } diff --git a/src/bacnet/rpm.c b/src/bacnet/rpm.c index a2678acd..60d6480d 100644 --- a/src/bacnet/rpm.c +++ b/src/bacnet/rpm.c @@ -1,36 +1,12 @@ -/*####COPYRIGHTBEGIN#### - ------------------------------------------- - Copyright (C) 2005 Steve Karg - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to: - The Free Software Foundation, Inc. - 59 Temple Place - Suite 330 - Boston, MA 02111-1307, USA. - - As a special exception, if other files instantiate templates or - use macros or inline functions from this file, or you compile - this file and link it with other works to produce a work based - on this file, this file does not by itself cause the resulting - work to be covered by the GNU General Public License. However - the source code for this file must still be made available in - accordance with section (3) of the GNU General Public License. - - This exception does not invalidate any other reasons why a work - based on this file might be covered by the GNU General Public - License. - ------------------------------------------- -####COPYRIGHTEND####*/ +/** + * @file + * @brief BACnet ReadPropertyMultiple -Request and -Ack encode and decode + * @author Steve Karg + * @author Peter McShane + * @author Roy Schneider + * @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" @@ -45,9 +21,9 @@ #if BACNET_SVC_RPM_A -/** +/** * @brief Encode the initial portion of the service - * @param apdu Pointer to the buffer for encoding, or NULL for length + * @param apdu application data unit buffer for encoding, or NULL for length * @param invoke_id Invoke ID * @return number of bytes encoded */ @@ -66,11 +42,11 @@ int rpm_encode_apdu_init(uint8_t *apdu, uint8_t invoke_id) /** Encode the beginning, including * Object-id and Read-Access of the service. * - * @param apdu Pointer to the buffer for encoding. + * @param apdu application data unit buffer for encoding, or NULL for length * @param object_type Object type to encode * @param object_instance Object instance to encode * - * @return Bytes encoded or zero on error. + * @return number of bytes encoded */ int rpm_encode_apdu_object_begin( uint8_t *apdu, BACNET_OBJECT_TYPE object_type, uint32_t object_instance) @@ -89,13 +65,14 @@ int rpm_encode_apdu_object_begin( /** Encode the object properties of the service. * - * @param apdu Pointer to the buffer for encoding. + * @param apdu application data unit buffer for encoding, or NULL for length * @param object_property Object property. * @param array_index Optional array index. * - * @return Bytes encoded or zero on error. + * @return number of bytes encoded */ -int rpm_encode_apdu_object_property(uint8_t *apdu, +int rpm_encode_apdu_object_property( + uint8_t *apdu, BACNET_PROPERTY_ID object_property, BACNET_ARRAY_INDEX array_index) { @@ -115,9 +92,9 @@ int rpm_encode_apdu_object_property(uint8_t *apdu, /** Encode the end (closing tag) of the service * - * @param apdu Pointer to the buffer for encoding. + * @param apdu application data unit buffer for encoding, or NULL for length * - * @return Bytes encoded or zero on error. + * @return number of bytes encoded */ int rpm_encode_apdu_object_end(uint8_t *apdu) { @@ -132,13 +109,12 @@ int rpm_encode_apdu_object_end(uint8_t *apdu) /** * @brief Encode the ReadPropertyMultiple-Request - * @param apdu Buffer to hold encoded bytes, or NULL for length + * @param apdu application data unit buffer for encoding, or NULL for length * @param read_access_data [in] The RPM data to be requested. - * @return Length of encoded bytes, or 0 on failure. + * @return number of bytes encoded */ int read_property_multiple_request_encode( - uint8_t *apdu, - BACNET_READ_ACCESS_DATA *data) + uint8_t *apdu, BACNET_READ_ACCESS_DATA *data) { int len = 0; /* length of each encoding */ int apdu_len = 0; /* total length of the apdu, return value */ @@ -147,8 +123,8 @@ int read_property_multiple_request_encode( rpm_object = data; while (rpm_object) { - len = encode_context_object_id(apdu, 0, - rpm_object->object_type, rpm_object->object_instance); + len = encode_context_object_id( + apdu, 0, rpm_object->object_type, rpm_object->object_instance); apdu_len += len; if (apdu) { apdu += len; @@ -191,7 +167,7 @@ int read_property_multiple_request_encode( /** * @brief Encode the ReadPropertyMultiple-Request service - * @param apdu Pointer to the buffer for encoding into + * @param apdu application data unit buffer for encoding, 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 @@ -213,13 +189,14 @@ size_t read_property_multiple_request_service_encode( /** Encode an RPM request, to be sent. * - * @param apdu [in,out] Buffer to hold encoded bytes. + * @param apdu application data unit buffer for encoding, or NULL for length * @param apdu_size [in] Length of apdu buffer. * @param invoke_id [in] The Invoke ID to use for this message. * @param data [in] The RPM data to be requested. - * @return Length of encoded bytes, or 0 on failure. + * @return number of bytes encoded, or zero if unable to encode or too large */ -int rpm_encode_apdu(uint8_t *apdu, +int rpm_encode_apdu( + uint8_t *apdu, size_t apdu_size, uint8_t invoke_id, BACNET_READ_ACCESS_DATA *data) @@ -237,7 +214,7 @@ int rpm_encode_apdu(uint8_t *apdu, apdu += len; } len = read_property_multiple_request_service_encode( - apdu, apdu_size-apdu_len, data); + apdu, apdu_size - apdu_len, data); if (len > 0) { apdu_len += len; } else { @@ -252,11 +229,11 @@ int rpm_encode_apdu(uint8_t *apdu, /** Decode the object portion of the service request only. Bails out if * tags are wrong or missing/incomplete. * - * @param apdu [in] Buffer of bytes received. + * @param apdu application data unit buffer for decoding * @param apdu_len [in] Count of valid bytes in the buffer. - * @param rpmdata [in] The data structure to be filled. + * @param rpmdata [in] The data structure to be filled, or NULL for length * - * @return Length of decoded bytes, or 0 on failure. + * @return number of decoded bytes, or negative on failure. */ int rpm_decode_object_id( uint8_t *apdu, unsigned apdu_len, BACNET_RPM_DATA *rpmdata) @@ -288,12 +265,11 @@ int rpm_decode_object_id( return len; } -/** Decode the end portion of the service request only. - * - * @param apdu [in] Buffer of bytes received. +/** + * @brief Decode the end portion of the service request only. + * @param apdu application data unit buffer for decoding * @param apdu_len [in] Count of valid bytes in the buffer. - * - * @return Length of decoded bytes (usually 1), or 0 on failure. + * @return number of decoded bytes, or negative on failure. */ int rpm_decode_object_end(uint8_t *apdu, unsigned apdu_len) { @@ -308,7 +284,9 @@ int rpm_decode_object_end(uint8_t *apdu, unsigned apdu_len) return len; } -/** Decode the object property portion of the service request only +/** + * @brief Decode the object property portion of the service request only + * * BACnetPropertyReference ::= SEQUENCE { * propertyIdentifier [0] BACnetPropertyIdentifier, * propertyArrayIndex [1] Unsigned OPTIONAL @@ -316,11 +294,10 @@ int rpm_decode_object_end(uint8_t *apdu, unsigned apdu_len) * -- if omitted with an array the entire array is referenced * } * - * @param apdu Pointer to received bytes. - * @param apdu_len Count of received bytes. - * @param rpmdata Pointer to the data structure to be filled. - * - * @return Bytes decoded or zero on failure. + * @param apdu application data unit buffer for decoding + * @param apdu_len Count of received bytes. + * @param rpmdata Pointer to the data structure to be filled. + * @return number of decoded bytes, or negative on failure. */ int rpm_decode_object_property( uint8_t *apdu, unsigned apdu_len, BACNET_RPM_DATA *rpmdata) @@ -377,246 +354,279 @@ int rpm_decode_object_property( return len; } -/** Encode the acknowledge for a RPM. - * - * @param apdu [in] Buffer of bytes to transmit. +/** + * @brief Encode the acknowledge for a RPM. + * @param apdu application data unit buffer for encoding, or NULL for length * @param invoke_id [in] Invoke Id. - * - * @return Length of encoded bytes (usually 3) or 0 on failure. + * @return number of bytes encoded */ int rpm_ack_encode_apdu_init(uint8_t *apdu, uint8_t invoke_id) { - 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_PROP_MULTIPLE; /* service choice */ - apdu_len = 3; } - return apdu_len; + return 3; } -/** Encode the object type for an acknowledge of a RPM. - * - * @param apdu [in] Buffer of bytes to transmit. +/** + * @brief Encode the object type for an acknowledge of a RPM. + * @param apdu application data unit buffer for encoding, or NULL for length * @param rpmdata [in] Pointer to the data used to fill in the APDU. - * - * @return Length of encoded bytes or 0 on failure. + * @return number of bytes encoded */ int rpm_ack_encode_apdu_object_begin(uint8_t *apdu, BACNET_RPM_DATA *rpmdata) { int apdu_len = 0; /* total length of the apdu, return value */ + int len = 0; + /* Tag 0: objectIdentifier */ + len = encode_context_object_id( + apdu, 0, rpmdata->object_type, rpmdata->object_instance); + apdu_len += len; if (apdu) { - /* Tag 0: objectIdentifier */ - apdu_len = encode_context_object_id( - &apdu[0], 0, rpmdata->object_type, rpmdata->object_instance); - /* Tag 1: listOfResults */ - apdu_len += encode_opening_tag(&apdu[apdu_len], 1); + apdu += len; } + /* Tag 1: listOfResults */ + len = encode_opening_tag(apdu, 1); + apdu_len += len; return apdu_len; } -/** Encode the object property for an acknowledge of a RPM. - * - * @param apdu [in] Buffer of bytes to transmit. +/** + * @brief Encode the object property for an acknowledge of a RPM. + * @param apdu application data unit buffer for encoding, or NULL for length * @param object_property [in] Object property ID. * @param array_index Optional array index - * - * @return Length of encoded bytes or 0 on failure. + * @return number of bytes encoded */ -int rpm_ack_encode_apdu_object_property(uint8_t *apdu, +int rpm_ack_encode_apdu_object_property( + uint8_t *apdu, BACNET_PROPERTY_ID object_property, BACNET_ARRAY_INDEX array_index) { int apdu_len = 0; /* total length of the apdu, return value */ + int len = 0; + /* Tag 2: propertyIdentifier */ + len = encode_context_enumerated(apdu, 2, object_property); + apdu_len += len; if (apdu) { - /* Tag 2: propertyIdentifier */ - apdu_len = encode_context_enumerated(&apdu[0], 2, object_property); - /* Tag 3: optional propertyArrayIndex */ - if (array_index != BACNET_ARRAY_ALL) { - apdu_len += - encode_context_unsigned(&apdu[apdu_len], 3, array_index); - } + apdu += len; + } + /* Tag 3: optional propertyArrayIndex */ + if (array_index != BACNET_ARRAY_ALL) { + len = encode_context_unsigned(apdu, 3, array_index); + apdu_len += len; } return apdu_len; } -/** Encode the object property value for an acknowledge of a RPM. - * - * @param apdu [in] Buffer of bytes to transmit. +/** + * @brief Encode the object property value for an acknowledge of a RPM. + * @param apdu application data unit buffer for encoding, or NULL for length * @param application_data [in] Pointer to the application data used to fill in * the APDU. * @param application_data_len [in] Length of the application data. - * - * @return Length of encoded bytes or 0 on failure. + * @return number of bytes encoded */ int rpm_ack_encode_apdu_object_property_value( uint8_t *apdu, uint8_t *application_data, unsigned application_data_len) { int apdu_len = 0; /* total length of the apdu, return value */ - unsigned len = 0; + int len = 0; + /* Tag 4: propertyValue */ + len = encode_opening_tag(apdu, 4); + apdu_len += len; if (apdu) { - /* Tag 4: propertyValue */ - apdu_len += encode_opening_tag(&apdu[apdu_len], 4); - if (application_data == - &apdu[apdu_len]) { /* Is Data already in place? */ - apdu_len += application_data_len; /* Yes, step over data */ - } else { /* No, copy data in */ - for (len = 0; len < application_data_len; len++) { - apdu[apdu_len++] = application_data[len]; + apdu += len; + } + if (application_data != apdu) { + /* skip application data already in the application data buffer */ + for (len = 0; len < application_data_len; len++) { + if (apdu) { + apdu[len] = application_data[len]; } } - apdu_len += encode_closing_tag(&apdu[apdu_len], 4); } + apdu_len += application_data_len; + if (apdu) { + apdu += application_data_len; + } + len = encode_closing_tag(apdu, 4); + apdu_len += len; return apdu_len; } -/** Encode the object property error for an acknowledge of a RPM. - * - * @param apdu [in] Buffer of bytes to transmit. +/** + * @brief Encode the object property error for an acknowledge of a RPM. + * @param apdu application data unit buffer for encoding, or NULL for length * @param error_class [in] Error Class * @param error_code [in] Error Code - * - * @return Length of encoded bytes or 0 on failure. + * @return number of bytes encoded */ int rpm_ack_encode_apdu_object_property_error( uint8_t *apdu, BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code) { int apdu_len = 0; /* total length of the apdu, return value */ + int len = 0; + /* Tag 5: propertyAccessError */ + len = encode_opening_tag(apdu, 5); + apdu_len += len; if (apdu) { - /* Tag 5: propertyAccessError */ - apdu_len += encode_opening_tag(&apdu[apdu_len], 5); - apdu_len += encode_application_enumerated(&apdu[apdu_len], error_class); - apdu_len += encode_application_enumerated(&apdu[apdu_len], error_code); - apdu_len += encode_closing_tag(&apdu[apdu_len], 5); + apdu += len; } + len = encode_application_enumerated(apdu, error_class); + apdu_len += len; + if (apdu) { + apdu += len; + } + len = encode_application_enumerated(apdu, error_code); + apdu_len += len; + if (apdu) { + apdu += len; + } + len = encode_closing_tag(apdu, 5); + apdu_len += len; return apdu_len; } -/** Encode the end tag for an acknowledge of a RPM. - * - * @param apdu [in] Buffer of bytes to transmit. - * - * @return Length of encoded bytes or 0 on failure. +/** + * @brief Encode the end tag for an acknowledge of a RPM. + * @param apdu application data unit buffer for encoding, or NULL for length + * @return number of bytes encoded */ int rpm_ack_encode_apdu_object_end(uint8_t *apdu) { - int apdu_len = 0; /* total length of the apdu, return value */ + return encode_closing_tag(apdu, 1); +} - if (apdu) { - apdu_len = encode_closing_tag(&apdu[0], 1); +#if BACNET_SVC_RPM_A +/** + * @brief Decode the ReadPropertyMultiple-Ack object-identifier + * @param apdu application data unit buffer for decoding + * @param apdu_size size of the application data unit buffer + * @param object_type [out] The object type of the object identifier. + * @param object_instance [out] The object instance of the object identifier. + * @return Number of bytes decoded, or negative on error. + */ +int rpm_ack_decode_object_id( + uint8_t *apdu, + unsigned apdu_size, + BACNET_OBJECT_TYPE *object_type, + uint32_t *object_instance) +{ + int apdu_len = 0, len = 0; + + /* check for valid buffer and length */ + if (apdu && apdu_size) { + /* Tag 0: objectIdentifier */ + len = bacnet_object_id_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 0, object_type, + object_instance); + if (len <= 0) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; + /* Tag 1: listOfResults */ + len = bacnet_is_opening_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, 1, &len); + if (len <= 0) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; } return apdu_len; } -#if BACNET_SVC_RPM_A - -/* decode the object portion of the service request only */ -int rpm_ack_decode_object_id(uint8_t *apdu, - unsigned apdu_len, - BACNET_OBJECT_TYPE *object_type, - uint32_t *object_instance) +/** + * @brief Decode the ReadPropertyMultiple-Ack closing tag + * @param apdu application data unit buffer for decoding + * @param apdu_size size of the application data unit buffer + * @return Number of bytes decoded, or negative on error. + */ +int rpm_ack_decode_object_end(uint8_t *apdu, unsigned apdu_size) { - unsigned len = 0; - BACNET_OBJECT_TYPE type = OBJECT_NONE; /* for decoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + int len = 0; - /* check for value pointers */ - if (apdu && apdu_len && object_type && object_instance) { - /* Tag 0: objectIdentifier */ - if (!decode_is_context_tag(&apdu[len++], 0)) { - return -1; - } - len += decode_object_id(&apdu[len], &type, object_instance); - if (object_type) { - *object_type = type; - } - /* Tag 1: listOfResults */ - if (!decode_is_opening_tag_number(&apdu[len], 1)) { - return -1; - } - len++; /* opening tag is only one octet */ + if (bacnet_is_closing_tag_number(apdu, apdu_size, 1, &len)) { + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; } - return (int)len; + return apdu_len; } -/* is this the end of the list of this objects properties values? */ -int rpm_ack_decode_object_end(uint8_t *apdu, unsigned apdu_len) -{ - int len = 0; /* total length of the apdu, return value */ - - if (apdu && apdu_len) { - if (decode_is_closing_tag_number(apdu, 1)) { - len = 1; - } - } - - return len; -} - -int rpm_ack_decode_object_property(uint8_t *apdu, - unsigned apdu_len, +/** + * @brief Decode the ReadPropertyMultiple-Ack object property + * @param apdu application data unit buffer for decoding + * @param apdu_size size of the application data unit buffer + * @param object_property [out] The object property of the object identifier. + * @param array_index [out] The array index of the object property. + * @return Number of bytes decoded, or negative on error. + */ +int rpm_ack_decode_object_property( + uint8_t *apdu, + unsigned apdu_size, BACNET_PROPERTY_ID *object_property, BACNET_ARRAY_INDEX *array_index) { - unsigned len = 0; - unsigned tag_len = 0; - uint8_t tag_number = 0; - uint32_t len_value_type = 0; - uint32_t property = 0; /* for decoding */ + int len = 0; + int apdu_len = 0; BACNET_UNSIGNED_INTEGER unsigned_value = 0; /* for decoding */ /* check for valid pointers */ - if (apdu && apdu_len && object_property && array_index) { - /* Tag 2: propertyIdentifier */ - if (!IS_CONTEXT_SPECIFIC(apdu[len])) { - return -1; - } - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value_type); - if (tag_number != 2) { - return -1; - } - len += decode_enumerated(&apdu[len], len_value_type, &property); - if (object_property) { - *object_property = (BACNET_PROPERTY_ID)property; - } - /* Tag 3: Optional propertyArrayIndex */ - if ((len < apdu_len) && IS_CONTEXT_SPECIFIC(apdu[len]) && - (!IS_CLOSING_TAG(apdu[len]))) { - tag_len = (unsigned)decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value_type); - if (tag_number == 3) { - len += tag_len; - len += decode_unsigned( - &apdu[len], len_value_type, &unsigned_value); - *array_index = unsigned_value; - } else { - *array_index = BACNET_ARRAY_ALL; + if (!apdu) { + return BACNET_STATUS_ERROR; + } + if (apdu_size == 0) { + return BACNET_STATUS_ERROR; + } + /* Tag 2: propertyIdentifier */ + len = bacnet_enumerated_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 2, object_property); + if (len > 0) { + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; + } + /* Tag 3: Optional propertyArrayIndex */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 3, &unsigned_value); + if (len > 0) { + apdu_len += len; + if (unsigned_value < UINT32_MAX) { + if (array_index) { + *array_index = (BACNET_ARRAY_INDEX)unsigned_value; } } else { + return BACNET_STATUS_ERROR; + } + } else { + /* optional - assume ALL array elements */ + if (array_index) { *array_index = BACNET_ARRAY_ALL; } } - return (int)len; + return apdu_len; } /** - * @brief Decode the RPM Ack and call the ReadProperty-ACK function to + * @brief Decode the RPM Ack and call the ReadProperty-ACK function to * process each property value of the reply. - * + * * ReadAccessResult ::= SEQUENCE { * object-identifier [0] BACnetObjectIdentifier, * list-of-results [1] SEQUENCE OF SEQUENCE { @@ -637,7 +647,7 @@ int rpm_ack_decode_object_property(uint8_t *apdu, * @param device_id [in] The device ID of the device that replied. * @param rp_data [in] The data structure to be filled. * @param callback [in] The function to call for each property value. -*/ + */ void rpm_ack_object_property_process( uint8_t *apdu, unsigned apdu_len, @@ -667,8 +677,9 @@ void rpm_ack_object_property_process( apdu_len -= len; apdu += len; while (apdu_len) { - len = rpm_ack_decode_object_property(apdu, apdu_len, - &rp_data->object_property, &rp_data->array_index); + len = rpm_ack_decode_object_property( + apdu, apdu_len, &rp_data->object_property, + &rp_data->array_index); if (len <= 0) { /* malformed */ return; @@ -676,8 +687,8 @@ void rpm_ack_object_property_process( apdu_len -= len; apdu += len; if (bacnet_is_opening_tag_number(apdu, apdu_len, 4, &len)) { - application_data_len = bacapp_data_len( - apdu, apdu_len, rp_data->object_property); + application_data_len = + bacapp_data_len(apdu, apdu_len, rp_data->object_property); /* propertyValue */ apdu_len -= len; apdu += len; @@ -699,8 +710,7 @@ void rpm_ack_object_property_process( if (callback) { callback(device_id, rp_data); } - } else if (bacnet_is_opening_tag_number( - apdu, apdu_len, 5, &len)) { + } else if (bacnet_is_opening_tag_number(apdu, apdu_len, 5, &len)) { apdu_len -= len; apdu += len; /* property-access-error */ @@ -742,7 +752,5 @@ void rpm_ack_object_property_process( apdu += len; } } - - return; } #endif diff --git a/src/bacnet/rpm.h b/src/bacnet/rpm.h index 5b053a81..95786d8c 100644 --- a/src/bacnet/rpm.h +++ b/src/bacnet/rpm.h @@ -1,28 +1,13 @@ -/************************************************************************** -* -* Copyright (C) 2012 Steve Karg -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be included -* in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*********************************************************************/ -#ifndef RPM_H -#define RPM_H +/** + * @file + * @brief BACnet ReadPropertyMultiple -Request and -Ack encode and decode + * API header file + * @author Steve Karg + * @date 2005 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_READ_PROPERTY_MULTIPLE_H +#define BACNET_READ_PROPERTY_MULTIPLE_H #include #include diff --git a/test/bacnet/rpm/CMakeLists.txt b/test/bacnet/rpm/CMakeLists.txt index dd91dd05..34faa113 100644 --- a/test/bacnet/rpm/CMakeLists.txt +++ b/test/bacnet/rpm/CMakeLists.txt @@ -24,12 +24,12 @@ add_compile_definitions( BIG_ENDIAN=0 CONFIG_ZTEST=1 BACAPP_ALL - ) +) include_directories( ${SRC_DIR} ${TST_DIR}/ztest/include - ) +) add_executable(${PROJECT_NAME} # File(s) under test @@ -62,4 +62,4 @@ add_executable(${PROJECT_NAME} ./src/main.c ${ZTST_DIR}/ztest_mock.c ${ZTST_DIR}/ztest.c - ) +) diff --git a/test/bacnet/rpm/src/main.c b/test/bacnet/rpm/src/main.c index f9c72d49..18e880cd 100644 --- a/test/bacnet/rpm/src/main.c +++ b/test/bacnet/rpm/src/main.c @@ -1,13 +1,11 @@ -/* - * Copyright (c) 2020 Legrand North America, LLC. - * - * SPDX-License-Identifier: MIT +/** + * @file + * @brief BACnet ReadPropertyMultiple -Request and -Ack codec unit test + * @author Steve Karg + * @author Greg Shue + * @date 2005 + * @copyright SPDX-License-Identifier: MIT */ - -/* @file - * @brief test BACnet integer encode/decode APIs - */ - #include #include /* For bacerror_decode_error_class_and_code() */ #include