/*
 *  Copyright (c) 1996-2007 Digi International Inc., All Rights Reserved
 *
 *  This software contains proprietary and confidential information of Digi
 *  International Inc.  By accepting transfer of this copy, Recipient agrees
 *  to retain this software in confidence, to prevent disclosure to others,
 *  and to make no use of this software other than that for which it was
 *  delivered.  This is an unpublished copyrighted work of Digi International
 *  Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 *  prohibited.
 *
 *  Restricted Rights Legend
 *
 *  Use, duplication, or disclosure by the Government is subject to
 *  restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 *  Technical Data and Computer Software clause at DFARS 252.227-7031 or
 *  subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 *  Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 *  Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 * Description.
 * =======================================================================
 * applicationStart() is the entry point for user applications.  This
 * This function will be called after the kernel has started and the
 * TCP/IP stack has loaded.  applicationTcpDown() is called periodically
 * after the kernel has started while the system is waiting for the
 * TCP/IP stack to start.  
 *                                                                        
 *                                                                        
 * Edit Date/Ver   Edit Description
 * ==============  ===================================================
 *
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <tx_api.h>
#include <naipfilter.h>
#include <bsp_api.h>
#include <sockapi.h>
#include "appconf.h"

/*
 * Thread size
 */
#define STACK_SIZE      8192

/*
 * UDP / TCP echo port numbers
 */
#define UDP_ECHO_PORT        10000
#define TCP_ECHO_PORT        7

#define TX_RX_BUFFER_SIZE    1500
#define UDP_BUFFER_SIZE      8192

static int block_incoming_icmp = 0;
static int block_outgoing_icmp = 0;
static int block_incoming_udp = 0;
static int block_outgoing_udp = 0;
static int block_incoming_tcp = 0;
static int block_outgoing_tcp = 0;

static unsigned long  udp_peer_addr = 0;
static unsigned short udp_peer_port = 0;
static int got_udp_peer_info = 0;

TX_THREAD udp_echo_thread;
TX_THREAD tcp_echo_thread;

/*
 * UDP / TCP Echo server thread entry function 
 */
void UdpEchoThread(unsigned long port);
void TcpEchoThread(unsigned long port);

int userIpFilterCallback(void * interfaceHandle, void * iphPtr, void * ulpPtr, int totalLength, int direction);

/*
 * Set this to 1 to run the manufacturing burn in tests.
 */
int APP_BURN_IN_TEST = 0; 








/*
 *
 *  Function: void applicationTcpDown (void)
 *
 *  Description:
 *
 *      This routine will be called by the NET+OS root thread once every 
 *      clock tick while it is waiting for the TCP/IP stack to come up.  
 *      This function can increment a counter everytime it's called to 
 *      keep track of how long we've been waiting for the stack to start.
 *      If we've been waiting too long, then this function can do something
 *      to handle the error.  
 *
 *      This function will not be called once the stack has started.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *      none
 *
 */

void applicationTcpDown (void)

{
    static int ticksPassed = 0;

    ticksPassed++;
/*
 * Code to handle error condition if the stack doesn't come up goes here.
 */
}




/*
 *
 *  Function: void applicationStart (void)
 *
 *  Description:
 *
 *      This routine is responsible for starting the user application.  It should 
 *      create any threads or other resources the application needs.
 *
 *      ThreadX, the NET+OS device drivers, and the TCP/IP stack will be running
 *      when this function is called.
 *
 *  Parameters:
 *
 *      none
 *
 *  Return Values:
 *
 *      none
 *
 */

