/*********************************************************************
 *
 *  Internet Control Message Protocol (ICMP) Server
 *  Module for Microchip TCP/IP Stack
 *   -Provides "ping" diagnostics
 *    -Reference: RFC 792
 *
 *********************************************************************
 * FileName:        ICMP.c
 * Dependencies:    IP, ARP
 * Processor:       PIC18, PIC24F, PIC24H, dsPIC30F, dsPIC33F, PIC32
 * Compiler:        Microchip C32 v1.05 or higher
 *               Microchip C30 v3.12 or higher
 *               Microchip C18 v3.30 or higher
 *               HI-TECH PICC-18 PRO 9.63PL2 or higher
 * Company:         Microchip Technology, Inc.
 *
 * Software License Agreement
 *
 * Copyright (C) 2002-2009 Microchip Technology Inc.  All rights
 * reserved.
 *
 * Microchip licenses to you the right to use, modify, copy, and
 * distribute:
 * (i)  the Software when embedded on a Microchip microcontroller or
 *      digital signal controller product ("Device") which is
 *      integrated into Licensee's product; or
 * (ii) ONLY the Software driver source files ENC28J60.c, ENC28J60.h,
 *      ENCX24J600.c and ENCX24J600.h ported to a non-Microchip device
 *      used in conjunction with a Microchip ethernet controller for
 *      the sole purpose of interfacing with the ethernet controller.
 *
 * You should refer to the license agreement accompanying this
 * Software for additional information regarding your rights and
 * obligations.
 *
 * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT
 * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF
 * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
 * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE
 * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER
 * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.
 *
 *
 * Author               Date       Comment
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Howard Schlunder      03/16/07   Original
 ********************************************************************/
#define __ICMP_C

#include "TCPIP Stack/TCPIP.h"

#if defined(STACK_USE_ICMP_SERVER) || defined(STACK_USE_ICMP_CLIENT)

#if defined(STACK_USE_ICMP_CLIENT)

#ifndef debug_icmp
#define debug_icmp(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)
#endif
#ifndef debug_icmp_array
#define debug_icmp_array(p,n)
#endif

// ICMP Timeout Value
#define ICMP_TIMEOUT   (4ul*TICK_SECOND)

// ICMP Packet Structure
typedef struct
{
   BYTE vType;
   BYTE vCode;
   WORD wChecksum;
   WORD wIdentifier;
   WORD wSequenceNumber;
   WORD wData;
} ICMP_PACKET;

// ICMP Sequence Number
static WORD wICMPSequenceNumber;
// ICMP tick timer variable
static DWORD ICMPTimer;

// ICMP Flag structure
static struct
{
   unsigned char bICMPInUse:1;         // Indicates that the ICMP Client is in use
   unsigned char bReplyValid:1;        // Indicates that a correct Ping response to one of our pings was received
   unsigned char bRemoteHostIsROM:1;   // Indicates that a remote host name was passed as a ROM pointer argument
} ICMPFlags = {0x00};

// ICMP Static Variables
static union
{
   union
   {
      ROM BYTE *szROM;
      BYTE *szRAM;
   } RemoteHost;
   NODE_INFO ICMPRemote;
} StaticVars;

// ICMP State Machine Enumeration
static enum
{
   SM_IDLE = 0,
   SM_DNS_SEND_QUERY,
   SM_DNS_GET_RESPONSE,
   SM_ARP_SEND_QUERY,
   SM_ARP_GET_RESPONSE,
   SM_ICMP_SEND_ECHO_REQUEST,
   SM_ICMP_GET_ECHO_RESPONSE
} ICMPState;

#endif

/*********************************************************************
 * Function:        void ICMPProcess(void)
 *
 * PreCondition:    MAC buffer contains ICMP type packet.
 *
 * Input:           *remote: Pointer to a NODE_INFO structure of the 
 *               ping requester
 *               len: Count of how many bytes the ping header and 
 *               payload are in this IP packet
 *
 * Output:          Generates an echo reply, if requested
 *               Validates and sets ICMPFlags.bReplyValid if a 
 *               correct ping response to one of ours is received.
 *
 * Side Effects:    None
 *
 * Overview:        None
 *
 * Note:            None
 ********************************************************************/
