/****************************************************************************
 * net/pkt/pkt_sockif.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>

#include <sys/types.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <netpacket/packet.h>

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

#include "netdev/netdev.h"
#include <socket/socket.h>
#include "pkt/pkt.h"

#ifdef CONFIG_NET_PKT

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int        pkt_setup(FAR struct socket *psock);
static sockcaps_t pkt_sockcaps(FAR struct socket *psock);
static void       pkt_addref(FAR struct socket *psock);
static int        pkt_bind(FAR struct socket *psock,
                    FAR const struct sockaddr *addr, socklen_t addrlen);
static int        pkt_close(FAR struct socket *psock);

/****************************************************************************
 * Public Data
 ****************************************************************************/

const struct sock_intf_s g_pkt_sockif =
{
  pkt_setup,       /* si_setup */
  pkt_sockcaps,    /* si_sockcaps */
  pkt_addref,      /* si_addref */
  pkt_bind,        /* si_bind */
  NULL,            /* si_getsockname */
  NULL,            /* si_getpeername */
  NULL,            /* si_listen */
  NULL,            /* si_connect */
  NULL,            /* si_accept */
  NULL,            /* si_poll */
  pkt_sendmsg,     /* si_sendmsg */
  pkt_recvmsg,     /* si_recvmsg */
  pkt_close        /* si_close */
};

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

/****************************************************************************
 * Name: pkt_sockif_alloc
 *
 * Description:
 *   Allocate and attach a raw packet connection structure.
 *
 ****************************************************************************/

static int pkt_sockif_alloc(FAR struct socket *psock)
{
  /* Allocate the packet socket connection structure and save in the new
   * socket instance.
   */

  FAR struct pkt_conn_s *conn = pkt_alloc();
  if (conn == NULL)
    {
      /* Failed to reserve a connection structure */

      return -ENOMEM;
    }

  /* Set the reference count on the connection structure.  This reference
   * count will be incremented only if the socket is dup'ed
   */

  DEBUGASSERT(conn->crefs == 0);
  conn->crefs = 1;

  /* Save the pre-allocated connection in the socket structure */

  psock->s_conn = conn;
  return OK;
}

/****************************************************************************
 * Name: pkt_setup
 *
 * Description:
 *   Called for socket() to verify that the provided socket type and
 *   protocol are usable by this address family.  Perform any family-
 *   specific socket fields.
 *
 * Input Parameters:
 *   psock    A pointer to a user allocated socket structure to be
 *            initialized.
 *
 * Returned Value:
 *   Zero (OK) is returned on success.  Otherwise, a negated errno value is
 *   returned.
 *
 ****************************************************************************/

static int pkt_setup(FAR struct socket *psock)
{
  /* Allocate the appropriate connection structure.  This reserves the
   * connection structure, it is unallocated at this point.  It will not
   * actually be initialized until the socket is connected.
   *
   * SOCK_RAW and SOCK_CTRL are supported.
   */

  if (psock->s_type == SOCK_RAW || psock->s_type == SOCK_CTRL)
    {
      return pkt_sockif_alloc(psock);
    }
  else
    {
      return -EPROTONOSUPPORT;
    }
}

/****************************************************************************
 * Name: pkt_sockcaps
 *
 * Description:
 *   Return the bit encoded capabilities of this socket.
 *
 * Input Parameters:
 *   psock - Socket structure of the socket whose capabilities are being
 *           queried.
 *
 * Returned Value:
 *   The set of socket capabilities is returned.
 *
 ****************************************************************************/

static sockcaps_t pkt_sockcaps(FAR struct socket *psock)
{
  return 0;
}

/****************************************************************************
 * Name: pkt_addref
 *
 * Description:
 *   Increment the reference count on the underlying connection structure.
 *
 * Input Parameters:
 *   psock - Socket structure of the socket whose reference count will be
 *           incremented.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void pkt_addref(FAR struct socket *psock)
{
  FAR struct pkt_conn_s *conn;

  DEBUGASSERT(psock->s_type == SOCK_RAW || psock->s_type == SOCK_CTRL);

  conn = psock->s_conn;
  DEBUGASSERT(conn->crefs > 0 && conn->crefs < 255);
  conn->crefs++;
}

/****************************************************************************
 * Name: pkt_bind
 *
 * Description:
 *   pkt_bind() gives the socket 'psock' the local address 'addr'.  'addr'
 *   is 'addrlen' bytes long.  Traditionally, this is called "assigning a
 *   name to a socket."  When a socket is created with socket(), it exists
 *   in a name space (address family) but has no name assigned.
 *
 * Input Parameters:
 *   psock    Socket structure of the socket to bind
 *   addr     Socket local address
 *   addrlen  Length of 'addr'
 *
 * Returned Value:
 *   0 on success;  A negated errno value is returned on failure.  See
 *   bind() for a list a appropriate error values.
 *
 ****************************************************************************/

static int pkt_bind(FAR struct socket *psock,
                    FAR const struct sockaddr *addr, socklen_t addrlen)
{
  int ifindex;

  /* Verify that a valid address has been provided */

  if (addr->sa_family != AF_PACKET || addrlen < sizeof(struct sockaddr_ll))
    {
      nerr("ERROR: Invalid address length: %d < %zu\n",
           addrlen, sizeof(struct sockaddr_ll));
      return -EBADF;
    }

  /* Bind a raw socket to a network device. */

  if (psock->s_type == SOCK_RAW || psock->s_type == SOCK_CTRL)
    {
      FAR struct pkt_conn_s *conn = psock->s_conn;
      FAR struct net_driver_s *dev;

      /* Look at the addr and identify the network interface */

      ifindex = ((FAR struct sockaddr_ll *)addr)->sll_ifindex;

      /* Check if we have that interface */

      dev = netdev_findbyindex(ifindex);
      if (dev == NULL)
        {
          return -EADDRNOTAVAIL;
        }

      /* Put ifindex into connection */

      conn->ifindex = ifindex;

      return OK;
    }
  else
    {
      return -EBADF;
    }
}

/****************************************************************************
 * Name: pkt_close
 *
 * Description:
 *   Performs the close operation on a raw packet socket instance
 *
 * Input Parameters:
 *   psock   Socket instance
 *
 * Returned Value:
 *   0 on success; a negated errno value is returned on any failure.
 *
 * Assumptions:
 *
 ****************************************************************************/

static int pkt_close(FAR struct socket *psock)
{
  /* Perform some pre-close operations for the raw packet address type */

  switch (psock->s_type)
    {
      case SOCK_RAW:
      case SOCK_CTRL:
        {
          FAR struct pkt_conn_s *conn = psock->s_conn;

          /* Is this the last reference to the connection structure (there
           * could be more if the socket was dup'ed).
           */

          if (conn->crefs <= 1)
            {
              /* Yes... free any read-ahead data */

              iob_free_queue(&conn->readahead);

              /* Then free the connection structure */

              conn->crefs = 0;          /* No more references on the connection */
              pkt_free(psock->s_conn);  /* Free network resources */
            }
          else
            {
              /* No.. Just decrement the reference count */

              conn->crefs--;
            }

          return OK;
        }

      default:
        return -EBADF;
    }
}

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

#endif /* CONFIG_NET_PKT */
