/**
 * ============================================================================
 * = COPYRIGHT
 *              INTEL CORPORATION PROPRIETARY INFORMATION
 *   This software is supplied under the terms of a license agreement or
 *   nondisclosure agreement with Intel Corporation and may not be copied 
 *   or disclosed except in accordance with the terms in that agreement.
 *      Copyright (C) 2002 Intel Corporation. All rights reserved.

 * = PRODUCT
 *      Intel(r) IXA SDK 3.0 for the IXP2000 Network Processor
 *
 * 
 *
 * = MODULE
 *     IPv4 Core Component - ICMP module 
 *
 * = FILENAME
 *      icmp.c
 *
 *
 * = DESCRIPTION
 *   This file contains the implementation of ICMP message building 
 *   functions.
 *   ICMP messages supported are ICMP parameter problem message, ICMP 
 *   destination unreachable message, ICMP time exceeded error message
 *   and ICMP redirect message. This file also contains internal
 *   functions for building ICMP messages and queueing these into
 *   ICMP queue.   
 *
 * 
 *
 *
 * = CREATION TIME
 *      6/15/2002 5:56:24 PM
 *
 * = CHANGE HISTORY
 *
 * ============================================================================
 */
#define IX_ERROR_FILE_IDENT "$Id: icmp.c,v 1.26 2003/11/04 21:43:29 rranjeet Exp $"

#include "ix_ossl.h"
#include "ix_cc_error.h"
#include "cc/ipv4/internal/icmp.h"
#include "cc/ipv4/internal/fragmentation_support.h"

 /**
 * Preprocessor symbols and macros used in this file.
 */

/**
 * Types definitions whose scope is limited to this file.
 */

/**
 * Variable declarations global to this file only.  Externs are followed by
 * static variables.
 */

/**
* g_IcmpQueueDepth represents the depth of the ICMP error message queue.
* That is, the maximum number of ICMP messages that can fit in the queue,
* not the number of messages currently in the queue
**/
ix_uint16 g_IcmpQueueDepth;

/**
* g_IcmpQueueDrain represents the maximum number of ICMP error messages 
* to send (from the ICMP message queue) each time the queue processing
* event fires
**/
ix_uint16 g_IcmpQueueDrain;

/**
*  g_IcmpSleepTime represents the number of seconds between when the event
*  will fire causing the ICMP messages to be dequeued and sent. The lower 
*  the value of g_IcmpSleepTime, the more frequent the ICMP messages will
*   be dequeued	and sent.
**/
ix_uint16 g_IcmpSleepTime;



/**
 * Function definitions.
 */

/* Initialization function */
void _ix_cc_ipv4_icmp_init()
{
    
       
    g_IcmpQueueDepth = IX_CC_IPV4_ICMPQ_MAXSIZE;
    g_IcmpQueueDrain = IX_CC_IPV4_ICMPQ_MAXSIZE;
    g_IcmpSleepTime = IX_CC_IPV4_ICMP_DEFAULT_SLEEP_TIME;
    _ix_cc_ipv4_icmp_queue_init();

    
    
}

/* This function builds ICMP parameter Problem message for the
packet passed in the buffer handle */
ix_error _ix_cc_ipv4_icmp_param_problem(
			ix_buffer_handle arg_hBuffer, 
			ix_uint32 arg_Param )
{
    ix_uint32 OkToSend; 
    IX_ERROR_CR(_ix_cc_ipv4_icmp_ok_to_send(arg_hBuffer, &OkToSend));
    if (!OkToSend)
    {

        /* Free the buffer */
        IX_ERROR_CRT(ix_rm_buffer_free_chain(arg_hBuffer), IX_CC_ERROR_INTERNAL, 0);
        return IX_SUCCESS;
    }

    IX_ERROR_CR(_ix_cc_ipv4_icmp_build_message(IX_ICMP_PARAMPROB, 0,
     (arg_Param << 24),
        1, arg_hBuffer)); 
    return IX_SUCCESS;
}

