/**
 * @file ip.c
 * @brief IPv4 and IPv6 common routines
 *
 * @section License
 *
 * Copyright (C) 2010-2014 Oryx Embedded. All rights reserved.
 *
 * This file is part of CycloneTCP Open.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * @author Oryx Embedded (www.oryx-embedded.com)
 * @version 1.4.4
 **/

//Switch to the appropriate trace level
#define TRACE_LEVEL IP_TRACE_LEVEL

//Dependencies
#include "tcp_ip_stack.h"
#include "ethernet.h"
#include "ip.h"
#include "ipv4.h"
#include "ipv6.h"
#include "debug.h"

//Special IP address
const IpAddr IP_ADDR_ANY = {0};


/**
 * @brief Send an IP datagram
 * @param[in] interface Underlying network interface
 * @param[in] pseudoHeader IP pseudo header
 * @param[in] buffer Multi-part buffer containing the payload
 * @param[in] offset Offset to the first payload byte
 * @param[in] ttl TTL value. Default Time-To-Live is used when this parameter is zero
 * @return Error code
 **/

error_t ipSendDatagram(NetInterface *interface, IpPseudoHeader *pseudoHeader,
   ChunkedBuffer *buffer, size_t offset, uint8_t ttl)
{
   error_t error;

#if (IPV4_SUPPORT == ENABLED)
   //Destination address is an IPv4 address?
   if(pseudoHeader->length == sizeof(Ipv4PseudoHeader))
   {
      //Form an IPv4 packet and send it
      error = ipv4SendDatagram(interface, &pseudoHeader->ipv4Data,
         buffer, offset, ttl);
   }
   else
#endif
#if (IPV6_SUPPORT == ENABLED)
   //Destination address is an IPv6 address?
   if(pseudoHeader->length == sizeof(Ipv6PseudoHeader))
   {
      //Form an IPv6 packet and send it
      error = ipv6SendDatagram(interface, &pseudoHeader->ipv6Data,
         buffer, offset, ttl);
   }
   else
#endif
   //Destination address is invalid
   {
      //Report an error
      error = ERROR_INVALID_ADDRESS;
   }

   //Return status code
   return error;
}


/**
 * @brief IP source address selection
 *
 * This function selects the source address and the relevant network interface
 * to be used in order to join the specified destination address
 *
 * @param[in,out] interface A pointer to a valid network interface may provided as
 *   a hint. The function returns a pointer identifying the interface to be used
 * @param[in] destAddr Destination IP address
 * @param[out] srcAddr Local IP address to be used
 * @return Error code
 **/

error_t ipSelectSourceAddr(NetInterface **interface,
   const IpAddr *destAddr, IpAddr *srcAddr)
{
#if (IPV4_SUPPORT == ENABLED)
   //The destination address is an IPv4 address?
   if(destAddr->length == sizeof(Ipv4Addr))
   {
      //An IPv4 address is expected
      srcAddr->length = sizeof(Ipv4Addr);
      //Get the most appropriate source address to use
      return ipv4SelectSourceAddr(interface, destAddr->ipv4Addr, &srcAddr->ipv4Addr);
   }
   else
#endif
#if (IPV6_SUPPORT == ENABLED)
   //The destination address is an IPv6 address?
   if(destAddr->length == sizeof(Ipv6Addr))
   {
      //An IPv6 address is expected
      srcAddr->length = sizeof(Ipv6Addr);
      //Get the most appropriate source address to use
      return ipv6SelectSourceAddr(interface, &destAddr->ipv6Addr, &srcAddr->ipv6Addr);
   }
   else
#endif
   //The destination address is not valid?
   {
      //Report an error
      return ERROR_INVALID_ADDRESS;
   }
}


/**
 * @brief IP checksum calculation
 * @param[in] data Pointer to the data over which to calculate the IP checksum
 * @param[in] length Number of bytes to process
 * @return Checksum value
 **/

uint16_t ipCalcChecksum(const void *data, size_t length)
{
   //Checksum preset value
   uint32_t checksum = 0x0000;

   //Process all the data
   while(length > 1)
   {
      //Update checksum value
      checksum += *((uint16_t *) data);
      //Point to the next 16-bit word
      data = (uint16_t *) data + 1;
      //Adjust the number of remaining words to process
      length -= 2;
   }

   //Add left-over byte, if any
   if(length > 0)
      checksum += *((uint8_t *) data);

   //Fold 32-bit sum to 16 bits
   while(checksum >> 16)
      checksum = (checksum & 0xFFFF) + (checksum >> 16);

   //Return 1's complement value
   return checksum ^ 0xFFFF;
}


