/****************************************************************************
 * net/utils/net_chksum.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#ifdef CONFIG_NET

#include "utils/utils.h"

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: chksum
 *
 * Description:
 *   Calculate the raw change sum over the memory region described by
 *   data and len.
 *
 * Input Parameters:
 *   sum  - Partial calculations carried over from a previous call to
 *          chksum().  This should be zero on the first time that check
 *          sum is called.
 *   data - Beginning of the data to include in the checksum.
 *   len  - Length of the data to include in the checksum.
 *
 * Returned Value:
 *   The updated checksum value.
 *
 ****************************************************************************/

#ifndef CONFIG_NET_ARCH_CHKSUM
uint16_t chksum(uint16_t sum, FAR const uint8_t *data, uint16_t len)
{
  FAR const uint8_t *dataptr;
  FAR const uint8_t *last_byte;
  uint16_t t;

  dataptr = data;
  last_byte = data + len - 1;

  while (dataptr < last_byte)
    {
      /* At least two more bytes */

      t = ((uint16_t)dataptr[0] << 8) + dataptr[1];
      sum += t;
      if (sum < t)
        {
          sum++; /* carry */
        }

      dataptr += 2;
    }

  if (dataptr == last_byte)
    {
      t = (dataptr[0] << 8) + 0;
      sum += t;
      if (sum < t)
        {
          sum++; /* carry */
        }
    }

  /* Return sum in host byte order. */

  return sum;
}
#endif /* CONFIG_NET_ARCH_CHKSUM */

/****************************************************************************
 * Name: chksum_iob
 *
 * Description:
 *   Calculate the Internet checksum over an iob chain buffer.
 *
 * Input Parameters:
 *   sum    - Partial calculations carried over from a previous call to
 *            chksum().  This should be zero on the first time that check
 *            sum is called.
 *   iob    - An iob chain buffer over which the checksum is to be computed.
 *   offset - Specifies the byte offset of the start of valid data.
 *
 * Returned Value:
 *   The updated checksum value.
 *
 ****************************************************************************/

#ifdef CONFIG_MM_IOB
uint16_t chksum_iob(uint16_t sum, FAR struct iob_s *iob, uint16_t offset)
{
  /* Skip to the I/O buffer containing the data offset */

  while (iob != NULL && offset > iob->io_len)
    {
      offset -= iob->io_len;
      iob     = iob->io_flink;
    }

  /* If the link pointer is not empty, loop to walk through all I/O buffer
   * and accumulate the sum
   */

  while (iob != NULL)
    {
      sum = chksum(sum, iob->io_data + iob->io_offset + offset,
                   iob->io_len - offset);
      iob = iob->io_flink;
      offset = 0;
    }

  return sum;
}
#endif /* CONFIG_MM_IOB */

/****************************************************************************
 * Name: net_chksum
 *
 * Description:
 *   Calculate the Internet checksum over a buffer.
 *
 *   The Internet checksum is the one's complement of the one's complement
 *   sum of all 16-bit words in the buffer.
 *
 *   See RFC1071.
 *
 *   If CONFIG_NET_ARCH_CHKSUM is defined, then this function must be
 *   provided by architecture-specific logic.
 *
 * Input Parameters:
 *
 *   buf - A pointer to the buffer over which the checksum is to be computed.
 *
 *   len - The length of the buffer over which the checksum is to be
 *         computed.
 *
 * Returned Value:
 *   The Internet checksum of the buffer.
 *
 ****************************************************************************/

#ifndef CONFIG_NET_ARCH_CHKSUM
uint16_t net_chksum(FAR uint16_t *data, uint16_t len)
{
  return HTONS(chksum(0, (uint8_t *)data, len));
}
#endif /* CONFIG_NET_ARCH_CHKSUM */

/****************************************************************************
 * Name: net_chksum_iob
 *
 * Description:
 *   Calculate the Internet checksum over an iob chain buffer.
 *
 *   The Internet checksum is the one's complement of the one's complement
 *   sum of all 16-bit words in the buffer.
 *
 *   See RFC1071.
 *
 *   If CONFIG_NET_ARCH_CHKSUM is defined, then this function must be
 *   provided by architecture-specific logic.
 *
 * Input Parameters:
 *   sum    - Partial calculations carried over from a previous call to
 *            chksum().  This should be zero on the first time that check
 *            sum is called.
 *   iob    - An iob chain buffer over which the checksum is to be computed.
 *   offset - Specifies the byte offset of the start of valid data.
 *
 * Returned Value:
 *   The Internet checksum of the given iob chain buffer.
 *
 ****************************************************************************/

#ifdef CONFIG_MM_IOB
uint16_t net_chksum_iob(uint16_t sum, FAR struct iob_s *iob, uint16_t offset)
{
  return HTONS(chksum_iob(sum, iob, offset));
}
#endif /* CONFIG_MM_IOB */

/****************************************************************************
 * Name: net_chksum_adjust
 *
 * Description:
 *   Adjusts the checksum of a packet without having to completely
 *   recalculate it, as described in RFC 3022, Section 4.2, Page 9.
 *
 * Input Parameters:
 *   chksum - points to the chksum in the packet
 *   optr   - points to the old data in the packet
 *   olen   - length of old data
 *   nptr   - points to the new data in the packet
 *   nlen   - length of new data
 *
 * Limitations:
 *   The algorithm is applicable only for even offsets and even lengths.
 ****************************************************************************/

void net_chksum_adjust(FAR uint16_t *chksum,
                       FAR const uint16_t *optr, ssize_t olen,
                       FAR const uint16_t *nptr, ssize_t nlen)
{
  int32_t x;
  int32_t oldval;
  int32_t newval;

  x = NTOHS(*chksum);
  x = ~x & 0xffff;
  while (olen > 0)
    {
      oldval = NTOHS(*optr);

      x -= oldval & 0xffff;
      if (x <= 0)
        {
          x--;
          x &= 0xffff;
        }

      optr++;
      olen -= 2;
    }
  while (nlen > 0)
    {
      newval = NTOHS(*nptr);

      x += newval & 0xffff;
      if ((x & 0x10000) != 0)
        {
          x++;
          x &= 0xffff;
        }

      nptr++;
      nlen -= 2;
    }

  x = ~x & 0xffff;
  *chksum = HTONS(x);
}

#endif /* CONFIG_NET */