/* This function attempts to build ICMP destination
unreachable error message for the packet passed in
buffer handle */
ix_error _ix_cc_ipv4_icmp_dest_unreachable(
				ix_buffer_handle arg_hBuffer,
				ix_uint8 arg_Reason, 
				ix_uint32 arg_Param)
{
    
    ix_uint32 OkToSend; 
    IX_ERROR_CR(_ix_cc_ipv4_icmp_ok_to_send(arg_hBuffer, &OkToSend));
    if (!OkToSend)
    {

        /* Free the buffer */
        ix_rm_buffer_free_chain(arg_hBuffer);
        return IX_SUCCESS;
    }

    IX_ERROR_CR(_ix_cc_ipv4_icmp_build_message(IX_ICMP_UNREACH, arg_Reason,
                       arg_Param, 1, 
                       arg_hBuffer));
    return IX_SUCCESS;

}

/**
* This function attempts to build ICMP redirect
*error message for the packet passed in buffer 
*handle
**/
ix_error _ix_cc_ipv4_icmp_redirect(
			ix_buffer_handle arg_hBuffer,
			ix_uint32 arg_Gateway)
{
    ix_uint32 OkToSend;
   
        
    IX_ERROR_CR(_ix_cc_ipv4_icmp_ok_to_send(arg_hBuffer, &OkToSend));
    if (!OkToSend )
    {

        /* Free the buffer */
        ix_rm_buffer_free_chain(arg_hBuffer);

        return IX_SUCCESS;
    }
    
    IX_ERROR_CR(_ix_cc_ipv4_icmp_build_message(IX_ICMP_REDIRECT,
                  IX_ICMP_REDIRECT_HOST,
                  arg_Gateway, 1, arg_hBuffer));
    return IX_SUCCESS;

}

/**
* This function attempts to build ICMP
*time Exceeded error message for the packet passed in
*as buffer handle
**/
ix_error _ix_cc_ipv4_icmp_time_exceeded(
				ix_buffer_handle arg_hBuffer)
{

    
    ix_uint32 OkToSend; 
    IX_ERROR_CR(_ix_cc_ipv4_icmp_ok_to_send(arg_hBuffer, &OkToSend));
    if (!OkToSend)
    {

        /* Free the buffer */
        ix_rm_buffer_free_chain(arg_hBuffer);
        return IX_SUCCESS;
    }

    IX_ERROR_CR(_ix_cc_ipv4_icmp_build_message(IX_ICMP_TIMEXCEED,
            IX_ICMP_TIMEXCEED_INTRANS,
                       0, 1, arg_hBuffer));
   
    return IX_SUCCESS;

}


/**
*This function prints the header and the contents of 
* the ICMP payload on the screen
**/
void _ix_cc_ipv4_icmp_print_message(
			const ix_uint8 *arg_pMessage, 
			ix_uint32 arg_size )
{
    ix_cc_ipv4_icmp_header icmpHdr;
    register ix_uint32 i=0;

#if (_IX_OS_TYPE_ == _IX_OS_LINUX_KERNEL_)
#else
    assert( arg_pMessage );
    assert( arg_size >= IX_ICMP_HDR_LEN );
#endif    
    ix_ossl_memcpy( &icmpHdr, arg_pMessage, IX_ICMP_HDR_LEN);
    
    ix_ossl_message_log("type: %d , code: %d, csum: %d ", 
         icmpHdr.type, icmpHdr.code, icmpHdr.checksum);
    
    ix_ossl_message_log("contents:\n" );
    for ( i=0;i < arg_size; i++)
    {
        ix_ossl_message_log("%02x ", arg_pMessage[i]);
        if ( (i+1) % 4 == 0)
        {
            ix_ossl_message_log("\n");
        }
    }
    ix_ossl_message_log("\n");
}

