/**
 * ============================================================================
 * = 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 for the IXP2000 Network Processor
 *
 *      
 *
 * = MODULE
 *      IPv4 Core Component
 *
 * = FILENAME
 *      fragmentation_support.c
 *
 * = DESCRIPTION
 *      This file contains implementation of fragmentation
 *       handling routines
 *
 *
 *      
 *
 * = CREATION TIME
 *      8/14/2002 10:20:24 AM
 *
 * = CHANGE HISTORY
 *
 * ============================================================================
 */
#define IX_ERROR_FILE_IDENT "$Id: fragmentation_support.c,v 1.27 2003/11/04 21:47:12 rranjeet Exp $"

#include "ix_ossl.h"

/**
 * User defined include files required.
 */
#include "ix_cc_error.h"
#include "cc/ipv4/internal/fragmentation_support.h"



/**
 * Function definitions.
 */

/**
 * NAME: _ix_cc_ipv4_header_get_DF
 *
 * DESCRIPTION: The value of the Don't Fragment(DF) bit in the IP Header is
 *               passed back through arg_pDFValue.
 *               
 * 
 * @Param:  - IN arg_hBuffer - the buffer handle containing the packet with 
 *		                     IP header 
 *            IN arg_pIpHdr - a pointer to IPv4 header
 * @Param:  - INOUT  - None
 * @Param:  - OUT - arg_pDFvalue - the value of DF bit in IP header
 *
 * @Return: IX_SUCCESS if successful or a valid ix_error token for failure.
 *          arg_pDFvalue should be set accordingly on return
 */
ix_error _ix_cc_ipv4_header_get_DF( 
                             ix_buffer_handle arg_hBuffer, 
                             ix_uint8 *arg_pIpHdr,
                             ix_uint8 *arg_pDFValue )
{
    
    ix_uint16 fragOffset;
    
    IX_ERROR_CR(ix_cc_fragv4_ip4hdr_frag_off_read(
                        (ix_cc_fragv4_ip4hdr *) arg_pIpHdr,
                                &fragOffset));
    *arg_pDFValue = 0;
    /** IX_CC_FRAGV4_IP_FRAG_DF is defined as 0x4000 in fragment.h ***/
    /* fragOffset represents 3 bits flags field + 13 bits fragment_offset*/
    /** First bit of flags field is 0 (reserved) **/
    /** Second bit is DF bit. If it is 1 means, do not fragment **/
    if (fragOffset & IX_CC_FRAGV4_IP_HEADER_FLAG_DF)
    {
#ifdef DUMP_TO_SCREEN
        ix_ossl_message_log(" DF bit set: should send"
            " ICMP unreachable message \n");
#endif
        *arg_pDFValue = 1;
    }

    return IX_SUCCESS;

}

/**
 * NAME: _ix_cc_ipv4_pass_packet_fragment
 *
 * DESCRIPTION: This function fragments the packet 
 * contained in buffer, and creates buffers
 * containing fragments of the original buffer.  
 * _ix_cc_ipv4_pass_packet is then
 * called on each buffer fragment. Assumes that fragmentation 
 * is required (ie., mtu < buf_size).The variable, arg_hBuffer,
 * is not deleted. It must be deleted by the caller.      
 *
 * @Param:  - IN arg_hBuffer -The buffer to forward on
 *            (with perhaps first fragmenting) 
 *            IN arg_pBufData  - A pointer to the beginning
 *                               of the buffer
 *            IN arg_BufLen  - length of the buffer   
 *            IN arg_Mtu  - mtu of the outgoing interface
 *
 * @Param:  - INOUT  - None
 * @Param:  - OUT - None
 *
 * @Return: IX_SUCCESS if successful or a valid ix_error token
 *           for failure.
 *          
 */
