Files
bacnet_stack/src/bacnet/basic/npdu/h_routed_npdu.c
T
Steve Karg eedfa58a55 Feature/refactor bacnet ipv4 add unit tests (#64)
* refactor BACnet/IPv4 BVLC into encode-decode library with unit tests
Added Read-Broadcast-Distribution-Table encoding and unit test.
Added Read-Broadcast-Distribution-Table-Ack encoding and unit test.
Added Read-Foreign-Device-Table-Ack encoding and unit test.
Added some BDT/FDT diff copy functions with unit tests
Added some FDT add and delete entry functions with unit tests
Added some BDT set append and clear entry functions with unit tests
Added some BIPv4 address conversion functions with unit tests
Added non-BBMD handling unit test
Added basic unit test for BBMD handler
Added BBMD broadcast mask get set and unit tests
Added IPv6 maintenance timer.
Added ReadFDT app
Fixed ReadBDT app
Added Who-Is to Makefile for individual app build
Fixed debugging code blocks projects by swapping bip.c for h_bbmd.c module.
Ported BACnet/IPv4 to refactored BVLC for Linux, BSD, Windows
Fix datalink debug for DLENV module
Improve BIPv4 linux driver debug info
Added BDT mask functions
Reduce debug info clutter in Who-Is app by using environment option
Fix TTL seconds upper bounds addition
Fix CIDR prefix calculation on Linux BIPv4.
Convert BSD BIPv4 to BVLCv4
Fix CMake build for BIPv4 (Linux, BSD, Windows)
Added [U]nsigned to 0xBAC0 constants
Cleanup POSIX and Win32 API sockets
Remove unnecessary file scope variable initialization
Fix routed NPDU to depend on datalink; fix warning
Remove OS dependent network code from gateway
Enable BBMD client in library by default
Co-authored-by: Steve Karg <skarg@users.sourceforge.net>
Co-authored-by: Steve Karg <steve.karg@legrand.us>
2020-04-04 11:31:54 -05:00

301 lines
13 KiB
C

/**************************************************************************
*
* Copyright (C) 2010 Steve Karg <skarg@users.sourceforge.net>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*********************************************************************/
/* Acknowledging the contribution of code and ideas used here that
* came from Paul Chapman's vmac demo project. */
#include <stdbool.h>
#include <stdint.h>
#include "bacnet/bacdef.h"
#include "bacnet/bacdcode.h"
#include "bacnet/bacint.h"
#include "bacnet/bacenum.h"
#include "bacnet/bits.h"
#include "bacnet/npdu.h"
#include "bacnet/apdu.h"
#include "bacnet/bactext.h"
#include "bacnet/basic/object/device.h"
#include "bacnet/basic/sys/debug.h"
#include "bacnet/basic/services.h"
#include "bacnet/datalink/datalink.h"
#if PRINT_ENABLED
#include <stdio.h>
#endif
/** @file h_routed_npdu.c Handles messages at the NPDU level of the BACnet
* stack, including routing and network control messages. */
/** Handler to manage the Network Layer Control Messages received in a packet.
* This handler is called if the NCPI bit 7 indicates that this packet is a
* network layer message and there is no further DNET to pass it to.
* The NCPI has already been decoded into the npdu_data structure.
* @ingroup MISCHNDLR
*
* @param src [in] The routing source information, if any.
* If src->net and src->len are 0, there is no
* routing source information.
* @param DNET_list [in] List of our reachable downstream BACnet Network
* numbers. Normally just one valid entry; terminated with a -1 value.
* @param npdu_data [in] Contains a filled-out structure with information
* decoded from the NCPI and other NPDU
* bytes.
* @param npdu [in] Buffer containing the rest of the NPDU, following the
* bytes that have already been decoded.
* @param npdu_len [in] The length of the remaining NPDU message in npdu[].
*/
static void network_control_handler(BACNET_ADDRESS *src,
int *DNET_list,
BACNET_NPDU_DATA *npdu_data,
uint8_t *npdu,
uint16_t npdu_len)
{
uint16_t npdu_offset = 0;
uint16_t dnet = 0;
uint16_t len = 0;
switch (npdu_data->network_message_type) {
case NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK:
/* Send I-am-router-to-network with our one-network list if
* our specific network is requested, or no specific
* network is requested. Silently drop other DNET requests.
*/
if (npdu_len >= 2) {
uint16_t network;
len += decode_unsigned16(&npdu[len], &network);
if (network == DNET_list[0]) {
Send_I_Am_Router_To_Network(DNET_list);
}
} else {
Send_I_Am_Router_To_Network(DNET_list);
}
break;
case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK:
/* Per the standard, we are supposed to process this message and
* add its DNETs to our routing table.
* However, since we only have one upstream port that these
* messages can come from and replies go to, it doesn't seem
* to provide us any value to do this; when we need to send to
* some remote device, we will start by pushing it out the
* upstream port and let the attached router(s) take it from there.
* Consequently, we'll do nothing interesting here.
* -- Unless we act upon NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK
* later for congestion control - then it could matter.
*/
debug_printf("%s for Networks: ",
bactext_network_layer_msg_name(
NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK));
while (npdu_len >= 2) {
len = decode_unsigned16(&npdu[npdu_offset], &dnet);
debug_printf("%hu", dnet);
npdu_len -= len;
npdu_offset += len;
if (npdu_len >= 2) {
debug_printf(", ");
}
}
debug_printf("\n");
break;
case NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK:
/* Do nothing, same as previous case. */
break;
case NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK:
if (npdu_len >= 3) {
decode_unsigned16(&npdu[1], &dnet);
debug_printf("Received %s for Network: ",
bactext_network_layer_msg_name(
NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK));
debug_printf("%hu, Reason code: %d \n", dnet, npdu[0]);
}
break;
case NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK:
case NETWORK_MESSAGE_ROUTER_AVAILABLE_TO_NETWORK:
/* Do nothing - don't support upstream traffic congestion control */
break;
case NETWORK_MESSAGE_INIT_RT_TABLE:
/* If sent with Number of Ports == 0, we respond with
* NETWORK_MESSAGE_INIT_RT_TABLE_ACK and a list of all our
* reachable networks.
*/
if (npdu_len > 0) {
/* If Number of Ports is 0, broadcast our "full" table */
if (npdu[0] == 0) {
Send_Initialize_Routing_Table_Ack(NULL, DNET_list);
} else {
/* If they sent us a list, just politely ACK it
* with no routing list of our own. But we don't DO
* anything with the info, either.
*/
int listTerminator = -1;
Send_Initialize_Routing_Table_Ack(src, &listTerminator);
}
break;
}
/* Else, fall through to do nothing. */
case NETWORK_MESSAGE_INIT_RT_TABLE_ACK:
/* Do nothing with the routing table info, since don't support
* upstream traffic congestion control */
break;
case NETWORK_MESSAGE_ESTABLISH_CONNECTION_TO_NETWORK:
case NETWORK_MESSAGE_DISCONNECT_CONNECTION_TO_NETWORK:
/* Do nothing - don't support PTP half-router control */
break;
default:
/* An unrecognized message is bad; send an error response. */
Send_Reject_Message_To_Network(
src, NETWORK_REJECT_UNKNOWN_MESSAGE_TYPE, DNET_list[0]);
/* Sending our DNET doesn't make a lot of sense, does it? */
break;
}
}
/** An APDU pre-handler that makes sure that the subsequent APDU handler call
* operates on the right Device Object(s), as addressed by the destination
* (routing) information.
*
* @note Even when the destination is "routed" to our virtual BACnet network,
* the src information does not need to change to reflect that (as it normally
* would for a routed message) because the reply will be sent from the level
* of the gateway Device.
*
* @param src [in] The BACNET_ADDRESS of the message's source.
* @param dest [in] The BACNET_ADDRESS of the message's destination.
* @param DNET_list [in] List of our reachable downstream BACnet Network
* numbers. Normally just one valid entry; terminated with a -1 value.
* @param apdu [in] The apdu portion of the request, to be processed.
* @param apdu_len [in] The total (remaining) length of the apdu.
*/
static void routed_apdu_handler(BACNET_ADDRESS *src,
BACNET_ADDRESS *dest,
int *DNET_list,
uint8_t *apdu,
uint16_t apdu_len)
{
int cursor = 0; /* Starting hint */
bool bGotOne = false;
if (!Routed_Device_Is_Valid_Network(dest->net, DNET_list)) {
/* We don't know how to reach this one.
* The protocol doesn't specifically state this, but if this message
* was broadcast to us, we should assume "someone else" is handling
* it and not get involved (ie, send a Reject-Message).
* Since we can't reach other routers that src couldn't already reach,
* we don't try the standard path of asking Who-Is-Router-to-Network. */
#if defined(BACDL_BIP)
/* If wasn't unicast to us, must have been one of the bcast types.
* Drop it. */
if (bvlc_get_function_code() != BVLC_ORIGINAL_UNICAST_NPDU) {
return;
}
#endif
/* Upper level handlers knew that this was sent as a bcast,
* but our only other way to guess at that here is if the dest->adr
* is absent, then we know this is some sort of bcast.
*/
if (dest->len > 0) {
Send_Reject_Message_To_Network(
src, NETWORK_REJECT_NO_ROUTE, dest->net);
} /* else, silently drop it */
return;
}
while (Routed_Device_GetNext(dest, DNET_list, &cursor)) {
apdu_handler(src, apdu, apdu_len);
bGotOne = true;
if (cursor < 0) { /* If no more matches, */
break; /* We don't need to keep looking */
}
}
if (!bGotOne) {
/* Just silently drop this packet. */
}
}
/** Handler for the NPDU portion of a received packet, which may have routing.
* This is a fuller handler than the regular npdu_handler, as it manages
* - Decoding of the NCPI byte
* - Further processing by network_control_handler() if this is a network
* layer message.
* - Further processing by routed_apdu_handler() if it contains an APDU
* - Normally (no routing) by apdu_handler()
* - With Routing (a further destination was indicated) by the decoded
* destination.
* - Errors in decoding.
* @note The npdu_data->data_expecting_reply status is discarded.
* @see npdu_handler
* @ingroup NMRC
*
* @param src [out] Returned with routing source information if the NPDU
* has any and if this points to non-null storage for it.
* If src->net and src->len are 0 on return, there is no
* routing source information.
* This src describes the original source of the message when
* it had to be routed to reach this BACnet Device, and this
* is passed down into the apdu_handler; however, I don't
* think this project's code has any use for the src info
* on return from this handler, since the response has
* already been sent via the apdu_handler.
* @param DNET_list [in] List of our reachable downstream BACnet Network
* numbers. Normally just one valid entry; terminated with a -1 value.
* @param pdu [in] Buffer containing the NPDU and APDU of the received packet.
* @param pdu_len [in] The size of the received message in the pdu[] buffer.
*/
void routing_npdu_handler(
BACNET_ADDRESS *src, int *DNET_list, uint8_t *pdu, uint16_t pdu_len)
{
int apdu_offset = 0;
BACNET_ADDRESS dest = { 0 };
BACNET_NPDU_DATA npdu_data = { 0 };
/* only handle the version that we know how to handle */
if (pdu[0] == BACNET_PROTOCOL_VERSION) {
apdu_offset = npdu_decode(&pdu[0], &dest, src, &npdu_data);
if (apdu_offset <= 0) {
debug_printf("NPDU: Decoding failed; Discarded!\n");
} else if (npdu_data.network_layer_message) {
if ((dest.net == 0) || (dest.net == BACNET_BROADCAST_NETWORK)) {
network_control_handler(src, DNET_list, &npdu_data,
&pdu[apdu_offset], (uint16_t)(pdu_len - apdu_offset));
} else {
/* The DNET is set, but we don't support downstream routers,
* so we just silently drop this network layer message,
* since only routers can handle it (even if for our DNET) */
}
} else if (apdu_offset <= pdu_len) {
if ((dest.net == 0) || (npdu_data.hop_count > 1)) {
routed_apdu_handler(src, &dest, DNET_list, &pdu[apdu_offset],
(uint16_t)(pdu_len - apdu_offset));
}
/* Else, hop_count bottomed out and we discard this one. */
}
} else {
/* Should we send NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK? */
debug_printf(
"NPDU: Unsupported BACnet Protocol Version=%u. Discarded!\n",
(unsigned)pdu[0]);
}
return;
}