/**
*This function prints the ICMP header into a string buffer
**/
ix_error _ix_cc_ipv4_icmp_dump_message(const ix_uint8 *arg_pMessage,
                                     ix_uint32 arg_MessageSize,
                                     ix_uint32 arg_BufferSize,
                                      ix_uint8 *arg_pBuffer)
{
    
   
    ix_uint32 bufSizeLeft = arg_BufferSize;
    ix_cc_ipv4_icmp_header *pIcmpHdr;
    ix_uint8 icmpString[IX_CC_IPV4_ICMP_DUMP_MSG_SIZE];          
   
    if (arg_BufferSize <= 0)
        return IX_ERROR_WARNING(IX_CC_ERROR_FULL,
        ("Buffer Size is too small\n"));
    /* Check that message size is not less than minimum ICMP
    header length */
    if (arg_MessageSize < IX_ICMP_HDR_LEN)
        return IX_ERROR_WARNING(IX_CC_ERROR_FULL,
        ("Message size is invalid\n"));

    if (bufSizeLeft < _IX_CC_IPV4_ICMPQUEUE_TITLE_SIZE +
                           _IX_CC_IPV4_ICMPQUEUE_TERM_SIZE + 1)
    {
        arg_pBuffer[0] = '\0';
        return IX_ERROR_WARNING(IX_CC_ERROR_FULL,
            ("Buf size is too small"));
              
    }

    strcpy(arg_pBuffer, _IX_CC_IPV4_ICMPQUEUE_TITLE_STRING);
    bufSizeLeft -= _IX_CC_IPV4_ICMPQUEUE_TITLE_SIZE;
    if (bufSizeLeft <  IX_CC_IPV4_ICMP_DUMP_MSG_SIZE )
    {
        ix_ossl_message_log("Buffer size is too small\n");
        return IX_ERROR_WARNING(IX_CC_ERROR_FULL,
         ("Buf size left is too small %d", bufSizeLeft));
    }
    pIcmpHdr = (ix_cc_ipv4_icmp_header*) arg_pMessage;
    sprintf(icmpString, "%d   %d   %d",
            pIcmpHdr->type,
            pIcmpHdr->code,
            pIcmpHdr->checksum);
    strcat(arg_pBuffer, icmpString); 
#ifdef DUMP_TO_SCREEN   
    ix_ossl_message_log("%s\n", arg_pBuffer);
#endif
    
    return IX_SUCCESS;


}


