mirror of
				https://github.com/stargieg/bacnet-stack
				synced 2025-10-26 23:35:52 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			338 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**************************************************************************
 | |
| *
 | |
| * Copyright (C) 2005 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 <errno.h>
 | |
| #include <string.h>
 | |
| #include "config.h"
 | |
| #include "txbuf.h"
 | |
| #include "bacdef.h"
 | |
| #include "bacdcode.h"
 | |
| #include "address.h"
 | |
| #include "tsm.h"
 | |
| #include "npdu.h"
 | |
| #include "apdu.h"
 | |
| #include "device.h"
 | |
| #include "datalink.h"
 | |
| #include "bactext.h"
 | |
| /* some demo stuff needed */
 | |
| #include "handlers.h"
 | |
| #include "txbuf.h"
 | |
| #include "client.h"
 | |
| #include "debug.h"
 | |
| 
 | |
| /** @file s_router.c  Methods to send various BACnet Router Network Layer Messages. */
 | |
| 
 | |
| /** 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;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Function to encode and send any supported Network Layer Message.
 | |
|  * The payload for the message is encoded from information in the iArgs[] array.
 | |
|  * The contents of iArgs are are, per message type:
 | |
|  * - NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK: Single int for DNET requested
 | |
|  * - NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK: Array of DNET(s) to send,
 | |
|  * 		terminated with -1
 | |
|  * - NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK: array of 2 ints,
 | |
|  *      first is reason, second is DNET of interest
 | |
|  * - NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK: same as I-Am-Router msg
 | |
|  * - NETWORK_MESSAGE_ROUTER_AVAILABLE_TO_NETWORK: same as I-Am-Router msg
 | |
|  * - NETWORK_MESSAGE_INIT_RT_TABLE and NETWORK_MESSAGE_INIT_RT_TABLE_ACK:
 | |
|  *      Array of DNET(s) to process as "Ports", terminated with -1.  Each DNET
 | |
|  *      will be expanded to a BACNET_ROUTER_PORT (with simple defaults for
 | |
|  *      most fields) and encoded.
 | |
|  *
 | |
|  * @param network_message_type [in] The type of message to be sent.
 | |
|  * @param dst [in/out] If not NULL, contains the destination for the message.
 | |
|  * @param iArgs [in] An optional array of values whose meaning depends on
 | |
|  *                   the type of message.
 | |
|  * @return Number of bytes sent, or <=0 if no message was sent.
 | |
|  */
 | |
| int Send_Network_Layer_Message(
 | |
|     BACNET_NETWORK_MESSAGE_TYPE network_message_type,
 | |
|     BACNET_ADDRESS * dst,
 | |
|     int *iArgs)
 | |