void ICMPProcess(NODE_INFO *remote, WORD len)
{
   DWORD_VAL dwVal;

   debug_icmp(debug_putc, "ICMPProcess(%lu) %X:%X:%X:%X:%X:%X %u.%u.%u.%u ",
         len,
         remote->MACAddr.v[0],
         remote->MACAddr.v[1],
         remote->MACAddr.v[2],
         remote->MACAddr.v[3],
         remote->MACAddr.v[4],
         remote->MACAddr.v[5],
         remote->IPAddr.v[0],
         remote->IPAddr.v[1],
         remote->IPAddr.v[2],
         remote->IPAddr.v[3]
      );
   debug_icmp_array(remote, 10);
   debug_icmp(debug_putc, "\r\n");

    // Obtain the ICMP header Type, Code, and Checksum fields
    MACGetArray((BYTE*)&dwVal, sizeof(dwVal));
   
   // See if this is an ICMP echo (ping) request
   if(dwVal.w[0] == 0x0008u)
   {
      // Validate the checksum using the Microchip MAC's DMA module
      // The checksum data includes the precomputed checksum in the 
      // header, so a valid packet will always have a checksum of 
      // 0x0000 if the packet is not disturbed.
      if(MACCalcRxChecksum(0+sizeof(IP_HEADER), len))
         return;
   
      // Calculate new Type, Code, and Checksum values
      dwVal.v[0] = 0x00;   // Type: 0 (ICMP echo/ping reply)
      dwVal.v[2] += 8;   // Subtract 0x0800 from the checksum
      if(dwVal.v[2] < 8u)
      {
         dwVal.v[3]++;
         if(dwVal.v[3] == 0u)
            dwVal.v[2]++;
      }
   
       // Wait for TX hardware to become available (finish transmitting 
       // any previous packet)
       while(!IPIsTxReady());

      // Position the write pointer for the next IPPutHeader operation
      // NOTE: do not put this before the IPIsTxReady() call for WF compatbility
       MACSetWritePtr(BASE_TX_ADDR + sizeof(ETHER_HEADER));
           
      // Create IP header in TX memory
      IPPutHeader(remote, IP_PROT_ICMP, len);
   
      // Copy ICMP response into the TX memory
      MACPutArray((BYTE*)&dwVal, sizeof(dwVal));
      MACMemCopyAsync(-1, -1, len-4);
      while(!MACIsMemCopyDone());
   
      // Transmit the echo reply packet
       MACFlush();
   }
#if defined(STACK_USE_ICMP_CLIENT)
   else if(dwVal.w[0] == 0x0000u)   // See if this an ICMP Echo reply to our request
   {
      // Get the sequence number and identifier fields
      MACGetArray((BYTE*)&dwVal, sizeof(dwVal));
      
      // See if the identifier matches the one we sent
      if(dwVal.w[0] != 0xEFBE)   
         return;
   
      if(dwVal.w[1] != wICMPSequenceNumber)
         return;

      // Validate the ICMP checksum field
       IPSetRxBuffer(0);
      if(CalcIPBufferChecksum(sizeof(ICMP_PACKET)))   // Two bytes of payload were sent in the echo request
         return;
      
      // Flag that we received the response and stop the timer ticking
      ICMPFlags.bReplyValid = 1;
      ICMPTimer = TickGet() - ICMPTimer;
   }
#endif
}

#if defined(STACK_USE_ICMP_CLIENT)
/*********************************************************************
 * Function:        void ICMPSendPing(DWORD dwRemoteIP)
 *
 * PreCondition:    ICMPBeginUsage() returned TRUE
 *
 * Input:           dwRemoteIP: IP Address to ping.  Must be stored 
 *                        big endian.  Ex. 192.168.0.1 should be
 *                        passed as 0x0100A8C0.
 *
 * Output:          Begins the process of transmitting an ICMP echo 
 *               request.  This normally involves an ARP 
 *               resolution procedure first.
 *
 * Side Effects:    None
 *
 * Overview:        None
 *
 * Note:            None
 ********************************************************************/
void ICMPSendPing(DWORD dwRemoteIP)
{
   ICMPFlags.bReplyValid = 0;
   ICMPTimer = TickGet();
   StaticVars.ICMPRemote.IPAddr.Val = dwRemoteIP;
   ICMPState = SM_ARP_SEND_QUERY;
}