void applicationStart (void)
{
    NaStatus result = 0;
    int kb_input = 0;
    void * stack_ptr = NULL;
    unsigned int rc;

    result = naIpFilterRegisterUserCallback(userIpFilterCallback);
    if (result)
    {
        printf("applicationStart: naIpFilterRegisterUserCallback failed, error 0x%x\n", result);
        goto exit;
    }

    result = naIpFilterEnableFiltering("eth0");
    if (result)
    {
        printf("applicationStart: naIpFilterEnableFiltering failed, error 0x%x\n", result);
        goto exit;
    }

    printf("IP packet filtering example started.\n");

    /*
     * Start UDP echo server thread
     */

    stack_ptr = malloc (STACK_SIZE);
    if (stack_ptr == NULL)
    {
        printf("applicationStart: malloc() failed\n");
        goto exit;
    }

    rc = tx_thread_create (&udp_echo_thread,         /* control block for thread */
                           "UDP Echo Thread",        /* thread name */
                           UdpEchoThread,            /* entry function */
                           UDP_ECHO_PORT,            /* parameter - port */
                           stack_ptr,                /* start of stack */
                           STACK_SIZE,               /* size of stack */
                           APP_DEFAULT_API_PRIORITY, /* priority */
                           APP_DEFAULT_API_PRIORITY, /* preemption threshold */
                           1,                        /* time slice threshold */
                           TX_AUTO_START);           /* start immediately */
        
    if (rc != TX_SUCCESS)
    {
        printf("applicationStart: tx_thread_create() failed, error %u\n", rc);
        goto exit;
    }

    /*
     * Start TCP echo server thread
     */

    stack_ptr = malloc (STACK_SIZE);
    if (stack_ptr == NULL)
    {
        printf("applicationStart: malloc() failed\n");
        goto exit;
    }
    
    rc = tx_thread_create (&tcp_echo_thread,         /* control block for thread */
                           "TCP Echo Thread",        /* thread name */
                           TcpEchoThread,            /* entry function */
                           0,                        /* parameter */
                           stack_ptr,                /* start of stack */
                           STACK_SIZE,               /* size of stack */
                           APP_DEFAULT_API_PRIORITY, /* priority */
                           APP_DEFAULT_API_PRIORITY, /* preemption threshold */
                           1,                        /* time slice threshold */
                           TX_AUTO_START);           /* start immediately */
        
    if (rc != TX_SUCCESS)
    {
        printf("applicationStart: tx_thread_create() failed, error %u\n", rc);
        goto exit;
    }
    
    tx_thread_sleep(NS_MILLISECONDS_TO_TICKS(10000)); /* 10 second delay */

    printf("\nNo IP packet filtering enabled at this time...\n");
    
    do
    {
        printf("\n\nPress 1 to toggle blocking of incoming ICMP packets.\n");
        printf("Press 2 to toggle blocking of outgoing ICMP packets.\n");        
        printf("Press 3 to toggle blocking of incoming UDP packets.\n");
        printf("Press 4 to toggle blocking of outgoing UDP packets.\n");
        printf("Press 5 to toggle blocking of incoming TCP packets.\n");
        printf("Press 6 to toggle blocking of outgoing TCP packets.\n");
        printf("Press 7 to exit application.\n");
        printf("Press any other key to display options menu.\n");

        kb_input = getchar();
        switch(kb_input)
        {
	    case '1':
                if (!block_incoming_icmp)
                    block_incoming_icmp = 1;
                else
                    block_incoming_icmp = 0;
                break;
		
	    case '2':
                if (!block_outgoing_icmp)
                    block_outgoing_icmp = 1;
                else
                    block_outgoing_icmp = 0;
                break;

            case '3':
                if (!block_incoming_udp)
                    block_incoming_udp = 1;
                else
                    block_incoming_udp = 0;
                break;

            case '4':
                if (!block_outgoing_udp)
                    block_outgoing_udp = 1;
                else
                    block_outgoing_udp = 0;
                break;

            case '5':
                if (!block_incoming_tcp)
                    block_incoming_tcp = 1;
                else
                    block_incoming_tcp = 0;
                break;

            case '6':
                if (!block_outgoing_tcp)
                    block_outgoing_tcp = 1;
                else
                    block_outgoing_tcp = 0;
                break;

            case '7':
                printf("\n\nIP packet filtering example ended.\n");
                break;

            default:
                break;
        }

        /* Display current ICMP packet filtering state */
        if (block_incoming_icmp)
            printf("\nIncoming ICMP packets are blocked.\n");
        else
            printf("\nIncoming ICMP packets are not blocked.\n");

        if (block_outgoing_icmp)
            printf("Outgoing ICMP packets are blocked.\n");
        else
            printf("Outgoing ICMP packets are not blocked.\n");
/*
	if (block_incoming_icmp || block_outgoing_icmp)
            printf("Pings to this device are not working.\n");
        else
            printf("Pings to this device are working.\n");
*/
        /* Display current UDP packet filtering state */
        if (block_incoming_udp)
            printf("\nIncoming UDP packets are blocked.\n");
        else
            printf("\nIncoming UDP packets are not blocked.\n");

        if (block_outgoing_udp)
            printf("Outgoing UDP packets are blocked.\n");
        else
            printf("Outgoing UDP packets are not blocked.\n");

        /* Display current TCP packet filtering state */
        if (block_incoming_tcp)
            printf("\nIncoming TCP packets are blocked.\n");
        else
            printf("\nIncoming TCP packets are not blocked.\n");

        if (block_outgoing_tcp)
            printf("Outgoing TCP packets are blocked.\n");
        else
            printf("Outgoing TCP packets are not blocked.\n");
	
    } while (kb_input != '7');
    
exit:
    return;
}

