mirror of
				https://github.com/stargieg/bacnet-stack
				synced 2025-10-26 23:35:52 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			1248 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1248 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**
 | |
| * @file
 | |
| * @author Steve Karg
 | |
| * @date 2016
 | |
| * @brief Simple BACnet/IP to BACnet/IPv6 router
 | |
| *
 | |
| * @section LICENSE
 | |
| *
 | |
| * Copyright (C) 2016 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.
 | |
| *
 | |
| */
 | |
| #include <stddef.h>
 | |
| #include <stdint.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <signal.h>
 | |
| #include <time.h>
 | |
| #include <assert.h>
 | |
| 
 | |
| #include "bacdef.h"
 | |
| #include "config.h"
 | |
| #include "debug.h"
 | |
| #include "bactext.h"
 | |
| #include "bacerror.h"
 | |
| #include "iam.h"
 | |
| #include "arf.h"
 | |
| #include "tsm.h"
 | |
| #include "address.h"
 | |
| #include "npdu.h"
 | |
| #include "apdu.h"
 | |
| #include "client.h"
 | |
| #include "net.h"
 | |
| #include "version.h"
 | |
| 
 | |
| /* our datalink layers */
 | |
| #include "bvlc6.h"
 | |
| #include "bip6.h"
 | |
| #undef MAX_HEADER
 | |
| #undef MAX_MPDU
 | |
| #include "bip.h"
 | |
| #include "bvlc.h"
 | |
| 
 | |
| /**
 | |
| * 6.6.1 Routing Tables
 | |
| *
 | |
| * By definition, a router is a device that is connected to at least
 | |
| * two BACnet networks. Each attachment is through a "port." A
 | |
| * "routing table" consists of the following information for each port:
 | |
| * (a) the MAC address of the port's connection to its network;
 | |
| * (b) the 2-octet network number of the directly connected network;
 | |
| * (c) a list of network numbers reachable through the port along
 | |
| *     with the MAC address of the next router on the path to each
 | |
| *     network number and the reachability status of each such network.
 | |
| *
 | |
| * The "reachability status" is an implementation-dependent value
 | |
| * that indicates whether the associated network is able to
 | |
| * receive traffic. The reachability status shall be able to
 | |
| * distinguish, at a minimum, between "permanent" failures of a route,
 | |
| * such as might result from the failure of a router, and "temporary"
 | |
| * unreachability due to the imposition of a congestion control
 | |
| * restriction.
 | |
| */
 | |
| typedef struct _dnet {
 | |
|     uint8_t mac[MAX_MAC_LEN];
 | |
|     uint8_t mac_len;
 | |
|     uint16_t net;
 | |
|     bool enabled;
 | |
|     struct _dnet *dnets;
 | |
|     struct _dnet *next;
 | |
| } DNET;
 | |
| /* The list of DNETs that our router can reach. */
 | |
| static DNET *Router_Table_Head;
 | |
| /* track our directly connected ports network number */
 | |
| static uint16_t BIP_Net;
 | |
| static uint16_t BIP6_Net;
 | |
| /* buffer for receiving packets from the directly connected ports */
 | |
| static uint8_t BIP_Rx_Buffer[MAX_MPDU];
 | |
| static uint8_t BIP6_Rx_Buffer[MAX_MPDU];
 | |
| /* buffer for transmitting from any port */
 | |
| static uint8_t Tx_Buffer[MAX_MPDU];
 | |
| /* main loop exit control */
 | |
| static bool Exit_Requested;
 | |
| 
 | |
| /**
 | |
|  * Search the router table to find a matching DNET entry
 | |
|  *
 | |
|  * @param net - network number to find a match
 | |
|  * @param addr - address to be filled with remote router address
 | |
|  *
 | |
|  * @return NULL if not found, or a pointer to the directly connected port.
 | |
|  * If addr is not NULL, the DNET entry address is copied to addr
 | |
|  * The caller will need to compare the sought after net with the
 | |
|  * returned port->net to determine if the addr is filled.
 | |
|  */
 | |
| static DNET *dnet_find(
 | |
|     uint16_t net,
 | |
|     BACNET_ADDRESS * addr)
 | |
