diff --git a/src/bacnet/whohas.c b/src/bacnet/whohas.c index ef85250d..952f7ae9 100644 --- a/src/bacnet/whohas.c +++ b/src/bacnet/whohas.c @@ -1,36 +1,10 @@ -/*####COPYRIGHTBEGIN#### - ------------------------------------------- - Copyright (C) 2006 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 WhoHas-Request encode and decode + * @author Steve Karg + * @date 2006 + * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ #include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" @@ -38,122 +12,212 @@ #include "bacnet/bacdcode.h" #include "bacnet/whohas.h" -/** @file whohas.c Encode/Decode Who-Has requests */ - -/** Encode Who-Hasservice - use -1 for limit for unlimited +/** + * @brief Encode Who-Has-Request - use -1 as limit for unlimited + * Who-Has-Request ::= SEQUENCE { + * limits SEQUENCE { + * device-instance-range-low-limit [0] Unsigned (0..4194303), + * device-instance-range-high-limit [1] Unsigned (0..4194303) + * } OPTIONAL, + * object CHOICE { + * object-identifier [2] BACnetObjectIdentifier, + * object-name [3] CharacterString + * } + * } * - * @param apdu Pointer to the APDU. - * @param data Pointer to the Who-Has application data. - * - * @return Bytes encoded. */ -int whohas_encode_apdu(uint8_t *apdu, BACNET_WHO_HAS_DATA *data) + * @param apdu application data unit buffer + * @param data application data to be encoded + * @return number of bytes encoded + */ +int bacnet_who_has_request_encode(uint8_t *apdu, BACNET_WHO_HAS_DATA *data) { int len = 0; /* length of each encoding */ int apdu_len = 0; /* total length of the apdu, return value */ - if (apdu && data) { - apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST; - apdu[1] = SERVICE_UNCONFIRMED_WHO_HAS; /* service choice */ - apdu_len = 2; - /* optional limits - must be used as a pair */ - if ((data->low_limit >= 0) && - (data->low_limit <= BACNET_MAX_INSTANCE) && - (data->high_limit >= 0) && - (data->high_limit <= BACNET_MAX_INSTANCE)) { - len = encode_context_unsigned(&apdu[apdu_len], 0, data->low_limit); - apdu_len += len; - len = encode_context_unsigned(&apdu[apdu_len], 1, data->high_limit); - apdu_len += len; + if (!data) { + return 0; + } + + /* optional limits - must be used as a pair */ + if ((data->low_limit >= 0) && (data->low_limit <= BACNET_MAX_INSTANCE) && + (data->high_limit >= 0) && (data->high_limit <= BACNET_MAX_INSTANCE)) { + len = encode_context_unsigned(apdu, 0, data->low_limit); + apdu_len += len; + if (apdu) { + apdu += len; } - if (data->is_object_name) { - len = encode_context_character_string( - &apdu[apdu_len], 3, &data->object.name); - apdu_len += len; - } else { - len = encode_context_object_id(&apdu[apdu_len], 2, - data->object.identifier.type, data->object.identifier.instance); - apdu_len += len; + len = encode_context_unsigned(apdu, 1, data->high_limit); + apdu_len += len; + if (apdu) { + apdu += len; } } + if (data->is_object_name) { + len = encode_context_character_string(apdu, 3, &data->object.name); + apdu_len += len; + } else { + len = encode_context_object_id( + apdu, 2, data->object.identifier.type, + data->object.identifier.instance); + apdu_len += len; + } return apdu_len; } -/** Decode the Who-Has service request only. - * - * @param apdu Pointer to the received data. - * @param apdu_len Bytes valid in the receive buffer. - * @param data Pointer to the application dta to be filled in. - * - * @return Bytes decoded. */ -int whohas_decode_service_request( - uint8_t *apdu, unsigned apdu_len, BACNET_WHO_HAS_DATA *data) +/** + * @brief Encode Who-Has-Request service + * @param apdu application data unit buffer + * @param apdu_size number of bytes available in the buffer + * @param data application data to be encoded + * @return number of bytes encoded, or zero if unable to encode or too large + */ +size_t bacnet_who_has_service_request_encode( + uint8_t *apdu, size_t apdu_size, BACNET_WHO_HAS_DATA *data) { + size_t apdu_len = 0; /* total length of the apdu, return value */ + + apdu_len = bacnet_who_has_request_encode(NULL, data); + if (apdu_len > apdu_size) { + apdu_len = 0; + } else { + apdu_len = bacnet_who_has_request_encode(apdu, data); + } + + return apdu_len; +} + +/** + * @brief Encode Who-Has service - use -1 for limit for unlimited + * @param apdu Pointer to the APDU. + * @param data Pointer to the Who-Has application data. + * @return Bytes encoded. + */ +int whohas_encode_apdu(uint8_t *apdu, BACNET_WHO_HAS_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_UNCONFIRMED_SERVICE_REQUEST; + apdu[1] = SERVICE_UNCONFIRMED_WHO_HAS; /* service choice */ + } + len = 2; + apdu_len += len; + if (apdu) { + apdu += len; + } + len = bacnet_who_has_request_encode(apdu, data); + if (len > 0) { + apdu_len += len; + } else { + apdu_len = 0; + } + + return apdu_len; +} + +/** + * @brief Decode the Who-Has service request only. + * @param apdu application protocol data unit buffer + * @param apdu_size number of valid bytes in the receive buffer. + * @param data application data to be decoded + * @return number of bytes decoded, or BACNET_STATUS_ERROR on error + */ +int whohas_decode_service_request( + uint8_t *apdu, unsigned apdu_size, BACNET_WHO_HAS_DATA *data) +{ + int apdu_len = 0; /* total length of the apdu, return value */ int len = 0; - uint8_t tag_number = 0; - uint32_t len_value = 0; BACNET_UNSIGNED_INTEGER unsigned_value = 0; BACNET_OBJECT_TYPE decoded_type = OBJECT_NONE; + uint32_t decoded_instance = 0; + BACNET_CHARACTER_STRING *char_string = NULL; - if (apdu_len && data) { + if (apdu_size == 0) { + /* message too short */ + return BACNET_STATUS_ERROR; + } + /* If the 'Device Instance Range Low Limit' parameter is present, then + the 'Device Instance Range High Limit' parameter shall also be present*/ + /* device-instance-range-low-limit [0] Unsigned OPTIONAL */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 0, &unsigned_value); + if (len > 0) { + apdu_len += len; /* optional limits - must be used as a pair */ - if (decode_is_context_tag(&apdu[len], 0)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - if ((unsigned)len < apdu_len) { - len += decode_unsigned(&apdu[len], len_value, &unsigned_value); - if ((unsigned)len < apdu_len) { - if (unsigned_value <= BACNET_MAX_INSTANCE) { - data->low_limit = unsigned_value; - } - if (!decode_is_context_tag(&apdu[len], 1)) { - return -1; - } - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - if ((unsigned)len < apdu_len) { - len += decode_unsigned( - &apdu[len], len_value, &unsigned_value); - if (unsigned_value <= BACNET_MAX_INSTANCE) { - data->high_limit = unsigned_value; - } - } - } + if (unsigned_value <= BACNET_MAX_INSTANCE) { + /* (0..4194303) */ + if (data) { + data->low_limit = unsigned_value; } } else { + /* parameter out of range */ + return BACNET_STATUS_ERROR; + } + /* device-instance-range-high-limit [1] Unsigned OPTIONAL */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 1, &unsigned_value); + if (len > 0) { + apdu_len += len; + if (unsigned_value <= BACNET_MAX_INSTANCE) { + /* (0..4194303) */ + if (data) { + data->high_limit = unsigned_value; + } + } else { + /* parameter out of range */ + return BACNET_STATUS_ERROR; + } + } else { + /* missing required parameters */ + return BACNET_STATUS_ERROR; + } + } else if (len == 0) { + /* If the 'Device Instance Range Low Limit' and + 'Device Instance Range High Limit' parameters are omitted, + then all devices that receive this message are + qualified to respond with an I-Have service request. */ + /* use -1 as limit for unlimited */ + if (data) { data->low_limit = -1; data->high_limit = -1; } - /* object id */ - if ((unsigned)len < apdu_len) { - if (decode_is_context_tag(&apdu[len], 2)) { - data->is_object_name = false; - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - if ((unsigned)len < apdu_len) { - len += decode_object_id(&apdu[len], &decoded_type, - &data->object.identifier.instance); - data->object.identifier.type = decoded_type; - } - } - /* object name */ - else if (decode_is_context_tag(&apdu[len], 3)) { + } else { + /* malformed */ + return BACNET_STATUS_ERROR; + } + /* object-identifier [2] BACnetObjectIdentifier, CHOICE */ + len = bacnet_object_id_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 2, &decoded_type, + &decoded_instance); + if (len > 0) { + apdu_len += len; + if (data) { + data->is_object_name = false; + data->object.identifier.type = decoded_type; + data->object.identifier.instance = decoded_instance; + } + } else if (len == 0) { + /* object-name [3] CharacterString, CHOICE */ + if (data) { + char_string = &data->object.name; + } + len = bacnet_character_string_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 3, char_string); + if (len > 0) { + apdu_len += len; + if (data) { data->is_object_name = true; - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - if ((unsigned)len < apdu_len) { - len += decode_character_string( - &apdu[len], len_value, &data->object.name); - } - } else { - /* missing required parameters */ - return -1; } } else { - /* message too short */ - return -1; + /* malformed */ + return BACNET_STATUS_ERROR; } + } else { + /* missing required parameters */ + return BACNET_STATUS_ERROR; } - return len; + return apdu_len; } diff --git a/src/bacnet/whohas.h b/src/bacnet/whohas.h index d5684640..31cdea1d 100644 --- a/src/bacnet/whohas.h +++ b/src/bacnet/whohas.h @@ -1,29 +1,12 @@ -/************************************************************************** -* -* 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 WHOHAS_H -#define WHOHAS_H - +/** + * @file + * @brief BACnet WhoHas-Request encode and decode API header file + * @author Steve Karg + * @date 2006 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_WHO_HAS_H +#define BACNET_WHO_HAS_H #include #include /* BACnet Stack defines - first */ @@ -31,10 +14,30 @@ /* BACnet Stack API */ #include "bacnet/bacstr.h" +/** + * @defgroup DMDOB Device Management-Dynamic Object Binding (DM-DOB) + * @ingroup RDMS + * + * 16.9 Who-Has and I-Have Services
+ * + * The Who-Has service is used by a sending BACnet-user to identify the device + * object identifiers and network addresses of other BACnet devices whose local + * databases contain an object with a given Object_Name or a given + * Object_Identifier. + * + * The I-Have service is used to respond to Who-Has service requests or to + * advertise the existence of an object with a given Object_Name or + * Object_Identifier. The I-Have service request may be issued at any time and + * does not need to be preceded by the receipt of a Who-Has service request. + * The Who-Has and I-Have services are unconfirmed services. + */ + typedef struct BACnet_Who_Has_Data { - int32_t low_limit; /* deviceInstanceRange */ + /* deviceInstanceRange - use -1 for limit if you want unlimited */ + int32_t low_limit; int32_t high_limit; - bool is_object_name; /* true if a string */ + /* true if a string */ + bool is_object_name; union { BACNET_OBJECT_ID identifier; BACNET_CHARACTER_STRING name; @@ -45,38 +48,25 @@ typedef struct BACnet_Who_Has_Data { extern "C" { #endif /* __cplusplus */ -/* encode service - use -1 for limit if you want unlimited */ - BACNET_STACK_EXPORT - int whohas_encode_apdu( - uint8_t * apdu, - BACNET_WHO_HAS_DATA * data); +BACNET_STACK_EXPORT +int whohas_encode_apdu(uint8_t *apdu, BACNET_WHO_HAS_DATA *data); - BACNET_STACK_EXPORT - int whohas_decode_service_request( - uint8_t * apdu, - unsigned apdu_len, - BACNET_WHO_HAS_DATA * data); +BACNET_STACK_EXPORT +int bacnet_who_has_request_encode(uint8_t *apdu, BACNET_WHO_HAS_DATA *data); +BACNET_STACK_EXPORT +size_t bacnet_who_has_service_request_encode( + uint8_t *apdu, size_t apdu_size, BACNET_WHO_HAS_DATA *data); - BACNET_STACK_EXPORT - int whohas_decode_apdu( - uint8_t * apdu, - unsigned apdu_len, - BACNET_WHO_HAS_DATA * data); +BACNET_STACK_EXPORT +int whohas_decode_service_request( + uint8_t *apdu, unsigned apdu_size, BACNET_WHO_HAS_DATA *data); + +/* defined in unit test */ +BACNET_STACK_EXPORT +int whohas_decode_apdu( + uint8_t *apdu, unsigned apdu_size, BACNET_WHO_HAS_DATA *data); #ifdef __cplusplus } #endif /* __cplusplus */ -/** @defgroup DMDOB Device Management-Dynamic Object Binding (DM-DOB) - * @ingroup RDMS - * 16.9 Who-Has and I-Have Services
- * The Who-Has service is used by a sending BACnet-user to identify the device - * object identifiers and network addresses of other BACnet devices whose local - * databases contain an object with a given Object_Name or a given Object_Identifier. - * The I-Have service is used to respond to Who-Has service requests or to - * advertise the existence of an object with a given Object_Name or - * Object_Identifier. The I-Have service request may be issued at any time and - * does not need to be preceded by the receipt of a Who-Has service request. - * The Who-Has and I-Have services are unconfirmed services. - * - */ #endif diff --git a/test/bacnet/whohas/src/main.c b/test/bacnet/whohas/src/main.c index cfe733d8..1f57133e 100644 --- a/test/bacnet/whohas/src/main.c +++ b/test/bacnet/whohas/src/main.c @@ -1,13 +1,11 @@ -/* - * Copyright (c) 2020 Legrand North America, LLC. - * - * SPDX-License-Identifier: MIT +/** + * @file + * @brief Unit test for BACnet WhoHas-Request encode and decode + * @author Steve Karg + * @author Greg Shue + * @date Aug 2022 + * @copyright SPDX-License-Identifier: MIT */ - -/* @file - * @brief test BACnet integer encode/decode APIs - */ - #include #include @@ -20,7 +18,7 @@ * @brief Test */ int whohas_decode_apdu( - uint8_t *apdu, unsigned apdu_len, BACNET_WHO_HAS_DATA *data) + uint8_t *apdu, unsigned apdu_size, BACNET_WHO_HAS_DATA *data) { int len = 0; @@ -32,8 +30,8 @@ int whohas_decode_apdu( if (apdu[1] != SERVICE_UNCONFIRMED_WHO_HAS) return -1; /* optional limits - must be used as a pair */ - if (apdu_len > 2) { - len = whohas_decode_service_request(&apdu[2], apdu_len - 2, data); + if (apdu_size > 2) { + len = whohas_decode_service_request(&apdu[2], apdu_size - 2, data); } return len; @@ -44,7 +42,9 @@ static void testWhoHasData(BACNET_WHO_HAS_DATA *data) uint8_t apdu[480] = { 0 }; int len = 0; int apdu_len = 0; - BACNET_WHO_HAS_DATA test_data; + int null_len = 0; + int test_len = 0; + BACNET_WHO_HAS_DATA test_data = { 0 }; len = whohas_encode_apdu(&apdu[0], data); zassert_not_equal(len, 0, NULL); @@ -70,6 +70,31 @@ static void testWhoHasData(BACNET_WHO_HAS_DATA *data) characterstring_same(&test_data.object.name, &data->object.name), NULL); } + /* encoder bounds checking */ + null_len = bacnet_who_has_request_encode(NULL, data); + apdu_len = bacnet_who_has_request_encode(apdu, data); + zassert_true(apdu_len > 0, NULL); + zassert_equal(apdu_len, null_len, NULL); + null_len = bacnet_who_has_service_request_encode(NULL, sizeof(apdu), data); + apdu_len = bacnet_who_has_service_request_encode(apdu, sizeof(apdu), data); + zassert_equal(apdu_len, null_len, NULL); + zassert_true(apdu_len > 0, NULL); + /* test short APDU buffer */ + while (--apdu_len) { + test_len = bacnet_who_has_service_request_encode(apdu, apdu_len, data); + zassert_equal(test_len, 0 , NULL); + } + /* decoder bounds checking */ + apdu_len = bacnet_who_has_request_encode(apdu, data); + zassert_true(apdu_len > 0, NULL); + test_len = whohas_decode_service_request(apdu, apdu_len, data); + null_len = whohas_decode_service_request(apdu, apdu_len, NULL); + zassert_equal(test_len, null_len, NULL); + /* test short APDU buffer */ + while (--apdu_len) { + test_len = whohas_decode_service_request(apdu, apdu_len, data); + zassert_equal(test_len, BACNET_STATUS_ERROR , NULL); + } } #if defined(CONFIG_ZTEST_NEW_API) @@ -84,7 +109,7 @@ static void testWhoHas(void) data.high_limit = -1; data.is_object_name = false; data.object.identifier.type = OBJECT_ANALOG_INPUT; - data.object.identifier.instance = 1; + data.object.identifier.instance = 0; testWhoHasData(&data); for (data.low_limit = 0; data.low_limit <= BACNET_MAX_INSTANCE;