Files
bacnet_stack/bacnet-stack/src/bvlc.c
T

1266 lines
44 KiB
C

/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2006 Steve Karg
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
#include <stdint.h> /* for standard integer types uint8_t etc. */
#include <stdbool.h> /* for the standard bool type. */
#include <time.h>
#include "bacenum.h"
#include "bacdcode.h"
#include "bacint.h"
#include "bvlc.h"
#include "bip.h"
#ifndef DEBUG_ENABLED
#define DEBUG_ENABLED 0
#endif
#include "debug.h"
/** @file bvlc.c Handle the BACnet Virtual Link Control (BVLC) */
/* Handle the BACnet Virtual Link Control (BVLC), which includes:
BACnet Broadcast Management Device,
Broadcast Distribution Table, and
Foreign Device Registration */
typedef struct {
/* true if valid entry - false if not */
bool valid;
/* BACnet/IP address */
struct in_addr dest_address; /* in network format */
/* BACnet/IP port number - not always 47808=BAC0h */
uint16_t dest_port; /* in network format */
/* Broadcast Distribution Mask */
struct in_addr broadcast_mask; /* in tework format */
} BBMD_TABLE_ENTRY;
#define MAX_BBMD_ENTRIES 128
static BBMD_TABLE_ENTRY BBMD_Table[MAX_BBMD_ENTRIES];
/*Each device that registers as a foreign device shall be placed
in an entry in the BBMD's Foreign Device Table (FDT). Each
entry shall consist 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. This value will be initialized
to the 2-octet Time-to-Live value supplied at the time of
registration.*/
typedef struct {
bool valid;
/* BACnet/IP address */
struct in_addr dest_address;
/* BACnet/IP port number - not always 47808=BAC0h */
uint16_t dest_port;
/* seconds for valid entry lifetime */
uint16_t time_to_live;
/* our counter */
time_t seconds_remaining; /* includes 30 second grace period */
} FD_TABLE_ENTRY;
#define MAX_FD_ENTRIES 128
static FD_TABLE_ENTRY FD_Table[MAX_FD_ENTRIES];
/* result from a client request */
BACNET_BVLC_RESULT BVLC_Result_Code = BVLC_RESULT_SUCCESSFUL_COMPLETION;
/* if we are a foreign device, store the
remote BBMD address/port here in network byte order */
static struct sockaddr_in Remote_BBMD;
#if defined(BBMD_ENABLED) && BBMD_ENABLED
void bvlc_maintenance_timer(
time_t seconds)
{
unsigned i = 0;
for (i = 0; i < MAX_FD_ENTRIES; i++) {
if (FD_Table[i].valid) {
if (FD_Table[i].seconds_remaining) {
if (FD_Table[i].seconds_remaining < seconds) {
FD_Table[i].seconds_remaining = 0;
} else {
FD_Table[i].seconds_remaining -= seconds;
}
if (FD_Table[i].seconds_remaining == 0) {
FD_Table[i].valid = false;
}
}
}
}
}
#endif
/* copy the source internet address to the BACnet address */
/* FIXME: IPv6? */
static void bvlc_internet_to_bacnet_address(
BACNET_ADDRESS * src, /* returns the BACnet source address */
struct sockaddr_in *sin)
{ /* source address in network order */
if (src && sin) {
memcpy(&src->mac[0], &sin->sin_addr.s_addr, 4);
memcpy(&src->mac[4], &sin->sin_port, 2);
src->mac_len = (uint8_t) 6;
src->net = 0;
src->len = 0;
}
return;
}
/* Addressing within B/IP Networks
In the case of B/IP networks, six octets consisting of the four-octet
IP address followed by a two-octet UDP port number (both of
which shall be transmitted most significant octet first).
Note: for local storage, the storage order is NETWORK byte order.
Note: BACnet unsigned is encoded as most significant octet. */
static int bvlc_encode_bip_address(
uint8_t * pdu, /* buffer to store encoding */
struct in_addr *address, /* in network format */
uint16_t port) /* in network format */
{
int len = 0;
if (pdu) {
memcpy (&pdu[0], &address->s_addr, 4);
memcpy (&pdu[4], &port, 2);
len = 6;
}
return len;
}
static int bvlc_decode_bip_address(
uint8_t * pdu, /* buffer to extract encoded address */
struct in_addr *address, /* in network format */
uint16_t * port) /* in network format */
{
int len = 0;
if (pdu) {
memcpy(&address->s_addr, &pdu[0], 4);
memcpy(port, &pdu[4], 2);
len = 6;
}
return len;
}
/* used for both read and write entries */
static int bvlc_encode_address_entry(
uint8_t * pdu,
struct in_addr *address,
uint16_t port, /* in network byte order */
struct in_addr *mask)
{
int len = 0;
if (pdu) {
len = bvlc_encode_bip_address(pdu, address, port);
memcpy(&pdu[len], &mask->s_addr, 4);
len += 4;
}
return len;
}
static int bvlc_encode_bvlc_result(
uint8_t * pdu,
BACNET_BVLC_RESULT result_code)
{
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_RESULT;
/* 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], 6);
encode_unsigned16(&pdu[4], (uint16_t) result_code);
}
return 6;
}
#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED
int bvlc_encode_write_bdt_init(
uint8_t * pdu,
unsigned entries)
{
int len = 0;
uint16_t BVLC_length = 0;
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE;
/* 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. */
BVLC_length = 4 + (uint16_t) (entries * 10);
encode_unsigned16(&pdu[2], BVLC_length);
len = 4;
}
return len;
}
#endif
#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED
int bvlc_encode_read_bdt(
uint8_t * pdu)
{
int len = 0;
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_READ_BROADCAST_DIST_TABLE;
/* 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], 4);
len = 4;
}
return len;
}
#endif
static int bvlc_encode_read_bdt_ack_init(
uint8_t * pdu,
unsigned entries)
{
int len = 0;
uint16_t BVLC_length = 0;
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_READ_BROADCAST_DIST_TABLE_ACK;
/* 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. */
BVLC_length = 4 + (uint16_t) (entries * 10);
encode_unsigned16(&pdu[2], BVLC_length);
len = 4;
}
return len;
}
static int bvlc_encode_read_bdt_ack(
uint8_t * pdu,
uint16_t max_pdu)
{
int pdu_len = 0; /* return value */
int len = 0;
unsigned count = 0;
unsigned i;
for (i = 0; i < MAX_BBMD_ENTRIES; i++) {
if (BBMD_Table[i].valid) {
count++;
}
}
len = bvlc_encode_read_bdt_ack_init(&pdu[0], count);
pdu_len += len;
for (i = 0; i < MAX_BBMD_ENTRIES; i++) {
if (BBMD_Table[i].valid) {
/* too much to send */
if ((pdu_len + 10) > max_pdu) {
pdu_len = 0;
break;
}
len =
bvlc_encode_address_entry(&pdu[pdu_len],
&BBMD_Table[i].dest_address, BBMD_Table[i].dest_port,
&BBMD_Table[i].broadcast_mask);
pdu_len += len;
}
}
return pdu_len;
}
static int bvlc_encode_forwarded_npdu(
uint8_t * pdu,
struct sockaddr_in *sin, /* source address in network order */
uint8_t * npdu,
unsigned npdu_length)
{
int len = 0;
unsigned i; /* for loop counter */
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_FORWARDED_NPDU;
/* 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], (uint16_t) (4 + 6 + npdu_length));
len = 4;
len += bvlc_encode_bip_address(&pdu[len], &sin->sin_addr, sin->sin_port);
for (i = 0; i < npdu_length; i++) {
pdu[len] = npdu[i];
len++;
}
}
return len;
}
static int bvlc_encode_register_foreign_device(
uint8_t * pdu,
uint16_t time_to_live_seconds)
{
int len = 0;
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_REGISTER_FOREIGN_DEVICE;
/* 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], 6);
encode_unsigned16(&pdu[4], time_to_live_seconds);
len = 6;
}
return len;
}
#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED
int bvlc_encode_read_fdt(
uint8_t * pdu)
{
int len = 0;
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE;
/* 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], 4);
len = 4;
}
return len;
}
#endif
static int bvlc_encode_read_fdt_ack_init(
uint8_t * pdu,
unsigned entries)
{
int len = 0;
uint16_t BVLC_length = 0;
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE_ACK;
/* 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. */
BVLC_length = 4 + (uint16_t) (entries * 10);
encode_unsigned16(&pdu[2], BVLC_length);
len = 4;
}
return len;
}
static int bvlc_encode_read_fdt_ack(
uint8_t * pdu,
uint16_t max_pdu)
{
int pdu_len = 0; /* return value */
int len = 0;
unsigned count = 0;
unsigned i;
uint16_t seconds_remaining = 0;
for (i = 0; i < MAX_FD_ENTRIES; i++) {
if (FD_Table[i].valid) {
count++;
}
}
len = bvlc_encode_read_fdt_ack_init(&pdu[0], count);
pdu_len += len;
for (i = 0; i < MAX_FD_ENTRIES; i++) {
if (FD_Table[i].valid) {
/* too much to send */
if ((pdu_len + 10) > max_pdu) {
pdu_len = 0;
break;
}
len =
bvlc_encode_bip_address(&pdu[pdu_len],
&FD_Table[i].dest_address, FD_Table[i].dest_port);
pdu_len += len;
len = encode_unsigned16(&pdu[pdu_len], FD_Table[i].time_to_live);
pdu_len += len;
seconds_remaining = (uint16_t) FD_Table[i].seconds_remaining;
len = encode_unsigned16(&pdu[pdu_len], seconds_remaining);
pdu_len += len;
}
}
return pdu_len;
}
#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED
int bvlc_encode_delete_fdt_entry(
uint8_t * pdu,
uint32_t address, /* in network byte order */
uint16_t port) /* in network byte order */
{
int len = 0;
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY;
/* 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], 10);
/* FDT Entry */
encode_unsigned32(&pdu[4], address);
encode_unsigned16(&pdu[8], port);
len = 10;
}
return len;
}
#endif
#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED
int bvlc_encode_original_unicast_npdu(
uint8_t * pdu,
uint8_t * npdu,
unsigned npdu_length)
{
int len = 0; /* return value */
unsigned i = 0; /* loop counter */
uint16_t BVLC_length = 0;
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_ORIGINAL_UNICAST_NPDU;
/* 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. */
BVLC_length = 4 + (uint16_t) npdu_length;
len = encode_unsigned16(&pdu[2], BVLC_length) + 2;
for (i = 0; i < npdu_length; i++) {
pdu[len] = npdu[i];
len++;
}
}
return len;
}
#endif
#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED
int bvlc_encode_original_broadcast_npdu(
uint8_t * pdu,
uint8_t * npdu,
unsigned npdu_length)
{
int len = 0; /* return value */
unsigned i = 0; /* loop counter */
uint16_t BVLC_length = 0;
if (pdu) {
pdu[0] = BVLL_TYPE_BACNET_IP;
pdu[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
/* 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. */
BVLC_length = 4 + (uint16_t) npdu_length;
len = encode_unsigned16(&pdu[2], BVLC_length) + 2;
for (i = 0; i < npdu_length; i++) {
pdu[len] = npdu[i];
len++;
}
}
return len;
}
#endif
static bool bvlc_create_bdt(
uint8_t * npdu,
uint16_t npdu_length)
{
bool status = false;
unsigned i = 0;
uint16_t pdu_offset = 0;
for (i = 0; i < MAX_BBMD_ENTRIES; i++) {
if (npdu_length >= 10) {
BBMD_Table[i].valid = true;
memcpy(&BBMD_Table[i].dest_address.s_addr, &npdu[pdu_offset], 4);
pdu_offset += 4;
memcpy(&BBMD_Table[i].dest_port, &npdu[pdu_offset], 2);
pdu_offset += 2;
memcpy(&BBMD_Table[i].broadcast_mask.s_addr, &npdu[pdu_offset], 4);
pdu_offset += 4;
npdu_length -= (4+2+4);
} else {
BBMD_Table[i].valid = false;
BBMD_Table[i].dest_address.s_addr = 0;
BBMD_Table[i].dest_port = 0;
BBMD_Table[i].broadcast_mask.s_addr = 0;
}
}
/* did they all fit? */
if (npdu_length < 10) {
status = true;
}
return status;
}
static bool bvlc_register_foreign_device(
struct sockaddr_in *sin, /* source address in network order */
uint16_t time_to_live)
{ /* time in seconds */
unsigned i = 0;
bool status = false;
/* am I here already? If so, update my time to live... */
for (i = 0; i < MAX_FD_ENTRIES; i++) {
if (FD_Table[i].valid) {
if ((FD_Table[i].dest_address.s_addr == sin->sin_addr.s_addr) &&
(FD_Table[i].dest_port == sin->sin_port)) {
status = true;
FD_Table[i].time_to_live = time_to_live;
/* 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. */
FD_Table[i].seconds_remaining = time_to_live + 30;
break;
}
}
}
if (!status) {
for (i = 0; i < MAX_FD_ENTRIES; i++) {
if (!FD_Table[i].valid) {
FD_Table[i].dest_address.s_addr = sin->sin_addr.s_addr;
FD_Table[i].dest_port = sin->sin_port;
FD_Table[i].time_to_live = time_to_live;
FD_Table[i].seconds_remaining = time_to_live + 30;
FD_Table[i].valid = true;
status = true;
break;
}
}
}
return status;
}
static bool bvlc_delete_foreign_device(
uint8_t * pdu)
{
struct sockaddr_in sin = { 0 }; /* the ip address */
uint16_t port = 0; /* the decoded port */
bool status = false; /* return value */
unsigned i = 0;
bvlc_decode_bip_address(pdu, &sin.sin_addr, &port);
for (i = 0; i < MAX_FD_ENTRIES; i++) {
if (FD_Table[i].valid) {
if ((FD_Table[i].dest_address.s_addr == sin.sin_addr.s_addr) &&
(FD_Table[i].dest_port == sin.sin_port)) {
FD_Table[i].valid = false;
FD_Table[i].seconds_remaining = 0;
status = true;
break;
}
}
}
return status;
}
static int bvlc_send_mpdu(
struct sockaddr_in *dest, /* the destination address */
uint8_t * mtu, /* the data */
uint16_t mtu_len)
{ /* amount of data to send */
struct sockaddr_in bvlc_dest = { 0 };
/* assumes that the driver has already been initialized */
if (bip_socket() < 0) {
return 0;
}
/* load destination IP address */
bvlc_dest.sin_family = AF_INET;
bvlc_dest.sin_addr.s_addr = dest->sin_addr.s_addr;
bvlc_dest.sin_port = dest->sin_port;
memset(&(bvlc_dest.sin_zero), '\0', 8);
/* Send the packet */
return sendto(bip_socket(), (char *) mtu, mtu_len, 0,
(struct sockaddr *) &bvlc_dest, sizeof(struct sockaddr));
}
static void bvlc_bdt_forward_npdu(
struct sockaddr_in *sin, /* source address in network order */
uint8_t * npdu, /* the NPDU */
uint16_t npdu_length)
{ /* length of the NPDU */
uint8_t mtu[MAX_MPDU] = { 0 };
uint16_t mtu_len = 0;
unsigned i = 0; /* loop counter */
struct sockaddr_in bip_dest = { 0 };
mtu_len =
(uint16_t) bvlc_encode_forwarded_npdu(&mtu[0], sin, npdu, npdu_length);
/* loop through the BDT and send one to each entry, except us */
for (i = 0; i < MAX_BBMD_ENTRIES; i++) {
if (BBMD_Table[i].valid) {
/* 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. */
bip_dest.sin_addr.s_addr =
((~BBMD_Table[i].broadcast_mask.
s_addr) | BBMD_Table[i].dest_address.s_addr);
bip_dest.sin_port = BBMD_Table[i].dest_port;
/* don't send to my broadcast address and same port */
if ((bip_dest.sin_addr.s_addr == bip_get_broadcast_addr())
&& (bip_dest.sin_port == bip_get_port())) {
continue;
}
/* don't send to my ip address and same port */
if ((bip_dest.sin_addr.s_addr == bip_get_addr()) &&
(bip_dest.sin_port == bip_get_port())) {
continue;
}
bvlc_send_mpdu(&bip_dest, mtu, mtu_len);
debug_printf("BVLC: BDT Sent Forwarded-NPDU to %s:%04X\n",
inet_ntoa(bip_dest.sin_addr), ntohs(bip_dest.sin_port));
}
}
return;
}
/* Generate BVLL Forwarded-NPDU message on its local IP subnet using
the local B/IP broadcast address as the destination address. */
static void bvlc_forward_npdu(
struct sockaddr_in *sin, /* source address in network order */
uint8_t * npdu, /* the NPDU */
uint16_t npdu_length)
{ /* length of the NPDU */
uint8_t mtu[MAX_MPDU] = { 0 };
uint16_t mtu_len = 0;
struct sockaddr_in bip_dest = { 0 };
mtu_len =
(uint16_t) bvlc_encode_forwarded_npdu(&mtu[0], sin, npdu, npdu_length);
bip_dest.sin_addr.s_addr = bip_get_broadcast_addr();
bip_dest.sin_port = bip_get_port();
bvlc_send_mpdu(&bip_dest, mtu, mtu_len);
debug_printf("BVLC: Sent Forwarded-NPDU as local broadcast.\n");
}
static void bvlc_fdt_forward_npdu(
struct sockaddr_in *sin, /* source address in network order */
uint8_t * npdu, /* returns the NPDU */
uint16_t max_npdu)
{ /* amount of space available in the NPDU */
uint8_t mtu[MAX_MPDU] = { 0 };
uint16_t mtu_len = 0;
unsigned i = 0; /* loop counter */
struct sockaddr_in bip_dest = { 0 };
mtu_len =
(uint16_t) bvlc_encode_forwarded_npdu(&mtu[0], sin, npdu, max_npdu);
/* loop through the FDT and send one to each entry */
for (i = 0; i < MAX_FD_ENTRIES; i++) {
if (FD_Table[i].valid && FD_Table[i].seconds_remaining) {
bip_dest.sin_addr.s_addr = FD_Table[i].dest_address.s_addr;
bip_dest.sin_port = FD_Table[i].dest_port;
/* don't send to my ip address and same port */
if ((bip_dest.sin_addr.s_addr == bip_get_addr()) &&
(bip_dest.sin_port == bip_get_port())) {
continue;
}
/* don't send to src ip address and same port */
if ((bip_dest.sin_addr.s_addr == sin->sin_addr.s_addr) &&
(bip_dest.sin_port == sin->sin_port)) {
continue;
}
bvlc_send_mpdu(&bip_dest, mtu, mtu_len);
debug_printf("BVLC: FDT Sent Forwarded-NPDU to %s:%04X\n",
inet_ntoa(bip_dest.sin_addr), ntohs(bip_dest.sin_port));
}
}
return;
}
void bvlc_register_with_bbmd(
uint32_t bbmd_address, /* in network byte order */
uint16_t bbmd_port, /* in network byte order */
uint16_t time_to_live_seconds)
{
uint8_t mtu[MAX_MPDU] = { 0 };
uint16_t mtu_len = 0;
/* Store the BBMD address and port so that we
won't broadcast locally. */
Remote_BBMD.sin_addr.s_addr = bbmd_address;
Remote_BBMD.sin_port = bbmd_port;
/* In order for their broadcasts to get here,
we need to register our address with the remote BBMD using
Write Broadcast Distribution Table, or
register with the BBMD as a Foreign Device */
mtu_len =
(uint16_t) bvlc_encode_register_foreign_device(&mtu[0],
time_to_live_seconds);
bvlc_send_mpdu(&Remote_BBMD, &mtu[0], mtu_len);
}
static void bvlc_send_result(
struct sockaddr_in *dest, /* the destination address */
BACNET_BVLC_RESULT result_code)
{
uint8_t mtu[MAX_MPDU] = { 0 };
uint16_t mtu_len = 0;
mtu_len = (uint16_t) bvlc_encode_bvlc_result(&mtu[0], result_code);
bvlc_send_mpdu(dest, mtu, mtu_len);
return;
}
static int bvlc_send_bdt(
struct sockaddr_in *dest)
{
uint8_t mtu[MAX_MPDU] = { 0 };
uint16_t mtu_len = 0;
mtu_len = (uint16_t) bvlc_encode_read_bdt_ack(&mtu[0], sizeof(mtu));
if (mtu_len) {
bvlc_send_mpdu(dest, &mtu[0], mtu_len);
}
return mtu_len;
}
static int bvlc_send_fdt(
struct sockaddr_in *dest)
{
uint8_t mtu[MAX_MPDU] = { 0 };
uint16_t mtu_len = 0;
mtu_len = (uint16_t) bvlc_encode_read_fdt_ack(&mtu[0], sizeof(mtu));
if (mtu_len) {
bvlc_send_mpdu(dest, &mtu[0], mtu_len);
}
return mtu_len;
}
static bool bvlc_bdt_member_mask_is_unicast(
struct sockaddr_in *sin)
{ /* network order address */
bool unicast = false;
unsigned i = 0; /* loop counter */
for (i = 0; i < MAX_BBMD_ENTRIES; i++) {
if (BBMD_Table[i].valid) {
/* find the source address in the table */
if ((BBMD_Table[i].dest_address.s_addr ==
sin->sin_addr.s_addr) &&
(BBMD_Table[i].dest_port == sin->sin_port)) {
/* unicast mask? */
if (BBMD_Table[i].broadcast_mask.s_addr == 0xFFFFFFFFL) {
unicast = true;
break;
}
}
}
}
return unicast;
}
/* returns:
Number of bytes received, or 0 if none or timeout. */
uint16_t bvlc_receive(
BACNET_ADDRESS * src, /* returns the source address */
uint8_t * npdu, /* returns the NPDU */
uint16_t max_npdu, /* amount of space available in the NPDU */
unsigned timeout)
{ /* number of milliseconds to wait for a packet */
uint16_t npdu_len = 0; /* return value */
fd_set read_fds;
int max = 0;
struct timeval select_timeout;
struct sockaddr_in sin = { 0 };
struct sockaddr_in original_sin = { 0 };
struct sockaddr_in dest = { 0 };
socklen_t sin_len = sizeof(sin);
int function_type = 0;
int received_bytes = 0;
uint16_t result_code = 0;
uint16_t i = 0;
bool status = false;
uint16_t time_to_live = 0;
/* Make sure the socket is open */
if (bip_socket() < 0) {
return 0;
}
/* we could just use a non-blocking socket, but that consumes all
the CPU time. We can use a timeout; it is only supported as
a select. */
if (timeout >= 1000) {
select_timeout.tv_sec = timeout / 1000;
select_timeout.tv_usec =
1000 * (timeout - select_timeout.tv_sec * 1000);
} else {
select_timeout.tv_sec = 0;
select_timeout.tv_usec = 1000 * timeout;
}
FD_ZERO(&read_fds);
FD_SET(bip_socket(), &read_fds);
max = bip_socket();
/* see if there is a packet for us */
if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) {
received_bytes =
recvfrom(bip_socket(), (char *) &npdu[0], max_npdu, 0,
(struct sockaddr *) &sin, &sin_len);
} else {
return 0;
}
/* See if there is a problem */
if (received_bytes < 0) {
return 0;
}
/* no problem, just no bytes */
if (received_bytes == 0) {
return 0;
}
/* the signature of a BACnet/IP packet */
if (npdu[0] != BVLL_TYPE_BACNET_IP) {
return 0;
}
function_type = npdu[1];
/* decode the length of the PDU - length is inclusive of BVLC */
(void) decode_unsigned16(&npdu[2], &npdu_len);
/* subtract off the BVLC header */
npdu_len -= 4;
switch (function_type) {
case BVLC_RESULT:
/* Upon receipt of a BVLC-Result message containing a result code
of X'0000' indicating the successful completion of the
registration, a foreign device shall start a timer with a value
equal to the Time-to-Live parameter of the preceding Register-
Foreign-Device message. At the expiration of the timer, the
foreign device shall re-register with the BBMD by sending a BVLL
Register-Foreign-Device message */
/* FIXME: clients may need this result */
(void) decode_unsigned16(&npdu[4], &result_code);
BVLC_Result_Code = (BACNET_BVLC_RESULT) result_code;
debug_printf("BVLC: Result Code=%d\n", BVLC_Result_Code);
/* not an NPDU */
npdu_len = 0;
break;
case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE:
debug_printf("BVLC: Received Write-BDT.\n");
/* Upon receipt of a BVLL Write-Broadcast-Distribution-Table
message, a BBMD shall attempt to create or replace its BDT,
depending on whether or not a BDT has previously existed.
If the creation or replacement of the BDT is successful, the BBMD
shall return a BVLC-Result message to the originating device with
a result code of X'0000'. Otherwise, the BBMD shall return a
BVLC-Result message to the originating device with a result code
of X'0010' indicating that the write attempt has failed. */
status = bvlc_create_bdt(&npdu[4], npdu_len);
if (status) {
bvlc_send_result(&sin, BVLC_RESULT_SUCCESSFUL_COMPLETION);
} else {
bvlc_send_result(&sin,
BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK);
}
/* not an NPDU */
npdu_len = 0;
break;
case BVLC_READ_BROADCAST_DIST_TABLE:
debug_printf("BVLC: Received Read-BDT.\n");
/* Upon receipt of a BVLL Read-Broadcast-Distribution-Table
message, a BBMD shall load the contents of its BDT into a BVLL
Read-Broadcast-Distribution-Table-Ack message and send it to the
originating device. If the BBMD is unable to perform the
read of its BDT, it shall return a BVLC-Result message to the
originating device with a result code of X'0020' indicating that
the read attempt has failed. */
if (bvlc_send_bdt(&sin) <= 0) {
bvlc_send_result(&sin,
BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK);
}
/* not an NPDU */
npdu_len = 0;
break;
case BVLC_READ_BROADCAST_DIST_TABLE_ACK:
debug_printf("BVLC: Received Read-BDT-Ack.\n");
/* FIXME: complete the code for client side read */
/* not an NPDU */
npdu_len = 0;
break;
case BVLC_FORWARDED_NPDU:
/* Upon receipt of a BVLL Forwarded-NPDU message, a BBMD shall
process it according to whether it was received from a peer
BBMD as the result of a directed broadcast or a unicast
transmission. A BBMD may ascertain the method by which Forwarded-
NPDU messages will arrive by inspecting the broadcast distribution
mask field in its own BDT entry since all BDTs are required
to be identical. If the message arrived via directed broadcast,
it was also received by the other devices on the BBMD's subnet. In
this case the BBMD merely retransmits the message directly to each
foreign device currently in the BBMD's FDT. If the
message arrived via a unicast transmission it has not yet been
received by the other devices on the BBMD's subnet. In this case,
the message is sent to the devices on the BBMD's subnet using the
B/IP broadcast address as well as to each foreign device
currently in the BBMD's FDT. A BBMD on a subnet with no other
BACnet devices may omit the broadcast using the B/IP
broadcast address. The method by which a BBMD determines whether
or not other BACnet devices are present is a local matter. */
/* decode the 4 byte original address and 2 byte port */
bvlc_decode_bip_address(&npdu[4], &original_sin.sin_addr,
&original_sin.sin_port);
npdu_len -= 6;
/* Broadcast locally if received via unicast from a BDT member */
if (bvlc_bdt_member_mask_is_unicast(&sin)) {
dest.sin_addr.s_addr = bip_get_broadcast_addr();
dest.sin_port = bip_get_port();
bvlc_send_mpdu(&dest, &npdu[4 + 6], npdu_len);
}
/* use the original addr from the BVLC for src */
dest.sin_addr.s_addr = original_sin.sin_addr.s_addr;
dest.sin_port = original_sin.sin_port;
bvlc_fdt_forward_npdu(&dest, &npdu[4 + 6], npdu_len);
debug_printf("BVLC: Received Forwarded-NPDU from %s:%04X.\n",
inet_ntoa(dest.sin_addr), ntohs(dest.sin_port));
bvlc_internet_to_bacnet_address(src, &dest);
if (npdu_len < max_npdu) {
/* shift the buffer to return a valid PDU */
for (i = 0; i < npdu_len; i++) {
npdu[i] = npdu[4 + 6 + i];
}
} else {
/* ignore packets that are too large */
/* clients should check my max-apdu first */
npdu_len = 0;
}
break;
case BVLC_REGISTER_FOREIGN_DEVICE:
/* 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,
within the period during which the timer is active, another BVLL
Register-Foreign-Device message from the same device is received,
the timer shall be reset and restarted. If the time expires
without the receipt of another BVLL Register-Foreign-Device
message from the same foreign device, the FDT entry for this
device shall be cleared. */
(void) decode_unsigned16(&npdu[4], &time_to_live);
if (bvlc_register_foreign_device(&sin, time_to_live)) {
bvlc_send_result(&sin, BVLC_RESULT_SUCCESSFUL_COMPLETION);
debug_printf("BVLC: Registered a Foreign Device.\n");
} else {
bvlc_send_result(&sin,
BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK);
debug_printf("BVLC: Failed to Register a Foreign Device.\n");
}
/* not an NPDU */
npdu_len = 0;
break;
case BVLC_READ_FOREIGN_DEVICE_TABLE:
debug_printf("BVLC: Received Read-FDT.\n");
/* Upon receipt of a BVLL Read-Foreign-Device-Table message, a
BBMD shall load the contents of its FDT into a BVLL Read-
Foreign-Device-Table-Ack message and send it to the originating
device. If the BBMD is unable to perform the read of its FDT,
it shall return a BVLC-Result message to the originating device
with a result code of X'0040' indicating that the read attempt has
failed. */
if (bvlc_send_fdt(&sin) <= 0) {
bvlc_send_result(&sin,
BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK);
}
/* not an NPDU */
npdu_len = 0;
break;
case BVLC_READ_FOREIGN_DEVICE_TABLE_ACK:
debug_printf("BVLC: Received Read-FDT-Ack.\n");
/* FIXME: complete the code for client side read */
/* not an NPDU */
npdu_len = 0;
break;
case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY:
debug_printf("BVLC: Received Delete-FDT-Entry.\n");
/* Upon receipt of a BVLL Delete-Foreign-Device-Table-Entry
message, a BBMD shall search its foreign device table for an entry
corresponding to the B/IP address supplied in the message. If an
entry is found, it shall be deleted and the BBMD shall return a
BVLC-Result message to the originating device with a result code
of X'0000'. Otherwise, the BBMD shall return a BVLCResult
message to the originating device with a result code of X'0050'
indicating that the deletion attempt has failed. */
if (bvlc_delete_foreign_device(&npdu[4])) {
bvlc_send_result(&sin, BVLC_RESULT_SUCCESSFUL_COMPLETION);
} else {
bvlc_send_result(&sin,
BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK);
}
/* not an NPDU */
npdu_len = 0;
break;
case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK:
debug_printf
("BVLC: Received Distribute-Broadcast-to-Network from %s:%04X.\n",
inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
/* Upon receipt of a BVLL Distribute-Broadcast-To-Network message
from a foreign device, the receiving BBMD shall transmit a
BVLL Forwarded-NPDU message on its local IP subnet using the
local B/IP broadcast address as the destination address. In
addition, a Forwarded-NPDU message shall be sent to each entry
in its BDT as described in the case of the receipt of a
BVLL Original-Broadcast-NPDU as well as directly to each foreign
device currently in the BBMD's FDT except the originating
node. If the BBMD is unable to perform the forwarding function,
it shall return a BVLC-Result message to the foreign device
with a result code of X'0060' indicating that the forwarding
attempt was unsuccessful */
bvlc_forward_npdu(&sin, &npdu[4], npdu_len);
bvlc_bdt_forward_npdu(&sin, &npdu[4], npdu_len);
bvlc_fdt_forward_npdu(&sin, &npdu[4], npdu_len);
/* not an NPDU */
npdu_len = 0;
break;
case BVLC_ORIGINAL_UNICAST_NPDU:
debug_printf("BVLC: Received Original-Unicast-NPDU.\n");
/* ignore messages from me */
if ((sin.sin_addr.s_addr == bip_get_addr()) &&
(sin.sin_port == bip_get_port())) {
npdu_len = 0;
} else {
bvlc_internet_to_bacnet_address(src, &sin);
if (npdu_len < max_npdu) {
/* shift the buffer to return a valid PDU */
for (i = 0; i < npdu_len; i++) {
npdu[i] = npdu[4 + i];
}
} else {
/* ignore packets that are too large */
/* clients should check my max-apdu first */
npdu_len = 0;
}
}
break;
case BVLC_ORIGINAL_BROADCAST_NPDU:
debug_printf("BVLC: Received Original-Broadcast-NPDU.\n");
/* Upon receipt of a BVLL Original-Broadcast-NPDU message,
a BBMD shall construct a BVLL Forwarded-NPDU message and
send it to each IP subnet in its BDT with the exception
of its own. 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. This process
produces either the directed broadcast address of the remote
subnet or the unicast address of the BBMD on that subnet
depending on the contents of the broadcast distribution
mask. See J.4.3.2.. In addition, the received BACnet NPDU
shall be sent directly to each foreign device currently in
the BBMD's FDT also using the BVLL Forwarded-NPDU message. */
bvlc_internet_to_bacnet_address(src, &sin);
if (npdu_len < max_npdu) {
/* shift the buffer to return a valid PDU */
for (i = 0; i < npdu_len; i++) {
npdu[i] = npdu[4 + i];
}
/* if BDT or FDT entries exist, Forward the NPDU */
bvlc_bdt_forward_npdu(&sin, &npdu[0], npdu_len);
bvlc_fdt_forward_npdu(&sin, &npdu[0], npdu_len);
} else {
/* ignore packets that are too large */
npdu_len = 0;
}
break;
default:
break;
}
return npdu_len;
}
/* function to send a packet out the BACnet/IP socket (Annex J) */
/* returns number of bytes sent on success, negative number on failure */
int bvlc_send_pdu(
BACNET_ADDRESS * dest, /* destination address */
BACNET_NPDU_DATA * npdu_data, /* network information */
uint8_t * pdu, /* any data to be sent - may be null */
unsigned pdu_len)
{ /* number of bytes of data */
struct sockaddr_in bvlc_dest = { 0 };
uint8_t mtu[MAX_MPDU] = { 0 };
uint16_t mtu_len = 0;
/* addr and port in network format */
struct in_addr address;
uint16_t port = 0;
uint16_t BVLC_length = 0;
/* bip datalink doesn't need to know the npdu data */
(void) npdu_data;
mtu[0] = BVLL_TYPE_BACNET_IP;
if (dest->net == BACNET_BROADCAST_NETWORK) {
/* if we are a foreign device */
if (Remote_BBMD.sin_port) {
mtu[1] = BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK;
address.s_addr = Remote_BBMD.sin_addr.s_addr;
port = Remote_BBMD.sin_port;
debug_printf("BVLC: Sent Distribute-Broadcast-to-Network.\n");
} else {
address.s_addr = bip_get_broadcast_addr();
port = bip_get_port();
mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
debug_printf("BVLC: Sent Original-Broadcast-NPDU.\n");
}
} else if (dest->mac_len == 6) {
/* valid unicast */
bvlc_decode_bip_address(&dest->mac[0], &address, &port);
mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU;
debug_printf("BVLC: Sent Original-Unicast-NPDU.\n");
} else {
/* invalid address */
return -1;
}
bvlc_dest.sin_addr.s_addr = address.s_addr;
bvlc_dest.sin_port = port;
BVLC_length = (uint16_t) pdu_len + 4 /*inclusive */ ;
mtu_len = 2;
mtu_len += (uint16_t) encode_unsigned16(&mtu[mtu_len], BVLC_length);
memcpy(&mtu[mtu_len], pdu, pdu_len);
mtu_len += (uint16_t) pdu_len;
return bvlc_send_mpdu(&bvlc_dest, mtu, mtu_len);
}
#ifdef TEST
#include <assert.h>
#include <string.h>
#include "ctest.h"
/* copy the source internet address to the BACnet address */
/* FIXME: IPv6? */
static void bvlc_bacnet_to_internet_address(
struct sockaddr_in *sin, /* source address in network order */
BACNET_ADDRESS * src)
{ /* returns the BACnet source address */
if (src && sin) {
if (src->mac_len == 6) {
memcpy(&sin->sin_addr.s_addr, &src->mac[0], 4);
memcpy(&sin->sin_port, &src->mac[4], 2);
}
}
return;
}
void testBIPAddress(
Test * pTest)
{
uint8_t apdu[50] = { 0 };
uint32_t value = 0, test_value = 0;
int len = 0, test_len = 0;
struct in_addr address;
struct in_addr test_address;
uint16_t port = 0, test_port = 0;
address.s_addr = 42;
len = bvlc_encode_bip_address(&apdu[0], &address, port);
test_len = bvlc_decode_bip_address(&apdu[0], &test_address, &test_port);
ct_test(pTest, len == test_len);
ct_test(pTest, address.s_addr == test_address.s_addr);
ct_test(pTest, port == test_port);
}
void testInternetAddress(
Test * pTest)
{
BACNET_ADDRESS src;
BACNET_ADDRESS test_src;
struct sockaddr_in sin = { 0 };
struct sockaddr_in test_sin = { 0 };
sin.sin_port = htons(0xBAC0);
sin.sin_addr.s_addr = inet_addr("192.168.0.1");
bvlc_internet_to_bacnet_address(&src, &sin);
bvlc_bacnet_to_internet_address(&test_sin, &src);
ct_test(pTest, sin.sin_port == test_sin.sin_port);
ct_test(pTest, sin.sin_addr.s_addr == test_sin.sin_addr.s_addr);
}
#ifdef TEST_BVLC
int main(
void)
{
Test *pTest;
bool rc;
pTest = ct_create("BACnet Virtual Link Control", NULL);
/* individual tests */
rc = ct_addTestFunction(pTest, testBIPAddress);
assert(rc);
rc = ct_addTestFunction(pTest, testInternetAddress);
assert(rc);
/* configure output */
ct_setStream(pTest, stdout);
ct_run(pTest);
(void) ct_report(pTest);
ct_destroy(pTest);
return 0;
}
#endif /* TEST_BBMD */
#endif /* TEST */