/**
 * @brief Calculate IP checksum over a multi-part buffer
 * @param[in] buffer Pointer to the multi-part buffer
 * @param[in] offset Offset from the beginning of the buffer
 * @param[in] length Number of bytes to process
 * @return Checksum value
 **/

uint16_t ipCalcChecksumEx(const ChunkedBuffer *buffer, size_t offset, size_t length)
{
   uint_t i;
   uint_t m;
   uint_t n;
   bool_t odd;
   uint8_t *data;
   uint32_t checksum;

   //Checksum preset value
   checksum = 0x0000;
   //Total number of bytes processed
   n = 0;

   //Loop through data chunks
   for(i = 0; i < buffer->chunkCount && n < length; i++)
   {
      //Is there any data to process in the current chunk?
      if(offset < buffer->chunk[i].length)
      {
         //Check whether the total number of bytes already processed is odd
         odd = (n & 1) ? TRUE : FALSE;

         //Point to the first data byte
         data = (uint8_t *) buffer->chunk[i].address + offset;

         //Number of bytes available in the current chunk
         m = buffer->chunk[i].length - offset;
         //Limit the number of byte to process
         m = min(m, length - n);

         //Now adjust the total length
         n += m;

         //Data buffer is not aligned on 16-bit boundaries?
         if((uint32_t) data & 1)
         {
            //The total number of bytes is even?
            if(!odd)
            {
               //Fold 32-bit sum to 16 bits
               while(checksum >> 16)
                  checksum = (checksum & 0xFFFF) + (checksum >> 16);
               //Swap checksum value
               checksum = ((checksum >> 8) | (checksum << 8)) & 0xFFFF;
            }

            //Restore the alignment on 16-bit boundaries
            if(m > 0)
            {
               //Update checksum value
               checksum += *data << 8;
               //Point to the next byte
               data += 1;
               m -= 1;
            }

            //Process the data 2 bytes at a time
            while(m > 1)
            {
               //Update checksum value
               checksum += *((uint16_t *) data);
               //Point to the next 16-bit word
               data += 2;
               m -= 2;
            }

            //Add left-over byte, if any
            if(m > 0)
               checksum += *data;

            //Restore checksum endianness
            if(!odd)
            {
               //Fold 32-bit sum to 16 bits
               while(checksum >> 16)
                  checksum = (checksum & 0xFFFF) + (checksum >> 16);
               //Swap checksum value
               checksum = ((checksum >> 8) | (checksum << 8)) & 0xFFFF;
            }
         }
         //Data buffer is aligned on 16-bit boundaries?
         else
         {
            //The total number of bytes is odd?
            if(odd)
            {
               //Fold 32-bit sum to 16 bits
               while(checksum >> 16)
                  checksum = (checksum & 0xFFFF) + (checksum >> 16);
               //Swap checksum value
               checksum = ((checksum >> 8) | (checksum << 8)) & 0xFFFF;
            }

            //Process the data 2 bytes at a time
            while(m > 1)
            {
               //Update checksum value
               checksum += *((uint16_t *) data);
               //Point to the next 16-bit word
               data += 2;
               m -= 2;
            }

            //Add left-over byte, if any
            if(m > 0)
               checksum += *data;

            //Restore checksum endianness
            if(odd)
            {
               //Fold 32-bit sum to 16 bits
               while(checksum >> 16)
                  checksum = (checksum & 0xFFFF) + (checksum >> 16);
               //Swap checksum value
               checksum = ((checksum >> 8) | (checksum << 8)) & 0xFFFF;
            }
         }

         //Process the next block from the start
         offset = 0;
      }
      else
      {
         //Skip the current chunk
         offset -= buffer->chunk[i].length;
      }
   }

   //Fold 32-bit sum to 16 bits
   while(checksum >> 16)
      checksum = (checksum & 0xFFFF) + (checksum >> 16);

   //Return 1's complement value
   return checksum ^ 0xFFFF;
}


/**
 * @brief Calculate IP upper-layer checksum
 * @param[in] pseudoHeader Pointer to the pseudo header
 * @param[in] pseudoHeaderLength Pseudo header length
 * @param[in] data Pointer to the upper-layer data
 * @param[in] dataLength Upper-layer data length
 * @return Checksum value
 **/