| {
 | |
| 
 | |
|     DNET *port = Router_Table_Head;
 | |
|     DNET *dnet = NULL;
 | |
|     unsigned int i = 0;
 | |
| 
 | |
|     while (port != NULL) {
 | |
|         if (net == port->net) {
 | |
|             /* DNET is directly connected to the router */
 | |
|             return port;
 | |
|         } else if (port->dnets) {
 | |
|             /* search router ports DNET list */
 | |
|             dnet = port->dnets;
 | |
|             while (dnet != NULL) {
 | |
|                 if (net == dnet->net) {
 | |
|                     if (addr) {
 | |
|                         addr->mac_len = dnet->mac_len;
 | |
|                         for (i = 0; i < MAX_MAC_LEN; i++) {
 | |
|                             addr->mac[i] = dnet->mac[i];
 | |
|                         }
 | |
|                     }
 | |
|                     return port;
 | |
|                 }
 | |
|                 dnet = dnet->next;
 | |
|             }
 | |
|         }
 | |
|         port = port->next;
 | |
|     }
 | |
| 
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| static bool port_find(
 | |
|     uint16_t snet,
 | |
|     BACNET_ADDRESS *addr)
 | |
| {
 | |
|     DNET *port = NULL;
 | |
|     bool found = false;
 | |
|     unsigned int i = 0;
 | |
| 
 | |
|     port = Router_Table_Head;
 | |
|     while (port) {
 | |
|         if (port->net == snet) {
 | |
|             if (addr) {
 | |
|                 addr->mac_len = port->mac_len;
 | |
|                 for (i = 0; i < MAX_MAC_LEN; i++) {
 | |
|                     addr->mac[i] = port->mac[i];
 | |
|                 }
 | |
|             }
 | |
|             found = true;
 | |
|             break;
 | |
|         }
 | |
|         port = port->next;
 | |
|     }
 | |
| 
 | |
|     return found;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add a directly connected port to the router table
 | |
|  *
 | |
|  * @param snet - router port SNET
 | |
|  * @param addr - address of port at the net to be added
 | |
|  */
 | |
| static void port_add(
 | |
|     uint16_t snet,
 | |
|     BACNET_ADDRESS *addr)
 | |
| {
 | |
|     DNET *port = NULL;
 | |
|     DNET *dnet = NULL;
 | |
|     unsigned int i = 0;
 | |
| 
 | |
|     port = dnet_find(snet, NULL);
 | |
|     if (!port) {
 | |
|         port = Router_Table_Head;
 | |
|         if (!port) {
 | |
|             /* create first port */
 | |
|             port = (DNET *) calloc(1, sizeof(DNET));
 | |
|             assert(port);
 | |
|             Router_Table_Head = port;
 | |
|         } else {
 | |
|             while (port) {
 | |
|                 if (port->next) {
 | |
|                     port = port->next;
 | |
|                 } else {
 | |
|                     /* create next port */
 | |
|                     dnet = (DNET *) calloc(1, sizeof(DNET));
 | |
|                     assert(dnet);
 | |
|                     port->next = dnet;
 | |
|                     port = port->next;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         port->net = snet;
 | |
|         if (addr) {
 | |
|             port->mac_len = addr->mac_len;
 | |
|             for (i = 0; i < MAX_MAC_LEN; i++) {
 | |
|                 port->mac[i] = addr->mac[i];
 | |
|             }
 | |
|         } else {
 | |
|             port->mac_len = 0;
 | |
|         }
 | |
|         port->enabled = true;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Add a route to the router table
 | |
|  *
 | |
|  * @param snet - router port SNET
 | |
|  * @param net - net to be added
 | |
|  * @param addr - address of router at the net to be added
 | |
|  */
 | |
| static void dnet_add(
 | |
|     uint16_t snet,
 | |
|     uint16_t net,
 | |
|     BACNET_ADDRESS *addr)
 | |
| {
 | |
| 
 | |
|     DNET *dnet = NULL;
 | |
|     DNET *port = NULL;
 | |
|     DNET *prior_dnet = NULL;
 | |
|     unsigned int i = 0;
 | |
| 
 | |
|     /* make sure NETs are not repeated */
 | |
|     dnet = dnet_find(net, NULL);
 | |
|     if (dnet) {
 | |
|         return;
 | |
|     }
 | |
|     /* start with the source network number table */
 | |
|     port = dnet_find(snet, NULL);
 | |
|     if (!port) {
 | |
|         return;
 | |
|     }
 | |
|     dnet = port->dnets;
 | |
|     if (dnet == NULL) {
 | |
|         /* first DNET to add */
 | |
|         dnet = (DNET *) calloc(1, sizeof(DNET));
 | |
|         assert(dnet);
 | |
|         port->dnets = dnet;
 | |
|         if (addr) {
 | |
|             dnet->mac_len = addr->mac_len;
 | |
|             for (i = 0; i < MAX_MAC_LEN; i++) {
 | |
|                 dnet->mac[i] = addr->mac[i];
 | |
|             }
 | |
|         }
 | |
|         dnet->net = net;
 | |
|         dnet->enabled = true;
 | |
|         dnet->next = NULL;
 | |
|     } else {
 | |
|         while (dnet != NULL) {
 | |
|             if (dnet->net == net) {
 | |
|                 /* make sure NETs are not repeated */
 | |
|                 return;
 | |
|             }
 | |
|             prior_dnet = dnet;
 | |
|             dnet = dnet->next;
 | |
|         }
 | |
|         /* next DNET to add */
 | |
|         dnet = (DNET *) calloc(1, sizeof(DNET));
 | |
|         if (addr) {
 | |
|             dnet->mac_len = addr->mac_len;
 | |
|             for (i = 0; i < MAX_MAC_LEN; i++) {
 | |
|                 dnet->mac[i] = addr->mac[i];
 | |
|             }
 | |
|         }
 | |
|         dnet->net = net;
 | |
|         dnet->enabled = true;
 | |
|         dnet->next = NULL;
 | |
|         prior_dnet->next = dnet;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Free the DNET data of a route
 | |
|  *
 | |
|  * @param dnets - router info to be freed
 | |
|  */
 | |
| static void dnet_cleanup(
 | |
|     DNET * dnets)
 | |
| {
 | |
|     DNET *dnet = dnets;
 | |
|     while (dnet != NULL) {
 | |
|         debug_printf("DNET %u removed\n", (unsigned)dnet->net);
 | |
|         dnet = dnet->next;
 | |
|         free(dnets);
 | |
|         dnets = dnet;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Initialize the a data link broadcast address
 | |
|  *
 | |
|  * @param dest - address to be filled with broadcast designator
 | |
|  */
 | |
| static void datalink_get_broadcast_address(
 | |
|     BACNET_ADDRESS * dest)
 | |
| {
 | |
|     if (dest) {
 | |
|         dest->mac_len = 0;
 | |
|         dest->net = BACNET_BROADCAST_NETWORK;
 | |
|         dest->len = 0;
 | |
|     }
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * function to send a packet out the BACnet/IP and BACnet/IPv6 ports
 | |
|  *
 | |
|  * @param snet - network number of the directly connected port to send
 | |
|  * @param dest - address to where packet is sent
 | |
|  * @param npdu_data - NPCI data to control network destination
 | |
|  * @param pdu - protocol data unit to be sent
 | |
|  * @param pdu_len - number of bytes to send
 | |
|  *
 | |
|  * @return number of bytes sent
 | |
|  */
 | |
| static int datalink_send_pdu(
 | |
|     uint16_t snet,
 | |
|     BACNET_ADDRESS * dest,
 | |
|     BACNET_NPDU_DATA * npdu_data,
 | |
|     uint8_t * pdu,
 | |
|     unsigned int pdu_len)
 | |
| {
 | |
|     int bytes_sent = 0;
 | |
| 
 | |
|     if (snet == 0) {
 | |
|         debug_printf("BVLC/BVLC6 Send to DNET %u\n", (unsigned)dest->net);
 | |
|         bytes_sent = bvlc_send_pdu(dest, npdu_data, pdu, pdu_len);
 | |
|         bytes_sent = bip6_send_pdu(dest, npdu_data, pdu, pdu_len);
 | |
|     } else if (snet == BIP_Net) {
 | |
|         debug_printf("BVLC Send to DNET %u\n", (unsigned)dest->net);
 | |
|         bytes_sent = bvlc_send_pdu(dest, npdu_data, pdu, pdu_len);
 | |
|     } else if (snet == BIP6_Net) {
 | |
|         debug_printf("BVLC6 Send to DNET %u\n",(unsigned)dest->net);
 | |
|         bytes_sent = bip6_send_pdu(dest, npdu_data, pdu, pdu_len);
 | |
|     }
 | |
| 
 | |
|     return bytes_sent;
 | |
| }
 | |
| 
 | |
| /** Initialize an npdu_data structure with given parameters and good defaults,
 | |
|  * and add the Network Layer Message fields.
 | |
|  * The name is a misnomer, as it doesn't do any actual encoding here.
 | |
|  * @see npdu_encode_npdu_data for a simpler version to use when sending an
 | |
|  *           APDU instead of a Network Layer Message.
 | |
|  *
 | |
|  * @param npdu_data [out] Returns a filled-out structure with information
 | |
|  * 					 provided by the other arguments and good defaults.
 | |
|  * @param network_message_type [in] The type of Network Layer Message.
 | |
|  * @param data_expecting_reply [in] True if message should have a reply.
 | |
|  * @param priority [in] One of the 4 priorities defined in section 6.2.2,
 | |
|  *                      like B'11' = Life Safety message
 | |
|  */
 | |
| static void npdu_encode_npdu_network(
 | |
|     BACNET_NPDU_DATA * npdu_data,
 | |
|     BACNET_NETWORK_MESSAGE_TYPE network_message_type,
 | |
|     bool data_expecting_reply,
 | |
|     BACNET_MESSAGE_PRIORITY priority)
 | |
| {
 | |
|     if (npdu_data) {
 | |
|         npdu_data->data_expecting_reply = data_expecting_reply;
 | |
|         npdu_data->protocol_version = BACNET_PROTOCOL_VERSION;
 | |
|         npdu_data->network_layer_message = true;        /* false if APDU */
 | |
|         npdu_data->network_message_type = network_message_type; /* optional */
 | |
|         npdu_data->vendor_id = 0;       /* optional, if net message type is > 0x80 */
 | |
|         npdu_data->priority = priority;
 | |
|         npdu_data->hop_count = HOP_COUNT_DEFAULT;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Broadcast an I-am-router-to-network message
 | |
|  *
 | |
|  * @param snet - the directly connected port network number
 | |
|  * @param dnet - the network number we are saying we are a router to.
 | |
|  * If the dnet is 0, send a broadcast out each port with an
 | |
|  * I-Am-Router-To-Network message containing the network
 | |
|  * numbers of each accessible network except the networks
 | |
|  * reachable via the network on which the broadcast is being made.
 | |
|  */
 | |
| static void send_i_am_router_to_network(uint16_t snet, uint16_t net)
 | |
| {
 | |
|     BACNET_ADDRESS dest;
 | |
|     bool data_expecting_reply = false;
 | |
|     BACNET_NPDU_DATA npdu_data;
 | |
|     int pdu_len = 0;
 | |
|     int len = 0;
 | |
|     DNET *port = NULL;
 | |
|     DNET *dnet = NULL;
 | |
| 
 | |
|     datalink_get_broadcast_address(&dest);
 | |
|     npdu_encode_npdu_network(&npdu_data,
 | |
|         NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK,
 | |
|         data_expecting_reply, MESSAGE_PRIORITY_NORMAL);
 | |
|     /* We don't need src information, since a message can't originate from
 | |
|        our downstream BACnet network. */
 | |
|     pdu_len =
 | |
|         npdu_encode_pdu(&Tx_Buffer[0], &dest, NULL, &npdu_data);
 | |
|     if (net) {
 | |
|         len = encode_unsigned16(&Tx_Buffer[pdu_len], net);
 | |
|         pdu_len += len;
 | |
|     } else {
 | |
|         debug_printf("I-Am-Router-To-Network ");
 | |
|         /*  Each router shall broadcast out each port
 | |
|             an I-Am-Router-To-Network message containing the network
 | |
|             numbers of each accessible network except the networks
 | |
|             reachable via the network on which the broadcast is being made.
 | |
|             This enables routers to build or update their routing table
 | |
|             entries for each of the network numbers contained in the message.
 | |
|         */
 | |
|         port = Router_Table_Head;
 | |
|         while (port != NULL) {
 | |
|             if (port->net != snet) {
 | |
|                 debug_printf("%u,", port->net);
 | |
|                 len = encode_unsigned16(&Tx_Buffer[pdu_len],
 | |
|                     port->net);
 | |
|                 pdu_len += len;
 | |
|                 dnet = port->dnets;
 | |
|                 while (dnet != NULL) {
 | |
|                     debug_printf("%u,", dnet->net);
 | |
|                     len = encode_unsigned16(&Tx_Buffer[pdu_len],
 | |
|                         dnet->net);
 | |
|                     pdu_len += len;
 | |
|                     dnet = dnet->next;
 | |
|                 }
 | |
|             }
 | |
|             port = port->next;
 | |
|         }
 | |
|         debug_printf("from %u\n", snet);
 | |
|     }
 | |
|     datalink_send_pdu(snet, &dest, &npdu_data, &Tx_Buffer[0], pdu_len);
 | |
| }
 | |
| 
 | |
| /** Sends our Routing Table, built from our DNET[] array, as an ACK.
 | |
|  * There are two cases here:
 | |
|  * 1) We are responding to a NETWORK_MESSAGE_INIT_RT_TABLE requesting our table.
 | |
|  *    We will normally broadcast that response.
 | |
|  * 2) We are ACKing the receipt of a NETWORK_MESSAGE_INIT_RT_TABLE containing a
 | |
|  *    routing table, and then we will want to respond to that dst router.
 | |
|  *    In that case, DNET[] should just have one entry of -1 (no routing table
 | |
|  *    is sent).
 | |
|  *
 | |
|  * @param dest [in] If NULL, Ack will be broadcast to the local BACnet network.
 | |
|  *  Optionally may designate a particular router destination,
 | |
|  *  especially when ACKing receipt of this message type.
 | |
|  */
 | |
| void send_initialize_routing_table_ack(
 | |
|     uint8_t snet,
 | |
|     BACNET_ADDRESS * dst)
 | |
| {
 | |
|     BACNET_ADDRESS dest;
 | |
|     bool data_expecting_reply = false;
 | |
|     BACNET_NPDU_DATA npdu_data;
 | |
|     int pdu_len = 0;
 | |
|     int len = 0;
 | |
|     uint8_t count = 0;
 | |
|     uint8_t port_id = 1;
 | |
|     DNET *port = NULL;
 | |
| 
 | |
|     if (dst) {
 | |
|         bacnet_address_copy(&dest, dst);
 | |
|     } else {
 | |
|         datalink_get_broadcast_address(&dest);
 | |
|     }
 | |
|     npdu_encode_npdu_network(&npdu_data,
 | |
|         NETWORK_MESSAGE_INIT_RT_TABLE_ACK,
 | |
|         data_expecting_reply, MESSAGE_PRIORITY_NORMAL);
 | |
|     /* We don't need src information, since a message can't originate from
 | |
|        our downstream BACnet network. */
 | |
|     pdu_len =
 | |
|         npdu_encode_pdu(&Tx_Buffer[0], &dest, NULL, &npdu_data);
 | |
|     /* First, count the number of Ports we will encode */
 | |
|     port = Router_Table_Head;
 | |
|     while (port != NULL) {
 | |
|         count++;
 | |
|         port = port->next;
 | |
|     }
 | |
|     Tx_Buffer[pdu_len] = count;
 | |
|     pdu_len++;
 | |
|     if (count > 0) {
 | |
|         /* Now encode each BACNET_ROUTER_PORT.
 | |
|          * We will simply use a positive index for PortID,
 | |
|          * and have no PortInfo.
 | |
|          */
 | |
|         port = Router_Table_Head;
 | |
|         while (port != NULL) {
 | |
|             len = encode_unsigned16(&Tx_Buffer[pdu_len], port->net);
 | |
|             pdu_len += len;
 | |
|             Tx_Buffer[pdu_len] = port_id;
 | |
|             pdu_len++;
 | |
|             port_id++;
 | |
|             Tx_Buffer[pdu_len] = 0;
 | |
|             pdu_len++;
 | |
|             port = port->next;
 | |
|         }
 | |
|     }
 | |
|     /* Now send the message */
 | |
|     datalink_send_pdu(snet, &dest, &npdu_data, &Tx_Buffer[0], pdu_len);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sends a reject network message
 | |
|  *
 | |
|  * @param snet [in] Which BACnet network orginated the message.
 | |
|  * @param dst [in] If NULL, request will be broadcast to the local BACnet
 | |
|  *                 network.  Otherwise, designates a particular router
 | |
|  *                 destination.
 | |
|  * @param reject_reason [in] One of the BACNET_NETWORK_REJECT_REASONS codes.
 | |
|  */
 | |
| void send_reject_message_to_network(
 | |
|     uint16_t snet,
 | |
|     BACNET_ADDRESS * dst,
 | |
|     uint8_t reject_reason,
 | |
|     uint16_t dnet)
 | |
| {
 | |
|     BACNET_ADDRESS dest;
 | |
|     bool data_expecting_reply = false;
 | |
|     BACNET_NPDU_DATA npdu_data;
 | |
|     int pdu_len = 0;
 | |
|     int len = 0;
 | |
| 
 | |
|     if (dst) {
 | |
|         bacnet_address_copy(&dest, dst);
 | |
|     } else {
 | |
|         datalink_get_broadcast_address(&dest);
 | |
|     }
 | |
|     npdu_encode_npdu_network(&npdu_data,
 | |
|         NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK,
 | |
|         data_expecting_reply, MESSAGE_PRIORITY_NORMAL);
 | |
|     /* We don't need src information, since a message can't originate from
 | |
|        our downstream BACnet network. */
 | |
|     pdu_len =
 | |
|         npdu_encode_pdu(&Tx_Buffer[0], &dest, NULL, &npdu_data);
 | |
|     /* encode the reject reason */
 | |
|     Tx_Buffer[pdu_len] = reject_reason;
 | |
|     pdu_len++;
 | |
|     if (dnet) {
 | |
|         len = encode_unsigned16(&Tx_Buffer[pdu_len], dnet);
 | |
|         pdu_len += len;
 | |
|     }
 | |
|     /* Now send the message */
 | |
|     datalink_send_pdu(snet, &dest, &npdu_data, &Tx_Buffer[0], pdu_len);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sends a who-is-router-to-network message
 | |
|  *
 | |
|  * @param dnet [in] Which BACnet network we are seeking
 | |
|  */
 | |
| static void send_who_is_router_to_network(
 | |
|     uint16_t snet,
 | |
|     uint16_t dnet)
 | |
| {
 | |
|     BACNET_ADDRESS dest;
 | |
|     bool data_expecting_reply = false;
 | |
|     BACNET_NPDU_DATA npdu_data;
 | |
|     int pdu_len = 0;
 | |
|     int len = 0;
 | |
| 
 | |
|     datalink_get_broadcast_address(&dest);
 | |
|     npdu_encode_npdu_network(&npdu_data,
 | |
|         NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK,
 | |
|         data_expecting_reply, MESSAGE_PRIORITY_NORMAL);
 | |
|     pdu_len =
 | |
|         npdu_encode_pdu(&Tx_Buffer[0], &dest, NULL, &npdu_data);
 | |
|     if (dnet) {
 | |
|         len = encode_unsigned16(&Tx_Buffer[pdu_len], dnet);
 | |
|         pdu_len += len;
 | |
|     }
 | |
|     /* Now send the message to port */
 | |
|     datalink_send_pdu(snet, &dest, &npdu_data, &Tx_Buffer[0], pdu_len);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Handler to manage the Who-Is-Router-To-Network Message
 | |
|  *
 | |
|  * 6.6.3.2 Who-Is-Router-To-Network
 | |
|  *
 | |
|  * When a router receives a Who-Is-Router-To-Network
 | |
|  * message specifying a particular network number,
 | |
|  * it shall search its routing table for the network number
 | |
|  * contained in the message. If the specified network number
 | |
|  * is found in its table and the port through which it is
 | |
|  * reachable is not the port from which the
 | |
|  * Who-Is-Router-To-Network message was received, the
 | |
|  * router shall construct an I-Am-Router-To-Network message
 | |
|  * containing the specified network number and send it to the node
 | |
|  * that generated the request using a broadcast MAC address,
 | |
|  * thus allowing other nodes on this network to take
 | |
|  * advantage of the routing information.
 | |
|  *
 | |
|  * If the network number is not found in the routing table, the router
 | |
|  * shall attempt to discover the next router on the path to the
 | |
|  * indicated destination network by generating a Who-Is-Router-To-Network
 | |
|  * message containing the specified destination
 | |
|  * network number and broadcasting it out all its ports other
 | |
|  * than the one from which the Who-Is-Router-To-Network message
 | |
|  * arrived. Two cases are possible. In case one the received
 | |
|  * Who-Is-Router-To-Network message was from the originating
 | |
|  * device. For this case, the router shall add SNET and SADR
 | |
|  * fields before broadcasting the subsequent Who-Is-Router-To-
 | |
|  * Network. This permits an I-Could-Be-Router-To-Network message
 | |
|  * to be directed to the originating device. The second case
 | |
|  * is that the received Who-Is-Router-To-Network message came
 | |
|  * from another router and it already contains SNET and SADR
 | |
|  * fields. For this case, the SNET and SADR shall be retained
 | |
|  * in the newly generated Who-Is-Router-To-Network message.
 | |
|  *
 | |
|  * If the Who-Is-Router-To-Network message does not specify a
 | |
|  * particular destination network number, the router shall
 | |
|  * construct an I-Am-Router-To-Network message containing a
 | |
|  * list of all the networks it is able to reach through other than the
 | |
|  * port from which the Who-Is-Router-To-Network message was
 | |
|  * received and transmit it in the same manner as described
 | |
|  * above. The message shall list all networks not flagged as
 | |
|  * permanently unreachable, including those that are temporarily
 | |
|  * unreachable due to the imposition of congestion control restrictions.
 | |
|  * Networks that may be reachable through a PTP
 | |
|  * connection shall be listed only if the connection is currently established.
 | |
|  *
 | |
|  * @param snet [in] source network port number
 | |
|  * @param src  [in] The routing source information, if any.
 | |
|  *  If src->net and src->len are 0, there is no routing source information.
 | |
|  * @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 who_is_router_to_network_handler(
 | |
|     uint16_t snet,
 | |
|     BACNET_ADDRESS * src,
 | |
|     BACNET_NPDU_DATA * npdu_data,
 | |
|     uint8_t * npdu,
 | |
|     uint16_t npdu_len)
 | |
| {
 | |
|     DNET *port = NULL;
 | |
|     uint16_t network = 0;
 | |
|     uint16_t len = 0;
 | |
| 
 | |
|     if (npdu) {
 | |
|         if (npdu_len >= 2) {
 | |
|             len += decode_unsigned16(&npdu[len], &network);
 | |
|             port = dnet_find(network, NULL);
 | |
|             if (port) {
 | |
|                 /* found in my list! */
 | |
|                 if (port->net != snet) {
 | |
|                     /* reachable not through the port this message received */
 | |
|                     send_i_am_router_to_network(snet, network);
 | |
|                 }
 | |
|             } else {
 | |
|                 /* discover the next router on the path to the network */
 | |
|                 port = Router_Table_Head;
 | |
|                 while (port) {
 | |
|                     if (port->net != snet) {
 | |
|                         send_who_is_router_to_network(port->net, network);
 | |
|                     }
 | |
|                     port = port->next;
 | |
|                 }
 | |
|             }
 | |
|         } else {
 | |
|             send_i_am_router_to_network(snet, 0);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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.
 | |
|  *
 | |
|  * @param snet [in] source network port number
 | |
|  * @param src  [in] The routing source information, if any.
 | |
|  *  If src->net and src->len are 0, there is no routing source information.
 | |
|  * @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(
 | |
|     uint16_t snet,
 | |
|     BACNET_ADDRESS * src,
 | |
|     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;
 | |
|     const char *msg_name = NULL;
 | |
| 
 | |
|     msg_name = bactext_network_layer_msg_name(npdu_data->network_message_type);
 | |
|     fprintf(stderr, "Received %s\n", msg_name);
 | |
|     switch (npdu_data->network_message_type) {
 | |
|         case NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK:
 | |
|             who_is_router_to_network_handler(
 | |
|                 snet, src, npdu_data, npdu, npdu_len);
 | |
|             break;
 | |
|         case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK:
 | |
|             /* add its DNETs to our routing table */
 | |
|             fprintf(stderr, "for Networks: ");
 | |
|             while (npdu_len) {
 | |
|                 len = decode_unsigned16(&npdu[npdu_offset], &dnet);
 | |
|                 fprintf(stderr, "%hu", dnet);
 | |
|                 dnet_add(snet, dnet, src);
 | |
|                 npdu_len -= len;
 | |
|                 npdu_offset += len;
 | |
|                 if (npdu_len) {
 | |
|                     fprintf(stderr, ", ");
 | |
|                 }
 | |
|             }
 | |
|             fprintf(stderr, ".\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);
 | |
|                 fprintf(stderr, "for Network:%hu\n", dnet);
 | |
|                 switch (npdu[0]) {
 | |
|                     case 0:
 | |
|                         fprintf(stderr, "Reason: Other Error.\n");
 | |
|                         break;
 | |
|                     case 1:
 | |
|                         fprintf(stderr, "Reason: Network unreachable.\n");
 | |
|                         break;
 | |
|                     case 2:
 | |
|                         fprintf(stderr, "Reason: Network is busy.\n");
 | |
|                         break;
 | |
|                     case 3:
 | |
|                         fprintf(stderr, "Reason: Unknown network message type.\n");
 | |
|                         break;
 | |
|                     case 4:
 | |
|                         fprintf(stderr, "Reason: Message too long.\n");
 | |
|                         break;
 | |
|                     case 5:
 | |
|                         fprintf(stderr, "Reason: Security Error.\n");
 | |
|                         break;
 | |
|                     case 6:
 | |
|                         fprintf(stderr, "Reason: Invalid address length.\n");
 | |
|                         break;
 | |
|                     default:
 | |
|                         fprintf(stderr, "Reason: %u\n", (unsigned int)npdu[0]);
 | |
|                         break;
 | |
|                 }
 | |
|             }
 | |
|             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(snet, NULL);
 | |
|                 } else {
 | |
|                     /* they sent us a list */
 | |
|                     int net_count = npdu[0];
 | |
|                     while (net_count--) {
 | |
|                         int i = 1;
 | |
|                         /* DNET */
 | |
|                         decode_unsigned16(&npdu[i], &dnet);
 | |
|                         /* update routing table */
 | |
|                         dnet_add(snet, dnet, src);
 | |
|                         if (npdu[i + 3] > 0) {
 | |
|                             /* find next NET value */
 | |
|                             i = npdu[i + 3] + 4;
 | |
|                         } else {
 | |
|                             i += 4;
 | |
|                         }
 | |
|                     }
 | |
|                     send_initialize_routing_table_ack(snet, NULL);
 | |
|                 }
 | |
|                 break;
 | |
|             }
 | |
|             break;
 | |
|         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(snet, src,
 | |
|                 NETWORK_REJECT_UNKNOWN_MESSAGE_TYPE, 0);
 | |
|             break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Fill the router src address with this port router, router network number,
 | |
|  * and the original src address.
 | |
|  *
 | |
|  * @param router_src [in] The src BACNET_ADDRESS for this routed message.
 | |
|  * @param snet [in] The source network port where the message came from
 | |
|  * @param src [in] The BACNET_ADDRESS of the message's original src.
 | |
|  */
 | |
| static void routed_src_address(
 | |
|     BACNET_ADDRESS * router_src,
 | |
|     uint16_t snet,
 | |
|     BACNET_ADDRESS * src)
 | |
| {
 | |
|     unsigned int i = 0;
 | |
| 
 | |
|     if (router_src && src) {
 | |
|         /* copy our directly connected port address */
 | |
|         if (port_find(snet, router_src)) {
 | |
|             if (src->net) {
 | |
|                 /* from a router - add router our table */
 | |
|                 dnet_add(snet, src->net, src);
 | |
|                 /* the routed address stays the same */
 | |
|                 router_src->net = src->net;
 | |
|                 router_src->len = src->len;
 | |
|                 for (i = 0; i < MAX_MAC_LEN; i++) {
 | |
|                     router_src->adr[i] = src->adr[i];
 | |
|                 }
 | |
|             } else {
 | |
|                 /* from our directly connected port */
 | |
|                 router_src->net = snet;
 | |
|                 router_src->len = src->mac_len;
 | |
|                 for (i = 0; i < MAX_MAC_LEN; i++) {
 | |
|                     router_src->adr[i] = src->mac[i];
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * If a BACnet NPDU is received with NPCI indicating that the message
 | |
|  * should be relayed by virtue of the presence of a non-broadcast
 | |
|  * DNET, the router shall search its routing table for the indicated
 | |
|  * network number. Normal routing procedures are described in 6.5.
 | |
|  * If, however, the network number cannot be found in the routing
 | |
|  * table or through the use of the Who-Is-Router-To-Network message,
 | |
|  * the router shall generate a Reject-Message-To-Network message and
 | |
|  * send it to the node that originated the BACnet NPDU.
 | |
|  * If the NPCI indicates either a remote or global broadcast,
 | |
|  * the message shall be processed as described in 6.3.2.
 | |
|  *
 | |
|  * @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(
 | |
|     uint16_t snet,
 | |
|     BACNET_NPDU_DATA * npdu,
 | |
|     BACNET_ADDRESS * src,
 | |
|     BACNET_ADDRESS * dest,
 | |
|     uint8_t * apdu,
 | |
|     uint16_t apdu_len)
 | |
| {
 | |
|     DNET *port = NULL;
 | |
|     BACNET_ADDRESS local_dest;
 | |
|     BACNET_ADDRESS remote_dest;
 | |
|     BACNET_ADDRESS router_src;
 | |
|     int npdu_len = 0;
 | |
| 
 | |
|     /* for broadcast messages no search is needed */
 | |
|     if (dest->net == BACNET_BROADCAST_NETWORK) {
 | |
|         /*  A global broadcast, indicated by a DNET of X'FFFF', is sent
 | |
|             to all networks through all routers. Upon receipt of a message
 | |
|             with the global broadcast DNET network number, a router shall
 | |
|             decrement the Hop Count. If the Hop Count is still greater
 | |
|             than zero, then the router shall broadcast the message on all
 | |
|             directly connected networks except the network of origin, using
 | |
|             the broadcast MAC address appropriate for each destination network.
 | |
|             If the Hop Count is zero, then the router shall discard
 | |
|             the message. In order for the message to be disseminated globally,
 | |
|             the originating device shall use a broadcast MAC address
 | |
|             on the originating network so that all attached routers may
 | |
|             receive the message and propagate it further. */
 | |
|         datalink_get_broadcast_address(&local_dest);
 | |
|         npdu->hop_count--;
 | |
|         routed_src_address(&router_src, snet, src);
 | |
|         /* encode both source and destination for broadcast */
 | |
|         npdu_len =
 | |
|             npdu_encode_pdu(&Tx_Buffer[0], &local_dest, &router_src, npdu);
 | |
|         memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
 | |
|         /* send to my other ports */
 | |
|         debug_printf("Routing a BROADCAST from %u\n", (unsigned)snet);
 | |
|         port = Router_Table_Head;
 | |
|         while (port != NULL) {
 | |
|             if (port->net != snet) {
 | |
|                 datalink_send_pdu(port->net, &local_dest, npdu,
 | |
|                     &Tx_Buffer[0], npdu_len+apdu_len);
 | |
|             }
 | |
|             port = port->next;
 | |
|         }
 | |
|         return;
 | |
|     }
 | |
|     port = dnet_find(dest->net, &remote_dest);
 | |
|     if (port) {
 | |
|         if (port->net == dest->net) {
 | |
|             debug_printf("Routing to Port %u\n", (unsigned)dest->net);
 | |
|             /*  Case 1: the router is directly
 | |
|                 connected to the network referred to by DNET. */
 | |
|             /*  In the first case, DNET, DADR, and Hop
 | |
|                 Count shall be removed from the NPCI and the message shall be
 | |
|                 sent directly to the destination device with DA set equal to
 | |
|                 DADR. The control octet shall be adjusted accordingly to
 | |
|                 indicate only the presence of SNET and SADR. */
 | |
|             memmove(&local_dest.mac, dest->adr, MAX_MAC_LEN);
 | |
|             local_dest.mac_len = dest->len;
 | |
|             local_dest.net = 0;
 | |
|             npdu->hop_count--;
 | |
|             routed_src_address(&router_src, snet, src);
 | |
|             npdu_len =
 | |
|                 npdu_encode_pdu(&Tx_Buffer[0], &local_dest, &router_src, npdu);
 | |
|             memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
 | |
|             datalink_send_pdu(port->net, &local_dest, npdu,
 | |
|                 &Tx_Buffer[0], npdu_len+apdu_len);
 | |
|         } else {
 | |
|             debug_printf("Routing to another Router %u\n",
 | |
|                 (unsigned)remote_dest.net);
 | |
|             /*  Case 2: the message must be
 | |
|                 relayed to another router for further transmission */
 | |
|             /*  In the second case, if the Hop Count is greater than zero,
 | |
|                 the message shall be sent to the next router on the
 | |
|                 path to the destination network.
 | |
|                 If the Hop Count is zero, then the message shall be
 | |
|                 discarded. */
 | |
|             npdu->hop_count--;
 | |
|             routed_src_address(&router_src, snet, src);
 | |
|             npdu_len =
 | |
|                 npdu_encode_pdu(&Tx_Buffer[0], &remote_dest, &router_src, npdu);
 | |
|             memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
 | |
|             datalink_send_pdu(port->net, &remote_dest, npdu,
 | |
|                 &Tx_Buffer[0], npdu_len+apdu_len);
 | |
|         }
 | |
|     } else if (dest->net) {
 | |
|         debug_printf("Routing to Unknown Route %u\n",
 | |
|             (unsigned)dest->net);
 | |
|         /* Case 3: a global broadcast is required. */
 | |
|         dest->mac_len = 0;
 | |
|         npdu->hop_count--;
 | |
|         /* encode both source and destination */
 | |
|         routed_src_address(&router_src, snet, src);
 | |
|         npdu_len =
 | |
|             npdu_encode_pdu(&Tx_Buffer[0], dest, &router_src, npdu);
 | |
|         memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
 | |
|         datalink_send_pdu(port->net, dest, npdu,
 | |
|             &Tx_Buffer[0], npdu_len+apdu_len);
 | |
|         /*  If the next router is unknown, an attempt shall be made to
 | |
|             identify it using a Who-Is-Router-To-Network message. */
 | |
|         send_who_is_router_to_network(0, dest->net);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Handler for the routing packets only
 | |
|  *
 | |
|  * @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.
 | |
|  * @param DNET_list [in] List of our reachable downstream BACnet Network
 | |
|  *  numbers 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.
 | |
|  */
 | |
| static void my_routing_npdu_handler(
 | |
|     uint16_t snet,
 | |
|     BACNET_ADDRESS * src,
 | |
|     uint8_t * pdu,
 | |
|     uint16_t pdu_len)
 | |
| {
 | |
|     int apdu_offset = 0;
 | |
|     BACNET_ADDRESS dest = { 0 };
 | |
|     BACNET_NPDU_DATA npdu_data = { 0 };
 | |
| 
 | |
|     if (!pdu) {
 | |
|         /* no packet */
 | |
|     } else if (pdu[0] == BACNET_PROTOCOL_VERSION) {
 | |
|         apdu_offset = npdu_decode(&pdu[0], &dest, src, &npdu_data);
 | |
|         if (apdu_offset <= 0) {
 | |
|             fprintf(stderr, "NPDU: Decoding failed; Discarded!\n");
 | |
|         } else if (npdu_data.network_layer_message) {
 | |
|             if ((dest.net == 0) || (dest.net == BACNET_BROADCAST_NETWORK)) {
 | |
|                 network_control_handler(snet, src, &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 > 0) && (apdu_offset <= pdu_len)) {
 | |
|             if ((dest.net == 0) ||
 | |
|                 (dest.net == BACNET_BROADCAST_NETWORK) ||
 | |
|                 (npdu_data.hop_count > 1)) {
 | |
|                 /* only handle the version that we know how to handle */
 | |
|                 /* and we are not a router, so ignore messages with
 | |
|                    routing information cause they are not for us */
 | |
|                 if ((dest.net == BACNET_BROADCAST_NETWORK) &&
 | |
|                     ((pdu[apdu_offset] & 0xF0) ==
 | |
|                         PDU_TYPE_CONFIRMED_SERVICE_REQUEST)) {
 | |
|                     /* hack for 5.4.5.1 - IDLE */
 | |
|                     /* ConfirmedBroadcastReceived */
 | |
|                     /* then enter IDLE - ignore the PDU */
 | |
|                 } else {
 | |
|                     routed_apdu_handler(snet, &npdu_data, src, &dest,
 | |
|                         &pdu[apdu_offset],
 | |
|                         (uint16_t) (pdu_len - apdu_offset));
 | |
|                 }
 | |
|             } else {
 | |
|                 fprintf(stderr, "NPDU: DNET=%u.  Discarded!\n",
 | |
|                 (unsigned) dest.net);
 | |
|             }
 | |
|         }
 | |
|     } else {
 | |
|         /* unsupported protocol version */
 | |
|     }
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Initialize the BACnet/IPv6 and BACnet/IP data links
 | |
|  */
 | |
| static void datalink_init(void)
 | |
| {
 | |
|     char *pEnv = NULL;
 | |
|     BACNET_ADDRESS my_address = {0};
 | |
|     extern bool BIP_Debug;
 | |
| 
 | |
|     /* BACnet/IP Initialization */
 | |
|     BIP_Debug = true;
 | |
|     pEnv = getenv("BACNET_IP_PORT");
 | |
|     if (pEnv) {
 | |
|         bip_set_port(htons((uint16_t) strtol(pEnv, NULL, 0)));
 | |
|     } else {
 | |
|         /* BIP_Port is statically initialized to 0xBAC0,
 | |
|          * so if it is different, then it was programmatically altered,
 | |
|          * and we shouldn't just stomp on it here.
 | |
|          * Unless it is set below 1024, since:
 | |
|          * "The range for well-known ports managed by the IANA is 0-1023."
 | |
|          */
 | |
|         if (ntohs(bip_get_port()) < 1024) {
 | |
|             bip_set_port(htons(0xBAC0));
 | |
|         }
 | |
|     }
 | |
|     if (!bip_init(getenv("BACNET_IFACE"))) {
 | |
|         exit(1);
 | |
|     }
 | |
|     atexit(bip_cleanup);
 | |
|     /* BACnet/IPv6 Initialization */
 | |
|     pEnv = getenv("BACNET_BIP6_PORT");
 | |
|     if (pEnv) {
 | |
|         bip6_set_port((uint16_t) strtol(pEnv, NULL, 0));
 | |
|     }
 | |
|     pEnv = getenv("BACNET_BIP6_BROADCAST");
 | |
|     if (pEnv) {
 | |
|         BACNET_IP6_ADDRESS addr;
 | |
|         bvlc6_address_set(&addr,
 | |
|                 (uint16_t) strtol(pEnv, NULL, 0), 0, 0, 0, 0, 0, 0,
 | |
|                 BIP6_MULTICAST_GROUP_ID);
 | |
|         bip6_set_broadcast_addr(&addr);
 | |
|     }
 | |
|     if (!bip6_init(getenv("BACNET_BIP6_IFACE"))) {
 | |
|         exit(1);
 | |
|     }
 | |
|     atexit(bip6_cleanup);
 | |
|     /* router network numbers */
 | |
|     pEnv = getenv("BACNET_IP_NET");
 | |
|     if (pEnv) {
 | |
|         BIP_Net = strtol(pEnv, NULL, 0);
 | |
|     } else {
 | |
|         BIP_Net = 1;
 | |
|     }
 | |
|     /* configure the first entry in the table - home port */
 | |
|     bip_get_my_address(&my_address);
 | |
|     port_add(BIP_Net, &my_address);
 | |
|     /* BACnet/IPv6 network */
 | |
|     pEnv = getenv("BACNET_IP6_NET");
 | |
|     if (pEnv) {
 | |
|         BIP6_Net = strtol(pEnv, NULL, 0);
 | |
|     } else {
 | |
|         BIP6_Net = 2;
 | |
|     }
 | |
|     /* configure the next entry in the table */
 | |
|     bip6_get_my_address(&my_address);
 | |
|     port_add(BIP6_Net, &my_address);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Cleanup memory
 | |
|  *
 | |
|  */
 | |
| static void cleanup(void)
 | |
| {
 | |
|     DNET *port = NULL;
 | |
| 
 | |
|     fprintf(stderr, "Cleaning up...\n");
 | |
|     /* clean up the remote networks */
 | |
|     port = Router_Table_Head;
 | |
|     while (port != NULL) {
 | |
|         dnet_cleanup(port->dnets);
 | |
|         port = port->next;
 | |
|     }
 | |
|     /* clean up the directly connected networks */
 | |
|     dnet_cleanup(Router_Table_Head);
 | |
| }
 | |
| 
 | |
| #if defined(_WIN32)
 | |
| static BOOL WINAPI CtrlCHandler(
 | |
|     DWORD dwCtrlType)
 | |
| {
 | |
|     dwCtrlType = dwCtrlType;
 | |
| 
 | |
|     /* signal to main loop to exit */
 | |
|     Exit_Requested = true;
 | |
|     while (Exit_Requested) {
 | |
|         Sleep(100);
 | |
|     }
 | |
|     exit(0);
 | |
| 
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| void control_c_hooks(void)
 | |
| {
 | |
|     SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
 | |
|     SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlCHandler, TRUE);
 | |
| }
 | |
| #else
 | |
| static void sig_int(
 | |
|     int signo)
 | |
| {
 | |
|     (void) signo;
 | |
|     Exit_Requested = true;
 | |
|     exit(0);
 | |
| }
 | |
| 
 | |
| void signal_init(
 | |
|     void)
 | |
| {
 | |
|     signal(SIGINT, sig_int);
 | |
|     signal(SIGHUP, sig_int);
 | |
|     signal(SIGTERM, sig_int);
 | |
| }
 | |
| 
 | |
| void control_c_hooks(void)
 | |
| {
 | |
|     signal_init();
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|  * Main function of simple router demo.
 | |
|  *
 | |
|  * @param argc [in] Arg count.
 | |
|  * @param argv [in] Takes one argument: the Device Instance #.
 | |
|  * @return 0 on success.
 | |
|  */
 | |
| int main(
 | |
|     int argc,
 | |
|     char *argv[])
 | |
| {
 | |
|     BACNET_ADDRESS src = {
 | |
|         0
 | |
|     };  /* address where message came from */
 | |
|     uint16_t pdu_len = 0;
 | |
|     time_t last_seconds = 0;
 | |
|     time_t current_seconds = 0;
 | |
|     uint32_t elapsed_seconds = 0;
 | |
| 
 | |
|     printf("BACnet Simple IP Router Demo\n");
 | |
|     printf("BACnet Stack Version %s\n", BACnet_Version);
 | |
|     datalink_init();
 | |
|     atexit(cleanup);
 | |
|     control_c_hooks();
 | |
|     /* configure the timeout values */
 | |
|     last_seconds = time(NULL);
 | |
|     /* broadcast an I-Am on startup */
 | |
|     printf("BACnet/IP Network: %u\n", (unsigned)BIP_Net);
 | |
|     send_i_am_router_to_network(BIP_Net, 0);
 | |
|     printf("BACnet/IPv6 Network: %u\n", (unsigned)BIP6_Net);
 | |
|     send_i_am_router_to_network(BIP6_Net, 0);
 | |
|     /* loop forever */
 | |
|     for (;;) {
 | |
|         /* input */
 | |
|         current_seconds = time(NULL);
 | |
|         /* returns 0 bytes on timeout */
 | |
|         pdu_len = bvlc_receive(&src, &BIP_Rx_Buffer[0], MAX_MPDU, 5);
 | |
|         /* process */
 | |
|         if (pdu_len) {
 | |
|             debug_printf("BACnet/IP Received packet\n");
 | |
|             my_routing_npdu_handler(BIP_Net, &src, &BIP_Rx_Buffer[0], pdu_len);
 | |
|         }
 | |
|         /* returns 0 bytes on timeout */
 | |
|         pdu_len = bip6_receive(&src, &BIP6_Rx_Buffer[0], MAX_MPDU, 5);
 | |
|         /* process */
 | |
|         if (pdu_len) {
 | |
|             debug_printf("BACnet/IPv6 Received packet\n");
 | |
|             my_routing_npdu_handler(BIP6_Net, &src, &BIP6_Rx_Buffer[0], pdu_len);
 | |
|         }
 | |
|         /* at least one second has passed */
 | |
|         elapsed_seconds = (uint32_t) (current_seconds - last_seconds);
 | |
|         if (elapsed_seconds) {
 | |
|             last_seconds = current_seconds;
 | |
|             bvlc_maintenance_timer(elapsed_seconds);
 | |
|         }
 | |
|         if (Exit_Requested) {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     /* tell signal interrupts we are done */
 | |
|     Exit_Requested = false;
 | |
| 
 | |
|     return 0;
 | |
| }
 | 
