Added Who-Is-Router process for Notification Class recipient unknown router addresses. (#1243)

* Added Who-Is-Router-To-Network process in basic Notification Class when recipient address is missing the router MAC address.

* Added buffer_length parameter to octet string buffer decode functions

* Fixed BACnet address handling for I-Am-Router address updating the recipient list address for the next hop router.

* Changed VMAC encoding and decoding to use octet string buffer API to reduce stack RAM.
This commit is contained in:
Steve Karg
2026-02-25 09:58:38 -06:00
committed by GitHub
parent e11cd319da
commit cf4f62f7e0
14 changed files with 225 additions and 69 deletions
+3 -1
View File
@@ -41,9 +41,11 @@ The git repositories are hosted at the following sites:
### Added
* Added Who-Is-Router-To-Network process in basic Notification Class when
recipient address router MAC address is unknown. (#1243)
* Add initialization and unpacking functions for BACnet Character
String buffer structure. (#1242)
* Add Host-N-Port minimal encode and decode which utilizes the octet
* Added Host-N-Port minimal encode and decode which utilizes the octet
string and character string buffer encode and decode. (#1239)
* Added API for extending the basic Device object and children with
proprietary properties for ReadProperty and WriteProperty services. (#1238)
+80 -30
View File
@@ -15,7 +15,6 @@
/* BACnet Stack API */
#include "bacnet/bacdcode.h"
#include "bacnet/bacint.h"
#include "bacnet/bacstr.h"
#include "bacnet/bactext.h"
#include "bacnet/bacaddr.h"
@@ -203,6 +202,33 @@ bool bacnet_address_init(
return true;
}
/**
* @brief Set the #BACNET_ADDRESS of the next-hop router to the MAC of the dest
* @param dest - #BACNET_ADDRESS to be configured
* @param router - #BACNET_ADDRESS MAC to be copied from
*/
void bacnet_address_router_set(
BACNET_ADDRESS *dest, const BACNET_ADDRESS *router)
{
uint8_t i = 0;
uint8_t mac_len = 0;
if (dest && router) {
mac_len = router->mac_len;
if (mac_len > MAX_MAC_LEN) {
mac_len = MAX_MAC_LEN;
}
dest->mac_len = mac_len;
for (i = 0; i < MAX_MAC_LEN; i++) {
if (i < mac_len) {
dest->mac[i] = router->mac[i];
} else {
dest->mac[i] = 0;
}
}
}
}
/**
* @brief Compare two #BACNET_MAC_ADDRESS values
* @param dest - #BACNET_MAC_ADDRESS to be compared
@@ -385,7 +411,8 @@ int bacnet_address_decode(
int apdu_len = 0;
uint8_t i = 0;
BACNET_UNSIGNED_INTEGER snet = 0;
BACNET_OCTET_STRING mac_addr = { 0 };
uint32_t mac_addr_len = 0;
uint8_t mac_addr[MAX_MAC_LEN] = { 0 };
if (!apdu) {
return BACNET_STATUS_ERROR;
@@ -406,33 +433,47 @@ int bacnet_address_decode(
}
apdu_len += len;
/* mac address as an octet-string */
len = bacnet_octet_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, &mac_addr);
len = bacnet_octet_string_buffer_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, &mac_addr[0], sizeof(mac_addr),
&mac_addr_len);
if (len <= 0) {
return BACNET_STATUS_ERROR;
}
if (mac_addr_len > MAX_MAC_LEN) {
return BACNET_STATUS_ERROR;
}
if (snet) {
if (value) {
if (mac_addr.length > sizeof(value->adr)) {
if (mac_addr_len > sizeof(value->adr)) {
return BACNET_STATUS_ERROR;
}
/* bounds checking - passed! */
value->len = mac_addr.length;
value->len = (uint8_t)mac_addr_len;
/* copy address */
for (i = 0; i < value->len; i++) {
value->adr[i] = mac_addr.value[i];
value->adr[i] = mac_addr[i];
}
/* zero the router address */
value->mac_len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
value->mac[i] = 0;
}
}
} else {
if (value) {
if (mac_addr.length > sizeof(value->mac)) {
if (mac_addr_len > sizeof(value->mac)) {
return BACNET_STATUS_ERROR;
}
/* bounds checking - passed! */
value->mac_len = mac_addr.length;
value->mac_len = (uint8_t)mac_addr_len;
/* copy address */
for (i = 0; i < value->mac_len; i++) {
value->mac[i] = mac_addr.value[i];
value->mac[i] = mac_addr[i];
}
/* zero the device behind a router address */
value->len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
value->adr[i] = 0;
}
}
}
@@ -581,24 +622,21 @@ int decode_context_bacnet_address(
int bacnet_vmac_entry_data_encode(uint8_t *apdu, const BACNET_VMAC_ENTRY *value)
{
int apdu_len = 0, len;
BACNET_OCTET_STRING address = { 0 };
if (!value) {
return 0;
}
/* virtual-mac-address [0] OctetString */
octetstring_init(
&address, value->virtual_mac_address.adr,
len = encode_context_octet_string_buffer(
apdu, 0, value->virtual_mac_address.adr,
value->virtual_mac_address.len);
len = encode_context_octet_string(apdu, 0, &address);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* native-mac-address */
octetstring_init(
&address, value->native_mac_address, value->native_mac_address_len);
len = encode_context_octet_string(apdu, 1, &address);
len = encode_context_octet_string_buffer(
apdu, 1, value->native_mac_address, value->native_mac_address_len);
apdu_len += len;
return apdu_len;
@@ -647,44 +685,56 @@ int bacnet_vmac_entry_decode(
int len = 0;
int apdu_len = 0;
size_t i = 0;
BACNET_OCTET_STRING mac_addr = { 0 };
BACNET_VMAC_ENTRY entry = { 0 };
uint32_t mac_addr_len = 0;
if (!apdu) {
return BACNET_STATUS_ERROR;
}
/* virtual-mac-address [0] OctetString */
len = bacnet_octet_string_context_decode(
&apdu[apdu_len], apdu_size - apdu_len, 0, &mac_addr);
len = bacnet_octet_string_buffer_context_decode(
&apdu[apdu_len], apdu_size - apdu_len, 0, entry.virtual_mac_address.adr,
sizeof(entry.virtual_mac_address.adr), &mac_addr_len);
if (len <= 0) {
return BACNET_STATUS_ERROR;
}
if (value) {
if (mac_addr.length > sizeof(value->virtual_mac_address.adr)) {
if (mac_addr_len > sizeof(value->virtual_mac_address.adr)) {
return BACNET_STATUS_ERROR;
}
/* bounds checking - passed! */
value->virtual_mac_address.len = mac_addr.length;
value->virtual_mac_address.len = mac_addr_len;
/* copy address */
for (i = 0; i < mac_addr.length; i++) {
value->virtual_mac_address.adr[i] = mac_addr.value[i];
for (i = 0; i < sizeof(value->virtual_mac_address.adr); i++) {
if (i < mac_addr_len) {
value->virtual_mac_address.adr[i] =
entry.virtual_mac_address.adr[i];
} else {
value->virtual_mac_address.adr[i] = 0;
}
}
}
apdu_len += len;
/* native-mac-address [1] OctetString */
len = bacnet_octet_string_context_decode(
&apdu[apdu_len], apdu_size - apdu_len, 1, &mac_addr);
len = bacnet_octet_string_buffer_context_decode(
&apdu[apdu_len], apdu_size - apdu_len, 1, entry.native_mac_address,
sizeof(entry.native_mac_address), &mac_addr_len);
if (len <= 0) {
return BACNET_STATUS_ERROR;
}
if (value) {
if (mac_addr.length > sizeof(value->native_mac_address)) {
if (mac_addr_len > sizeof(value->native_mac_address)) {
return BACNET_STATUS_ERROR;
}
/* bounds checking - passed! */
value->native_mac_address_len = mac_addr.length;
value->native_mac_address_len = mac_addr_len;
/* copy address */
for (i = 0; i < mac_addr.length; i++) {
value->native_mac_address[i] = mac_addr.value[i];
for (i = 0; i < sizeof(value->native_mac_address); i++) {
if (i < mac_addr_len) {
value->native_mac_address[i] = entry.native_mac_address[i];
} else {
value->native_mac_address[i] = 0;
}
}
}
apdu_len += len;
+3
View File
@@ -58,6 +58,9 @@ bool bacnet_address_init(
const BACNET_MAC_ADDRESS *mac,
uint16_t dnet,
const BACNET_MAC_ADDRESS *adr);
BACNET_STACK_EXPORT
void bacnet_address_router_set(
BACNET_ADDRESS *dest, const BACNET_ADDRESS *router);
BACNET_STACK_EXPORT
bool bacnet_address_mac_same(
+12 -2
View File
@@ -2438,6 +2438,7 @@ int bacnet_octet_string_buffer_decode(
* or NULL for length only.
* @param buffer_size - number of bytes in the buffer where the
* decoded value is stored
* @param buffer_length - number of bytes decoded into the buffer
*
* @return number of bytes decoded, zero if tag mismatch,
* or #BACNET_STATUS_ERROR (-1) if malformed
@@ -2446,7 +2447,8 @@ int bacnet_octet_string_buffer_application_decode(
const uint8_t *apdu,
uint32_t apdu_size,
uint8_t *buffer,
size_t buffer_size)
size_t buffer_size,
uint32_t *buffer_length)
{
int apdu_len = BACNET_STATUS_ERROR;
int len = 0;
@@ -2464,6 +2466,9 @@ int bacnet_octet_string_buffer_application_decode(
&apdu[len], apdu_size - apdu_len, tag.len_value_type, buffer,
buffer_size);
if (len >= 0) {
if (buffer_length) {
*buffer_length = tag.len_value_type;
}
apdu_len += len;
} else {
apdu_len = BACNET_STATUS_ERROR;
@@ -2488,6 +2493,7 @@ int bacnet_octet_string_buffer_application_decode(
* or NULL for length only.
* @param buffer_size - number of bytes in the buffer where the
* decoded value is stored
* @param buffer_length - number of bytes decoded into the buffer
*
* @return number of bytes decoded, or zero if tag mismatch, or
* #BACNET_STATUS_ERROR (-1) if malformed
@@ -2497,7 +2503,8 @@ int bacnet_octet_string_buffer_context_decode(
uint32_t apdu_size,
uint8_t tag_value,
uint8_t *buffer,
size_t buffer_size)
size_t buffer_size,
uint32_t *buffer_length)
{
int apdu_len = BACNET_STATUS_ERROR;
int len = 0;
@@ -2514,6 +2521,9 @@ int bacnet_octet_string_buffer_context_decode(
&apdu[apdu_len], apdu_size - apdu_len, tag.len_value_type,
buffer, buffer_size);
if (len >= 0) {
if (buffer_length) {
*buffer_length = tag.len_value_type;
}
apdu_len += len;
} else {
apdu_len = BACNET_STATUS_ERROR;
+4 -2
View File
@@ -433,14 +433,16 @@ int bacnet_octet_string_buffer_application_decode(
const uint8_t *apdu,
uint32_t apdu_size,
uint8_t *buffer,
size_t buffer_size);
size_t buffer_size,
uint32_t *buffer_length);
BACNET_STACK_EXPORT
int bacnet_octet_string_buffer_context_decode(
const uint8_t *apdu,
uint32_t apdu_size,
uint8_t tag_value,
uint8_t *buffer,
size_t buffer_size);
size_t buffer_size,
uint32_t *buffer_length);
BACNET_STACK_EXPORT
int encode_octet_string(uint8_t *apdu, const BACNET_OCTET_STRING *octet_string);
+22
View File
@@ -125,6 +125,28 @@ void bacnet_recipient_address_set(
}
}
/**
* @brief Inspect the BACnetRecipient data structure for valid router address
* @param recipient - BACnetRecipient structure
* @return true if BACnetRecipient is a valid address
*/
bool bacnet_recipient_address_router_unknown(const BACNET_RECIPIENT *recipient)
{
bool status = false;
if (recipient) {
if ((recipient->tag == BACNET_RECIPIENT_TAG_ADDRESS) &&
(recipient->type.address.net != 0) &&
(recipient->type.address.net != BACNET_BROADCAST_NETWORK) &&
(recipient->type.address.len != 0) &&
(recipient->type.address.mac_len == 0)) {
status = true;
}
}
return status;
}
/**
* @brief Copy the BACnetRecipient complex data from src to dest
* @param src - BACnetRecipient 1 structure
+2
View File
@@ -98,6 +98,8 @@ void bacnet_recipient_copy(BACNET_RECIPIENT *dest, const BACNET_RECIPIENT *src);
BACNET_STACK_EXPORT
bool bacnet_recipient_same(
const BACNET_RECIPIENT *r1, const BACNET_RECIPIENT *r2);
BACNET_STACK_EXPORT
bool bacnet_recipient_address_router_unknown(const BACNET_RECIPIENT *recipient);
BACNET_STACK_EXPORT
void bacnet_recipient_device_wildcard_set(BACNET_RECIPIENT *recipient);
+35 -4
View File
@@ -90,6 +90,33 @@ void Notification_Class_Writable_Property_List(
}
}
/**
* @brief Handle I-Am router to network for out of network recipients
* @param src - source address of the router
* @param network - network number of the router
*/
static void Notification_Class_I_Am_Router_To_Network_Handler(
BACNET_ADDRESS *src, uint16_t network)
{
NOTIFICATION_CLASS_INFO *notification;
BACNET_DESTINATION *destination;
BACNET_RECIPIENT *recipient;
unsigned i, j;
for (i = 0; i < MAX_NOTIFICATION_CLASSES; i++) {
notification = &NC_Info[i];
for (j = 0; j < NC_MAX_RECIPIENTS; j++) {
destination = &notification->Recipient_List[j];
recipient = &destination->Recipient;
/* update recipient addresses for this network */
if ((recipient->tag == BACNET_RECIPIENT_TAG_ADDRESS) &&
(recipient->type.address.net == network)) {
bacnet_address_router_set(&recipient->type.address, src);
}
}
}
}
void Notification_Class_Init(void)
{
uint8_t NotifyIdx = 0;
@@ -111,6 +138,8 @@ void Notification_Class_Init(void)
bacnet_destination_default_init(destination);
}
}
npdu_set_i_am_router_to_network_handler(
Notification_Class_I_Am_Router_To_Network_Handler);
return;
}
@@ -727,12 +756,11 @@ void Notification_Class_common_reporting_function(
"Notification Class[%u]: send notification to ADDR\n",
event_data->notificationClass);
/* send notification to the address indicated */
bacnet_address_copy(&dest, &pBacDest->Recipient.type.address);
if (pBacDest->ConfirmedNotify == true) {
if (address_get_device_id(&dest, &device_id)) {
Send_CEvent_Notify(device_id, event_data);
}
Send_CEvent_Notify_Address(
Event_Buffer, sizeof(Event_Buffer), event_data, &dest);
} else {
dest = pBacDest->Recipient.type.address;
Send_UEvent_Notify(Event_Buffer, event_data, &dest);
}
}
@@ -764,6 +792,9 @@ void Notification_Class_find_recipient(void)
address of device is unknown. */
Send_WhoIs(device_id, device_id);
}
} else if (bacnet_recipient_address_router_unknown(recipient)) {
Send_Who_Is_Router_To_Network(
NULL, recipient->type.address.net);
}
}
}
+7 -2
View File
@@ -683,6 +683,7 @@ int bvlc_broadcast_distribution_table_decode(
int len = 0, apdu_len = 0;
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL;
BACNET_HOST_N_PORT_MINIMAL host_n_port = { 0 };
uint32_t address_length = 0;
/* default reject code */
if (error_code) {
@@ -700,7 +701,6 @@ int bvlc_broadcast_distribution_table_decode(
apdu_len += len;
bdt_entry->dest_address.port = host_n_port.port;
if (host_n_port.tag == BACNET_HOST_ADDRESS_TAG_IP_ADDRESS) {
bdt_entry->valid = true;
bdt_entry->dest_address.address[0] =
host_n_port.host.ip_address.address[0];
bdt_entry->dest_address.address[1] =
@@ -719,8 +719,13 @@ int bvlc_broadcast_distribution_table_decode(
len = bacnet_octet_string_buffer_context_decode(
&apdu[apdu_len], apdu_size - apdu_len, 1,
bdt_entry->broadcast_mask.address,
sizeof(bdt_entry->broadcast_mask.address));
sizeof(bdt_entry->broadcast_mask.address), &address_length);
if (len > 0) {
if (address_length == sizeof(bdt_entry->broadcast_mask.address)) {
bdt_entry->valid = true;
} else {
bdt_entry->valid = false;
}
apdu_len += len;
} else {
if (error_code) {
+25 -15
View File
@@ -150,6 +150,12 @@ static void test_BACNET_ADDRESS(void)
zassert_equal(dest.mac_len, 0, NULL);
zassert_equal(dest.len, 0, NULL);
zassert_equal(dest.net, 0, NULL);
/* copy the MAC to the ADR */
bacnet_address_from_ascii(&src, "{192.168.1.1:47808,0,0}");
zassert_equal(src.mac_len, 6, "len=%d", src.mac_len);
bacnet_address_router_set(&dest, &src);
zassert_equal(dest.mac_len, 6, "len=%d", dest.mac_len);
zassert_equal(dest.len, 0, NULL);
}
#if defined(CONFIG_ZTEST_NEW_API)
@@ -282,13 +288,13 @@ static void test_BACnetAddress_Codec(void)
zassert_equal(len, test_len, "len=%d test_len=%d", len, test_len);
test_len = bacnet_address_decode(apdu, sizeof(apdu), &test_value);
zassert_equal(len, test_len, NULL);
zassert_equal(value.net, test_value.net, NULL);
zassert_equal(value.mac_len, test_value.mac_len, NULL);
zassert_equal(value.len, test_value.len, NULL);
zassert_equal(value.len, 3, NULL);
zassert_equal(value.adr[0], test_value.adr[0], NULL);
zassert_equal(value.adr[1], test_value.adr[1], NULL);
zassert_equal(value.adr[2], test_value.adr[2], NULL);
zassert_equal(test_value.net, value.net, NULL);
zassert_equal(test_value.len, value.len, NULL);
zassert_equal(test_value.len, 3, NULL);
zassert_equal(test_value.adr[0], value.adr[0], NULL);
zassert_equal(test_value.adr[1], value.adr[1], NULL);
zassert_equal(test_value.adr[2], value.adr[2], NULL);
zassert_equal(test_value.mac_len, 0, NULL);
/* context tagged */
tag_number = 1;
len = encode_context_bacnet_address(NULL, tag_number, &value);
@@ -299,17 +305,21 @@ static void test_BACnetAddress_Codec(void)
test_len = bacnet_address_context_decode(
apdu, sizeof(apdu), tag_number, &test_value);
zassert_equal(len, test_len, NULL);
zassert_equal(value.net, test_value.net, NULL);
zassert_equal(value.mac_len, test_value.mac_len, NULL);
zassert_equal(value.mac_len, 6, NULL);
zassert_equal(test_value.len, value.len, NULL);
zassert_equal(test_value.len, 3, NULL);
zassert_equal(test_value.adr[0], value.adr[0], NULL);
zassert_equal(test_value.adr[1], value.adr[1], NULL);
zassert_equal(test_value.adr[2], value.adr[2], NULL);
zassert_equal(test_value.mac_len, 0, NULL);
/* validate deprecated function */
test_len = decode_context_bacnet_address(apdu, tag_number, &test_value);
zassert_equal(len, test_len, NULL);
zassert_equal(value.net, test_value.net, NULL);
zassert_equal(value.mac_len, test_value.mac_len, NULL);
zassert_equal(value.mac_len, 6, NULL);
test_len = bacnet_address_context_decode(
apdu, sizeof(apdu), tag_number, &test_value);
zassert_equal(test_value.len, value.len, NULL);
zassert_equal(test_value.len, 3, NULL);
zassert_equal(test_value.adr[0], value.adr[0], NULL);
zassert_equal(test_value.adr[1], value.adr[1], NULL);
zassert_equal(test_value.adr[2], value.adr[2], NULL);
zassert_equal(test_value.mac_len, 0, NULL);
/* negative tests - NULL value */
test_len =
bacnet_address_context_decode(apdu, sizeof(apdu), tag_number, NULL);
+6 -2
View File
@@ -2358,6 +2358,7 @@ static void test_octet_string_buffer(void)
uint8_t buffer[32] = { 0 }, test_buffer[32] = { 0 };
int apdu_len = 0, null_len = 0, test_len = 0;
size_t buffer_size = 0;
uint32_t buffer_length = 0;
uint8_t tag_number = 0;
unsigned i;
int diff = 0; /* for memcmp */
@@ -2381,10 +2382,11 @@ static void test_octet_string_buffer(void)
NULL, 0, buffer, buffer_size);
zassert_equal(null_len, 0, NULL);
test_len = bacnet_octet_string_buffer_application_decode(
apdu, apdu_len, test_buffer, sizeof(test_buffer));
apdu, apdu_len, test_buffer, sizeof(test_buffer), &buffer_length);
zassert_equal(
apdu_len, test_len, "apdu_len=%d test_len=%d i=%d", apdu_len,
test_len, i);
zassert_equal(buffer_size, buffer_length, NULL);
diff = memcmp(buffer, test_buffer, buffer_size);
zassert_equal(diff, 0, NULL);
/* context tagged */
@@ -2398,10 +2400,12 @@ static void test_octet_string_buffer(void)
NULL, 0, tag_number, buffer, buffer_size);
zassert_equal(null_len, 0, NULL);
test_len = bacnet_octet_string_buffer_context_decode(
apdu, apdu_len, tag_number, test_buffer, sizeof(test_buffer));
apdu, apdu_len, tag_number, test_buffer, sizeof(test_buffer),
&buffer_length);
zassert_equal(
apdu_len, test_len, "apdu_len=%d test_len=%d i=%d", apdu_len,
test_len, i);
zassert_equal(buffer_size, buffer_length, NULL);
diff = memcmp(buffer, test_buffer, buffer_size);
zassert_equal(diff, 0, NULL);
}
+5
View File
@@ -238,6 +238,11 @@ static void test_BACnetRecipient_ASCII(void)
zassert_true(status, "ascii=%s", ascii);
status = bacnet_recipient_same(&value, &test_value);
zassert_true(status, NULL);
status = bacnet_recipient_address_router_unknown(&value);
zassert_false(status, NULL);
value.type.address.mac_len = 0;
status = bacnet_recipient_address_router_unknown(&value);
zassert_true(status, NULL);
}
#if defined(CONFIG_ZTEST_NEW_API)
@@ -72,6 +72,8 @@ add_executable(${PROJECT_NAME}
# Test and test library files
./stubs.c
./src/main.c
${TST_DIR}/bacnet/basic/object/test/datetime_local.c
${TST_DIR}/bacnet/basic/object/test/device_mock.c
${ZTST_DIR}/ztest_mock.c
${ZTST_DIR}/ztest.c
)
+19 -11
View File
@@ -11,10 +11,19 @@
#include "bacnet/datetime.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/basic/npdu/h_npdu.h"
#include "bacnet/basic/object/nc.h"
uint32_t Device_Object_Instance_Number(void)
uint8_t Send_CEvent_Notify_Address(
uint8_t *pdu,
uint16_t pdu_size,
const BACNET_EVENT_NOTIFICATION_DATA *data,
BACNET_ADDRESS *dest)
{
(void)pdu;
(void)pdu_size;
(void)data;
(void)dest;
return 0;
}
@@ -43,15 +52,14 @@ void Send_WhoIs(int32_t low_limit, int32_t high_limit)
(void)high_limit;
}
bool datetime_local(
BACNET_DATE *bdate,
BACNET_TIME *btime,
int16_t *utc_offset_minutes,
bool *dst_active)
void Send_Who_Is_Router_To_Network(BACNET_ADDRESS *dst, int dnet)
{
(void)bdate;
(void)btime;
(void)utc_offset_minutes;
(void)dst_active;
return true;
(void)dst;
(void)dnet;
}
void npdu_set_i_am_router_to_network_handler(
i_am_router_to_network_function pFunction)
{
(void)pFunction;
}