/**
*This function builds a complete ICMP message and queues it so  
* it can be sent.
**/
ix_error _ix_cc_ipv4_icmp_build_message(
				ix_uint8 arg_Type,
				ix_uint8 arg_Code,
				ix_uint32 arg_Param,
				ix_uint32 arg_SendMsg,
				ix_buffer_handle arg_hBuffer
				)
{
   
    ix_uint8 icmpMessage[IX_CC_IPV4_IP_HDR_PAYLOAD_LIMIT];
    ix_cc_ipv4_header inIpHdr;
    ix_cc_ipv4_header *pOutIpHdr;
    ix_cc_ipv4_icmp_header *pOutIcmpHdr;
    ix_uint16 inPort;
    ix_uint16 index;
    void *pData;
    ix_uint32 payloadLength;
    ix_hw_buffer_meta *pHwMeta; 
    ix_uint16 length;
    ix_cc_rtmv4_next_hop_info nexthopInfo;
    ix_uint32 ipAddr;
    ix_uint32 cell_count=0;
    
    /* Get the incoming IP header from the incoming packet */
    /* Allocate the outgoing ICMP message */
    /* Initialize outgoing ICMP message headers */
    /* Copy data from incoming packet to outgoing ICMP message */
    pOutIpHdr = (ix_cc_ipv4_header *)icmpMessage;
    pOutIcmpHdr = (ix_cc_ipv4_icmp_header *)(icmpMessage + MIN_IP_LEN);
        
    /** Note that in these error cases, buffer handle is not freed
    **/
    /** In these error cases, it is the responsibility of caller to 
    free the buffer 
    **/
    /* Need to find out the port through which the packet came in */
    IX_ERROR_CRT(ix_rm_buffer_get_meta( arg_hBuffer, (void **)&pHwMeta), 
            IX_CC_ERROR_INTERNAL, 0);
        
    /* Note m_InputPort is ix_uint16 and is in Network byte order */
    inPort = IX_NTOH16(pHwMeta->m_InputPort);

    
    IX_ERROR_CRT(ix_cc_hw_get_packet_data (arg_hBuffer, &pData),
         IX_CC_ERROR_INTERNAL, 0);
    
    
    (void)_ix_cc_ipv4_init_ip_header( (ix_uint8 *) pData, &inIpHdr);
      
    /* Initialize an ICMP header to have type, code and param field
    ** as passed in the argument **/        
    pOutIcmpHdr->type = arg_Type;
    pOutIcmpHdr->code = arg_Code;
    pOutIcmpHdr->extra = IX_HTON32(arg_Param);
    pOutIcmpHdr->checksum = 0;      
    
   
	ix_ossl_memcpy(icmpMessage + MIN_IP_LEN + IX_ICMP_HDR_LEN, (ix_uint8 *)pData,
		(inIpHdr.headerLength * 4) + 8);
    payloadLength = (inIpHdr.headerLength * 4) + 8;	

    pOutIpHdr->version = 4;
    pOutIpHdr->headerLength = 5;
    pOutIpHdr->tos = 0;

#if IX_BYTE_ORDER == IX_LITTLE_ENDIAN
    pOutIpHdr->fragOffset = 0;
#elif IX_BYTE_ORDER == IX_BIG_ENDIAN
    pOutIpHdr->flags = 0;
    pOutIpHdr->offset = 0;
#endif

    pOutIpHdr->ttl = IX_IP_DEFAULTTTL;
    pOutIpHdr->protocol = IX_IP_PROTOICMP;
    
    /** Get IP address for input port in host byte order **/
    IX_ERROR_CRT(_ix_cc_ipv4_get_port_ip_address(g_Ipv4RegistryData.localBladeId,
                         inPort, &ipAddr), IX_CC_ERROR_INTERNAL, 0);
    
    /* propData returns IP address in host byte order */
    /* Before storing the source IP address in packet, it has 
    to be converted to network byte order **/
    pOutIpHdr->sourceAddress =  IX_HTON32(ipAddr);
    pOutIpHdr->destAddress = inIpHdr.sourceAddress;

    /** ID is 16 bits identifying value assigned by the sender**/
    /** to aid in assembling the fragments of the datagram **/
    pOutIpHdr->id = _ix_cc_ipv4_get_ip_id();
    length = (ix_uint16)(MIN_IP_LEN + IX_ICMP_HDR_LEN + payloadLength);
    
    
    pOutIpHdr->totalLength = IX_HTON16(length);
    pOutIpHdr->checksum = 0;
    pOutIpHdr->checksum = IX_HTON16(_ix_cc_ipv4_calculate_checksum(
                             (ix_uint8 *)pOutIpHdr,
                           pOutIpHdr->headerLength * 4));

    
    pOutIcmpHdr->checksum = IX_HTON16(_ix_cc_ipv4_calculate_checksum( 
                            (ix_uint8 *)(icmpMessage + MIN_IP_LEN),
                            IX_ICMP_HDR_LEN + payloadLength));
    
    
    /* copy the icmp message in the buffer */
    ix_ossl_memcpy(pData, (void *)icmpMessage, MIN_IP_LEN + IX_ICMP_HDR_LEN + ((inIpHdr.headerLength * 4) + 8));
    
    /** Need to fill up meta data before sending buffer out so that
     ** L2 can use it 
    **/
    /** Get the nexthop information from RTM **/
    /** RTM expect destination IP address in host order **/
    IX_ERROR_CRT(ix_cc_rtmv4_lookup(g_pIpv4Context->hRtm, IX_NTOH32(pOutIpHdr->destAddress), &nexthopInfo),
                IX_CC_ERROR_INTERNAL, 0);
    if ((nexthopInfo.l2Index == IX_CC_RTMV4_L2INDEX_NO_ROUTE) || 
        (nexthopInfo.flags & IPV4_NH_FLAGS_DROP_BIT))
    {
    	IX_ERROR_CRT(ix_rm_buffer_free_chain(arg_hBuffer ),
         IX_CC_ERROR_INTERNAL, 0);

		return IX_SUCCESS;

    }
    /*
     * Since our host TCP/IP stack forwards IP broadcasts for us (no broad-
     * cast forwarding in IPv4 CC) we can get here with a packet coming
     * from the host's stack what means the input port is not set.
     * So don't use the input port from meta-data, but the port from the
     * nexthopInfo.
     */
    index = (ix_uint16)nexthopInfo.portID;
    pHwMeta->m_InputPort = IX_HTON16(index);
    pHwMeta->m_OutputPort = IX_HTON16(index);
    
    index = (ix_uint16)nexthopInfo.l2Index;
    pHwMeta->m_NextHopID = IX_HTON16(index);
    pHwMeta->m_FabricPort = (ix_uint8)nexthopInfo.bladeID;
    pHwMeta->m_BufferSize = IX_HTON16(length);
    pHwMeta->m_PacketSize = IX_HTON16(length);
  
    if (arg_SendMsg)
    {
        ix_uint32 isEOP;
        IX_ERROR_CRT(ix_rm_buffer_is_eop(arg_hBuffer, &isEOP),
                IX_CC_ERROR_INTERNAL, IX_ERROR_LEVEL_WARNING);

        /*
         * If the buffer is not the end of the packet (the incoming packet
         * was bigger than could fit in a single buffer), free up the rest of
         * the chain.  We don't need the rest of the chain.
         */
        if (!isEOP)
        {
            ix_buffer_handle hUnlinked = IX_NULL_BUFFER_HANDLE;
            IX_ERROR_CRT(ix_rm_buffer_unlink(&arg_hBuffer, &hUnlinked),
                    IX_CC_ERROR_INTERNAL, IX_ERROR_LEVEL_WARNING);
            IX_ERROR_CRT(ix_rm_buffer_free_chain(hUnlinked),
                    IX_CC_ERROR_INTERNAL, IX_ERROR_LEVEL_WARNING);
        }

        /* Set cellcount field in buffer */
        cell_count = ix_cc_calculate_cell_count((ix_uint32)length, 1);

        /* Write the cell count into the buffer handle */
        IX_ERROR_CRT(ix_rm_buffer_cell_count_set(&arg_hBuffer, cell_count),
              IX_CC_ERROR_INTERNAL, 0);

        IX_ERROR_CRT(_ix_cc_ipv4_icmp_queue_message(arg_hBuffer), 
        IX_CC_ERROR_INTERNAL, 0);
       
    }
	

    return IX_SUCCESS;   
    
}

