/**
 * @file
 * @brief Handles messages at the NPDU level of the BACnet
 * stack, including routing and network control messages.
 * @author Tom Brennan <tbrennan3@users.sourceforge.net>
 * @note Acknowledging the contribution of code and ideas used here that
 * came from Paul Chapman's vmac demo project.
 * @date 2010
 * @copyright SPDX-License-Identifier: MIT
 */
#include <stdbool.h>
#include <stdint.h>
/* BACnet Stack defines - first */
//#include "bacnet/bacdef.h"
#include "../../../bacnet/bacdef.h"
/* BACnet Stack API */
//#include "bacnet/bacdcode.h"
#include "../../../bacnet/bacdcode.h"
//#include "bacnet/bacint.h"
#include "../../../bacnet/bacint.h"
//#include "bacnet/npdu.h"
#include "../../../bacnet/npdu.h"
//#include "bacnet/apdu.h"
#include "../../../bacnet/apdu.h"
//#include "bacnet/bactext.h"
#include "../../../bacnet/bactext.h"
//#include "bacnet/basic/object/device.h"
#include "../../../bacnet/basic/object/device.h"
//#include "bacnet/basic/sys/debug.h"
#include "../../../bacnet/basic/sys/debug.h"
//#include "bacnet/basic/services.h"
#include "../../../bacnet/basic/services.h"
//#include "bacnet/datalink/datalink.h"
#include "../../../bacnet/datalink/datalink.h"
#if defined(BACDL_BIP)
//#include "bacnet/basic/bbmd/h_bbmd.h"
#include "../../../bacnet/basic/bbmd/h_bbmd.h"
#endif

#if PRINT_ENABLED
#include <stdio.h>
#endif

/** 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,
    const 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;
        case NETWORK_MESSAGE_WHAT_IS_NETWORK_NUMBER:
            /* FIXME: add procedure for this message */
            break;
        case NETWORK_MESSAGE_NETWORK_NUMBER_IS:
            /* FIXME: add procedure for this message */
            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,
    const 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) {
            debug_printf("NPDU: not unicast - dropped!\n");
            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 */
            debug_printf("NPDU: broadcast - dropped!\n");
        }
        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. */
        debug_printf("NPDU: dest not found - dropped!\n");
    }
}

/** 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,const 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 = bacnet_npdu_decode(pdu, pdu_len, &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 {
                debug_printf("NPDU: message for our router? Discarded!\n");
                /* 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;
}