#if defined(STACK_USE_DNS)
/*********************************************************************
 * Function:        void ICMPSendPingToHost (BYTE * szRemoteHost)
 *
 * PreCondition:    ICMPBeginUsage() returned TRUE
 *
 * Input:           szRemoteHost: Host name to ping.  Must be stored 
 *                          in RAM if being called by PIC18.
 *                          Ex. www.microchip.com
 *
 * Output:          Begins the process of transmitting an ICMP echo 
 *               request.  This normally involves an ARP 
 *               resolution procedure first.
 *
 * Side Effects:    None
 *
 * Overview:        None
 *
 * Note:            None
 ********************************************************************/
void ICMPSendPingToHost(BYTE * szRemoteHost)
{
   ICMPFlags.bReplyValid = 0;
   ICMPTimer = TickGet();
   ICMPFlags.bRemoteHostIsROM = 0;
   StaticVars.RemoteHost.szRAM = szRemoteHost;
   ICMPState = SM_DNS_SEND_QUERY;
}

#if defined(__18CXX)

/*********************************************************************
 * Function:        void ICMPSendPingToHostROM (ROM BYTE * szRemoteHost)
 *
 * PreCondition:    ICMPBeginUsage() returned TRUE
 *
 * Input:           szRemoteHost: Host name to ping.  Must be stored 
 *                          in ROM. Should only be called by PIC18.
 *                          Ex. www.microchip.com
 *
 * Output:          None
 *
 * Side Effects:    None
 *
 * Overview:        Begins the process of transmitting an ICMP echo 
 *               request.  This normally involves an ARP 
 *               resolution procedure first.
 *
 * Note:            None
 ********************************************************************/
void ICMPSendPingToHostROM(ROM BYTE * szRemoteHost)
{
   ICMPFlags.bReplyValid = 0;
   ICMPTimer = TickGet();
   ICMPFlags.bRemoteHostIsROM = 1;
   StaticVars.RemoteHost.szROM = szRemoteHost;
   ICMPState = SM_DNS_SEND_QUERY;
}

#endif
#endif

/*********************************************************************
 * Function:        LONG ICMPGetReply(void)
 *
 * PreCondition:    ICMPBeginUsage() returned TRUE and ICMPSendPing() 
 *               was called
 *
 * Input:           None
 *
 * Output:          -3: Could not resolve hostname (DNS timeout or 
 *                    hostname invalid)
 *               -2: No response received yet
 *               -1: Operation timed out (longer than ICMP_TIMEOUT) 
 *                  has elapsed.
 *               >=0: Number of TICKs that elapsed between 
 *                   initial ICMP transmission and reception of 
 *                   a valid echo.
 *
 * Side Effects:    None
 *
 * Overview:        None
 *
 * Note:            None
 ********************************************************************/