/* This function will return the number of bytes copied in host byte order
This function expects arg_Length in host byte order **/

ix_uint32 _ix_cc_ipv4_icmp_copy_payload(
				ix_uint8 *arg_pSource, 
				ix_uint8 *arg_pDest, 
				ix_uint32 arg_Length 
				)
{
#if (_IX_OS_TYPE_ == _IX_OS_LINUX_KERNEL_)
    if (arg_pSource == NULL)
        return IX_ERROR_WARNING(IX_CC_ERROR_NULL,
		("arg_pSource is Null"));
    if (arg_pDest == NULL)
        return IX_ERROR_WARNING(IX_CC_ERROR_NULL,
		("arg_pDest is NULL"));
#else
    assert(arg_pSource);
    assert(arg_pDest);
#endif
    ix_ossl_memcpy( arg_pDest, arg_pSource, 
        arg_Length + IX_ICMP_MIN_DATA);
    return arg_Length + IX_ICMP_MIN_DATA;

}


ix_error _ix_cc_ipv4_icmp_ok_to_send(ix_buffer_handle arg_hBuffer,
                                      ix_uint32 *arg_pOkToSend)
{
    ix_uint8 inPacket[MIN_IP_LEN + IX_ICMP_HDR_LEN];
   
    ix_cc_ipv4_header *pInIpHdr;
    void *pData = 0;
    	
    ix_uint32 dip;
    ix_cc_ipv4_icmp_header *pIcmpHdr;
    *arg_pOkToSend = 0;
    
    pInIpHdr = (ix_cc_ipv4_header *)inPacket;

    IX_ERROR_CRT(ix_cc_hw_get_packet_data (arg_hBuffer, &pData),
                IX_CC_ERROR_INTERNAL, 0);
   
    ix_ossl_memcpy(&inPacket, pData, MIN_IP_LEN + IX_ICMP_HDR_LEN);
    
    pIcmpHdr = (ix_cc_ipv4_icmp_header *)(inPacket + MIN_IP_LEN);
    if (pInIpHdr->protocol == IX_IP_PROTOICMP)
    {


        if (( pIcmpHdr->type == IX_ICMP_UNREACH )||
           ( pIcmpHdr->type == IX_ICMP_REDIRECT)||
           ( pIcmpHdr->type == IX_ICMP_TIMEXCEED)||
           ( pIcmpHdr->type == IX_ICMP_PARAMPROB))
        {
#ifdef DUMP_TO_SCREEN
            ix_ossl_message_log("Trying to send ICMP message for an"
               " ICMP error\n");
#endif
            *arg_pOkToSend = 0;
            return IX_SUCCESS;
        }
    }
    /** dip is in host byte order **/   
    dip =  IX_NTOH32(pInIpHdr->destAddress); 
    
    if (((dip & 0xffffffff) == 0xffffffff) ||
		((dip & 0xf0000000) == 0xe0000000))
    {
#ifdef DUMP_TO_SCREEN
        ix_ossl_message_log("Trying to send ICMP message for IPV4 broadcast\n");
#endif
        *arg_pOkToSend = 0;   
        return IX_SUCCESS;
    }
	
    
#if IX_BYTE_ORDER == IX_LITTLE_ENDIAN
    if ( ((IX_NTOH16(pInIpHdr->fragOffset)) & 0x1fff) != 0)
#elif IX_BYTE_ORDER == IX_BIG_ENDIAN
    if ( (pInIpHdr->offset) != 0)
#endif
    {
#ifdef DUMP_TO_SCREEN
        ix_ossl_message_log("Trying to send ICMP message for an IP fragment\n");
#endif
        *arg_pOkToSend = 0;
        return IX_SUCCESS;
    }
    *arg_pOkToSend = 1;   
    return IX_SUCCESS;    
}