int userIpFilterCallback(void * interfaceHandle, void * iphPtr, void * ulpPtr, int totalLength, int direction)
{
    ttIpHeaderPtr ipHeaderPtr;
    ttUdpHeaderPtr udpHeaderPtr;
    ttTcpHeaderPtr tcpHeaderPtr;
    int errorCode = NA_IP_FILTER_ENOERROR;
    unsigned int multihome_index = 0;
    NaStatus result = 0;
    static void * eth0_ifhandle = NULL;
    
    NA_UNUSED_PARAM(totalLength);

    if (eth0_ifhandle == NULL)
    {
        result = naIfConfigGetInterfaceHandle("eth0", &eth0_ifhandle, &multihome_index);
        if (result)
        {
            printf("userIpFilterCallback: naIfConfigGetInterfaceHandle() failed, error 0x%x\n");
            eth0_ifhandle = NULL;

            /* retrieving the interface handle failed, so no IP packet filtering is performed */
            goto exit;
        }
    }

    if (eth0_ifhandle != interfaceHandle)
    {
        /* The interface handle passed into the callback function does not match  */
        /* the interface handle of the interface we want to filter IP packets, so */
        /* we exit the function and don't perform any IP packet filtering.        */ 
        goto exit;
    }

    ipHeaderPtr = (ttIpHeaderPtr)iphPtr;

    /* Filter incoming packets */
    if (direction == NA_IP_FILTER_INCOMING)
    {
        switch (ipHeaderPtr->iphUlp)
        {
            case TM_IP_ICMP:
                if (block_incoming_icmp)
                {
                    /* Block all incoming ICMP traffic */
                    printf("\nuserIpFilterCallback: Incoming ICMP packet dropped.");
                    errorCode = NA_IP_FILTER_EOPNOTSUPP;
                }
                break;
            case TM_IP_IGMP:
            case TM_IP_IP_ENCAP:
                break;
		
            case TM_IP_TCP:
                if (block_incoming_tcp)
                {
                    tcpHeaderPtr = (ttTcpHeaderPtr)ulpPtr;
                    if (tcpHeaderPtr->tcpDstPort == TCP_ECHO_PORT)
                    {
                        /* Block all TCP traffic destined for port 7 */
                        printf("\nuserIpFilterCallback: Incoming TCP packet dropped.");
                        errorCode = NA_IP_FILTER_EOPNOTSUPP;
                    }
                }
                break;

            case TM_IP_UDP:
                if (block_incoming_udp)
                {
                    if (!got_udp_peer_info)
                    {
                        /* Block all incoming UDP incoming traffic */
                        printf("\nuserIpFilterCallback: Incoming UDP packet dropped.");
                        errorCode = NA_IP_FILTER_EOPNOTSUPP;
                    }
                    else
                    {
                        udpHeaderPtr = (ttUdpHeaderPtr)ulpPtr;
                        if ((udpHeaderPtr->udpDstPort == UDP_ECHO_PORT) &&
                            (ipHeaderPtr->iphSrcAddr == udp_peer_addr) )
                        {
                            /* Block all UDP traffic destined for port 10000 from the UDP client */
                            printf("\nuserIpFilterCallback: Incoming UDP packet dropped.");
                            errorCode = NA_IP_FILTER_EOPNOTSUPP;
                        }
                    }
                }
                break;
	    
            case TM_IP_IPV6:
            case TM_IP_GRE:
            case TM_IP_ESP:
            case TM_IP_AH:
            case TM_IP_MIN_ENCAP:
            case TM_IP_ICMPV6:
            case TM_IP_OSPF:
            case TM_IP_IPCOMP:
            case TM_IP_VRRP:
            default:
                break;
        }
    }
    else /* Filter outgoing packets */
    {
        switch (ipHeaderPtr->iphUlp)
        {
            case TM_IP_ICMP:
                if (block_outgoing_icmp)
                {
                    /* Block all outgoing ICMP traffic */
                    printf("\nuserIpFilterCallback: Outgoing ICMP packet dropped.");
                    errorCode = NA_IP_FILTER_EOPNOTSUPP;
                }

                break;
            case TM_IP_IGMP:
            case TM_IP_IP_ENCAP:
                break;
		
            case TM_IP_TCP:
                if (block_outgoing_tcp)
                {
                    tcpHeaderPtr = (ttTcpHeaderPtr)ulpPtr;
                    if (tcpHeaderPtr->tcpDstPort == TCP_ECHO_PORT)
                    {
                        /* Block all TCP traffic destined for port 7 */
                        printf("\nuserIpFilterCallback: Outgoing TCP packet dropped.");
                        errorCode = NA_IP_FILTER_EOPNOTSUPP;
                    }
                }
                break;

            case TM_IP_UDP:
                if (block_outgoing_udp)
                {
                    if (!got_udp_peer_info)
                    {
                        /* Block all outgoing UDP incoming traffic */
                        printf("\nuserIpFilterCallback: Outgoing UDP packet dropped.");
                        errorCode = NA_IP_FILTER_EOPNOTSUPP;
                    }
                    else
                    {
                        udpHeaderPtr = (ttUdpHeaderPtr)ulpPtr;
                        if ((udpHeaderPtr->udpDstPort == udp_peer_port) &&
                            (ipHeaderPtr->iphDestAddr == udp_peer_addr) )
                        {
                            /* Block all outgoing UDP traffic destined for the UDP client;s UDP port */
                            printf("\nuserIpFilterCallback: Outgoing UDP packet dropped.");
                            errorCode = NA_IP_FILTER_EOPNOTSUPP;
                        }
                    }
                }
                break;
	    
            case TM_IP_IPV6:
            case TM_IP_GRE:
            case TM_IP_ESP:
            case TM_IP_AH:
            case TM_IP_MIN_ENCAP:
            case TM_IP_ICMPV6:
            case TM_IP_OSPF:
            case TM_IP_IPCOMP:
            case TM_IP_VRRP:
            default:
                break;
        }
    }

exit:
    fflush(stdout);
    return errorCode;
}

