Secured the WhoHas codec and improved unit test coverage. (#649)

This commit is contained in:
Steve Karg
2024-05-20 11:39:10 -05:00
committed by GitHub
parent cf00e9e094
commit 53fd7a2e1f
3 changed files with 271 additions and 192 deletions
+186 -122
View File
@@ -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 <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"
@@ -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;
}
+46 -56
View File
@@ -1,29 +1,12 @@
/**************************************************************************
*
* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
*
* 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 <skarg@users.sourceforge.net>
* @date 2006
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_WHO_HAS_H
#define BACNET_WHO_HAS_H
#include <stdint.h>
#include <stdbool.h>
/* 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 <br>
*
* 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 <br>
* 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
+39 -14
View File
@@ -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 <skarg@users.sourceforge.net>
* @author Greg Shue <greg.shue@outlook.com>
* @date Aug 2022
* @copyright SPDX-License-Identifier: MIT
*/
/* @file
* @brief test BACnet integer encode/decode APIs
*/
#include <zephyr/ztest.h>
#include <bacnet/whohas.h>
@@ -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;