/****************************************************************************
 * net/udp/udp_finddev.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>
#if defined(CONFIG_NET) && defined(CONFIG_NET_UDP)

#include <string.h>

#include <nuttx/net/netdev.h>
#include <nuttx/net/ip.h>

#include "netdev/netdev.h"
#include "inet/inet.h"
#include "udp/udp.h"
#include "utils/utils.h"

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

/****************************************************************************
 * Name: udp_find_laddr_device
 *
 * Description:
 *   Select the network driver to use with the UDP transaction using the
 *   locally bound IP address.
 *
 *   This is currently used in the UDP network poll setup to determine
 *   which device is being polled.
 *
 * Input Parameters:
 *   conn - UDP connection structure (not currently used).
 *
 * Returned Value:
 *   A pointer to the network driver to use.  NULL is returned if driver is
 *   not bound to any local device.
 *
 ****************************************************************************/

FAR struct net_driver_s *udp_find_laddr_device(FAR struct udp_conn_s *conn)
{
  /* There are multiple network devices.  We need to select the device that
   * is going to route the UDP packet based on the provided IP address.
   */

#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
      if (conn->domain == PF_INET)
#endif
        {
          /* Make sure that the socket is bound to some non-zero, local
           * address.  Zero is used as an indication that the laddr is
           * uninitialized and that the socket is, hence, not bound.
           */

          if (conn->u.ipv4.laddr == 0)
            {
              return NULL;
            }
          else
            {
              return netdev_findby_ripv4addr(conn->u.ipv4.laddr,
                                             conn->u.ipv4.laddr);
            }
        }
#endif

#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
      else
#endif
        {
          /* Make sure that the socket is bound to some non-zero, local
           * address.  The IPv6 unspecified address is used as an indication
           * that the laddr is uninitialized and that the socket is, hence,
           * not bound.
           */

          if (net_ipv6addr_cmp(conn->u.ipv6.laddr, g_ipv6_unspecaddr))
            {
              return NULL;
            }
          else
            {
              return netdev_findby_ripv6addr(conn->u.ipv6.laddr,
                                             conn->u.ipv6.laddr);
            }
        }
#endif
}

/****************************************************************************
 * Name: udp_find_raddr_device
 *
 * Description:
 *   Select the network driver to use with the UDP transaction using the
 *   remote IP address.
 *
 *   This function is called for UDP sendto() in order to determine which
 *   network device that the UDP pack should be sent on.
 *
 * Input Parameters:
 *   conn - UDP connection structure.
 *
 * Returned Value:
 *   A pointer to the network driver to use.
 *
 ****************************************************************************/

FAR struct net_driver_s *
udp_find_raddr_device(FAR struct udp_conn_s *conn,
                      FAR struct sockaddr_storage *remote)
{
  /* We need to select the device that is going to route the UDP packet
   * based on the provided IP address.
   */

#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
      if (conn->domain == PF_INET)
#endif
        {
          in_addr_t raddr;

          if (remote)
            {
              FAR const struct sockaddr_in *inaddr =
                (FAR const struct sockaddr_in *)remote;
              net_ipv4addr_copy(raddr, inaddr->sin_addr.s_addr);
            }
          else
            {
              net_ipv4addr_copy(raddr, conn->u.ipv4.raddr);
            }

#if defined(CONFIG_NET_IGMP) && defined(CONFIG_NET_BINDTODEVICE)
          if (IN_MULTICAST(NTOHL(raddr)))
            {
              if ((conn->sconn.s_boundto == 0) &&
                  (conn->mreq.imr_ifindex != 0))
                {
                  return netdev_findbyindex(conn->mreq.imr_ifindex);
                }
            }
          else
#endif
            {
              if (conn->u.ipv4.laddr != INADDR_ANY)
                {
                  /* If the socket is bound to some non-zero, local address.
                   * Normal lookup using the verified local address.
                   */

                  return netdev_findby_lipv4addr(conn->u.ipv4.laddr);
                }

#ifdef CONFIG_NET_BINDTODEVICE
              if (conn->sconn.s_boundto != 0)
                {
                  /* If the socket is bound to a local network device.
                   * Select the network device that has been bound.
                   * If the index is invalid, return NULL.
                   */

                  return netdev_findbyindex(conn->sconn.s_boundto);
                }
#endif
            }

          /* Normal lookup using the verified remote address */

          return netdev_findby_ripv4addr(conn->u.ipv4.laddr, raddr);
        }
#endif

#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
      else
#endif
        {
          struct in6_addr raddr;
          if (remote)
            {
              FAR const struct sockaddr_in6 *inaddr =
                (FAR const struct sockaddr_in6 *)remote;
              net_ipv6addr_copy(raddr.in6_u.u6_addr16,
                                inaddr->sin6_addr.s6_addr16);
            }
          else
            {
              net_ipv6addr_copy(raddr.in6_u.u6_addr16, conn->u.ipv6.raddr);
            }

#if defined(CONFIG_NET_MLD) && defined(CONFIG_NET_BINDTODEVICE)
          if (IN6_IS_ADDR_MULTICAST(&raddr))
            {
              if (conn->mreq.imr_ifindex != 0)
                {
                  return netdev_findbyindex(conn->mreq.imr_ifindex);
                }
              else if (conn->sconn.s_boundto != 0)
                {
                  return netdev_findbyindex(conn->sconn.s_boundto);
                }
            }
          else
#endif
            {
              if (!net_ipv6addr_cmp(conn->u.ipv6.laddr, g_ipv6_unspecaddr))
                {
                  /* If the socket is bound to some non-zero, local address.
                   * Normal lookup using the verified local address.
                   */

                  return netdev_findby_lipv6addr(conn->u.ipv6.laddr);
                }

#ifdef CONFIG_NET_BINDTODEVICE
              if (conn->sconn.s_boundto != 0)
                {
                  /* If the socket is bound to a local network device.
                   * Select the network device that has been bound.
                   * If the index is invalid, return NULL.
                   */

                  return netdev_findbyindex(conn->sconn.s_boundto);
                }
#endif
            }

          /* Normal lookup using the verified remote address */

          return netdev_findby_ripv6addr(conn->u.ipv6.laddr,
                                         raddr.in6_u.u6_addr16);
        }
#endif
}

#endif /* CONFIG_NET && CONFIG_NET_UDP */