| {
 | |
|     int len = 0;
 | |
|     int pdu_len = 0;
 | |
|     int bytes_sent = 0;
 | |
|     int *pVal = iArgs;  /* Start with first value */
 | |
|     bool data_expecting_reply = false;
 | |
|     BACNET_NPDU_DATA npdu_data;
 | |
|     BACNET_ADDRESS bcastDest;
 | |
| 
 | |
|     if (iArgs == NULL)
 | |
|         return 0;       /* Can't do anything here */
 | |
| 
 | |
|     /* If dst was NULL, get our (local net) broadcast MAC address. */
 | |
|     if (dst == NULL) {
 | |
|         datalink_get_broadcast_address(&bcastDest);
 | |
|         dst = &bcastDest;
 | |
|     }
 | |
| 
 | |
|     if (network_message_type == NETWORK_MESSAGE_INIT_RT_TABLE)
 | |
|         data_expecting_reply = true;    /* DER in this one case */
 | |
|     npdu_encode_npdu_network(&npdu_data, network_message_type,
 | |
|         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(&Handler_Transmit_Buffer[0], dst, NULL, &npdu_data);
 | |
| 
 | |
|     /* Now encode the optional payload bytes, per message type */
 | |
|     switch (network_message_type) {
 | |
|         case NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK:
 | |
|             if (*pVal >= 0) {
 | |
|                 len =
 | |
|                     encode_unsigned16(&Handler_Transmit_Buffer[pdu_len],
 | |
|                     (uint16_t) * pVal);
 | |
|                 pdu_len += len;
 | |
|             }
 | |
|             /* else, don't encode a DNET */
 | |
|             break;
 | |
| 
 | |
|         case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK:
 | |
|         case NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK:
 | |
|         case NETWORK_MESSAGE_ROUTER_AVAILABLE_TO_NETWORK:
 | |
|             while (*pVal >= 0) {
 | |
|                 len =
 | |
|                     encode_unsigned16(&Handler_Transmit_Buffer[pdu_len],
 | |
|                     (uint16_t) * pVal);
 | |
|                 pdu_len += len;
 | |
|                 pVal++;
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK:
 | |
|             /* Encode the Reason byte, then the DNET */
 | |
|             Handler_Transmit_Buffer[pdu_len++] = (uint8_t) * pVal;
 | |
|             pVal++;
 | |
|             len =
 | |
|                 encode_unsigned16(&Handler_Transmit_Buffer[pdu_len],
 | |
|                 (uint16_t) * pVal);
 | |
|             pdu_len += len;
 | |
|             break;
 | |
| 
 | |
|         case NETWORK_MESSAGE_INIT_RT_TABLE:
 | |
|         case NETWORK_MESSAGE_INIT_RT_TABLE_ACK:
 | |
|             /* First, count the number of Ports we will encode */
 | |
|             len = 0;    /* Re-purpose len as our counter here */
 | |
|             while (*pVal >= 0) {
 | |
|                 len++;
 | |
|                 pVal++;
 | |
|             }
 | |
|             Handler_Transmit_Buffer[pdu_len++] = (uint8_t) len;
 | |
| 
 | |
|             if (len > 0) {
 | |
|                 uint8_t portID = 1;
 | |
|                 pVal = iArgs;   /* Reset to beginning */
 | |
|                 /* Now encode each (virtual) BACNET_ROUTER_PORT.
 | |
|                  * We will simply use a positive index for PortID,
 | |
|                  * and have no PortInfo.
 | |
|                  */
 | |
|                 while (*pVal >= 0) {
 | |
|                     len =
 | |
|                         encode_unsigned16(&Handler_Transmit_Buffer[pdu_len],
 | |
|                         (uint16_t) * pVal);
 | |
|                     pdu_len += len;
 | |
|                     Handler_Transmit_Buffer[pdu_len++] = portID++;
 | |
|                     Handler_Transmit_Buffer[pdu_len++] = 0;
 | |
|                     debug_printf("  Sending Routing Table entry for %u \n",
 | |
|                         *pVal);
 | |
|                     pVal++;
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             debug_printf("Not sent: %s message unsupported \n",
 | |
|                 bactext_network_layer_msg_name(network_message_type));
 | |
|             return 0;
 | |
|             break;      /* Will never reach this line */
 | |
|     }
 | |
| 
 | |
|     if (dst != NULL)
 | |
|         debug_printf("Sending %s message to BACnet network %u \n",
 | |
|             bactext_network_layer_msg_name(network_message_type), dst->net);
 | |
|     else
 | |
|         debug_printf("Sending %s message to local BACnet network \n",
 | |
|             bactext_network_layer_msg_name(network_message_type));
 | |
| 
 | |
|     /* Now send the message */
 | |
|     bytes_sent =
 | |
|         datalink_send_pdu(dst, &npdu_data, &Handler_Transmit_Buffer[0],
 | |
|         pdu_len);
 | |
| #if PRINT_ENABLED
 | |
|     if (bytes_sent <= 0) {
 | |
|         int wasErrno = errno;   /* preserve the errno */
 | |
|         debug_printf("Failed to send %s message (%s)!\n",
 | |
|             bactext_network_layer_msg_name(network_message_type),
 | |
|             strerror(wasErrno));
 | |
|     }
 | |
| #endif
 | |
|     return bytes_sent;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Finds a specific router, or all reachable BACnet networks.
 | |
|  * The response(s) will come in I-am-router-to-network message(s).
 | |
|  * @ingroup NMRC
 | |
|  *
 | |
|  * @param dst [in] If NULL, request will be broadcast to the local BACnet
 | |
|  *                 network.  Optionally may designate a particular router
 | |
|  *                 destination to respond.
 | |
|  * @param dnet [in] Which BACnet network to request for; if -1, no DNET
 | |
|  *                 will be sent and the receiving router(s) will send
 | |
|  *                 their full list of reachable BACnet networks.
 | |
|  */
 | |
| void Send_Who_Is_Router_To_Network(
 | |
|     BACNET_ADDRESS * dst,
 | |
|     int dnet)
 | |
| {
 | |
|     Send_Network_Layer_Message(NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK, dst,
 | |
|         &dnet);
 | |
| }
 | |
| 
 | |
| /** Broadcast an I-am-router-to-network message, giving the list of networks
 | |
|  * we can reach.
 | |
|  * The message will be sent to our normal DataLink Layer interface,
 | |
|  * not the routed backend.
 | |
|  * @ingroup NMRC
 | |
|  *
 | |
|  * @param DNET_list [in] List of BACnet network numbers for which I am a router,
 | |
|  *                       terminated with -1
 | |
|  */
 | |
| void Send_I_Am_Router_To_Network(
 | |
|     const int DNET_list[])
 | |
| {
 | |
|     /* Use a NULL dst here since we want a broadcast MAC address. */
 | |
|     Send_Network_Layer_Message(NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK, NULL,
 | |
|         (int *) DNET_list);
 | |
| }
 | |
| 
 | |
| /** Finds a specific router, or all reachable BACnet networks.
 | |
|  * The response(s) will come in I-am-router-to-network message(s).
 | |
|  * @ingroup NMRC
 | |
|  *
 | |
|  * @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.
 | |
|  * @param dnet [in] Which BACnet network orginated the message.
 | |
|  */
 | |
| void Send_Reject_Message_To_Network(
 | |
|     BACNET_ADDRESS * dst,
 | |
|     uint8_t reject_reason,
 | |
|     int dnet)
 | |
| {
 | |
|     int iArgs[2];
 | |
|     iArgs[0] = reject_reason;
 | |
|     iArgs[1] = dnet;
 | |
|     Send_Network_Layer_Message(NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK, dst,
 | |
|         iArgs);
 | |
|     debug_printf("  Reject Reason=%d, DNET=%u\n", reject_reason, dnet);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Send an Initialize Routing Table message, built from an optional DNET[]
 | |
|  * array.
 | |
|  * There are two cases here:
 | |
|  * 1) We are requesting a destination router's Routing Table.
 | |
|  *    In that case, DNET[] should just have one entry of -1 (no routing table
 | |
|  *    is sent).
 | |
|  * 2) We are sending out our Routing Table for some reason (normally bcast it).
 | |
|  * @ingroup NMRC
 | |
|  *
 | |
|  * @param dst [in] If NULL, msg will be broadcast to the local BACnet network.
 | |
|  *                 Optionally may designate a particular router destination,
 | |
|  *                 especially when requesting a Routing Table.
 | |
|  * @param DNET_list [in] List of BACnet network numbers for which I am a router,
 | |
|  *                       terminated with -1.  Will be just -1 when we are
 | |
|  *                       requesting a routing table.
 | |
|  */
 | |
| void Send_Initialize_Routing_Table(
 | |
|     BACNET_ADDRESS * dst,
 | |
|     const int DNET_list[])
 | |
| {
 | |
|     /* Use a NULL dst here since we want a broadcast MAC address. */
 | |
|     Send_Network_Layer_Message(NETWORK_MESSAGE_INIT_RT_TABLE, dst,
 | |
|         (int *) DNET_list);
 | |
| }
 | |
| 
 | |
| 
 | |
| /** 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).
 | |
|  * @ingroup NMRC
 | |
|  *
 | |
|  * @param dst [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.
 | |
|  * @param DNET_list [in] List of BACnet network numbers for which I am a router,
 | |
|  *                       terminated with -1.  May be just -1 when no table
 | |
|  *                       should be sent.
 | |
|  */
 | |
| void Send_Initialize_Routing_Table_Ack(
 | |
|     BACNET_ADDRESS * dst,
 | |
|     const int DNET_list[])
 | |
| {
 | |
|     Send_Network_Layer_Message(NETWORK_MESSAGE_INIT_RT_TABLE_ACK, dst,
 | |
|         (int *) DNET_list);
 | |
| }
 | 