uint16_t ipCalcUpperLayerChecksum(const void *pseudoHeader,
   size_t pseudoHeaderLength, const void *data, size_t dataLength)
{
   //Checksum preset value
   uint32_t checksum = 0x0000;

   //Process pseudo header
   while(pseudoHeaderLength > 1)
   {
      //Update checksum value
      checksum += *((uint16_t *) pseudoHeader);
      //Point to the next 16-bit word
      pseudoHeader = (uint16_t *) pseudoHeader + 1;
      //Adjust the number of remaining words to process
      pseudoHeaderLength -= 2;
   }

   //Process upper-layer data
   while(dataLength > 1)
   {
      //Update checksum value
      checksum += *((uint16_t *) data);
      //Point to the next 16-bit word
      data = (uint16_t *) data + 1;
      //Adjust the number of remaining words to process
      dataLength -= 2;
   }

   //Add left-over byte, if any
   if(dataLength > 0)
      checksum += *((uint8_t *) data);

   //Fold 32-bit sum to 16 bits
   while(checksum >> 16)
      checksum = (checksum & 0xFFFF) + (checksum >> 16);

   //Calculate 1's complement value
   checksum = checksum ^ 0xFFFF;

   //Return checksum value
   return (checksum == 0x0000) ? 0xFFFF : checksum;
}


/**
 * @brief Calculate IP upper-layer checksum over a multi-part buffer
 * @param[in] pseudoHeader Pointer to the pseudo header
 * @param[in] pseudoHeaderLength Pseudo header length
 * @param[in] buffer Multi-part buffer containing the upper-layer data
 * @param[in] offset Offset from the first data byte to process
 * @param[in] length Number of data bytes to process
 * @return Checksum value
 **/

uint16_t ipCalcUpperLayerChecksumEx(const void *pseudoHeader,
   size_t pseudoHeaderLength, const ChunkedBuffer *buffer, size_t offset, size_t length)
{
   uint32_t checksum;

   //Process upper-layer data
   checksum = ipCalcChecksumEx(buffer, offset, length);
   //Calculate 1's complement value
   checksum = checksum ^ 0xFFFF;

   //Process pseudo header
   while(pseudoHeaderLength > 1)
   {
      //Update checksum value
      checksum += *((uint16_t *) pseudoHeader);
      //Point to the next 16-bit word
      pseudoHeader = (uint16_t *) pseudoHeader + 1;
      //Adjust the number of remaining words to process
      pseudoHeaderLength -= 2;
   }

   //Fold 32-bit sum to 16 bits
   while(checksum >> 16)
      checksum = (checksum & 0xFFFF) + (checksum >> 16);

   //Calculate 1's complement value
   checksum = checksum ^ 0xFFFF;

   //Return checksum value
   return (checksum == 0x0000) ? 0xFFFF : checksum;
}


/**
 * @brief Allocate a buffer to hold an IP packet
 * @param[in] length Desired payload length
 * @param[out] offset Offset to the first byte of the payload
 * @return The function returns a pointer to the newly allocated
 *   buffer. If the system is out of resources, NULL is returned
 **/

ChunkedBuffer *ipAllocBuffer(size_t length, size_t *offset)
{
   size_t headerLength;
   ChunkedBuffer *buffer;

#if (IPV6_SUPPORT == ENABLED)
   //Maximum overhead when using IPv6
   headerLength = sizeof(Ipv6Header) + sizeof(Ipv6FragmentHeader);
#else
   //Maximum overhead when using IPv4
   headerLength = sizeof(Ipv4Header);
#endif

#if (ETH_SUPPORT == ENABLED)
   //Allocate a buffer to hold the IP header and the payload
   buffer = ethAllocBuffer(length + headerLength, offset);
   //Failed to allocate buffer?
   if(!buffer) return NULL;

   //Offset to the first byte of the payload
   *offset += headerLength;
#else
   //Allocate a buffer to hold the IP header and the payload
   buffer = chunkedBufferAlloc(length + headerLength);
   //Failed to allocate buffer?
   if(!buffer) return NULL;

   //Offset to the first byte of the payload
   *offset = headerLength;
#endif

   //Return a pointer to the freshly allocated buffer
   return buffer;
}


/**
 * @brief Join the specified host group
 * @param[in] interface Underlying network interface (optional parameter)
 * @param[in] groupAddr IP address identifying the host group to join
 * @return Error code
 **/

error_t ipJoinMulticastGroup(NetInterface *interface, const IpAddr *groupAddr)
{
   //Use default network interface?
   if(!interface)
      interface = tcpIpStackGetDefaultInterface();

#if (IPV4_SUPPORT == ENABLED)
   //IPv4 multicast address?
   if(groupAddr->length == sizeof(Ipv4Addr))
   {
      //Join the specified host group
      return ipv4JoinMulticastGroup(interface, groupAddr->ipv4Addr);
   }
   else
#endif
#if (IPV6_SUPPORT == ENABLED)
   //IPv6 multicast address?
   if(groupAddr->length == sizeof(Ipv6Addr))
   {
      //Join the specified host group
      return ipv6JoinMulticastGroup(interface, &groupAddr->ipv6Addr);
   }
   else
#endif
   //Invalid IP address?
   {
      return ERROR_INVALID_ADDRESS;
   }
}


