Files
bacnet_stack/src/bacnet/datalink/bvlc.c
T
Steve Karg 5cd453c925 Fixed WriteProperty error code for FD_BBMD_ADDRESS and FD_SUBSCRIPTION_LIFETIME (#925)
* Fixed WriteProperty error code for PROP_FD_BBMD_ADDRESS and PROP_FD_SUBSCRIPTION_LIFETIME properties.  

* Fixed dead-code warning after enabling all datalinks for basic network port object using the property list as the R/W checking for the port type.
2025-03-03 08:04:07 -06:00

2792 lines
83 KiB
C

/**
* @file
* @brief BACnet/IP virtual link control module encode and decode
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2004
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
* @defgroup DLBIP BACnet/IP DataLink Network Layer
* @ingroup DataLink
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/bacdcode.h"
#include "bacnet/bacint.h"
#include "bacnet/hostnport.h"
#include "bacnet/datalink/bvlc.h"
/**
* @brief Encode the BVLC header
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param message_type - BVLL Messages
* @param length - number of bytes for this message type
*
* @return number of bytes encoded
*/
int bvlc_encode_header(
uint8_t *pdu, uint16_t pdu_size, uint8_t message_type, uint16_t length)
{
int bytes_encoded = 0;
if (pdu && (pdu_size >= 2)) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = message_type;
/* The 2-octet BVLC Length field is the length, in octets,
of the entire BVLL message, including the two octets of the
length field itself, most significant octet first. */
encode_unsigned16(&pdu[2], length);
bytes_encoded = 4;
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Result message
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param message_type - BVLL Messages
* @param message_length - number of bytes for this message type
*
* @return number of bytes decoded
*/
int bvlc_decode_header(
const uint8_t *pdu,
uint16_t pdu_len,
uint8_t *message_type,
uint16_t *message_length)
{
int bytes_consumed = 0;
if (pdu && (pdu_len >= 4)) {
if (pdu[0] == BVLL_TYPE_BACNET_IP) {
if (message_type) {
*message_type = pdu[1];
}
if (message_length) {
decode_unsigned16(&pdu[2], message_length);
}
bytes_consumed = 4;
}
}
return bytes_consumed;
}
/**
* @brief J.2.1 BVLC-Result: Encode
*
* This message provides a mechanism to acknowledge the result
* of those BVLL service requests that require an acknowledgment,
* whether successful (ACK) or unsuccessful (NAK).
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param result_code - BVLC result code
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IPv4
* BVLC Function: 1-octet X'00' BVLC-Result
* BVLC Length: 2-octets X'0006' Length of the BVLL message
* Result Code: 2-octets X'0000' Successful completion
* X'0010' Write-Broadcast-Distribution-Table NAK
* X'0020' Read-Broadcast-Distribution-Table NAK
* X'0030' Register-Foreign-Device NAK
* X'0040' Read-Foreign-Device-Table NAK
* X'0050' Delete-Foreign-Device-Table-Entry NAK
* X'0060' Distribute-Broadcast-To-Network NAK
*/
int bvlc_encode_result(uint8_t *pdu, uint16_t pdu_size, uint16_t result_code)
{
int bytes_encoded = 0;
const uint16_t length = 6;
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(pdu, pdu_size, BVLC_RESULT, length);
if (bytes_encoded == 4) {
encode_unsigned16(&pdu[4], result_code);
bytes_encoded = (int)length;
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Result message, after header is decoded
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param result_code - BVLC result code
*
* @return number of bytes decoded
*/
int bvlc_decode_result(
const uint8_t *pdu, uint16_t pdu_len, uint16_t *result_code)
{
int bytes_consumed = 0;
const uint16_t length = 2;
if (pdu && (pdu_len >= length)) {
if (result_code) {
decode_unsigned16(&pdu[0], result_code);
}
bytes_consumed = (int)length;
}
return bytes_consumed;
}
/**
* @brief Copy the BVLC Broadcast Distribution Mask
* @param dst - BVLC Broadcast Distribution Mask that will be filled with src
* @param src - BVLC Broadcast Distribution Mask that will be copied into dst
* @return true if the mask was copied
*/
bool bvlc_broadcast_distribution_mask_copy(
BACNET_IP_BROADCAST_DISTRIBUTION_MASK *dst,
const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *src)
{
bool status = false;
unsigned int i = 0;
if (src && dst) {
for (i = 0; i < IP_ADDRESS_MAX; i++) {
dst->address[i] = src->address[i];
}
status = true;
}
return status;
}
/**
* @brief Compare the BVLC Broadcast Distribution Masks
* @param dst - BVLC Broadcast Distribution Mask that will be compared to src
* @param src - BVLC Broadcast Distribution Mask that will be compared to dst
* @return true if the masks are different
*/
bool bvlc_broadcast_distribution_mask_different(
const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *dst,
const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *src)
{
bool status = false;
unsigned int i = 0;
if (src && dst) {
for (i = 0; i < IP_ADDRESS_MAX; i++) {
if (dst->address[i] != src->address[i]) {
status = true;
}
}
}
return status;
}
/**
* @brief Compare the Broadcast-Distribution-Table entry
* @param dst - Broadcast-Distribution-Table entry that will be compared to src
* @param src - Broadcast-Distribution-Table entry that will be compared to dst
* @return true if the addresses are different
*/
bool bvlc_broadcast_distribution_table_entry_different(
const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *dst,
const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *src)
{
bool status = false;
if (src && dst) {
status = bvlc_address_different(&dst->dest_address, &src->dest_address);
if (!status) {
status = bvlc_broadcast_distribution_mask_different(
&dst->broadcast_mask, &src->broadcast_mask);
}
}
return status;
}
/**
* @brief Copy the Broadcast-Distribution-Table entry
* @param dst - Broadcast-Distribution-Table entry that will be filled with src
* @param src - Broadcast-Distribution-Table entry that will be copied into dst
* @return true if the address was copied
*/
bool bvlc_broadcast_distribution_table_entry_copy(
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *dst,
const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *src)
{
bool status = false;
if (src && dst) {
status = bvlc_address_copy(&dst->dest_address, &src->dest_address);
if (status) {
status = bvlc_broadcast_distribution_mask_copy(
&dst->broadcast_mask, &src->broadcast_mask);
}
}
return status;
}
/**
* @brief Count the number of valid Write-Broadcast-Distribution-Table entries
*
* @param bdt_list - first element in array BDT entries
* @return number of elements of BDT entries that are valid
*/
uint16_t bvlc_broadcast_distribution_table_valid_count(
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list)
{
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL;
uint16_t bdt_entry_count = 0;
/* count the number of entries */
bdt_entry = bdt_list;
while (bdt_entry) {
if (bdt_entry->valid) {
bdt_entry_count++;
}
bdt_entry = bdt_entry->next;
}
return bdt_entry_count;
}
/**
* @brief Clear all Write-Broadcast-Distribution-Table entries
* @param bdt_list - first element in array BDT entries
*/
void bvlc_broadcast_distribution_table_valid_clear(
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list)
{
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry;
/* count the number of entries */
bdt_entry = bdt_list;
while (bdt_entry) {
bdt_entry->valid = false;
bdt_entry = bdt_entry->next;
}
}
/**
* @brief Count the total number of Write-Broadcast-Distribution-Table entries
*
* @param bdt_list - first element in array BDT entries
* @return number of elements of BDT entries
*/
uint16_t bvlc_broadcast_distribution_table_count(
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list)
{
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL;
uint16_t bdt_entry_count = 0;
/* count the number of entries */
bdt_entry = bdt_list;
while (bdt_entry) {
bdt_entry_count++;
bdt_entry = bdt_entry->next;
}
return bdt_entry_count;
}
/**
* @brief Convert Write-Broadcast-Distribution-Table entry array
* into linked list.
*
* @param bdt_list - first element in array BDT entries
* @param bdt_array_size - number of array elements of BDT entries
*/
void bvlc_broadcast_distribution_table_link_array(
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list,
const size_t bdt_array_size)
{
size_t i = 0;
for (i = 0; i < bdt_array_size; i++) {
if (i > 0) {
bdt_list[i - 1].next = &bdt_list[i];
}
bdt_list[i].next = NULL;
}
}
/**
* @brief Append an entry to the Broadcast-Distribution-Table
* @param bdt_list - first entry in list of BDT entries
* @param bdt_new - new entry to append to list of BDT entries
* @return true if the Broadcast-Distribution-Table entry was appended
*/
bool bvlc_broadcast_distribution_table_entry_append(
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list,
const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_new)
{
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL;
bool status = false;
bdt_entry = bdt_list;
while (bdt_entry) {
if (bdt_entry->valid) {
if (!bvlc_broadcast_distribution_table_entry_different(
bdt_entry, bdt_new)) {
status = true;
break;
}
} else {
/* first empty slot! Assume the remaining are empty. */
status = true;
/* Copy new entry to the empty slot */
bvlc_broadcast_distribution_table_entry_copy(bdt_entry, bdt_new);
bdt_entry->valid = true;
break;
}
bdt_entry = bdt_entry->next;
}
return status;
}
/**
* @brief Set an entry to the Broadcast-Distribution-Table
* @param bdt_entry - first element in list of BDT entries
* @param addr - B/IPv4 address to match, along with mask
* @param mask - BVLC Broadcast Distribution Mask to match, along with addr
* @return true if the Broadcast Distribution entry was set
*/
bool bvlc_broadcast_distribution_table_entry_set(
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry,
const BACNET_IP_ADDRESS *addr,
const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask)
{
bool status = false;
if (bdt_entry && addr && mask) {
status = bvlc_address_copy(&bdt_entry->dest_address, addr);
if (status) {
status = bvlc_broadcast_distribution_mask_copy(
&bdt_entry->broadcast_mask, mask);
}
}
return status;
}
/**
* @brief Set the Broadcast-Distribution-Table entry distribution mask
* @param mask - broadcast distribution mask
* @param broadcast_mask - 32-bit broadcast mask in host byte order
* @return true if the broadcast distribution was set
*/
bool bvlc_broadcast_distribution_mask_from_host(
BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask, uint32_t broadcast_mask)
{
bool status = false;
if (mask) {
encode_unsigned32(mask->address, broadcast_mask);
status = true;
}
return status;
}
/**
* @brief Get the Broadcast-Distribution-Table entry distribution mask
* @param broadcast_mask - 32-bit broadcast mask in host byte order
* @param mask - broadcast distribution mask
* @return true if the broadcast distribution was retrieved
*/
bool bvlc_broadcast_distribution_mask_to_host(
uint32_t *broadcast_mask, const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask)
{
bool status = false;
if (broadcast_mask && mask) {
decode_unsigned32(mask->address, broadcast_mask);
status = true;
}
return status;
}
/**
* @brief Set the Broadcast-Distribution-Table entry distribution mask
* @param mask - broadcast distribution mask
* @param addr0 - broadcast distribution mask octet
* @param addr1 - broadcast distribution mask octet
* @param addr2 - broadcast distribution mask octet
* @param addr3 - broadcast distribution mask octet
*/
void bvlc_broadcast_distribution_mask_set(
BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask,
uint8_t addr0,
uint8_t addr1,
uint8_t addr2,
uint8_t addr3)
{
if (mask) {
mask->address[0] = addr0;
mask->address[1] = addr1;
mask->address[2] = addr2;
mask->address[3] = addr3;
}
}
/**
* @brief Get the Broadcast-Distribution-Table entry distribution mask
* @param mask - broadcast distribution mask
* @param addr0 - broadcast distribution mask octet
* @param addr1 - broadcast distribution mask octet
* @param addr2 - broadcast distribution mask octet
* @param addr3 - broadcast distribution mask octet
*/
void bvlc_broadcast_distribution_mask_get(
const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask,
uint8_t *addr0,
uint8_t *addr1,
uint8_t *addr2,
uint8_t *addr3)
{
if (mask) {
if (addr0) {
*addr0 = mask->address[0];
}
if (addr1) {
*addr1 = mask->address[1];
}
if (addr2) {
*addr2 = mask->address[2];
}
if (addr3) {
*addr3 = mask->address[3];
}
}
}
/**
* @brief Set the B/IP address for a Forwarded-NPDU message
*
* The B/IP address to which the Forwarded-NPDU message is
* sent is formed by inverting the broadcast distribution
* mask in the BDT entry and logically ORing it with the
* BBMD address of the same entry.
*
* @param addr - B/IPv4 address to match, along with mask
* @param bdt_entry - The BDT entry containing an address and mask
* @return true if the B/IPv4 address was set
*/
bool bvlc_broadcast_distribution_table_entry_forward_address(
BACNET_IP_ADDRESS *addr,
const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry)
{
bool status = false;
if (bdt_entry && addr) {
status = bvlc_address_mask(
addr, &bdt_entry->dest_address, &bdt_entry->broadcast_mask);
}
return status;
}
/**
* @brief Encode the Broadcast-Distribution-Table for Network Port object
*
* BACnetLIST of BACnetBDTEntry
*
* BACnetBDTEntry ::= SEQUENCE {
* bbmd-address [0] BACnetHostNPort,
* BACnetHostNPort ::= SEQUENCE {
* host [0] BACnetHostAddress,
* BACnetHostAddress ::= CHOICE {
* ip-address [1] OCTET STRING, -- 4 octets for B/IP
* }
* port [1] Unsigned16
* }
* broadcast-mask [1] OCTET STRING
* }
*
* @param apdu - the APDU buffer, or NULL for length
* @param bdt_head - one BACnetBDTEntry
* @return length of the APDU buffer
*/
int bvlc_broadcast_distribution_table_entry_encode(
uint8_t *apdu,
const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry)
{
int len = 0;
int apdu_len = 0;
BACNET_OCTET_STRING octet_string;
if (bdt_entry) {
/* bbmd-address [0] BACnetHostNPort - opening */
len = encode_opening_tag(apdu, 0);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* host [0] BACnetHostAddress - opening */
len = encode_opening_tag(apdu, 0);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* CHOICE - ip-address [1] OCTET STRING */
octetstring_init(
&octet_string, &bdt_entry->dest_address.address[0], IP_ADDRESS_MAX);
len = encode_context_octet_string(apdu, 1, &octet_string);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* host [0] BACnetHostAddress - closing */
len = encode_closing_tag(apdu, 0);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* port [1] Unsigned16 */
len = encode_context_unsigned(apdu, 1, bdt_entry->dest_address.port);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* bbmd-address [0] BACnetHostNPort - closing */
len = encode_closing_tag(apdu, 0);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* broadcast-mask [1] OCTET STRING */
octetstring_init(
&octet_string, &bdt_entry->broadcast_mask.address[0],
IP_ADDRESS_MAX);
len = encode_context_octet_string(apdu, 1, &octet_string);
apdu_len += len;
}
return apdu_len;
}
/**
* @brief Encode the Broadcast-Distribution-Table for Network Port object
*
* BACnetLIST of BACnetBDTEntry
*
* @param apdu - the APDU buffer
* @param apdu_size - the APDU buffer size
* @param bdt_head - head of the BDT linked list
* @return length of the APDU buffer
*/
int bvlc_broadcast_distribution_table_list_encode(
uint8_t *apdu, const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_head)
{
int len = 0;
int apdu_len = 0;
const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry;
bdt_entry = bdt_head;
while (bdt_entry) {
if (bdt_entry->valid) {
len =
bvlc_broadcast_distribution_table_entry_encode(apdu, bdt_entry);
apdu_len += len;
if (apdu) {
apdu += len;
}
}
/* next entry */
bdt_entry = bdt_entry->next;
}
return apdu_len;
}
/**
* @brief Encode the Broadcast-Distribution-Table for Network Port object
* @param apdu - the APDU buffer
* @param apdu_size - the APDU buffer size
* @param bdt_head - head of the BDT linked list
* @return length of the APDU buffer, or BACNET_STATUS_ERROR on error
*/
int bvlc_broadcast_distribution_table_encode(
uint8_t *apdu,
uint16_t apdu_size,
const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_head)
{
int len = 0;
len = bvlc_broadcast_distribution_table_list_encode(NULL, bdt_head);
if (len <= apdu_size) {
len = bvlc_broadcast_distribution_table_list_encode(apdu, bdt_head);
} else {
len = BACNET_STATUS_ERROR;
}
return len;
}
/**
* @brief Decode the Broadcast-Distribution-Table for Network Port object
* @param apdu - the APDU buffer
* @param apdu_len - the APDU buffer length
* @param bdt_head - head of a BDT linked list
* @return length of the APDU buffer decoded, or ERROR, REJECT, or ABORT
*/
int bvlc_broadcast_distribution_table_decode(
const uint8_t *apdu,
uint16_t apdu_len,
BACNET_ERROR_CODE *error_code,
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_head)
{
int len = 0;
BACNET_OCTET_STRING octet_string = { 0 };
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL;
uint8_t tag_number = 0;
uint32_t len_value_type = 0;
BACNET_UNSIGNED_INTEGER unsigned_value = 0;
/* default reject code */
if (error_code) {
*error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
}
/* check for value pointers */
if ((apdu_len == 0) || (!apdu)) {
return BACNET_STATUS_REJECT;
}
bdt_entry = bdt_head;
while (bdt_entry) {
/* bbmd-address [0] BACnetHostNPort - opening */
if (!decode_is_opening_tag_number(&apdu[len++], 0)) {
if (error_code) {
*error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
return BACNET_STATUS_REJECT;
}
if (len > apdu_len) {
return BACNET_STATUS_REJECT;
}
/* host [0] BACnetHostAddress - opening */
if (!decode_is_opening_tag_number(&apdu[len++], 0)) {
if (error_code) {
*error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
return BACNET_STATUS_REJECT;
}
if (len > apdu_len) {
return BACNET_STATUS_REJECT;
}
/* CHOICE - ip-address [1] OCTET STRING */
len += decode_tag_number_and_value(
&apdu[len], &tag_number, &len_value_type);
if (tag_number != 1) {
if (error_code) {
*error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
return BACNET_STATUS_REJECT;
}
len += decode_octet_string(&apdu[len], len_value_type, &octet_string);
if (len > apdu_len) {
return BACNET_STATUS_REJECT;
}
(void)octetstring_copy_value(
&bdt_entry->dest_address.address[0], IP_ADDRESS_MAX, &octet_string);
/* host [0] BACnetHostAddress - closing */
if (!decode_is_closing_tag_number(&apdu[len++], 0)) {
if (error_code) {
*error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
return BACNET_STATUS_REJECT;
}
if (len > apdu_len) {
return BACNET_STATUS_REJECT;
}
/* port [1] Unsigned16 */
len += decode_tag_number_and_value(
&apdu[len], &tag_number, &len_value_type);
if (tag_number != 1) {
if (error_code) {
*error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
return BACNET_STATUS_REJECT;
}
len += decode_unsigned(&apdu[len], len_value_type, &unsigned_value);
if (len > apdu_len) {
return BACNET_STATUS_REJECT;
}
if (unsigned_value <= UINT16_MAX) {
bdt_entry->dest_address.port = unsigned_value;
} else {
if (error_code) {
*error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE;
}
return BACNET_STATUS_REJECT;
}
/* bbmd-address [0] BACnetHostNPort - closing */
if (!decode_is_closing_tag_number(&apdu[len++], 0)) {
if (error_code) {
*error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
return BACNET_STATUS_REJECT;
}
if (len > apdu_len) {
return BACNET_STATUS_REJECT;
}
/* broadcast-mask [1] OCTET STRING */
len += decode_tag_number_and_value(
&apdu[len], &tag_number, &len_value_type);
if (tag_number != 1) {
if (error_code) {
*error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
return BACNET_STATUS_REJECT;
}
if (len > apdu_len) {
return BACNET_STATUS_REJECT;
}
len += decode_octet_string(&apdu[len], len_value_type, &octet_string);
if (len > apdu_len) {
return BACNET_STATUS_REJECT;
}
(void)octetstring_copy_value(
&bdt_entry->broadcast_mask.address[0], IP_ADDRESS_MAX,
&octet_string);
bdt_entry->valid = true;
/* next entry */
bdt_entry = bdt_entry->next;
}
return apdu_len;
}
/**
* @brief J.2.2 Write-Broadcast-Distribution-Table: encode
*
* This message provides a mechanism for initializing or updating a
* Broadcast Distribution Table (BDT) in a BACnet Broadcast Management
* Device (BBMD).
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param bdt_list - list of BDT entries
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'01' Write-Broadcast-Distribution-Table
* BVLC Length: 2-octets L Length L, in octets, of the BVLL message
* List of BDT Entries: N*10-octets
*/
int bvlc_encode_write_broadcast_distribution_table(
uint8_t *pdu,
uint16_t pdu_size,
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list)
{
int bytes_encoded = 0;
int len = 0;
uint16_t offset = 0;
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL;
uint16_t bdt_entry_count = 0;
uint16_t length = 0;
/* count the number of entries */
bdt_entry_count = bvlc_broadcast_distribution_table_valid_count(bdt_list);
length = 4 + (bdt_entry_count * BACNET_IP_BDT_ENTRY_SIZE);
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE, length);
if (bytes_encoded == 4) {
offset = 4;
/* encode the entries */
bdt_entry = bdt_list;
while (bdt_entry) {
if (bdt_entry->valid) {
len = bvlc_encode_broadcast_distribution_table_entry(
&pdu[offset], pdu_size - offset, bdt_entry);
offset += len;
}
bdt_entry = bdt_entry->next;
}
bytes_encoded = offset;
}
}
return bytes_encoded;
}
/**
* @brief Decode the Write-Broadcast-Distribution-Table
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param bdt_list - BDT Entry list
*
* @return number of bytes decoded
*/
int bvlc_decode_write_broadcast_distribution_table(
const uint8_t *pdu,
uint16_t pdu_len,
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 bdt_entry_count = 0;
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL;
uint16_t list_len = 0;
/* count the number of available entries */
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)) {
bdt_entry = bdt_list;
while (bdt_entry) {
pdu_bytes = pdu_len - offset;
if (pdu_bytes >= BACNET_IP_BDT_ENTRY_SIZE) {
len = bvlc_decode_broadcast_distribution_table_entry(
&pdu[offset], pdu_bytes, bdt_entry);
if (len > 0) {
bdt_entry->valid = true;
}
offset += len;
} else {
bdt_entry->valid = false;
}
bdt_entry = bdt_entry->next;
}
bytes_consumed = (int)offset;
}
return bytes_consumed;
}
/**
* @brief J.2.3 Read-Broadcast-Distribution-Table: encode
*
* The message provides a mechanism for retrieving the contents of a BBMD's
* BDT.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param bdt_list - list of BDT entries
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'02' Read-Broadcast-Distribution-Table
* BVLC Length: 2-octets X'0004' Length, in octets, of the BVLL message
*/
int bvlc_encode_read_broadcast_distribution_table(
uint8_t *pdu, uint16_t pdu_size)
{
int bytes_encoded = 0;
uint16_t length = 1 + 1 + 2;
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_READ_BROADCAST_DIST_TABLE, length);
}
return bytes_encoded;
}
/**
* @brief J.2.4 Read-Broadcast-Distribution-Table-Ack: encode
*
* The message provides a mechanism for retrieving the contents of a BBMD's
* BDT.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param bdt_list - list of BDT entries
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'02' Read-Broadcast-Distribution-Table
* BVLC Length: 2-octets L length, in octets, of the BVLL message
* List of BDT Entries: N*10-octets
*/
int bvlc_encode_read_broadcast_distribution_table_ack(
uint8_t *pdu,
uint16_t pdu_size,
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list)
{
int bytes_encoded = 0;
int len = 0;
uint16_t offset = 0;
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL;
uint16_t bdt_entry_count = 0;
uint16_t length = 0;
/* count the number of entries */
bdt_entry_count = bvlc_broadcast_distribution_table_valid_count(bdt_list);
length = 4 + (bdt_entry_count * BACNET_IP_BDT_ENTRY_SIZE);
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_READ_BROADCAST_DIST_TABLE_ACK, length);
if (bytes_encoded == 4) {
offset = 4;
/* encode the entries */
bdt_entry = bdt_list;
while (bdt_entry) {
if (bdt_entry->valid) {
len = bvlc_encode_broadcast_distribution_table_entry(
&pdu[offset], pdu_size - offset, bdt_entry);
offset += len;
}
bdt_entry = bdt_entry->next;
}
bytes_encoded = (int)length;
}
}
return bytes_encoded;
}
/**
* @brief Decode the Read-Broadcast-Distribution-Table-Ack
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param bdt_list - list of BDT Entries to be overwritten
*
* @return number of bytes decoded
*/
int bvlc_decode_read_broadcast_distribution_table_ack(
const uint8_t *pdu,
uint16_t pdu_len,
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list)
{
int bytes_consumed = 0;
int len = 0;
uint16_t offset = 0;
uint16_t pdu_bytes = 0;
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL;
if (pdu && (pdu_len >= BACNET_IP_BDT_ENTRY_SIZE)) {
bdt_entry = bdt_list;
while (bdt_entry) {
pdu_bytes = pdu_len - offset;
if (pdu_bytes >= BACNET_IP_BDT_ENTRY_SIZE) {
len = bvlc_decode_broadcast_distribution_table_entry(
&pdu[offset], pdu_bytes, bdt_entry);
if (len > 0) {
bdt_entry->valid = true;
}
offset += len;
} else {
bdt_entry->valid = false;
}
bdt_entry = bdt_entry->next;
}
bytes_consumed = (int)offset;
}
return bytes_consumed;
}
/**
* @brief J.2.5 Forwarded-NPDU: Encode
*
* This BVLL message is used in broadcast messages from a BBMD
* as well as in messages forwarded to registered foreign devices.
* It contains the source address of the original node, or
* if NAT is being used, the address with which the original node
* is accessed, as well as the original BACnet NPDU
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param bip_address - Original-Source-B/IPv4-Address
* @param npdu - BACnet NPDU from Originating Device buffer
* @param npdu_len - size of the BACnet NPDU buffer
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'04' Forwarded-NPDU
* BVLC Length: 2-octets L Length of the BVLL message
* B/IP Address of Originating Device: 6-octets
* BACnet NPDU from Originating Device: N-octets (N=L-10)
*/
int bvlc_encode_forwarded_npdu(
uint8_t *pdu,
uint16_t pdu_size,
const BACNET_IP_ADDRESS *bip_address,
const uint8_t *npdu,
uint16_t npdu_len)
{
int bytes_encoded = 0;
uint16_t length = 1 + 1 + 2 + BIP_ADDRESS_MAX;
uint16_t i = 0;
uint16_t offset = 0;
length += npdu_len;
if (pdu && (pdu_size >= length)) {
bytes_encoded =
bvlc_encode_header(pdu, pdu_size, BVLC_FORWARDED_NPDU, length);
if (bytes_encoded == 4) {
offset = 4;
bvlc_encode_address(&pdu[offset], pdu_size - offset, bip_address);
offset += BIP_ADDRESS_MAX;
if (npdu && (length > 0)) {
for (i = 0; i < npdu_len; i++) {
pdu[offset + i] = npdu[i];
}
}
bytes_encoded = (int)length;
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Forwarded-NPDU message, after decoded header
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param bip_address - Original-Source-B/IPv4-Address
* @param npdu - BACnet NPDU buffer
* @param npdu_size - size of the buffer for the decoded BACnet NPDU
* @param npdu_len - decoded length of the BACnet NPDU buffer
*
* @return number of bytes decoded
*/
int bvlc_decode_forwarded_npdu(
const uint8_t *pdu,
uint16_t pdu_len,
BACNET_IP_ADDRESS *bip_address,
uint8_t *npdu,
uint16_t npdu_size,
uint16_t *npdu_len)
{
int bytes_consumed = 0;
uint16_t length = 0;
uint16_t i = 0;
uint16_t offset = 0;
if (pdu && (pdu_len >= BIP_ADDRESS_MAX)) {
if (bip_address) {
bvlc_decode_address(&pdu[offset], pdu_len - offset, bip_address);
}
offset += BIP_ADDRESS_MAX;
length = pdu_len - BIP_ADDRESS_MAX;
if (npdu && (length <= npdu_size)) {
for (i = 0; i < length; i++) {
npdu[i] = pdu[offset + i];
}
}
if (npdu_len) {
*npdu_len = length;
}
bytes_consumed = (int)pdu_len;
}
return bytes_consumed;
}
/**
* @brief J.2.6 Register-Foreign-Device: encode
*
* This message allows a foreign device, as defined in Clause J.5.1,
* to register with a BBMD for the purpose of receiving broadcast messages.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param ttl_seconds - Time-to-Live T, in seconds
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'05' Register-Foreign-Device
* BVLC Length: 2-octets X'0006' Length of the BVLL message
* Time-to-Live: 2-octets T Time-to-Live T, in seconds
*/
int bvlc_encode_register_foreign_device(
uint8_t *pdu, uint16_t pdu_size, uint16_t ttl_seconds)
{
int bytes_encoded = 0;
const uint16_t length = 6;
uint16_t offset = 0;
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_REGISTER_FOREIGN_DEVICE, length);
if (bytes_encoded == 4) {
offset = 4;
encode_unsigned16(&pdu[offset], ttl_seconds);
bytes_encoded = (int)length;
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Register-Foreign-Device message, after decoded header
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param ttl_seconds - Time-to-Live T, in seconds
*
* @return number of bytes decoded
*/
int bvlc_decode_register_foreign_device(
const uint8_t *pdu, uint16_t pdu_len, uint16_t *ttl_seconds)
{
int bytes_consumed = 0;
const uint16_t length = 2;
uint16_t offset = 0;
if (pdu && (pdu_len >= length)) {
if (ttl_seconds) {
decode_unsigned16(&pdu[offset], ttl_seconds);
}
bytes_consumed = (int)length;
}
return bytes_consumed;
}
/**
* @brief Encode the Foreign_Device-Table for Network Port object
*
* BACnetLIST of BACnetFDTEntry
*
* BACnetFDTEntry ::= SEQUENCE {
* bacnetip-address [0] OCTET STRING, -- 6-octet B/IP registrant address
* time-to-live [1] Unsigned16, -- time to live in seconds
* remaining-time-to-live [2] Unsigned16 -- remaining time in seconds
* }
*
* @param apdu - the APDU buffer, or NULL for length
* @param fdt_head - head of the BDT linked list
* @return length of the APDU buffer
*/
int bvlc_foreign_device_table_entry_encode(
uint8_t *apdu, const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry)
{
int len = 0;
int apdu_len = 0;
BACNET_OCTET_STRING octet_string = { 0 };
if (fdt_entry && fdt_entry->valid) {
/* bacnetip-address [0] OCTET STRING */
len = bvlc_encode_address(
octetstring_value(&octet_string),
octetstring_capacity(&octet_string), &fdt_entry->dest_address);
octetstring_truncate(&octet_string, len);
len = encode_context_octet_string(apdu, 0, &octet_string);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* time-to-live [1] Unsigned16 */
len = encode_context_unsigned(apdu, 1, fdt_entry->ttl_seconds);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* remaining-time-to-live [2] Unsigned16 */
len =
encode_context_unsigned(apdu, 2, fdt_entry->ttl_seconds_remaining);
apdu_len += len;
}
return apdu_len;
}
/**
* @brief Encode the Foreign_Device-Table for Network Port object
*
* BACnetLIST of BACnetFDTEntry
*
* @param apdu - the APDU buffer, or NULL for length
* @param fdt_head - head of the BDT linked list
* @return length of the APDU buffer
*/
int bvlc_foreign_device_table_list_encode(
uint8_t *apdu, const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_head)
{
int len = 0;
int apdu_len = 0;
const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry;
fdt_entry = fdt_head;
while (fdt_entry) {
if (fdt_entry->valid) {
len = bvlc_foreign_device_table_entry_encode(apdu, fdt_entry);
apdu_len += len;
if (apdu) {
apdu += len;
}
}
/* next entry */
fdt_entry = fdt_entry->next;
}
return apdu_len;
}
/**
* @brief Encode the Foreign_Device-Table for Network Port object
*
* BACnetLIST of BACnetFDTEntry
*
* BACnetFDTEntry ::= SEQUENCE {
* bacnetip-address [0] OCTET STRING, -- 6-octet B/IP registrant address
* time-to-live [1] Unsigned16, -- time to live in seconds
* remaining-time-to-live [2] Unsigned16 -- remaining time in seconds
* }
*
* @param apdu - the APDU buffer
* @param apdu_size - the APDU buffer size
* @param fdt_head - head of the BDT linked list
* @return length of the APDU buffer
*/
int bvlc_foreign_device_table_encode(
uint8_t *apdu,
uint16_t apdu_size,
const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_head)
{
int len = 0;
len = bvlc_foreign_device_table_list_encode(NULL, fdt_head);
if (len <= apdu_size) {
len = bvlc_foreign_device_table_list_encode(apdu, fdt_head);
} else {
len = BACNET_STATUS_ERROR;
}
return len;
}
/**
* @brief J.2.7 Read-Foreign-Device-Table: encode
*
* The message provides a mechanism for retrieving the contents of a BBMD's
* Foreign-Device-Table.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param bdt_list - list of FDT entries
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'06' Read-Foreign-Device-Table
* BVLC Length: 2-octets X'0004' Length, in octets, of the BVLL message
*/
int bvlc_encode_read_foreign_device_table(uint8_t *pdu, uint16_t pdu_size)
{
int bytes_encoded = 0;
uint16_t length = 1 + 1 + 2;
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_READ_FOREIGN_DEVICE_TABLE, length);
}
return bytes_encoded;
}
/**
* @brief Compare the Foreign Device Table entry
* @param entry1 - Foreign Device Table entry that will be compared to entry2
* @param entry2 - Foreign Device Table entry that will be compared to entry1
* @return true if the entries are different
*/
bool bvlc_foreign_device_table_entry_different(
const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *entry1,
const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *entry2)
{
if (entry1 && entry2) {
if (bvlc_address_different(
&entry1->dest_address, &entry2->dest_address)) {
return true;
}
}
return false;
}
/**
* @brief Copy the Foreign Device Table entry
* @param entry1 - Foreign Device Table entry that will be filled with entry2
* @param entry2 - Foreign Device Table entry that will be copied into entry1
* @return true if the Foreign Device Table entry was copied
*/
bool bvlc_foreign_device_table_entry_copy(
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *entry1,
const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *entry2)
{
bool status = false;
if (entry1 && entry2) {
entry1->ttl_seconds = entry2->ttl_seconds;
entry1->ttl_seconds_remaining = entry2->ttl_seconds_remaining;
status =
bvlc_address_copy(&entry1->dest_address, &entry2->dest_address);
}
return status;
}
/**
* @brief Foreign-Device-Table timer maintenance
* @param fdt_list - first element in list of FDT entries
* @param seconds - number of elapsed seconds since the last call
*/
void bvlc_foreign_device_table_maintenance_timer(
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, uint16_t seconds)
{
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL;
fdt_entry = fdt_list;
while (fdt_entry) {
if (fdt_entry->valid) {
if (fdt_entry->ttl_seconds_remaining) {
if (fdt_entry->ttl_seconds_remaining < seconds) {
fdt_entry->ttl_seconds_remaining = 0;
} else {
fdt_entry->ttl_seconds_remaining -= seconds;
}
if (fdt_entry->ttl_seconds_remaining == 0) {
fdt_entry->valid = false;
}
}
}
fdt_entry = fdt_entry->next;
}
}
/**
* @brief Delete an entry in the Foreign-Device-Table
* @param fdt_list - first element in list of FDT entries
* @param addr - B/IPv4 address to be deleted
* @return true if the Foreign Device entry was found and removed.
*/
bool bvlc_foreign_device_table_entry_delete(
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list,
const BACNET_IP_ADDRESS *addr)
{
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL;
bool status = false;
fdt_entry = fdt_list;
while (fdt_entry) {
if (fdt_entry->valid) {
if (!bvlc_address_different(&fdt_entry->dest_address, addr)) {
status = true;
fdt_entry->valid = false;
fdt_entry->ttl_seconds_remaining = 0;
break;
}
}
fdt_entry = fdt_entry->next;
}
return status;
}
/**
* @brief Add an entry to the Foreign-Device-Table
* @param fdt_list - first element in list of FDT entries
* @param addr - B/IPv4 address to be added
* @param ttl_seconds - Time-to-Live T, in seconds
* @return true if the Foreign Device entry was added or already exists
*/
bool bvlc_foreign_device_table_entry_add(
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list,
const BACNET_IP_ADDRESS *addr,
uint16_t ttl_seconds)
{
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL;
bool status = false;
fdt_entry = fdt_list;
while (fdt_entry) {
if (fdt_entry->valid) {
/* am I here already? If so, update my time to live... */
if (!bvlc_address_different(&fdt_entry->dest_address, addr)) {
status = true;
fdt_entry->ttl_seconds = ttl_seconds;
/* Upon receipt of a BVLL Register-Foreign-Device message,
a BBMD shall start a timer with a value equal to the
Time-to-Live parameter supplied plus a fixed grace
period of 30 seconds. */
if (ttl_seconds < (UINT16_MAX - 30)) {
fdt_entry->ttl_seconds_remaining = ttl_seconds + 30;
} else {
fdt_entry->ttl_seconds_remaining = UINT16_MAX;
}
break;
}
}
fdt_entry = fdt_entry->next;
}
if (!status) {
fdt_entry = fdt_list;
while (fdt_entry) {
if (!fdt_entry->valid) {
/* add to the first empty entry */
bvlc_address_copy(&fdt_entry->dest_address, addr);
fdt_entry->ttl_seconds = ttl_seconds;
if (ttl_seconds < (UINT16_MAX - 30)) {
fdt_entry->ttl_seconds_remaining = ttl_seconds + 30;
} else {
fdt_entry->ttl_seconds_remaining = UINT16_MAX;
}
fdt_entry->valid = true;
status = true;
break;
}
fdt_entry = fdt_entry->next;
}
}
return status;
}
/**
* @brief Count the number of valid Foreign-Device-Table entries
* @param fdt_list - first element in list of FDT entries
* @return number of elements of FDT entries that are valid
*/
uint16_t bvlc_foreign_device_table_valid_count(
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list)
{
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL;
uint16_t entry_count = 0;
/* count the number of entries */
fdt_entry = fdt_list;
while (fdt_entry) {
if (fdt_entry->valid) {
entry_count++;
}
fdt_entry = fdt_entry->next;
}
return entry_count;
}
/**
* @brief Count the total number of Foreign-Device-Table entries
*
* @param bdt_list - first element in array BDT entries
* @return number of elements of BDT entries
*/
uint16_t
bvlc_foreign_device_table_count(BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list)
{
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL;
uint16_t entry_count = 0;
/* count the number of entries */
fdt_entry = fdt_list;
while (fdt_entry) {
entry_count++;
fdt_entry = fdt_entry->next;
}
return entry_count;
}
/**
* @brief Convert Foreign-Device-Table entry array into linked list.
*
* @param fdt_list - first element in array FDT entries
* @param array_size - number of array elements of FDT entries
*/
void bvlc_foreign_device_table_link_array(
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, const size_t array_size)
{
size_t i = 0;
for (i = 0; i < array_size; i++) {
if (i > 0) {
fdt_list[i - 1].next = &fdt_list[i];
}
fdt_list[i].next = NULL;
}
}
/**
* @brief J.2.8 Read-Foreign-Device-Table-Ack: encode
*
* This message returns the current contents of a BBMD's FDT to the requester.
* An empty FDT shall be signified by a list of length zero.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param fdt_list - list of FDT entries
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'07' Read-Foreign-Device-Table-Ack
* BVLC Length: 2-octets L length, in octets, of the BVLL message
* List of FDT Entries: N*10-octets
*
* N indicates the number of entries in the FDT whose contents are being
* returned. Each returned entry consists of the 6-octet B/IP address of
* the registrant; the 2-octet Time-to-Live value supplied at the time of
* registration; and a 2-octet value representing the number of seconds
* remaining before the BBMD will purge the registrant's FDT entry if no
* re-registration occurs. The time remaining includes the 30-second grace
* period as defined in Clause J.5.2.3.
*/
int bvlc_encode_read_foreign_device_table_ack(
uint8_t *pdu,
uint16_t pdu_size,
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list)
{
int bytes_encoded = 0;
int len = 0;
uint16_t offset = 0;
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL;
uint16_t entry_count = 0;
uint16_t length = 0;
/* count the number of entries */
entry_count = bvlc_foreign_device_table_valid_count(fdt_list);
length = 4 + (entry_count * BACNET_IP_FDT_ENTRY_SIZE);
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_READ_FOREIGN_DEVICE_TABLE_ACK, length);
if (bytes_encoded == 4) {
offset = 4;
/* encode the entries */
fdt_entry = fdt_list;
while (fdt_entry) {
if (fdt_entry->valid) {
len = bvlc_encode_foreign_device_table_entry(
&pdu[offset], pdu_size - offset, fdt_entry);
offset += len;
}
fdt_entry = fdt_entry->next;
}
bytes_encoded = (int)length;
}
}
return bytes_encoded;
}
/**
* @brief Decode Read-Foreign-Device-Table-Ack
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param fdt_list - list of FDT entries
*
* @return number of bytes decoded
*/
int bvlc_decode_read_foreign_device_table_ack(
const uint8_t *pdu,
uint16_t pdu_len,
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list)
{
int bytes_consumed = 0;
int len = 0;
uint16_t offset = 0;
uint16_t pdu_bytes = 0;
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL;
if (pdu && (pdu_len >= BACNET_IP_FDT_ENTRY_SIZE)) {
fdt_entry = fdt_list;
while (fdt_entry) {
pdu_bytes = pdu_len - offset;
if (pdu_bytes >= BACNET_IP_BDT_ENTRY_SIZE) {
len = bvlc_decode_foreign_device_table_entry(
&pdu[offset], pdu_bytes, fdt_entry);
if (len > 0) {
fdt_entry->valid = true;
}
offset += len;
} else {
fdt_entry->valid = false;
}
fdt_entry = fdt_entry->next;
}
bytes_consumed = (int)offset;
}
return bytes_consumed;
}
/**
* @brief J.2.9 Delete-Foreign-Device-Table-Entry: encode
*
* This message is used to delete an entry from the Foreign-Device-Table.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param ip_address - FDT Entry IP address
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'08' Delete-Foreign-Device
* BVLC Length: 2-octets X'000A' Length of the BVLL message
* FDT Entry: 6-octets The FDT entry is the B/IP address
* of the table entry to be deleted.
*/
int bvlc_encode_delete_foreign_device(
uint8_t *pdu, uint16_t pdu_size, const BACNET_IP_ADDRESS *ip_address)
{
int bytes_encoded = 0;
const uint16_t length = 0x000A;
uint16_t offset = 0;
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY, length);
if (bytes_encoded == 4) {
offset = 4;
if (ip_address) {
bytes_encoded += bvlc_encode_address(
&pdu[offset], pdu_size - offset, ip_address);
}
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Delete-Foreign-Device message
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param ip_address - FDT Entry IP address
*
* @return number of bytes decoded
*/
int bvlc_decode_delete_foreign_device(
const uint8_t *pdu, uint16_t pdu_len, BACNET_IP_ADDRESS *ip_address)
{
int bytes_consumed = 0;
const uint16_t length = BIP_ADDRESS_MAX;
if (pdu && (pdu_len >= length)) {
if (ip_address) {
bvlc_decode_address(&pdu[0], pdu_len, ip_address);
}
bytes_consumed = (int)length;
}
return bytes_consumed;
}
/**
* @brief J.2.10 Distribute-Broadcast-To-Network: encode
*
* This message provides a mechanism whereby a foreign device may cause
* a BBMD to broadcast a message on all IP subnets in the BBMD's BDT.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param npdu - BACnet NPDU from Originating Device buffer
* @param npdu_len - size of the BACnet NPDU buffer
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'09' Original-Unicast-NPDU
* BVLC Length: 2-octets L Length of the BVLL message
* BACnet NPDU from Originating Device: Variable length
*/
int bvlc_encode_distribute_broadcast_to_network(
uint8_t *pdu, uint16_t pdu_size, const uint8_t *npdu, uint16_t npdu_len)
{
int bytes_encoded = 0;
uint16_t length = 1 + 1 + 2;
uint16_t i = 0;
length += npdu_len;
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK, length);
if (bytes_encoded == 4) {
if (npdu && (npdu_len > 0)) {
for (i = 0; i < npdu_len; i++) {
pdu[4 + i] = npdu[i];
}
}
bytes_encoded = (int)length;
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Original-Broadcast-NPDU message
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param npdu - buffer to copy the decoded BACnet NDPU
* @param npdu_size - size of the buffer for the decoded BACnet NPDU
* @param npdu_len - decoded length of the BACnet NPDU
*
* @return number of bytes decoded
*/
int bvlc_decode_distribute_broadcast_to_network(
const uint8_t *pdu,
uint16_t pdu_len,
uint8_t *npdu,
uint16_t npdu_size,
uint16_t *npdu_len)
{
int bytes_consumed = 0;
uint16_t i = 0;
if (pdu && npdu && (pdu_len > 0) && (pdu_len <= npdu_size)) {
for (i = 0; i < pdu_len; i++) {
npdu[i] = pdu[i];
}
}
if (npdu_len) {
*npdu_len = pdu_len;
}
bytes_consumed = (int)pdu_len;
return bytes_consumed;
}
/**
* @brief J.2.11 Original-Unicast-NPDU: Encode
*
* This message is used to send directed NPDUs to another
* B/IP device or router.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param npdu - BACnet NPDU buffer
* @param npdu_len - size of the BACnet NPDU buffer
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IPv4
* BVLC Function: 1-octet X'0A' Original-Unicast-NPDU
* BVLC Length: 2-octets L Length L, in octets, of the BVLL message
* BACnet NPDU: Variable length
*/
int bvlc_encode_original_unicast(
uint8_t *pdu, uint16_t pdu_size, const uint8_t *npdu, uint16_t npdu_len)
{
int bytes_encoded = 0;
uint16_t length = 4;
uint16_t i = 0;
length += npdu_len;
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_ORIGINAL_UNICAST_NPDU, length);
if (bytes_encoded == 4) {
if (npdu && (npdu_len > 0)) {
for (i = 0; i < npdu_len; i++) {
pdu[4 + i] = npdu[i];
}
bytes_encoded = (int)length;
}
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Original-Unicast-NPDU message, after decoding header
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param npdu - BACnet NPDU buffer
* @param npdu_size - size of the buffer for the decoded BACnet NPDU
* @param npdu_len - decoded length of the BACnet NPDU buffer
*
* @return number of bytes decoded
*/
int bvlc_decode_original_unicast(
const uint8_t *pdu,
uint16_t pdu_len,
uint8_t *npdu,
uint16_t npdu_size,
uint16_t *npdu_len)
{
int bytes_consumed = 0;
uint16_t i = 0;
if (pdu && npdu && (pdu_len > 0) && (pdu_len <= npdu_size)) {
for (i = 0; i < pdu_len; i++) {
npdu[i] = pdu[i];
}
}
if (npdu_len) {
*npdu_len = pdu_len;
}
bytes_consumed = (int)pdu_len;
return bytes_consumed;
}
/**
* @brief J.2.12 Original-Broadcast-NPDU: Encode
*
* This message is used by B/IP devices and routers which are
* not foreign devices to broadcast NPDUs on a B/IP network
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param npdu - BACnet NPDU buffer
* @param npdu_len - size of the BACnet NPDU buffer
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IPv4
* BVLC Function: 1-octet X'0B' Original-Broadcast-NPDU
* BVLC Length: 2-octets L Length of the BVLL message
* BACnet NPDU: Variable length
*/
int bvlc_encode_original_broadcast(
uint8_t *pdu, uint16_t pdu_size, const uint8_t *npdu, uint16_t npdu_len)
{
int bytes_encoded = 0;
uint16_t length = 4;
uint16_t i = 0;
length += npdu_len;
if (pdu && (pdu_size >= length)) {
bytes_encoded = bvlc_encode_header(
pdu, pdu_size, BVLC_ORIGINAL_BROADCAST_NPDU, length);
if (bytes_encoded == 4) {
if (npdu && (npdu_len > 0)) {
for (i = 0; i < npdu_len; i++) {
pdu[4 + i] = npdu[i];
}
bytes_encoded = (int)length;
}
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Original-Broadcast-NPDU message
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param npdu - buffer to copy the decoded BACnet NDPU
* @param npdu_size - size of the buffer for the decoded BACnet NPDU
* @param npdu_len - decoded length of the BACnet NPDU
*
* @return number of bytes decoded
*/
int bvlc_decode_original_broadcast(
const uint8_t *pdu,
uint16_t pdu_len,
uint8_t *npdu,
uint16_t npdu_size,
uint16_t *npdu_len)
{
int bytes_consumed = 0;
uint16_t i = 0;
if (pdu && npdu && (pdu_len > 0) && (pdu_len <= npdu_size)) {
for (i = 0; i < pdu_len; i++) {
npdu[i] = pdu[i];
}
}
if (npdu_len) {
*npdu_len = pdu_len;
}
bytes_consumed = (int)pdu_len;
return bytes_consumed;
}
/**
* @brief J.2.13 Secure-BVLL: encode
*
* This message is used to secure BVLL messages that do not contain NPDUs.
* Its use is described in Clause 24.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param sbuf - Security Wrapper buffer
* @param sbuf_len - size of the Security Wrapper buffer
*
* @return number of bytes encoded
*
* BVLC Type: 1-octet X'81' BVLL for BACnet/IP
* BVLC Function: 1-octet X'0C' Secure-BVLL
* BVLC Length: 2-octets L Length of the BVLL message
* Security Wrapper: Variable length
*/
int bvlc_encode_secure_bvll(
uint8_t *pdu, uint16_t pdu_size, const uint8_t *sbuf, uint16_t sbuf_len)
{
int bytes_encoded = 0;
uint16_t length = 1 + 1 + 2;
uint16_t i = 0;
length += sbuf_len;
if (pdu && (pdu_size >= length)) {
bytes_encoded =
bvlc_encode_header(pdu, pdu_size, BVLC_SECURE_BVLL, length);
if (bytes_encoded == 4) {
if (sbuf && sbuf_len) {
for (i = 0; i < sbuf_len; i++) {
pdu[4 + i] = sbuf[i];
}
}
bytes_encoded = (int)length;
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Secure-BVLL message
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param sbuf - Security Wrapper buffer
* @param sbuf_size - size of the Security Wrapper buffer
* @param sbuf_len - number of bytes decoded into the Security Wrapper buffer
*
* @return number of bytes decoded
*/
int bvlc_decode_secure_bvll(
const uint8_t *pdu,
uint16_t pdu_len,
uint8_t *sbuf,
uint16_t sbuf_size,
uint16_t *sbuf_len)
{
int bytes_consumed = 0;
uint16_t i = 0;
if (pdu && sbuf && (pdu_len > 0) && (pdu_len <= sbuf_size)) {
for (i = 0; i < pdu_len; i++) {
sbuf[i] = pdu[i];
}
}
if (sbuf_len) {
*sbuf_len = pdu_len;
}
bytes_consumed = (int)pdu_len;
return bytes_consumed;
}
/**
* @brief Encode the BVLC Address
*
* Data link layer addressing between B/IPv4 nodes consists of a 32-bit
* IPv4 address followed by a two-octet UDP port number (both of which
* shall be transmitted with the most significant octet first). This
* address shall be referred to as a B/IPv4 address.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param bip_address - B/IPv4 address
*
* @return number of bytes encoded
*/
int bvlc_encode_address(
uint8_t *pdu, uint16_t pdu_size, const BACNET_IP_ADDRESS *bip_address)
{
int bytes_encoded = 0;
uint16_t length = BIP_ADDRESS_MAX;
unsigned i = 0;
if (pdu && (pdu_size >= length) && bip_address) {
for (i = 0; i < IP_ADDRESS_MAX; i++) {
pdu[i] = bip_address->address[i];
}
encode_unsigned16(&pdu[IP_ADDRESS_MAX], bip_address->port);
bytes_encoded = (int)length;
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Address
*
* Data link layer addressing between B/IPv4 nodes consists of a 32-bit
* IPv4 address followed by a two-octet UDP port number (both of which
* shall be transmitted with the most significant octet first). This
* address shall be referred to as a B/IPv4 address.
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param bip_address - B/IPv4 address
*
* @return number of bytes decoded
*/
int bvlc_decode_address(
const uint8_t *pdu, uint16_t pdu_len, BACNET_IP_ADDRESS *bip_address)
{
int bytes_consumed = 0;
uint16_t length = BIP_ADDRESS_MAX;
unsigned i = 0;
if (pdu && (pdu_len >= length) && bip_address) {
for (i = 0; i < IP_ADDRESS_MAX; i++) {
bip_address->address[i] = pdu[i];
}
decode_unsigned16(&pdu[IP_ADDRESS_MAX], &bip_address->port);
bytes_consumed = (int)length;
}
return bytes_consumed;
}
/**
* @brief Copy the BVLC Address
*
* Data link layer addressing between B/IPv4 nodes consists of a 32-bit
* IPv4 address followed by a two-octet UDP port number (both of which
* shall be transmitted with the most significant octet first). This
* address shall be referred to as a B/IPv4 address.
*
* @param dst - B/IPv4 address that will be filled with src
* @param src - B/IPv4 address that will be copied into dst
*
* @return true if the address was copied
*/
bool bvlc_address_copy(BACNET_IP_ADDRESS *dst, const BACNET_IP_ADDRESS *src)
{
bool status = false;
unsigned int i = 0;
if (src && dst) {
for (i = 0; i < IP_ADDRESS_MAX; i++) {
dst->address[i] = src->address[i];
}
dst->port = src->port;
status = true;
}
return status;
}
/**
* @brief Compare the BVLC Address
*
* Data link layer addressing between B/IPv4 nodes consists of a 32-bit
* IPv4 address followed by a two-octet UDP port number (both of which
* shall be transmitted with the most significant octet first). This
* address shall be referred to as a B/IPv4 address.
*
* @param dst - B/IPv4 address that will be compared to src
* @param src - B/IPv4 address that will be compared to dst
*
* @return true if the addresses are different
*/
bool bvlc_address_different(
const BACNET_IP_ADDRESS *dst, const BACNET_IP_ADDRESS *src)
{
bool status = false;
unsigned int i = 0;
if (src && dst) {
for (i = 0; i < IP_ADDRESS_MAX; i++) {
if (dst->address[i] != src->address[i]) {
status = true;
}
}
if (dst->port != src->port) {
status = true;
}
}
return status;
}
/**
* @brief Apply the Broadcast Distribution Mask to an address
* @param dst - B/IPv4 address that will be masked
* @param src - B/IPv4 address that will be ORed with the mask
* @param mask - B/IPv4 broadcast distribution mask
* @return true if the addresses are different
*/
bool bvlc_address_mask(
BACNET_IP_ADDRESS *dst,
const BACNET_IP_ADDRESS *src,
const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask)
{
bool status = false;
unsigned int i = 0;
if (src && dst && mask) {
for (i = 0; i < IP_ADDRESS_MAX; i++) {
dst->address[i] = src->address[i] | ~mask->address[i];
}
dst->port = src->port;
}
return status;
}
/**
* @brief Set a BVLC Address from 4 octets
*
* Data link layer addressing between B/IPv4 nodes consists of a 32-bit
* IPv4 address followed by a two-octet UDP port number (both of which
* shall be transmitted with the most significant octet first). This
* address shall be referred to as a B/IPv4 address.
*
* @param addr - B/IPv4 address that be set
* @param addr0 - B/IPv4 address octet
* @param addr1 - B/IPv4 address octet
* @param addr2 - B/IPv4 address octet
* @param addr3 - B/IPv4 address octet
*
* @return true if the address is set
*/
bool bvlc_address_set(
BACNET_IP_ADDRESS *addr,
uint8_t addr0,
uint8_t addr1,
uint8_t addr2,
uint8_t addr3)
{
bool status = false;
if (addr) {
addr->address[0] = addr0;
addr->address[1] = addr1;
addr->address[2] = addr2;
addr->address[3] = addr3;
status = true;
}
return status;
}
/**
* @brief Get a BVLC Address into 4 octets
*
* Data link layer addressing between B/IPv4 nodes consists of a 128-bit
* IPv4 address followed by a two-octet UDP port number (both of which
* shall be transmitted with the most significant octet first). This
* address shall be referred to as a B/IPv4 address.
*
* @param addr - B/IPv4 address that be set
* @param addr0 - B/IPv4 address octet
* @param addr1 - B/IPv4 address octet
* @param addr2 - B/IPv4 address octet
* @param addr3 - B/IPv4 address octet
*
* @return true if the address is set
*/
bool bvlc_address_get(
const BACNET_IP_ADDRESS *addr,
uint8_t *addr0,
uint8_t *addr1,
uint8_t *addr2,
uint8_t *addr3)
{
bool status = false;
if (addr) {
if (addr0) {
*addr0 = addr->address[0];
}
if (addr1) {
*addr1 = addr->address[1];
}
if (addr2) {
*addr2 = addr->address[2];
}
if (addr3) {
*addr3 = addr->address[3];
}
status = true;
}
return status;
}
/**
* @brief Convert IPv4 Address from ASCII
*
* IPv4 addresses are represented as four octets, separated by dots/periods,
* of four decimal digits.
*
* Adapted from uiplib.c uIP TCP/IP stack and the Contiki operating system.
* Thank you, Adam Dunkel, and the Swedish Institute of Computer Science.
*
* @param addr - B/IPv4 address that is set
* @param addrstr - B/IPv4 address in ASCII dotted decimal format
*
* @return true if a valid address was set
*/
bool bvlc_address_from_ascii(BACNET_IP_ADDRESS *addr, const char *addrstr)
{
uint16_t tmp = 0;
char c = 0;
unsigned char i = 0, j = 0;
if (!addr) {
return false;
}
if (!addrstr) {
return false;
}
for (i = 0; i < 4; ++i) {
j = 0;
do {
c = *addrstr;
++j;
if (j > 4) {
return false;
}
if ((c == '.') || (c == 0) || (c == ' ')) {
addr->address[i] = (uint8_t)tmp;
tmp = 0;
} else if ((c >= '0') && (c <= '9')) {
tmp = (tmp * 10) + (c - '0');
if (tmp > UINT8_MAX) {
return false;
}
} else {
return false;
}
++addrstr;
} while ((c != '.') && (c != 0) && (c != ' '));
}
return true;
}
/**
* @brief Convert IPv4 Address and UDP port number from ASCII
*
* @param addr - B/IPv4 address and port that is set
* @param addrstr - B/IPv4 address in ASCII dotted decimal format
* @param portstr - B/IPv4 port in 16-bit ASCII hex compressed format
*
* @return true if a valid address was set
*/
bool bvlc_address_port_from_ascii(
BACNET_IP_ADDRESS *addr, const char *addrstr, const char *portstr)
{
bool status = false;
unsigned long port = 0;
if (bvlc_address_from_ascii(addr, addrstr)) {
port = strtoul(portstr, NULL, 0);
if (port <= UINT16_MAX) {
addr->port = port;
status = true;
}
}
return status;
}
/**
* @brief Convert IPv4 Address from network byte order 32-bit value
*
* @param dst - B/IPv4 address that is set
* @param addr - B/IPv4 address in network byte order 32-bit value
*/
void bvlc_address_from_network(BACNET_IP_ADDRESS *dst, uint32_t addr)
{
if (dst) {
/* copy most significant octet first, network byte order, big endian */
encode_unsigned32(&dst->address[0], addr);
}
}
/**
* @brief Convert IPv4 Address to local BACnet address
*
* Data link layer addressing between B/IPv4 nodes consists of a 32-bit
* IPv4 address followed by a two-octet UDP port number (both of which
* shall be transmitted with the most significant octet first).
* This address shall be referred to as a B/IPv4 address.
*
* @param dst - BACnet MAC address that is set
* @param addr - B/IPv4 address in network byte order 32-bit value
* @return true if a valid address was set
*/
bool bvlc_ip_address_to_bacnet_local(
BACNET_ADDRESS *addr, const BACNET_IP_ADDRESS *ipaddr)
{
bool status = false;
if (addr && ipaddr) {
/* most significant octet first, network byte order, big endian */
addr->mac[0] = ipaddr->address[0];
addr->mac[1] = ipaddr->address[1];
addr->mac[2] = ipaddr->address[2];
addr->mac[3] = ipaddr->address[3];
encode_unsigned16(&addr->mac[4], ipaddr->port);
addr->mac[6] = 0;
addr->mac_len = 6;
/* local only, no routing */
addr->net = 0;
/* no SLEN/DLEN */
addr->len = 0;
/* no SADR/DADR */
addr->adr[0] = 0;
addr->adr[1] = 0;
addr->adr[2] = 0;
addr->adr[3] = 0;
addr->adr[4] = 0;
addr->adr[5] = 0;
addr->adr[6] = 0;
status = true;
}
return status;
}
/**
* @brief Convert IPv4 Address from local BACnet address
* @param addr - BACnet MAC address that is converted into B/IPv4 address/port
* @param ipaddr - B/IPv4 address that is set
* @return true if a valid address was set
*/
bool bvlc_ip_address_from_bacnet_local(
BACNET_IP_ADDRESS *ipaddr, const BACNET_ADDRESS *addr)
{
bool status = false;
if (addr && ipaddr) {
if (addr->mac_len == 6) {
/* most significant octet first, network byte order, big endian */
ipaddr->address[0] = addr->mac[0];
ipaddr->address[1] = addr->mac[1];
ipaddr->address[2] = addr->mac[2];
ipaddr->address[3] = addr->mac[3];
decode_unsigned16(&addr->mac[4], &ipaddr->port);
status = true;
}
}
return status;
}
/**
* @brief Convert IPv4 Address to remote BACnet address
* @param dst - BACnet MAC address that is set
* @param dnet - network number of BACnet address
* @param addr - B/IPv4 address in network byte order 32-bit value
* @return true if a valid address was set
*/
bool bvlc_ip_address_to_bacnet_remote(
BACNET_ADDRESS *addr, uint16_t dnet, const BACNET_IP_ADDRESS *ipaddr)
{
bool status = false;
if (addr && ipaddr) {
/* don't modify local MAC or MAC len */
/* add DNET/SNET */
addr->net = dnet;
/* no SADR/DADR */
/* most significant octet first, network byte order, big endian */
addr->adr[0] = ipaddr->address[0];
addr->adr[1] = ipaddr->address[1];
addr->adr[2] = ipaddr->address[2];
addr->adr[3] = ipaddr->address[3];
encode_unsigned16(&addr->adr[4], ipaddr->port);
addr->adr[6] = 0;
/* set SLEN/DLEN for BACnet/IPv4 */
addr->len = 6;
status = true;
}
return status;
}
/**
* @brief Convert IPv4 Address from remote BACnet address
* @param addr - BACnet MAC address that is converted into B/IPv4 address/port
* @param dnet - network number of BACnet address
* @param ipaddr - B/IPv4 address that is set
* @return true if a valid address was set
*/
bool bvlc_ip_address_from_bacnet_remote(
BACNET_IP_ADDRESS *ipaddr, uint16_t *dnet, const BACNET_ADDRESS *addr)
{
bool status = false;
if (addr && ipaddr) {
if (addr->len == 6) {
/* most significant octet first, network byte order, big endian */
ipaddr->address[0] = addr->adr[0];
ipaddr->address[1] = addr->adr[1];
ipaddr->address[2] = addr->adr[2];
ipaddr->address[3] = addr->adr[3];
decode_unsigned16(&addr->adr[4], &ipaddr->port);
if (dnet) {
*dnet = addr->net;
}
status = true;
}
}
return status;
}
/**
* @brief Encode the BVLC Broadcast Distribution Mask
*
* The Broadcast Distribution Mask is a 4-octet field that
* indicates how broadcast messages are to be distributed on
* the IP subnet served by the BBMD.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param bd_mask - Broadcast Distribution Mask
*
* @return number of bytes encoded
*/
int bvlc_encode_broadcast_distribution_mask(
uint8_t *pdu,
uint16_t pdu_size,
const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *bd_mask)
{
int bytes_encoded = 0;
unsigned i = 0;
if (pdu && (pdu_size >= BACNET_IP_BDT_MASK_SIZE) && bd_mask) {
for (i = 0; i < IP_ADDRESS_MAX; i++) {
pdu[i] = bd_mask->address[i];
}
bytes_encoded = BACNET_IP_BDT_MASK_SIZE;
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Broadcast Distribution Mask
*
* The Broadcast Distribution Mask is a 4-octet field that
* indicates how broadcast messages are to be distributed on
* the 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 bd_mask - Broadcast Distribution Mask
*
* @return number of bytes decoded
*/
int bvlc_decode_broadcast_distribution_mask(
const uint8_t *pdu,
uint16_t pdu_len,
BACNET_IP_BROADCAST_DISTRIBUTION_MASK *bd_mask)
{
int bytes_consumed = 0;
unsigned i = 0;
if (pdu && (pdu_len >= BACNET_IP_BDT_MASK_SIZE)) {
if (bd_mask) {
for (i = 0; i < IP_ADDRESS_MAX; i++) {
bd_mask->address[i] = pdu[i];
}
}
bytes_consumed = (int)BACNET_IP_BDT_MASK_SIZE;
}
return bytes_consumed;
}
/**
* @brief Encode the BVLC Broadcast Distribution Table Entry
*
* Each BDT entry consists of the 6-octet B/IP address of a
* BBMD followed by a 4-octet field called the broadcast distribution mask
* that indicates how broadcast messages are to be distributed on the
* IP subnet served by the BBMD
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param bdt_entry - BDT Entry
*
* @return number of bytes encoded
*/
int bvlc_encode_broadcast_distribution_table_entry(
uint8_t *pdu,
uint16_t pdu_size,
const BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry)
{
int bytes_encoded = 0;
int len = 0;
int offset = 0;
if (pdu && (pdu_size >= BACNET_IP_BDT_ENTRY_SIZE)) {
if (bdt_entry) {
len = bvlc_encode_address(
&pdu[offset], pdu_size - offset, &bdt_entry->dest_address);
if (len > 0) {
offset += len;
len = bvlc_encode_broadcast_distribution_mask(
&pdu[offset], pdu_size - offset,
&bdt_entry->broadcast_mask);
}
if (len > 0) {
offset += len;
bytes_encoded = offset;
}
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Broadcast Distribution Table Entry
*
* Each BDT entry consists of the 6-octet B/IP address of a
* BBMD followed by a 4-octet field called the broadcast distribution mask
* that indicates how broadcast messages are to be distributed on the
* 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
*
* @return number of bytes decoded
*/
int bvlc_decode_broadcast_distribution_table_entry(
const uint8_t *pdu,
uint16_t pdu_len,
BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry)
{
int bytes_consumed = 0;
int len = 0;
int offset = 0;
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 (len > 0) {
offset += len;
bytes_consumed = offset;
}
}
}
return bytes_consumed;
}
/**
* @brief Encode the BVLC Foreign Device Table Entry
*
* Each BDT entry consists of the 6-octet B/IP address of the registrant;
* the 2-octet Time-to-Live value supplied at the time of registration;
* and a 2-octet value representing the number of seconds remaining.
*
* @param pdu - buffer to store the encoding
* @param pdu_size - size of the buffer to store encoding
* @param fdt_entry - Foreign Device Table (FDT) Entry
*
* @return number of bytes encoded
*/
int bvlc_encode_foreign_device_table_entry(
uint8_t *pdu,
uint16_t pdu_size,
const BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry)
{
int bytes_encoded = 0;
int len = 0;
int offset = 0;
if (pdu && (pdu_size >= BACNET_IP_FDT_ENTRY_SIZE)) {
if (fdt_entry) {
len = bvlc_encode_address(
&pdu[offset], pdu_size - offset, &fdt_entry->dest_address);
if (len > 0) {
offset += len;
len = encode_unsigned16(&pdu[offset], fdt_entry->ttl_seconds);
}
if (len > 0) {
offset += len;
len = encode_unsigned16(
&pdu[offset], fdt_entry->ttl_seconds_remaining);
}
if (len > 0) {
offset += len;
bytes_encoded = offset;
}
}
}
return bytes_encoded;
}
/**
* @brief Decode the BVLC Foreign Device Table Entry
*
* Each BDT entry consists of the 6-octet B/IP address of the registrant;
* the 2-octet Time-to-Live value supplied at the time of registration;
* and a 2-octet value representing the number of seconds remaining.
*
* @param pdu - buffer from which to decode the message
* @param pdu_len - length of the buffer that needs decoding
* @param fdt_entry - Foreign Device Table (FDT) Entry
*
* @return number of bytes decoded
*/
int bvlc_decode_foreign_device_table_entry(
const uint8_t *pdu,
uint16_t pdu_len,
BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry)
{
int bytes_consumed = 0;
int len = 0;
int offset = 0;
if (pdu && (pdu_len >= BACNET_IP_FDT_ENTRY_SIZE)) {
if (fdt_entry) {
len = bvlc_decode_address(
&pdu[offset], pdu_len - offset, &fdt_entry->dest_address);
if (len > 0) {
offset += len;
len = decode_unsigned16(&pdu[offset], &fdt_entry->ttl_seconds);
}
if (len > 0) {
offset += len;
len = decode_unsigned16(
&pdu[offset], &fdt_entry->ttl_seconds_remaining);
}
if (len > 0) {
offset += len;
bytes_consumed = offset;
}
}
}
return bytes_consumed;
}
/**
* @brief Get a text name for each BVLC result code
* @param result_code - BVLC result code
* @return ASCII text name for each BVLC result code or empty string
*/
const char *bvlc_result_code_name(uint16_t result_code)
{
const char *name = "";
switch (result_code) {
case BVLC_RESULT_SUCCESSFUL_COMPLETION:
name = "Successful Completion";
break;
case BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK:
name = "Write-Broadcast-Distribution-Table NAK";
break;
case BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK:
name = "Read-Broadcast-Distribution-Table NAK";
break;
case BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK:
name = "Register-Foreign-Device NAK";
break;
case BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK:
name = "Read-Foreign-Device-Table NAK";
break;
case BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK:
name = "Delete-Foreign-Device-Table-Entry NAK";
break;
case BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK:
name = "Distribute-Broadcast-To-Network NAK";
break;
default:
break;
}
return name;
}
/**
* @brief Encode a BBMD Address for Network Port object
* @param apdu - the APDU buffer
* @param apdu_size - the APDU buffer size
* @param ip_address - IP address and port number
* @return length of the APDU buffer
*/
int bvlc_foreign_device_bbmd_host_address_encode(
uint8_t *apdu, uint16_t apdu_size, const BACNET_IP_ADDRESS *ip_address)
{
BACNET_HOST_N_PORT address = { 0 };
int apdu_len = 0;
address.host_ip_address = true;
address.host_name = false;
octetstring_init(
&address.host.ip_address, &ip_address->address[0], IP_ADDRESS_MAX);
address.port = ip_address->port;
apdu_len = host_n_port_encode(NULL, &address);
if (apdu_len <= apdu_size) {
apdu_len = host_n_port_encode(apdu, &address);
}
return apdu_len;
}
/**
* @brief Decode the Broadcast-Distribution-Table for Network Port object
* @param apdu - the APDU buffer
* @param apdu_len - the APDU buffer length
* @param ip_address - IP address and port number
* @return length of the APDU buffer decoded, or ERROR, REJECT, or ABORT
*/
int bvlc_foreign_device_bbmd_host_address_decode(
const uint8_t *apdu,
uint16_t apdu_len,
BACNET_ERROR_CODE *error_code,
BACNET_IP_ADDRESS *ip_address)
{
BACNET_HOST_N_PORT address = { 0 };
int len = 0;
len = host_n_port_decode(apdu, apdu_len, error_code, &address);
if (len > 0) {
if (address.host_ip_address) {
ip_address->port = address.port;
(void)octetstring_copy_value(
&ip_address->address[0], IP_ADDRESS_MAX,
&address.host.ip_address);
} else {
len = BACNET_STATUS_REJECT;
if (error_code) {
*error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE;
}
}
}
return len;
}