LONG ICMPGetReply(void)
{
   ICMP_PACKET ICMPPacket;

   switch(ICMPState)
   {
#if defined(STACK_USE_DNS)
      case SM_DNS_SEND_QUERY:
         // Obtain DNS module ownership
         if(!DNSBeginUsage())
            break;
         
         // Send DNS query
         if(ICMPFlags.bRemoteHostIsROM)
            DNSResolveROM(StaticVars.RemoteHost.szROM, DNS_TYPE_A);
         else
            DNSResolve(StaticVars.RemoteHost.szRAM, DNS_TYPE_A);
         
         ICMPState = SM_DNS_GET_RESPONSE;
         break;
            
      case SM_DNS_GET_RESPONSE:
         // See if DNS is done, and if so, get the remote IP address
         if(!DNSIsResolved(&StaticVars.ICMPRemote.IPAddr))
            break;
         
         // Free the DNS module
         DNSEndUsage();
         
         // Return error code if the DNS query failed
         if(StaticVars.ICMPRemote.IPAddr.Val == 0x00000000ul)
         {
            ICMPState = SM_IDLE;
            return -3;
         }

         ICMPState = SM_ARP_SEND_QUERY;   
         // No break;   
#endif

      case SM_ARP_SEND_QUERY:
         ARPResolve(&StaticVars.ICMPRemote.IPAddr);
         ICMPState = SM_ARP_GET_RESPONSE;
         break;
         
      case SM_ARP_GET_RESPONSE:
         // See if the ARP reponse was successfully received
         if(!ARPIsResolved(&StaticVars.ICMPRemote.IPAddr, &StaticVars.ICMPRemote.MACAddr))
            break;
         
         ICMPState = SM_ICMP_SEND_ECHO_REQUEST;
         // No break; 
      
      case SM_ICMP_SEND_ECHO_REQUEST:
          if(!IPIsTxReady())
             break;

         // Set up the ping packet
         ICMPPacket.vType = 0x08;   // 0x08: Echo (ping) request
         ICMPPacket.vCode = 0x00;
         ICMPPacket.wChecksum = 0x0000;
         ICMPPacket.wIdentifier = 0xEFBE;
         wICMPSequenceNumber++; 
         ICMPPacket.wSequenceNumber = wICMPSequenceNumber;
         ICMPPacket.wData = 0x2860;
         ICMPPacket.wChecksum = CalcIPChecksum((BYTE*)&ICMPPacket, sizeof(ICMPPacket));
      
         // Record the current time.  This will be used as a basis for 
         // finding the echo response time, which exludes the ARP and DNS 
         // steps
         ICMPTimer = TickGet();

         // Position the write pointer for the next IPPutHeader operation
          MACSetWritePtr(BASE_TX_ADDR + sizeof(ETHER_HEADER));
      
         // Create IP header in TX memory
         IPPutHeader(&StaticVars.ICMPRemote, IP_PROT_ICMP, sizeof(ICMPPacket));
         MACPutArray((BYTE*)&ICMPPacket, sizeof(ICMPPacket));
         MACFlush();

         // Echo sent, advance state
         ICMPState = SM_ICMP_GET_ECHO_RESPONSE;
         break;

      case SM_ICMP_GET_ECHO_RESPONSE:
         // See if the echo was successfully received
         if(ICMPFlags.bReplyValid)
            return (LONG)ICMPTimer;
      
         break;
      
      // SM_IDLE or illegal/impossible state:
      default:
         return -1;
   }

   // See if the DNS/ARP/echo request timed out
   if(TickGet() - ICMPTimer > ICMP_TIMEOUT)
   {
      // Free DNS module if we have it in use
      #if defined(STACK_USE_DNS)
         if(ICMPState == SM_DNS_GET_RESPONSE)
            DNSEndUsage();
      #endif
      
      // Stop ICMP echo test and return error to caller
      ICMPState = SM_IDLE;
      return -1;
   }

   // Still working.  No response to report yet.
   return -2;
}


/*********************************************************************
 * Function:        BOOL ICMPBeginUsage(void)
 *
 * PreCondition:    None
 *
 * Input:           None
 *
 * Output:          TRUE: You have successfully gained ownership of 
 *                    the ICMP client module and can now use the 
 *                    ICMPSendPing() and ICMPGetReply() functions.
 *               FALSE: Some other application is using the ICMP 
 *                     client module.  Calling ICMPSendPing() 
 *                     will corrupt the other application's ping 
 *                     result.
 *
 * Side Effects:    None
 *
 * Overview:        Claims ownership of the ICMP module.
 *
 * Note:            None
 ********************************************************************/
BOOL ICMPBeginUsage(void)
{
   if(ICMPFlags.bICMPInUse)
      return FALSE;

   ICMPFlags.bICMPInUse = TRUE;
   return TRUE;
}


/*********************************************************************
 * Function:        void ICMPEndUsage(void)
 *
 * PreCondition:    ICMPBeginUsage() was called by you and it 
 *               returned TRUE.
 *
 * Input:           None
 *
 * Output:          Your ownership of the ICMP module is released.  
 *               You can no longer use ICMPSendPing().
 *
 * Side Effects:    None
 *
 * Overview:        Gives up ownership of the ICMP module.
 *
 * Note:            None
 ********************************************************************/
void ICMPEndUsage(void)
{
   ICMPFlags.bICMPInUse = FALSE;
}

#endif //#if defined(STACK_USE_ICMP_CLIENT)

#endif //#if defined(STACK_USE_ICMP_SERVER) || defined(STACK_USE_ICMP_CLIENT)