/**
 * @brief Leave the specified host group
 * @param[in] interface Underlying network interface (optional parameter)
 * @param[in] groupAddr IP address identifying the host group to leave
 * @return Error code
 **/

error_t ipLeaveMulticastGroup(NetInterface *interface, const IpAddr *groupAddr)
{
   //Use default network interface?
   if(!interface)
      interface = tcpIpStackGetDefaultInterface();

#if (IPV4_SUPPORT == ENABLED)
   //IPv4 multicast address?
   if(groupAddr->length == sizeof(Ipv4Addr))
   {
      //Drop membership
      return ipv4LeaveMulticastGroup(interface, groupAddr->ipv4Addr);
   }
   else
#endif
#if (IPV6_SUPPORT == ENABLED)
   //IPv6 multicast address?
   if(groupAddr->length == sizeof(Ipv6Addr))
   {
      //Drop membership
      return ipv6LeaveMulticastGroup(interface, &groupAddr->ipv6Addr);
   }
   else
#endif
   //Invalid IP address?
   {
      return ERROR_INVALID_ADDRESS;
   }
}


/**
 * @brief Compare an IP address against the unspecified address
 * @param[in] ipAddr IP address
 * @return TRUE if the IP address is unspecified, else FALSE
 **/

bool_t ipIsUnspecifiedAddr(const IpAddr *ipAddr)
{
#if (IPV4_SUPPORT == ENABLED)
   //IPv4 address?
   if(ipAddr->length == sizeof(Ipv4Addr))
   {
      //Compare IPv4 address
      return (ipAddr->ipv4Addr == IPV4_UNSPECIFIED_ADDR) ? TRUE : FALSE;
   }
   else
#endif
#if (IPV6_SUPPORT == ENABLED)
   //IPv6 address?
   if(ipAddr->length == sizeof(Ipv6Addr))
   {
      //Compare IPv6 address
      return ipv6CompAddr(&ipAddr->ipv6Addr, &IPV6_UNSPECIFIED_ADDR);
   }
   else
#endif
   //Invalid IP address?
   {
      return FALSE;
   }
}


/**
 * @brief Convert a string representation of an IP address to a binary IP address
 * @param[in] str NULL-terminated string representing the IP address
 * @param[out] ipAddr Binary representation of the IP address
 * @return Error code
 **/

error_t ipStringToAddr(const char_t *str, IpAddr *ipAddr)
{
#if (IPV6_SUPPORT == ENABLED)
   //IPv6 address?
   if(strchr(str, ':'))
   {
      //IPv6 addresses are 16-byte long
      ipAddr->length = sizeof(Ipv6Addr);
      //Convert the string to IPv6 address
      return ipv6StringToAddr(str, &ipAddr->ipv6Addr);
   }
   else
#endif
#if (IPV4_SUPPORT == ENABLED)
   //IPv4 address?
   if(strchr(str, '.'))
   {
      //IPv4 addresses are 4-byte long
      ipAddr->length = sizeof(Ipv4Addr);
      //Convert the string to IPv4 address
      return ipv4StringToAddr(str, &ipAddr->ipv4Addr);
   }
   else
#endif
   //Invalid IP address?
   {
      //Report an error
      return ERROR_FAILURE;
   }
}


/**
 * @brief Convert a binary IP address to a string representation
 * @param[in] ipAddr Binary representation of the IP address
 * @param[out] str NULL-terminated string representing the IP address
 * @return Pointer to the formatted string
 **/

char_t *ipAddrToString(const IpAddr *ipAddr, char_t *str)
{
#if (IPV4_SUPPORT == ENABLED)
   //IPv4 address?
   if(ipAddr->length == sizeof(Ipv4Addr))
   {
      //Convert IPv4 address to string representation
      return ipv4AddrToString(ipAddr->ipv4Addr, str);
   }
   else
#endif
#if (IPV6_SUPPORT == ENABLED)
   //IPv6 address?
   if(ipAddr->length == sizeof(Ipv6Addr))
   {
      //Convert IPv6 address to string representation
      return ipv6AddrToString(&ipAddr->ipv6Addr, str);
   }
   else
#endif
   //Invalid IP address?
   {
      static char_t c;
      //The str parameter is optional
      if(!str) str = &c;
      //Properly terminate the string
      str[0] = '\0';
      //Return an empty string
      return str;
   }
}
