From 5d45d43da509f794f8bbd8f1ba96488396e53787 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Tue, 10 Jun 2025 09:48:10 -0500 Subject: [PATCH] Fixed BVLC Write Broadcast Distribution Table for protocol revision 17 and later. (#1005) * Fixed BVLC Write Broadcast Distribution Table for protocol revision 17 and later. * Added check for Network Port object bbmd-accept-fd-registrations property in BBMD handler. * Added bvlc_foreign_device_table_decode() function and unit test. * Added BDT and FTD write property to network port for IPv4 --- src/bacnet/basic/bbmd/h_bbmd.c | 45 +++++ src/bacnet/basic/bbmd/h_bbmd.h | 5 + src/bacnet/basic/object/netport.c | 245 ++++++++++++++++++++++++++ src/bacnet/datalink/bvlc.c | 246 +++++++++++++++++++++++---- src/bacnet/datalink/bvlc.h | 20 +++ src/bacnet/datalink/dlenv.c | 38 +++++ test/bacnet/datalink/bvlc/src/main.c | 160 +++++++++++++++-- 7 files changed, 711 insertions(+), 48 deletions(-) diff --git a/src/bacnet/basic/bbmd/h_bbmd.c b/src/bacnet/basic/bbmd/h_bbmd.c index a725bc2b..7c4bfb85 100644 --- a/src/bacnet/basic/bbmd/h_bbmd.c +++ b/src/bacnet/basic/bbmd/h_bbmd.c @@ -50,6 +50,8 @@ static bool BVLC_NAT_Handling = false; static BACNET_IP_ADDRESS Remote_BBMD; /** if we are a foreign device, store the Time-To-Live Seconds here */ static uint16_t Remote_BBMD_TTL_Seconds; +/** Dynamic enable or disable of accepting FD registrations */ +static bool BBMD_Accept_FD_Registrations = 1; #if BBMD_ENABLED || BBMD_CLIENT_ENABLED /* local buffer & length for sending */ static uint8_t BVLC_Buffer[BIP_MPDU_MAX]; @@ -873,6 +875,22 @@ int bvlc_bbmd_enabled_handler( } break; case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE: + /* J.2.2.1 Write-Broadcast-Distribution-Table: Format + ... + Prior to the introduction of the Network Port object + in Protocol_Revision 17, this message was the interoperable + means of updating BDTs. That function is now performed + by writes to the Network Port object + ... + J.4.4.2 Use of the BVLL Write-Broadcast-Distribution-Table Message + Upon receipt of a BVLL Write-Broadcast-Distribution-Table message, + B/IP devices shall always return a BVLC-Result message to the + originating device with a result code of X'0010' indicating that + the Write-Broadcast-Distribution BVLL message is not supported.*/ +#if (BACNET_PROTOCOL_REVISION >= 17) + result_code = BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK; + send_result = true; +#else debug_print_bip("Received Write-BDT", addr); function_len = bvlc_decode_write_broadcast_distribution_table( pdu, pdu_len, &BBMD_Table[0]); @@ -886,6 +904,7 @@ int bvlc_bbmd_enabled_handler( BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK; send_result = true; } +#endif /* not an NPDU */ offset = 0; break; @@ -971,6 +990,13 @@ int bvlc_bbmd_enabled_handler( without the receipt of another BVLL Register-Foreign-Device message from the same foreign device, the FDT entry for this device shall be cleared. */ +#if (BACNET_PROTOCOL_REVISION >= 17) + if (!BBMD_Accept_FD_Registrations) { + result_code = BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK; + send_result = true; + break; + } +#endif function_len = bvlc_decode_register_foreign_device(pdu, pdu_len, &ttl_seconds); if (function_len) { @@ -1203,6 +1229,25 @@ int bvlc_broadcast_handler( return offset; } +/** + * @brief Get the status of the BBMD_Accept_FD_Registrations flag. + * @return true if BBMD_Accept_FD_Registrations is enabled, false otherwise. + */ +bool bvlc_bbmd_accept_fd_registrations(void) +{ + return BBMD_Accept_FD_Registrations; +} + +/** + * @brief Set the status of the BBMD_Accept_FD_Registrations flag. + * @param flag - true to enable accepting foreign device registrations, + * false to disable. + */ +void bvlc_bbmd_accept_fd_registrations_set(bool flag) +{ + BBMD_Accept_FD_Registrations = flag; +} + #if BBMD_CLIENT_ENABLED /** Register as a foreign device with the indicated BBMD. * @param bbmd_addr - IPv4 address of BBMD with which to register diff --git a/src/bacnet/basic/bbmd/h_bbmd.h b/src/bacnet/basic/bbmd/h_bbmd.h index 6d7fd103..c84a7460 100644 --- a/src/bacnet/basic/bbmd/h_bbmd.h +++ b/src/bacnet/basic/bbmd/h_bbmd.h @@ -98,6 +98,11 @@ void bvlc_remote_bbmd_address(BACNET_IP_ADDRESS *address); BACNET_STACK_EXPORT uint16_t bvlc_remote_bbmd_lifetime(void); +BACNET_STACK_EXPORT +bool bvlc_bbmd_accept_fd_registrations(void); +BACNET_STACK_EXPORT +void bvlc_bbmd_accept_fd_registrations_set(bool flag); + /* Local interface to manage BBMD. * The interface user needs to handle mutual exclusion if needed i.e. * BACnet packet is not being handled when the BBMD table is modified. diff --git a/src/bacnet/basic/object/netport.c b/src/bacnet/basic/object/netport.c index f0fea92e..97e2b6e4 100644 --- a/src/bacnet/basic/object/netport.c +++ b/src/bacnet/basic/object/netport.c @@ -1869,6 +1869,110 @@ static int BBMD_Broadcast_Distribution_Table_Encode( return apdu_len; } +/** + * @brief Get the BACnetLIST capacity + * @param object_instance [in] BACnet network port object instance number + * @return capacity of the BACnetList (number of possible entries) or + * zero on error + */ +static size_t +BBMD_Broadcast_Distribution_Table_Capacity(uint32_t object_instance) +{ + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list; + size_t capacity = 0; + unsigned index = 0; + + index = Network_Port_Instance_To_Index(object_instance); + if (index < BACNET_NETWORK_PORTS_MAX) { + if (Object_List[index].Network_Type == PORT_TYPE_BIP) { + bdt_list = Object_List[index].Network.IPv4.BBMD_BD_Table; + capacity = bvlc_broadcast_distribution_table_count(bdt_list); + } + } + + return capacity; +} + +/** + * @brief Decode a BACnetLIST property element to determine the element length + * @param object_instance [in] BACnet network port object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int BBMD_Broadcast_Distribution_Table_Element_Length( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + int len = 0; + unsigned index = 0; + + index = Network_Port_Instance_To_Index(object_instance); + if (index < BACNET_NETWORK_PORTS_MAX) { + if (Object_List[index].Network_Type == PORT_TYPE_BIP) { + len = bvlc_decode_broadcast_distribution_table_entry( + apdu, apdu_size, NULL); + } + } + + return len; +} + +/** + * @brief Write a value to a BACnetLIST property element value + * using a BACnetARRAY write utility function + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param application_data [in] encoded element value + * @param application_data_len [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE BBMD_Broadcast_Distribution_Table_Element_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + uint8_t *application_data, + size_t application_data_len) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY bdt_entry = { 0 }; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list; + uint16_t capacity = 0; + int len; + bool status = false; + unsigned index = 0; + + index = Network_Port_Instance_To_Index(object_instance); + if (index < BACNET_NETWORK_PORTS_MAX) { + if (Object_List[index].Network_Type == PORT_TYPE_BIP) { + bdt_list = Object_List[index].Network.IPv4.BBMD_BD_Table; + capacity = bvlc_broadcast_distribution_table_count(bdt_list); + if (array_index == 0) { + error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + } else if (array_index <= capacity) { + len = bvlc_decode_broadcast_distribution_table_entry( + application_data, application_data_len, &bdt_entry); + if (len > 0) { + status = bvlc_broadcast_distribution_table_entry_insert( + bdt_list, &bdt_entry, array_index); + if (status) { + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } else { + error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + } else { + error_code = ERROR_CODE_ABORT_OTHER; + } + } + + return error_code; +} + /** * For a given object instance-number, sets the BBMD-BD-Table head * property value @@ -1980,6 +2084,108 @@ static int BBMD_Foreign_Device_Table_Encode( return apdu_len; } +/** + * @brief Get the BACnetLIST capacity + * @param object_instance [in] BACnet network port object instance number + * @return capacity of the BACnetList (number of possible entries) or + * zero on error + */ +static size_t BBMD_Foreign_Device_Table_Capacity(uint32_t object_instance) +{ + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list; + size_t capacity = 0; + unsigned index = 0; + + index = Network_Port_Instance_To_Index(object_instance); + if (index < BACNET_NETWORK_PORTS_MAX) { + if (Object_List[index].Network_Type == PORT_TYPE_BIP) { + fdt_list = Object_List[index].Network.IPv4.BBMD_FD_Table; + capacity = bvlc_foreign_device_table_count(fdt_list); + } + } + + return capacity; +} + +/** + * @brief Decode a BACnetLIST property element to determine the element length + * @param object_instance [in] BACnet network port object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int BBMD_Foreign_Device_Table_Element_Length( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + int len = 0; + unsigned index = 0; + + index = Network_Port_Instance_To_Index(object_instance); + if (index < BACNET_NETWORK_PORTS_MAX) { + if (Object_List[index].Network_Type == PORT_TYPE_BIP) { + len = bvlc_decode_foreign_device_table_entry(apdu, apdu_size, NULL); + } + } + + return len; +} + +/** + * @brief Write a value to a BACnetLIST property element value + * using a BACnetARRAY write utility function + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param application_data [in] encoded element value + * @param application_data_len [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE BBMD_Foreign_Device_Table_Element_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + uint8_t *application_data, + size_t application_data_len) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY fdt_entry = { 0 }; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list; + uint16_t capacity = 0; + int len; + bool status = false; + unsigned index = 0; + + index = Network_Port_Instance_To_Index(object_instance); + if (index < BACNET_NETWORK_PORTS_MAX) { + if (Object_List[index].Network_Type == PORT_TYPE_BIP) { + fdt_list = Object_List[index].Network.IPv4.BBMD_FD_Table; + capacity = bvlc_foreign_device_table_count(fdt_list); + if (array_index == 0) { + error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + } else if (array_index <= capacity) { + len = bvlc_decode_foreign_device_table_entry( + application_data, application_data_len, &fdt_entry); + if (len > 0) { + status = bvlc_foreign_device_table_entry_insert( + fdt_list, &fdt_entry, array_index - 1); + if (status) { + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } else { + error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + } else { + error_code = ERROR_CODE_ABORT_OTHER; + } + } + + return error_code; +} + /** * @brief For a given object instance-number, gets the HostNPort * @note depends on Network_Type being set for this object @@ -4100,6 +4306,7 @@ bool Network_Port_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) { bool status = false; /* return value */ int len = 0; + uint32_t capacity; BACNET_APPLICATION_DATA_VALUE value = { 0 }; if (!Network_Port_Valid_Instance(wp_data->object_instance)) { @@ -4181,6 +4388,44 @@ bool Network_Port_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) &wp_data->error_class, &wp_data->error_code); } break; + case PROP_BBMD_ACCEPT_FD_REGISTRATIONS: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); + if (status) { + status = Network_Port_BBMD_Accept_FD_Registrations_Set( + wp_data->object_instance, value.type.Boolean); + if (!status) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_BBMD_BROADCAST_DISTRIBUTION_TABLE: + /* BACnetLIST */ + capacity = BBMD_Broadcast_Distribution_Table_Capacity( + wp_data->object_instance); + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + BBMD_Broadcast_Distribution_Table_Element_Length, + BBMD_Broadcast_Distribution_Table_Element_Write, capacity, + wp_data->application_data, wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } + break; + case PROP_BBMD_FOREIGN_DEVICE_TABLE: + /* BACnetLIST */ + capacity = + BBMD_Foreign_Device_Table_Capacity(wp_data->object_instance); + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + BBMD_Foreign_Device_Table_Element_Length, + BBMD_Foreign_Device_Table_Element_Write, capacity, + wp_data->application_data, wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } + break; default: if (Property_List_Member( wp_data->object_instance, wp_data->object_property)) { diff --git a/src/bacnet/datalink/bvlc.c b/src/bacnet/datalink/bvlc.c index 1e035c4c..28bb9d7e 100644 --- a/src/bacnet/datalink/bvlc.c +++ b/src/bacnet/datalink/bvlc.c @@ -356,6 +356,37 @@ bool bvlc_broadcast_distribution_table_entry_append( return status; } +/** + * @brief Append an entry to the Broadcast-Distribution-Table + * @param bdt_list - first entry in list of BDT entries + * @param bdt_entry - entry to insert into to list of BDT entries + * @param bdt_index - 0..N where N is count-1 + * @return true if the Broadcast-Distribution-Table entry was inserted + */ +bool bvlc_broadcast_distribution_table_entry_insert( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list, + const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry, + uint16_t bdt_index) +{ + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_node = NULL; + uint16_t count = 0; + bool status = false; + + bdt_node = bdt_list; + while (bdt_node) { + if (count == bdt_index) { + status = true; + bvlc_broadcast_distribution_table_entry_copy(bdt_node, bdt_entry); + bdt_node->valid = true; + break; + } + bdt_node = bdt_node->next; + count++; + } + + return status; +} + /** * @brief Set an entry to the Broadcast-Distribution-Table * @param bdt_entry - first element in list of BDT entries @@ -831,17 +862,15 @@ int bvlc_encode_write_broadcast_distribution_table( * @param pdu_len - length of the buffer that needs decoding * @param bdt_list - BDT Entry list * - * @return number of bytes decoded + * @return number of bytes decoded, or 0 for none or error */ int bvlc_decode_write_broadcast_distribution_table( const uint8_t *pdu, - uint16_t pdu_len, + uint16_t pdu_size, BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) { - int bytes_consumed = 0; int len = 0; - uint16_t offset = 0; - uint16_t pdu_bytes = 0; + uint16_t pdu_len = 0; uint16_t bdt_entry_count = 0; BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL; uint16_t list_len = 0; @@ -850,26 +879,40 @@ int bvlc_decode_write_broadcast_distribution_table( bdt_entry_count = bvlc_broadcast_distribution_table_count(bdt_list); list_len = bdt_entry_count * BACNET_IP_BDT_ENTRY_SIZE; /* will the entries fit */ - if (pdu && (pdu_len <= list_len)) { + if (pdu && (pdu_size <= list_len)) { + /* check payload for valid entries */ + while (pdu_len < pdu_size) { + len = bvlc_decode_broadcast_distribution_table_entry( + &pdu[pdu_len], pdu_size - pdu_len, NULL); + if (len > 0) { + pdu_len += len; + } else { + return 0; + } + } + pdu_len = 0; bdt_entry = bdt_list; while (bdt_entry) { - pdu_bytes = pdu_len - offset; - if (pdu_bytes >= BACNET_IP_BDT_ENTRY_SIZE) { + if (pdu_len < pdu_size) { + /* decode valid entries */ len = bvlc_decode_broadcast_distribution_table_entry( - &pdu[offset], pdu_bytes, bdt_entry); + &pdu[pdu_len], pdu_size - pdu_len, bdt_entry); if (len > 0) { + pdu_len += len; bdt_entry->valid = true; + } else { + /* set the available entries as invalid */ + bdt_entry->valid = false; } - offset += len; } else { + /* set the available entries as invalid */ bdt_entry->valid = false; } bdt_entry = bdt_entry->next; } - bytes_consumed = (int)offset; } - return bytes_consumed; + return pdu_len; } /** @@ -1274,6 +1317,92 @@ int bvlc_foreign_device_table_encode( return len; } +/** + * @brief Decode the Foreign_Device-Table for Network Port object + * @param apdu - the APDU buffer + * @param apdu_size - the APDU buffer length + * @param fdt_head - head of a FDT linked list + * @return length of the APDU buffer decoded, or ERROR, REJECT, or ABORT + */ +int bvlc_foreign_device_table_decode( + const uint8_t *apdu, + uint16_t apdu_size, + BACNET_ERROR_CODE *error_code, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_head) +{ + int len = 0, apdu_len = 0; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL; + BACNET_UNSIGNED_INTEGER unsigned_value = 0; + BACNET_OCTET_STRING octet_string = { 0 }; + + /* default reject code */ + if (error_code) { + *error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; + } + /* check for value pointers */ + if ((apdu_size == 0) || (!apdu)) { + return BACNET_STATUS_REJECT; + } + fdt_entry = fdt_head; + while (fdt_entry) { + /* bacnetip-address [0] OCTET STRING */ + len = bacnet_octet_string_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 0, &octet_string); + if (len <= 0) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_REJECT; + } + bvlc_decode_address( + octetstring_value(&octet_string), octetstring_length(&octet_string), + &fdt_entry->dest_address); + apdu_len += len; + /* time-to-live [1] Unsigned16 */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 1, &unsigned_value); + if (len <= 0) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_REJECT; + } + apdu_len += len; + if (unsigned_value <= UINT16_MAX) { + fdt_entry->ttl_seconds = unsigned_value; + } else { + if (error_code) { + *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + /* remaining-time-to-live [2] Unsigned16 */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 2, &unsigned_value); + if (len <= 0) { + if (error_code) { + *error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_REJECT; + } + apdu_len += len; + if (unsigned_value <= UINT16_MAX) { + fdt_entry->ttl_seconds_remaining = unsigned_value; + } else { + if (error_code) { + *error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + /* mark as valid */ + fdt_entry->valid = true; + /* next entry */ + fdt_entry = fdt_entry->next; + } + + return apdu_len; +} + /** * @brief J.2.7 Read-Foreign-Device-Table: encode * @@ -1461,6 +1590,53 @@ bool bvlc_foreign_device_table_entry_add( return status; } +/** + * @brief Append an entry to the Foreign-Device-Table + * @param bdt_list - first entry in list of FDT entries + * @param bdt_entry - entry to insert into to list of FDT entries + * @param bdt_index - 0..N where N is count-1 + * @return true if the entry was inserted + */ +bool bvlc_foreign_device_table_entry_insert( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, + const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry, + uint16_t array_index) +{ + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_node = NULL; + uint16_t count = 0; + bool status = false; + + fdt_node = fdt_list; + while (fdt_node) { + if (count == array_index) { + status = bvlc_foreign_device_table_entry_copy(fdt_node, fdt_entry); + fdt_node->valid = true; + break; + } + fdt_node = fdt_node->next; + count++; + } + + return status; +} + +/** + * @brief Clear all Write-Broadcast-Distribution-Table entries + * @param bdt_list - first element in array BDT entries + */ +void bvlc_foreign_device_table_valid_clear( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list) +{ + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry; + + /* clear the valid entries */ + fdt_entry = fdt_list; + while (fdt_entry) { + fdt_entry->valid = false; + fdt_entry = fdt_entry->next; + } +} + /** * @brief Count the number of valid Foreign-Device-Table entries * @param fdt_list - first element in list of FDT entries @@ -2143,6 +2319,7 @@ bool bvlc_address_mask( dst->address[i] = src->address[i] | ~mask->address[i]; } dst->port = src->port; + status = true; } return status; @@ -2569,37 +2746,46 @@ int bvlc_encode_broadcast_distribution_table_entry( * IP subnet served by the BBMD * * @param pdu - buffer from which to decode the message - * @param pdu_len - length of the buffer that needs decoding - * @param bdt_entry - BDT Entry + * @param pdu_size - length of the buffer that needs decoding + * @param bdt_entry - BDT Entry, or NULL for length only * - * @return number of bytes decoded + * @return number of bytes decoded, or 0 if error */ int bvlc_decode_broadcast_distribution_table_entry( const uint8_t *pdu, - uint16_t pdu_len, + uint16_t pdu_size, BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry) { - int bytes_consumed = 0; + int pdu_len = 0; int len = 0; - int offset = 0; + BACNET_IP_ADDRESS dest_address; + BACNET_IP_BROADCAST_DISTRIBUTION_MASK broadcast_mask; - if (pdu && (pdu_len >= BACNET_IP_BDT_ENTRY_SIZE)) { - if (bdt_entry) { - len = bvlc_decode_address( - &pdu[offset], pdu_len - offset, &bdt_entry->dest_address); - if (len > 0) { - offset += len; - len = bvlc_decode_broadcast_distribution_mask( - &pdu[offset], pdu_len - offset, &bdt_entry->broadcast_mask); + if (pdu) { + len = bvlc_decode_address( + &pdu[pdu_len], pdu_size - pdu_len, &dest_address); + if (len > 0) { + pdu_len += len; + if (bdt_entry) { + bvlc_address_copy(&bdt_entry->dest_address, &dest_address); } - if (len > 0) { - offset += len; - bytes_consumed = offset; + } else { + return 0; + } + len = bvlc_decode_broadcast_distribution_mask( + &pdu[pdu_len], pdu_size - pdu_len, &broadcast_mask); + if (len > 0) { + pdu_len += len; + if (bdt_entry) { + bvlc_broadcast_distribution_mask_copy( + &bdt_entry->broadcast_mask, &broadcast_mask); } + } else { + return 0; } } - return bytes_consumed; + return pdu_len; } /** diff --git a/src/bacnet/datalink/bvlc.h b/src/bacnet/datalink/bvlc.h index edfc7333..fe111ff9 100644 --- a/src/bacnet/datalink/bvlc.h +++ b/src/bacnet/datalink/bvlc.h @@ -272,6 +272,12 @@ bool bvlc_broadcast_distribution_table_entry_append( BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list, const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry); +BACNET_STACK_EXPORT +bool bvlc_broadcast_distribution_table_entry_insert( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list, + const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry, + uint16_t array_index); + BACNET_STACK_EXPORT bool bvlc_broadcast_distribution_table_entry_set( BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry, @@ -380,6 +386,9 @@ void bvlc_foreign_device_table_maintenance_timer( BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, uint16_t seconds); BACNET_STACK_EXPORT +void bvlc_foreign_device_table_valid_clear( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list); +BACNET_STACK_EXPORT uint16_t bvlc_foreign_device_table_valid_count( BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list); @@ -412,6 +421,12 @@ bool bvlc_foreign_device_table_entry_add( const BACNET_IP_ADDRESS *ip_address, uint16_t ttl_seconds); +BACNET_STACK_EXPORT +bool bvlc_foreign_device_table_entry_insert( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, + const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry, + uint16_t array_index); + BACNET_STACK_EXPORT int bvlc_encode_foreign_device_table_entry( uint8_t *pdu, @@ -435,6 +450,11 @@ int bvlc_foreign_device_table_encode( uint8_t *apdu, uint16_t apdu_size, const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_head); +int bvlc_foreign_device_table_decode( + const uint8_t *apdu, + uint16_t apdu_len, + BACNET_ERROR_CODE *error_code, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_head); BACNET_STACK_EXPORT int bvlc_encode_read_foreign_device_table(uint8_t *pdu, uint16_t pdu_size); diff --git a/src/bacnet/datalink/dlenv.c b/src/bacnet/datalink/dlenv.c index e1151f2d..28ac3b10 100644 --- a/src/bacnet/datalink/dlenv.c +++ b/src/bacnet/datalink/dlenv.c @@ -321,6 +321,38 @@ static int bbmd6_register_as_foreign_device(void) return retval; } +/** + * @brief + * + * @param instance + */ +static void bip_network_port_activate_changes(uint32_t instance) +{ +#if defined(BACDL_BIP) + bvlc_bbmd_accept_fd_registrations_set( + Network_Port_BBMD_Accept_FD_Registrations(instance)); +#else + /* if we are not using BIP, then we don't have any changes to discard */ + (void)instance; +#endif +} + +/** + * @brief + * + * @param instance + */ +static void bip_network_port_discard_changes(uint32_t instance) +{ +#if defined(BACDL_BIP) + Network_Port_BBMD_Accept_FD_Registrations_Set( + instance, bvlc_bbmd_accept_fd_registrations()); +#else + /* if we are not using BIP, then we don't have any changes to discard */ + (void)instance; +#endif +} + /** * Datalink network port object settings */ @@ -401,6 +433,8 @@ static void dlenv_network_port_bip_init(uint32_t instance) instance, addr0, addr1, addr2, addr3); Network_Port_Remote_BBMD_BIP_Port_Set(instance, BBMD_Address.port); Network_Port_Remote_BBMD_BIP_Lifetime_Set(instance, BBMD_TTL_Seconds); + Network_Port_BBMD_Accept_FD_Registrations_Set( + instance, bvlc_bbmd_accept_fd_registrations()); #endif /* common NP data */ Network_Port_Reliability_Set(instance, RELIABILITY_NO_FAULT_DETECTED); @@ -411,6 +445,10 @@ static void dlenv_network_port_bip_init(uint32_t instance) /* last thing - clear pending changes - we don't want to set these since they are already set */ Network_Port_Changes_Pending_Set(instance, false); + Network_Port_Changes_Pending_Activate_Callback_Set( + instance, bip_network_port_activate_changes); + Network_Port_Changes_Pending_Discard_Callback_Set( + instance, bip_network_port_discard_changes); } /** diff --git a/test/bacnet/datalink/bvlc/src/main.c b/test/bacnet/datalink/bvlc/src/main.c index 08681ddb..34a03e08 100644 --- a/test/bacnet/datalink/bvlc/src/main.c +++ b/test/bacnet/datalink/bvlc/src/main.c @@ -97,13 +97,14 @@ static void test_BVLC_Result_Code(uint16_t result_code) int len = 0, test_len = 0; len = bvlc_encode_result(pdu, sizeof(pdu), result_code); - zassert_equal(len, 6, NULL); + zassert_equal(len, 6, "result-code=%s", bvlc_result_code_name(result_code)); test_len = test_BVLC_Header(pdu, len, &message_type, &length); zassert_equal(test_len, 4, NULL); zassert_equal(message_type, BVLC_RESULT, NULL); zassert_equal(length, 6, NULL); test_len += bvlc_decode_result(&pdu[4], length - 4, &test_result_code); - zassert_equal(len, test_len, NULL); + zassert_equal( + len, test_len, "result-code=%s", bvlc_result_code_name(result_code)); zassert_equal(result_code, test_result_code, NULL); } @@ -493,6 +494,9 @@ static void test_BVLC_Broadcast_Distribution_Table_Encode(void) uint16_t i = 0; uint16_t count = 0; uint16_t test_count = 0; + uint8_t message_type = 0; + uint16_t message_length = 0; + int test_message_len = 0; bool status = false; BACNET_ERROR_CODE error_code; BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY bdt_list[5] = { 0 }; @@ -540,6 +544,25 @@ static void test_BVLC_Broadcast_Distribution_Table_Encode(void) &bdt_list[i], &test_bdt_list[i]); zassert_false(status, NULL); } + apdu_len = bvlc_encode_read_broadcast_distribution_table_ack( + apdu, sizeof(apdu), &bdt_list[0]); + zassert_not_equal(apdu_len, 0, NULL); + test_message_len = + test_BVLC_Header(apdu, apdu_len, &message_type, &message_length); + zassert_equal(test_message_len, 4, NULL); + zassert_equal(message_type, BVLC_READ_BROADCAST_DIST_TABLE_ACK, NULL); + test_apdu_len = bvlc_decode_read_broadcast_distribution_table_ack( + &apdu[test_message_len], apdu_len, &test_bdt_list[0]); + zassert_equal( + test_message_len + test_apdu_len, apdu_len, + "apdu_len=%u test_apdu_len=%u", apdu_len, test_apdu_len); + count = bvlc_broadcast_distribution_table_count(&test_bdt_list[0]); + zassert_equal(test_count, count, NULL); + for (i = 0; i < count; i++) { + status = bvlc_broadcast_distribution_table_entry_different( + &bdt_list[i], &test_bdt_list[i]); + zassert_false(status, NULL); + } } static void test_BVLC_Write_Broadcast_Distribution_Table_Message( @@ -552,6 +575,8 @@ static void test_BVLC_Write_Broadcast_Distribution_Table_Message( BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *test_bdt_list = NULL; uint16_t i = 0; uint16_t count = 0; + BACNET_IP_ADDRESS dest_address = { { 1, 1, 1, 1 }, 47808 }; + bool status = false; count = bvlc_broadcast_distribution_table_valid_count(bdt_list); test_bdt_list = @@ -573,6 +598,15 @@ static void test_BVLC_Write_Broadcast_Distribution_Table_Message( test_BVLC_Broadcast_Distribution_Table_Entry( &bdt_list[i], &test_bdt_list[i]); } + status = bvlc_broadcast_distribution_table_entry_forward_address( + &dest_address, bdt_list); + zassert_true(status, NULL); + status = bvlc_broadcast_distribution_table_entry_forward_address( + &dest_address, NULL); + zassert_false(status, NULL); + status = + bvlc_broadcast_distribution_table_entry_forward_address(NULL, NULL); + zassert_false(status, NULL); } #if defined(CONFIG_ZTEST_NEW_API) @@ -622,6 +656,10 @@ static void test_BVLC_Write_Broadcast_Distribution_Table(void) zassert_true(status, NULL); } test_BVLC_Write_Broadcast_Distribution_Table_Message(&bdt_list[0]); + /* cleanup */ + bvlc_broadcast_distribution_table_valid_clear(&bdt_list[0]); + test_count = bvlc_broadcast_distribution_table_valid_count(&bdt_list[0]); + zassert_equal(test_count, 0, NULL); } static void test_BVLC_Read_Foreign_Device_Table_Ack_Message( @@ -654,33 +692,28 @@ static void test_BVLC_Read_Foreign_Device_Table_Ack_Message( } } -#if defined(CONFIG_ZTEST_NEW_API) -ZTEST(bvlc_tests, test_BVLC_Read_Foreign_Device_Table_Ack) -#else -static void test_BVLC_Read_Foreign_Device_Table_Ack(void) -#endif +static int test_BVLC_Foreign_Device_Table_Setup( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, int count) { uint16_t i = 0; - uint16_t count = 0; uint16_t test_count = 0; uint16_t test_port_start = 0xBAC1; + uint16_t ttl_seconds = 12345; bool status = false; - BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY fdt_list[5] = { 0 }; BACNET_IP_ADDRESS dest_address = { 0 }; status = bvlc_address_from_ascii(&dest_address, "192.168.0.1"); zassert_true(status, NULL); /* configure a FDT entry */ - count = sizeof(fdt_list) / sizeof(fdt_list[0]); bvlc_foreign_device_table_link_array(fdt_list, count); for (i = 0; i < count; i++) { dest_address.port = test_port_start + i; status = bvlc_foreign_device_table_entry_add( - &fdt_list[0], &dest_address, 12345); + fdt_list, &dest_address, ttl_seconds); zassert_true(status, NULL); /* add again should only update TTL */ status = bvlc_foreign_device_table_entry_add( - &fdt_list[0], &dest_address, 12345); + fdt_list, &dest_address, ttl_seconds); zassert_true(status, NULL); } test_count = bvlc_foreign_device_table_count(fdt_list); @@ -690,15 +723,100 @@ static void test_BVLC_Read_Foreign_Device_Table_Ack(void) } test_count = bvlc_foreign_device_table_valid_count(fdt_list); zassert_equal(test_count, count, NULL); + + return count; +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bvlc_tests, test_BVLC_Read_Foreign_Device_Table_Ack) +#else +static void test_BVLC_Read_Foreign_Device_Table_Ack(void) +#endif +{ + uint16_t count = 0; + uint16_t test_count = 0; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY fdt_list[5] = { 0 }; + + count = sizeof(fdt_list) / sizeof(fdt_list[0]); + test_count = test_BVLC_Foreign_Device_Table_Setup(fdt_list, count); + zassert_equal(test_count, count, NULL); + if (test_count != count) { + printf("size=%u count=%u\n", count, test_count); + } + test_count = bvlc_foreign_device_table_valid_count(fdt_list); + zassert_equal(test_count, count, NULL); test_BVLC_Read_Foreign_Device_Table_Ack_Message(fdt_list); /* cleanup */ - for (i = 0; i < count; i++) { - dest_address.port = test_port_start + i; - status = - bvlc_foreign_device_table_entry_delete(&fdt_list[0], &dest_address); - zassert_true(status, NULL); + bvlc_foreign_device_table_valid_clear(&fdt_list[0]); + test_count = bvlc_foreign_device_table_valid_count(fdt_list); + zassert_equal(test_count, 0, NULL); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(bvlc_tests, test_BVLC_Network_Port_Foreign_Device) +#else +static void test_BVLC_Network_Port_Foreign_Device(void) +#endif +{ + uint8_t apdu[480] = { 0 }; + int apdu_len = 0, test_apdu_len = 0; + BACNET_ERROR_CODE error_code; + uint16_t i = 0; + uint16_t count = 0; + uint16_t test_count = 0; + uint16_t test_ttl_seconds = 0; + int len; + bool status = false; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY fdt_list[5] = { 0 }, + test_fdt_list[5] = { 0 }, + test_entry = { 0 }; + + count = sizeof(fdt_list) / sizeof(fdt_list[0]); + test_count = test_BVLC_Foreign_Device_Table_Setup(fdt_list, count); + zassert_equal(test_count, count, NULL); + if (test_count != count) { + printf("size=%u count=%u\n", count, test_count); } test_count = bvlc_foreign_device_table_valid_count(fdt_list); + zassert_equal(test_count, count, NULL); + /* test the encode/decode pair */ + apdu_len = bvlc_foreign_device_table_encode(apdu, sizeof(apdu), fdt_list); + zassert_not_equal(apdu_len, 0, NULL); + test_count = sizeof(test_fdt_list) / sizeof(test_fdt_list[0]); + bvlc_foreign_device_table_link_array(&test_fdt_list[0], test_count); + test_apdu_len = bvlc_foreign_device_table_decode( + &apdu[0], apdu_len, &error_code, &test_fdt_list[0]); + zassert_equal(test_apdu_len, apdu_len, NULL); + count = bvlc_foreign_device_table_count(&test_fdt_list[0]); + zassert_equal(test_count, count, NULL); + /* test timer */ + test_ttl_seconds = fdt_list[0].ttl_seconds_remaining; + bvlc_foreign_device_table_maintenance_timer(fdt_list, 60); + test_ttl_seconds -= 60; + for (i = 0; i < count; i++) { + zassert_equal( + fdt_list[i].ttl_seconds_remaining, test_ttl_seconds, + "entry[%u].seconds_remaining=%u expected=%u", i, + fdt_list[i].ttl_seconds_remaining, test_ttl_seconds); + } + bvlc_foreign_device_table_maintenance_timer(fdt_list, 60); + test_ttl_seconds -= 60; + for (i = 0; i < count; i++) { + zassert_equal( + fdt_list[i].ttl_seconds_remaining, test_ttl_seconds, + "entry[%u].seconds_remaining=%u expected=%u", i, + fdt_list[i].ttl_seconds_remaining, test_ttl_seconds); + } + status = bvlc_foreign_device_table_entry_copy(&test_entry, &fdt_list[0]); + zassert_true(status, NULL); + status = + bvlc_foreign_device_table_entry_different(&fdt_list[0], &test_entry); + zassert_false(status, NULL); + len = bvlc_encode_read_foreign_device_table(NULL, 0); + zassert_equal(len, 0, NULL); + /* cleanup */ + bvlc_foreign_device_table_valid_clear(&fdt_list[0]); + test_count = bvlc_foreign_device_table_valid_count(fdt_list); zassert_equal(test_count, 0, NULL); } @@ -811,6 +929,9 @@ static void test_BVLC_Address_Get_Set(void) (unsigned)test_octet2, (unsigned)test_octet3); } zassert_false(status, NULL); + bvlc_address_from_network(&dst, 0x7F000000); + status = bvlc_address_different(&dst, &src); + zassert_false(status, NULL); /* BACnet to IPv4 address conversions */ status = bvlc_address_port_from_ascii(&src, "192.168.0.1", "0xBAC0"); zassert_true(status, NULL); @@ -848,6 +969,8 @@ static void test_BVLC_Address_Get_Set(void) zassert_equal(octet1, test_octet1, NULL); zassert_equal(octet2, test_octet2, NULL); zassert_equal(octet3, test_octet3, NULL); + status = bvlc_address_mask(&dst, &src, &mask); + zassert_true(status, NULL); } #if defined(CONFIG_ZTEST_NEW_API) @@ -907,7 +1030,8 @@ void test_main(void) ztest_unit_test(test_BVLC_Secure_BVLL), ztest_unit_test(test_BVLC_Address_Copy), ztest_unit_test(test_BVLC_Address_Get_Set), - ztest_unit_test(test_BVLC_BBMD_Address)); + ztest_unit_test(test_BVLC_BBMD_Address), + ztest_unit_test(test_BVLC_Network_Port_Foreign_Device)); ztest_run_test_suite(bvlc_tests); }