/****************************************************************************
 * net/netdev/netdev_checksum.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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/net/netdev.h>
#include <nuttx/net/udp.h>
#include <nuttx/net/tcp.h>
#include <nuttx/mm/iob.h>

#include "netdev/netdev.h"

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: hardware_chksum_start
 *
 * Description:
 *   get checksum start offset position with iob buffer
 *
 * Input Parameters:
 *   dev       -  The driver structure
 *   iphdrlen  -  ipv4/ipv6 header length
 *
 * Returned Value:
 *   The checksum start offset position
 *
 ****************************************************************************/

#if defined(CONFIG_MM_IOB) && \
    (defined(CONFIG_NET_IPv4) || defined(CONFIG_NET_IPv6))
static int32_t hardware_chksum_start(FAR struct iob_s *iob,
                                     uint16_t iphdrlen)
{
  if (!iob || iphdrlen > iob->io_len)
    {
      return -EINVAL;
    }

  return iob->io_offset + iphdrlen;
}
#endif

/****************************************************************************
 * Name: hardware_chksum_get_proto
 *
 * Description:
 *   get proto with dev.
 *
 * Input Parameters:
 *   dev  -  The driver structure
 *
 * Returned Value:
 *   The proto value
 *
 ****************************************************************************/

#if defined(CONFIG_NET_UDP) || defined(CONFIG_NET_TCP)
static uint8_t hardware_chksum_get_proto(FAR struct net_driver_s *dev)
{
  uint8_t proto = 0;

#ifdef CONFIG_NET_IPv6
#  ifdef CONFIG_NET_IPv4
  if (IFF_IS_IPv6(dev->d_flags))
#  endif
    {
      FAR struct ipv6_hdr_s *ipv6 = IPv6BUF;
      proto = ipv6->proto;
    }
#endif
#ifdef CONFIG_NET_IPv4
#  ifdef CONFIG_NET_IPv6
  else
#  endif
    {
      FAR struct ipv4_hdr_s *ipv4 = IPv4BUF;
      proto = ipv4->proto;
    }
#endif

  return proto;
}
#endif

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

/****************************************************************************
 * Name: netdev_checksum_start
 *
 * Description:
 *   get checksum start offset position with iob, then hardwear can
 *   use to calculate the package payload checksum value.
 *
 * Input Parameters:
 *   dev  -  The driver structure
 *
 * Returned Value:
 *   The checksum start offset position, -EINVAL is mean not need calculate
 *   with hardware
 *
 ****************************************************************************/

int netdev_checksum_start(FAR struct net_driver_s *dev)
{
#ifdef CONFIG_MM_IOB
  int start = -EINVAL;

#ifdef CONFIG_NET_IPv6
#  ifdef CONFIG_NET_IPv4
  if (IFF_IS_IPv6(dev->d_flags))
#  endif
    {
      FAR struct ipv6_hdr_s *ipv6 =
        (FAR struct ipv6_hdr_s *)(IOB_DATA(dev->d_iob));

      if ((ipv6->proto == IP_PROTO_UDP) || (ipv6->proto == IP_PROTO_TCP))
        {
          start = hardware_chksum_start(dev->d_iob, IPv6_HDRLEN);
        }
      else
        {
          return -EINVAL;
        }
    }
#endif
#ifdef CONFIG_NET_IPv4
#  ifdef CONFIG_NET_IPv6
  else
#  endif
    {
      FAR struct ipv4_hdr_s *ipv4 =
        (FAR struct ipv4_hdr_s *)(IOB_DATA(dev->d_iob));

      if ((ipv4->proto == IP_PROTO_UDP) || (ipv4->proto == IP_PROTO_TCP))
        {
          start = hardware_chksum_start(dev->d_iob,
                                        ((ipv4->vhl & IPv4_HLMASK) << 2));
        }
      else
        {
          return -EINVAL;
        }
    }
#endif

  return start;
#else
  return -EINVAL;
#endif
}

/****************************************************************************
 * Name: netdev_checksum_offset
 *
 * Description:
 *   get checksum field offset with tcp/udp header.
 *
 * Input Parameters:
 *   dev  -  The driver structure
 *
 * Returned Value:
 *   The checksum field offset with L4, -EINVAL is mean not need calculate
 *   with hardware
 *
 ****************************************************************************/

int netdev_checksum_offset(FAR struct net_driver_s *dev)
{
#if defined(CONFIG_NET_UDP) || defined(CONFIG_NET_TCP)
  uint8_t proto = hardware_chksum_get_proto(dev);
#endif

#ifdef CONFIG_NET_UDP
  if (proto == IP_PROTO_UDP)
    {
      return offsetof(struct udp_hdr_s, udpchksum);
    }
#endif

#ifdef CONFIG_NET_TCP
  if (proto == IP_PROTO_TCP)
    {
      return offsetof(struct tcp_hdr_s, tcpchksum);
    }
#endif

  return -EINVAL;
}

/****************************************************************************
 * Name: netdev_upperlayer_header_checksum
 *
 * Description:
 *   get upperlayer header checksum with tcp/udp header.
 *
 * Input Parameters:
 *   dev  -  The driver structure
 *
 * Returned Value:
 *   The upperlayer header checksum
 *
 ****************************************************************************/

uint16_t netdev_upperlayer_header_checksum(FAR struct net_driver_s *dev)
{
#ifdef CONFIG_NET_IPv6
#  ifdef CONFIG_NET_IPv4
  if (IFF_IS_IPv6(dev->d_flags))
#  endif
    {
      FAR struct ipv6_hdr_s *ipv6 = IPv6BUF;

      return HTONS(ipv6_upperlayer_header_chksum(dev,
                                                 ipv6->proto,
                                                 IPv6_HDRLEN));
    }
#endif
#ifdef CONFIG_NET_IPv4
#  ifdef CONFIG_NET_IPv6
  else
#  endif
    {
      FAR struct ipv4_hdr_s *ipv4 = IPv4BUF;

      return HTONS(ipv4_upperlayer_header_chksum(dev,
                                                 ipv4->proto));
    }
#endif

  return 0;
}