/**
 *This function attempts to queue an ICMP error message so that
 * it can be sent at a later time when the event fires to send the
 * message from the  queue. There is no retry attempted if the
 * queue is full.
**/
ix_error _ix_cc_ipv4_icmp_queue_message(ix_buffer_handle arg_hBuffer)
{
    
    ix_uint32 value = 0;
    ix_error err = IX_SUCCESS;

    IX_ERROR_CG( _ix_cc_ipv4_icmp_queue_enqueue( arg_hBuffer, &value),
           err, freeBuffer);
    if ( value != 0)
    {
        /* need to delete the buffer */
        
        goto freeBuffer;
        
    }
    return IX_SUCCESS;

freeBuffer:
    IX_ERROR_CRT(ix_rm_buffer_free_chain(arg_hBuffer), IX_CC_ERROR_INTERNAL, 0);
    return IX_SUCCESS;    
}

/**
 *This function attempts to dequeue ICMP messages and
 * send them to the destination.
**/
ix_error _ix_cc_ipv4_icmp_send_message(void *arg_pContext)
{
    register ix_uint32 count = 0;
   
    ix_uint32 ret = 0;
    void *pData;
    
    ix_buffer_handle arg_hBuffer;
    /* Check if context returned by ix_cci_cc_add_event_handler function 
    ** is not null**/
    if (arg_pContext == NULL)
    {
       
        return IX_ERROR_WARNING(IX_CC_ERROR_NULL, ("It is a null context"));
    }
		
   
    while ( count < g_IcmpQueueDrain && !ret )
    {
        IX_ERROR_CR(_ix_cc_ipv4_icmp_queue_dequeue( &arg_hBuffer, &ret));
        if ( ret == 0)
        {

            IX_ERROR_CRT(ix_cc_hw_get_packet_data(arg_hBuffer, &pData), 
            IX_CC_ERROR_INTERNAL, 0);
#ifdef DUMP_TO_SCREEN
            {
            ix_uint8 *inPacket;
            inPacket = (ix_uint8 *)pData;
            
            _ix_cc_ipv4_icmp_print_message(inPacket + MIN_IP_LEN, 20);
            }
#endif
            /* Call _ix_cc_ipv4_pass_packet function to send the ICMP 
            message out */
            IX_ERROR_CR(_ix_cc_ipv4_pass_packet(arg_hBuffer));
            count++;
        }
    }
   
    return IX_SUCCESS;
}