ix_error _ix_cc_ipv4_pass_packet_fragment(
                                 ix_buffer_handle arg_hBuffer,
                                 ix_uint8 *arg_pBufData, 
                                 ix_uint16 arg_BufLen,
                                 ix_uint32 arg_Mtu
                                  )
{
	
    ix_cc_fragv4_fragment *pNewFrag = NULL;
    ix_cc_fragv4_fragment *pFirst = NULL;
    ix_error err = IX_SUCCESS;
    ix_cc_fragv4_fragment *pTemp, *pTemp2;
    
   
    (void)arg_BufLen; /* unused */
    /* make fragment from parent buffer */
    IX_ERROR_CR(ix_cc_fragv4_fragment_make_from_buffer(arg_hBuffer, 
        0, &pNewFrag));

#ifdef DUMP_TO_SCREEN
    ix_ossl_message_log("after making fragment from buffer:"
     " frag len: %d\n",  pNewFrag->dataLen);


    IX_ERROR_CR(ix_cc_fragv4_fragment_print(pNewFrag, 1, 1));
    
#endif    

    /* fragment newly created fragment into smaller fragments */
    if ((err = ix_cc_fragv4_fragment_divide(pNewFrag, 
          arg_Mtu, g_pIpv4Context->hBufferFreeList,&pFirst)) != IX_SUCCESS)
    {
        /*
         * Free the fragment element that holds the input packet.
         * (Caller to free the input ix_buffer for error return)
         */
        ix_cc_fragv4_fragment_delete_frag_only(pNewFrag);
        return(err);
    }
    
    /* traverse fragment list */
    pTemp = pFirst;
    while (pTemp != NULL)
    {
         
        /* call _ix_cc_ipv4_pass_packet on the buffer associated 
        with each of smaller fragment ***/
        if(pTemp->hBuffer != 0)
            _ix_cc_ipv4_pass_packet(pTemp->hBuffer); 
        
        pTemp2 = pTemp;
        pTemp = (ix_cc_fragv4_fragment *) pTemp->pNext; 
        /* free the fragment -- the buffer should be deleted in 
          _ix_cc_ipv4_pass_packet*/
        ix_cc_fragv4_fragment_delete_frag_only(pTemp2);
		
    }
    /* the associated buffer is not used anywhere else */
    /* so it is deleted here */
    IX_ERROR_CR(ix_cc_fragv4_fragment_delete_frag_and_data_buff(pNewFrag));
    
    return IX_SUCCESS;
   
}

/**
 * NAME: _ix_cc_ipv4_pass_packet
 *
 * DESCRIPTION: This function hands the given packet off to
 *              outgoing target. The packet must be completely
 *              formed.Fragmentation will occur within this routine.
 *              This fn should take care of freeing buffer if
 *              any error occurs
 *
 * @Param:  - IN arg_hBuffer - The buffer to forward 
 *             (with perhaps first fragmenting it) 
 *            
 *
 * @Param:  - INOUT  - None
 * @Param:  - OUT - None
 *
 * @Return: IX_SUCCESS if successful or a valid ix_error token
 *          for failure.
 *          
 */