/*
 *
 *  Function: void UdpEchoThread(unsigned long port)
 *
 *  Description:
 *
 *      This is the UDP Echo thread function. 
 *      It creates a socket sends back received datagrams.
 *
 *  Parameters:
 *
 *      port - The port number to bind the UDP server socket to.
 *
 *  Return Values:
 *
 *      none
 *
 */

void UdpEchoThread(unsigned long port)
{
    int fd, bufsize = UDP_BUFFER_SIZE, datagram_count = 0;
    struct sockaddr_storage addr;
    static char buffer[TX_RX_BUFFER_SIZE];
    int addrlen = sizeof(addr);

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd <= 0)
    {
        printf("UdpEchoThread: socket() failed\n");
        goto exit;
    }

    memset(&addr, 0, addrlen);
    addr.ss_family = AF_INET;
    addr.ss_len = addrlen;
    addr.addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.ss_port = htons(port);

    if (bind(fd, (struct sockaddr *)&addr, addrlen))
    {
        closesocket(fd);
        printf("UdpEchoThread: bind() failed\n");
        goto exit;
    }

    if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *)&bufsize, sizeof bufsize) != 0)
    {
        printf("UdpEchoThread: setsockopt() failed\n");
        goto exit;
    }

    if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *)&bufsize, sizeof bufsize) != 0)
    {
        printf("UdpEchoThread: setsockopt() failed\n");
        goto exit;
    }

    printf("UdpEchoThread: UDP Echo Server ready\n");
    
    /*
     * Echo received datagrams
     */
    while (1)
    {
        int recv_count, send_count;

        recv_count = recvfrom(fd, buffer, sizeof buffer, 0, (struct sockaddr *)&addr, &addrlen);
        if (recv_count <= 0)
        {
            printf("\nUdpEchoThread: recvfrom() failed\n");
            goto exit;
        }

        if (!got_udp_peer_info)
        {
            /* retrieve the peer address and port number */
            udp_peer_addr = addr.addr.ipv4.sin_addr.s_addr;
            udp_peer_port = addr.ss_port;
            got_udp_peer_info = 1;
        }
        else
        {
            /* double check to be sure that the UDP client matches what we cached */
            if (udp_peer_addr != addr.addr.ipv4.sin_addr.s_addr)
                udp_peer_addr = addr.addr.ipv4.sin_addr.s_addr;
            if (udp_peer_port != addr.ss_port)
                udp_peer_port = addr.ss_port;
        }

        send_count = sendto(fd, buffer, recv_count, 0, (struct sockaddr *)&addr, sizeof(addr));
        if (send_count <= 0)
        {
            printf("\nUdpEchoThread: sendto() failed.");
            continue;
        }

        if ((++datagram_count % 1000) == 0)
        {
            printf("\nUdpEchoThread: received %d datagrams.", datagram_count);
        }
        fflush(stdout);
    }
