From b3896ea4f4a09cc3ea5477771edce3e0f66fdd35 Mon Sep 17 00:00:00 2001 From: skarg Date: Tue, 16 Oct 2007 01:24:22 +0000 Subject: [PATCH] Added more functionality to BVLC (untested and uncompiled), and corrected BIP. --- bacnet-stack/src/bip.c | 5 +- bacnet-stack/src/bvlc.c | 418 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 383 insertions(+), 40 deletions(-) diff --git a/bacnet-stack/src/bip.c b/bacnet-stack/src/bip.c index a192c4ec..8dcd5294 100644 --- a/bacnet-stack/src/bip.c +++ b/bacnet-stack/src/bip.c @@ -215,9 +215,10 @@ uint16_t bip_receive(BACNET_ADDRESS * src, /* source address */ if ((pdu[1] == BVLC_ORIGINAL_UNICAST_NPDU) || (pdu[1] == BVLC_ORIGINAL_BROADCAST_NPDU)) { /* ignore messages from me */ - if (sin.sin_addr.s_addr == htonl(BIP_Address.s_addr)) + if ((sin.sin_addr.s_addr == htonl(BIP_Address.s_addr)) && + (sin.sin_port == htonl(BIP_Port))) { pdu_len = 0; - else { + } else { /* copy the source address FIXME: IPv6? */ src->mac_len = 6; diff --git a/bacnet-stack/src/bvlc.c b/bacnet-stack/src/bvlc.c index a53a4dd6..bf6333c1 100644 --- a/bacnet-stack/src/bvlc.c +++ b/bacnet-stack/src/bvlc.c @@ -85,6 +85,9 @@ typedef struct #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; + void bvlc_maintenance_timer(unsigned seconds) { unsigned i = 0; @@ -225,6 +228,42 @@ int bvlc_encode_read_bdt_ack_init( return len; } +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; +} + + int bvlc_encode_forwarded_npdu(uint8_t * pdu, BACNET_ADDRESS * src, uint8_t * npdu, @@ -255,7 +294,6 @@ int bvlc_encode_forwarded_npdu(uint8_t * pdu, return len; } - int bvlc_encode_register_foreign_device(uint8_t * pdu, uint16_t time_to_live_seconds) { @@ -268,7 +306,7 @@ int bvlc_encode_register_foreign_device(uint8_t * pdu, 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[2], time_to_live_seconds); + encode_unsigned16(&pdu[4], time_to_live_seconds); len = 6; } @@ -312,6 +350,45 @@ int bvlc_encode_read_fdt_ack_init( return len; } +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; + + 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; + encode_unsigned16(&pdu[pdu_len], FD_Table[i].time_to_live); + pdu_len += len; + encode_unsigned16(&pdu[pdu_len], FD_Table[i].seconds_remaining); + pdu_len += len; + } + } + + return pdu_len; +} + + int bvlc_encode_delete_fdt_entry(uint8_t * pdu, struct in_addr *address, uint16_t port) @@ -326,8 +403,8 @@ int bvlc_encode_delete_fdt_entry(uint8_t * pdu, length field itself, most significant octet first. */ encode_unsigned16(&pdu[2], 10); /* FDT Entry */ - encode_unsigned32(&pdu[0], address->s_addr); - encode_unsigned16(&pdu[4], port); + encode_unsigned32(&pdu[4], address->s_addr); + encode_unsigned16(&pdu[8], port); len = 10; } @@ -450,6 +527,95 @@ void bvlc_bacnet_to_internet_address( return; } +bool bvlc_create_bdt( + uint8_t * npdu, + uint16_t npdu_length) +{ + bool status = false; + struct in_addr dest_address; + uint16_t dest_port; + unsigned i = 0; + + for (i = 0; i < MAX_BBMD_ENTRIES; i++) { + if (npdu_length >= 10) { + BBMD_Table[i].valid = true; + decode_unsigned32(&pdu[0], &dest_address.s_addr); + BBMD_Table[i].dest_address.s_addr = ntohl(dest_address.s_addr); + decode_unsigned16(&pdu[4], &dest_port); + BBMD_Table[i].dest_port = ntohs(dest_port); + decode_unsigned32(&pdu[6], &dest_address.s_addr); + BBMD_Table[i].broadcast_mask.s_addr = ntohl(dest_address.s_addr); + npdu_length -= 10; + } 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 = 0; + } + } + /* did they all fit? */ + if (npdu_length < 10) { + status = true; + } +} + +bool bvlc_register_foreign_device( + struct sockaddr_in *sin, /* the source address */ + 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 == ntohl(sin->sin_addr.s_addr)) && + (FD_Table[i].dest_port == ntohs(sin->sin_port))) { + status = true; + FD_Table[i].time_to_live = time_to_live; + 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 = ntohl(sin->sin_addr.s_addr); + FD_Table[i].dest_port = ntohs(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; +} + +bool bvlc_delete_foreign_device(uint8_t * pdu) +{ + struct sockaddr_in sin; /* the ip address */ + uint16_t port; /* the decoded port */ + bool status = false; /* return value */ + + bvlc_decode_bip_address(pdu, &sin, &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; +} + void bvlc_bdt_forward_npdu( struct sockaddr_in *sin, /* the source address */ uint8_t * npdu, /* the NPDU */ @@ -547,6 +713,85 @@ void bvlc_fdt_forward_npdu( return; } +void bvlc_send_mpdu( + struct sockaddr_in *dest, /* the destination address */ + uint8_t * mtu, /* the data */ + uint16_t mtu_len) /* amount of data to send */ +{ + int bytes_sent = 0; + unsigned i = 0; /* loop counter */ + struct sockaddr_in bvlc_dest; + BACNET_ADDRESS src; + + /* assumes that the driver has already been initialized */ + if (bip_socket() < 0) { + return; + } + /* 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; + /* Send the packet */ + bytes_sent = + sendto(bip_socket(), (char *) mtu, mtu_len, 0, + (struct sockaddr *) &bvlc_dest, + sizeof(struct sockaddr)); + + return; +} + +void bvlc_send_result( + struct sockaddr_in *dest, /* the destination address */ + BACNET_BVLC_RESULT result_code) +{ + uint8_t mtu[MAX_MPDU] = {0}; + int mtu_len = 0; + + mtu_len = bvlc_encode_bvlc_result(&mtu[0], result_code); + bvlc_send_mpdu(dest, mtu, mtu_len); + + return; +} + +int bvlc_send_bdt(struct sockaddr_in *dest) +{ + uint8_t mtu[MAX_MPDU] = {0}; + int mtu_len = 0; + + mtu_len = bvlc_encode_read_bdt_ack(&mtu[0], sizeof(mtu)); + if (mtu_len) { + bvlc_send_mpdu(&sin, &mtu[0], mtu_len); + } + + return mtu_len; +} + +int bvlc_send_fdt(struct sockaddr_in *dest) +{ + uint8_t mtu[MAX_MPDU] = {0}; + int mtu_len = 0; + + mtu_len = bvlc_encode_read_fdt_ack(&mtu[0], sizeof(mtu)); + if (mtu_len) { + bvlc_send_mpdu(dest, &mtu[0], mtu_len); + } + + return mtu_len; +} + +bool bvlc_broadcast_address_same( + struct sockaddr_in *sin) /* network order address */ +{ + bool same = false; + + if ((sin->sin_addr.s_addr == htonl(bip_get_broadcast_addr())) && + (sin->sin_port == htons(bip_get_port()))) { + same = true; + } + + return same; +} + /* returns: Number of bytes received, or 0 if none or timeout. */ uint16_t bvlc_receive( @@ -560,10 +805,14 @@ uint16_t bvlc_receive( int max = 0; struct timeval select_timeout; struct sockaddr_in sin = { -1 }; + struct sockaddr_in dest = { -1 }; socklen_t sin_len = sizeof(sin); int function_type = 0; int received_bytes = 0; + uint16_t result_code = 0; unsigned i =0; + bool status = false; + uint16_t time_to_live = 0; /* Make sure the socket is open */ if (bip_socket() < 0) { @@ -613,58 +862,151 @@ uint16_t bvlc_receive( 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 = result_code; break; case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE: /* 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. */ + 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); + } break; case BVLC_READ_BROADCAST_DISTRIBUTION_TABLE: + /* 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); + } break; case BVLC_READ_BROADCAST_DISTRIBUTION_TABLE_ACK: + /* FIXME: complete the code for client side read */ 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. */ - bvlc_broadcast_npdu(&sin, &npdu[4], npdu_len); + 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. */ + /* if this was received via Broadcast, don't broadcast it */ + + /* FIXME: how do I know if I received a unicast or broadcast? */ + if (!bvlc_broadcast_address_same(&sin)) { + dest.sin_addr.s_addr = htonl(bip_get_broadcast_addr()); + dest.sin_port == htons(bip_get_port()); + bvlc_send_mpdu(&dest, &npdu[4], npdu_len); + } bvlc_fdt_forward_npdu(&sin, &npdu[4], npdu_len); 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); + } else { + bvlc_send_result(&sin, + BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK); + } break; case BVLC_READ_FOREIGN_DEVICE_TABLE: + /* 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); + } break; case BVLC_READ_FOREIGN_DEVICE_TABLE_ACK: + /* FIXME: complete the code for client side read */ break; case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY: + /* 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); + } break; case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK: + /* 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 above 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*/ + /* FIXME: complete the function code */ bvlc_broadcast_forward_npdu(&sin, &npdu[4], npdu_len); bvlc_fdt_forward_npdu(&sin, &npdu[4], npdu_len); break; case BVLC_ORIGINAL_UNICAST_NPDU: /* ignore messages from me */ - if (sin.sin_addr.s_addr == htonl(bip_get_addr())) { + if ((sin.sin_addr.s_addr == htonl(bip_get_addr())) && + (sin.sin_port == htons(bip_get_port()))) { npdu_len = 0; } else { bvlc_internet_to_bacnet_address(src, &sin); @@ -734,19 +1076,19 @@ int bvlc_send_pdu(BACNET_ADDRESS * dest, /* destination address */ return BIP_Socket; mtu[0] = BVLL_TYPE_BACNET_IP; - bip_dest.sin_family = AF_INET; + bvlc_dest.sin_family = AF_INET; if (dest->net == BACNET_BROADCAST_NETWORK) { /* broadcast */ - bip_dest.sin_addr.s_addr = htonl(BIP_Broadcast_Address.s_addr); - bip_dest.sin_port = htons(BIP_Port); - memset(&(bip_dest.sin_zero), '\0', 8); + bvlc_dest.sin_addr.s_addr = htonl(BIP_Broadcast_Address.s_addr); + bvlc_dest.sin_port = htons(BIP_Port); + memset(&(bvlc_dest.sin_zero), '\0', 8); mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; } else if (dest->mac_len == 6) { /* valid unicast */ (void) decode_unsigned32(&dest->mac[0], - &(bip_dest.sin_addr.s_addr)); - (void) decode_unsigned16(&dest->mac[4], &(bip_dest.sin_port)); - memset(&(bip_dest.sin_zero), '\0', 8); + &(bvlc_dest.sin_addr.s_addr)); + (void) decode_unsigned16(&dest->mac[4], &(bvlc_dest.sin_port)); + memset(&(bvlc_dest.sin_zero), '\0', 8); mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU; } else { /* invalid address */ @@ -762,7 +1104,7 @@ int bvlc_send_pdu(BACNET_ADDRESS * dest, /* destination address */ /* Send the packet */ bytes_sent = sendto(BIP_Socket, (char *) mtu, mtu_len, 0, - (struct sockaddr *) &bip_dest, sizeof(struct sockaddr)); + (struct sockaddr *) &bvlc_dest, sizeof(struct sockaddr)); return bytes_sent; }