ix_error _ix_cc_ipv4_pass_packet(ix_buffer_handle arg_hBuffer)
{
 
  
    ix_error err = IX_SUCCESS;    
    void *pData;
    ix_uint32 oMtu;
    ix_uint16 bufSize;
    ix_uint8 *ipHdr;
    ix_cc_ipv4_header ipv4Hdr;
    ix_cc_rtmv4_next_hop_info nexthopInfo;
    ix_uint8 DFSet;
    ix_hw_buffer_meta *pHwMeta;
    
    
    err = ix_cc_hw_get_packet_data(arg_hBuffer, &pData);
    if (err)
    {
        
		ix_rm_buffer_free_chain(arg_hBuffer);
		
		return IX_ERROR_NEW(IX_CC_ERROR_INTERNAL,
			IX_ERROR_LEVEL_WARNING);
        
    }
        
    (void)_ix_cc_ipv4_init_ip_header( (ix_uint8 *)(pData),&ipv4Hdr);

    
    ix_cc_rtmv4_lookup(g_pIpv4Context->hRtm, IX_NTOH32(ipv4Hdr.destAddress),
       &nexthopInfo);
	
    if ((nexthopInfo.l2Index == IX_CC_RTMV4_L2INDEX_NO_ROUTE) || 
        (nexthopInfo.flags & IPV4_NH_FLAGS_DROP_BIT))
    {
    	
		ix_rm_buffer_free_chain(arg_hBuffer );
		return IX_SUCCESS;

    }
   
    /* oMtu is in host byte order */
    oMtu = nexthopInfo.mtu;
    
    /** Get size of the packet **/
    
    err = ix_rm_buffer_get_meta(arg_hBuffer, (void **)&pHwMeta);
    if (err)
    {
        
		ix_rm_buffer_free_chain(arg_hBuffer);
		
		return IX_ERROR_NEW(IX_CC_ERROR_INTERNAL,
			IX_ERROR_LEVEL_WARNING);
        
    }
    bufSize = IX_NTOH16(IX_RM_MEM_UINT16_READ(
                      &pHwMeta->m_PacketSize));
    
     
               
#ifdef DUMP_TO_SCREEN  
    ix_ossl_message_log(" packet size: %d, mtu: %d \n", 
              bufSize, oMtu);
#endif /* DUMP_TO_SCREEN */
  
    /* By definition, the MTU specifies the maximum size IP datagram
    *that can be handled by a network interface**/

    /* check if mtu < Packet size */
    if (oMtu < bufSize)
    {

#ifdef DUMP_TO_SCREEN         
        ix_ossl_message_log(" Need to fragment buffer (%x)\n", arg_hBuffer);
#endif /*DUMP_TO_SCREEN*/
        
        /* Get pointer to IP header */        
        ipHdr = (ix_uint8 *)pData;        
        
        /* Check if DF bit is set.  If it is set or the packet is an IP
         * broadcast, send an ICMP unreachable message with code
         * IP_UNREACH_NEED_FRAG	create new fragment for buffer pass in
         * pointer to IP header
         */
        IX_ERROR_CR(_ix_cc_ipv4_header_get_DF(arg_hBuffer, ipHdr, &DFSet));
        
        if ((nexthopInfo.flags & IPV4_NH_FLAGS_BROADCAST_BIT) || DFSet)
        {
            /** since DF bit is set, an ICMP message must be generated 
            for the original packet, and send out of the incoming 
            interface.  But the IP lookup was done in ix_cc_ipv4_forward()
            and the meta data has already been filled up in buffer 
             Assuming NHID and output port,
            blade id was already filled up ix_cc_ipv4_forward() 
            for the incoming buffer so we generate ICMP message 
            **/
      
            err = _ix_cc_ipv4_icmp_dest_unreachable(arg_hBuffer,
            IX_ICMP_UNREACH_NEED_FRAG,0xFFFF & oMtu);
            if (err)
            {
                
				ix_rm_buffer_free_chain(arg_hBuffer);
				
				return IX_ERROR_NEW(IX_CC_ERROR_INTERNAL,
				IX_ERROR_LEVEL_WARNING);
            }
            return IX_SUCCESS;
        }
	
        
		err = _ix_cc_ipv4_pass_packet_fragment(arg_hBuffer,
                                      ipHdr, bufSize, oMtu);
		if (err != IX_SUCCESS)
		{
		        ix_rm_buffer_free_chain(arg_hBuffer);
			
                return IX_ERROR_NEW(IX_CC_ERROR_INTERNAL,
				IX_ERROR_LEVEL_WARNING);
		}

    }
    else /* if MTU is not less than buffer size,send the buffer to next target*/
    {
#ifdef DUMP_TO_SCREEN      
        ix_ossl_message_log(" NO need to fragment this buffer \n");
#endif /*DUMP_TO_SCREEN */

        
        /* Send the packet to queue Manager */
        err = ix_rm_packet_send(IX_CC_IPV4_PKT_QM_OUTPUT,
                 arg_hBuffer, IX_CC_IPV4_PKT_QM_BID);
        if (err)
        {
            /** Free the buffer and return error to caller **/
            
			ix_rm_buffer_free_chain(arg_hBuffer);
			
			return IX_ERROR_NEW(IX_CC_ERROR_SEND_FAIL, IX_ERROR_LEVEL_WARNING);
            
            
        }       
    }

    return IX_SUCCESS;
}                         				                                                   