exit:
    return;
}


void TcpEchoThread(unsigned long thread_input)
{
    long sock = 0, fd;                    /* socket descriptor    */
    struct sockaddr_storage fsin;     /* Internet endpoint address	*/
    int result, szfsin;
    int packet_count = 0;
    static char rcvData[TX_RX_BUFFER_SIZE];

    /*  open the stream socket    */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock <= 0)
    {
        printf("TcpEchoThread: socket() failed, error %d.\n", getErrno());
        goto exit;
    }

    memset(&fsin, 0, sizeof fsin);
    fsin.ss_family = AF_INET;
    fsin.ss_len = sizeof fsin;
    fsin.addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
    fsin.ss_port = htons(0x7);

    result = bind(sock, (const struct sockaddr *)&fsin, sizeof(fsin));
    if (result < 0)
    {
        printf("TcpEchoThread: bind() failed, error %d.\n", getErrno());
        goto exit;
    }

    /*  queue up no more than 1 client */
    result = listen(sock, 1);
    if (result < 0)
    {
        printf("TcpEchoThread: listen() failed, error %d.\n", getErrno());
        goto exit;
    }

    printf("TcpEchoThread: TCP Echo Server ready.\n");

    do 
    {
        /*  open the new client */
        szfsin = sizeof fsin;
        fd = accept(sock, (struct sockaddr *)&fsin, &szfsin);
        if (fd <= 0)
        {
            printf("TcpEchoThread: accept() failed, error %d.\n", getErrno());
            goto exit;
        }
        do
        {
            result = recv(fd, rcvData, TX_RX_BUFFER_SIZE, 0);
            if (result <= 0)
            {
                printf("\nTcpEchoThread: recv() failed, error %d.\n", getErrno());
                goto exit;
            }
            /* echo received packet */
            result = send(fd, rcvData, result, 0);
            if (result <= 0)
            {
                printf("TcpEchoThread: send() failed, error %d.\n", getErrno());
                goto exit;
            }
            if ((++packet_count % 1000) == 0)
            {
                printf("\nTcpEchoThread: received %d packets.", packet_count);
            }
            fflush(stdout);
        }while(1);
    }while (1);

exit:
    if (sock > 0)
    {
        if (closesocket(sock))
        {
            printf("TcpEchoThread: closesocket() failed, error %d\n", getErrno());
        }
    }
    return;
}
