/****************************************************************************
 * net/tcp/tcp_accept.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/config.h>
#ifdef CONFIG_NET_TCP

#include <sys/types.h>
#include <sys/socket.h>

#include <string.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>

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

#include "socket/socket.h"
#include "tcp/tcp.h"

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct accept_s
{
  sem_t                  acpt_sem;        /* Wait for driver event */
  FAR struct sockaddr   *acpt_addr;       /* Return connection address */
  FAR socklen_t         *acpt_addrlen;    /* Return length of address */
  FAR struct tcp_conn_s *acpt_newconn;    /* The accepted connection */
  int                    acpt_result;     /* The result of the wait */
};

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

/****************************************************************************
 * Name: accept_tcpsender
 *
 * Description:
 *   Get the sender's address from the UDP packet
 *
 * Input Parameters:
 *   listener - The connection structure of the listener
 *   conn     - The newly accepted TCP connection
 *   pstate   - the recvfrom state structure
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   The network is locked
 *
 ****************************************************************************/

static inline void accept_tcpsender(FAR struct tcp_conn_s *listener,
                                    FAR struct tcp_conn_s *conn,
                                    FAR struct sockaddr *addr,
                                    socklen_t *addrlen)
{
  if (addr)
    {
      /* If an address is provided, then the length must also be provided. */

      DEBUGASSERT(addrlen);

#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
      /* If both IPv4 and IPv6 support are enabled, then we will need to
       * select which one to use when obtaining the sender's IP address.
       */

      if (listener->domain == PF_INET)
#endif /* CONFIG_NET_IPv6 */
        {
          FAR struct sockaddr_in *inaddr = (FAR struct sockaddr_in *)addr;

          inaddr->sin_family = AF_INET;
          inaddr->sin_port   = conn->rport;
          net_ipv4addr_copy(inaddr->sin_addr.s_addr, conn->u.ipv4.raddr);
          memset(inaddr->sin_zero, 0, sizeof(inaddr->sin_zero));

          *addrlen = sizeof(struct sockaddr_in);
        }
#endif /* CONFIG_NET_IPv4 */

#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
      /* Otherwise, this the IPv6 address is needed */

      else
#endif /* CONFIG_NET_IPv4 */
        {
          FAR struct sockaddr_in6 *inaddr = (FAR struct sockaddr_in6 *)addr;

#if defined(CONFIG_NET_IPv4) && defined(CONFIG_NET_IPv6)
          DEBUGASSERT(listener->domain == PF_INET6);
#endif
          inaddr->sin6_family = AF_INET6;
          inaddr->sin6_port   = conn->rport;
          net_ipv6addr_copy(inaddr->sin6_addr.s6_addr, conn->u.ipv6.raddr);

          *addrlen = sizeof(struct sockaddr_in6);
        }
#endif /* CONFIG_NET_IPv6 */
    }
}

/****************************************************************************
 * Name: accept_eventhandler
 *
 * Description:
 *   Receive event callbacks when connections occur
 *
 * Input Parameters:
 *   listener The connection structure of the listener
 *   conn     The connection structure that was just accepted
 *
 * Returned Value:
 *   None
 *
 * Assumptions:
 *   The network is locked
 *
 ****************************************************************************/

static int accept_eventhandler(FAR struct tcp_conn_s *listener,
                               FAR struct tcp_conn_s *conn)
{
  struct accept_s *pstate = (struct accept_s *)listener->accept_private;
  int ret = -EINVAL;

  if (pstate)
    {
      /* Get the connection address */

      accept_tcpsender(listener, conn, pstate->acpt_addr,
                       pstate->acpt_addrlen);

      /* Save the connection structure */

      pstate->acpt_newconn     = conn;
      pstate->acpt_result      = OK;

      /* There should be a reference of one on the new connection */

      DEBUGASSERT(conn->crefs == 1);

      /* Wake-up the waiting caller thread */

      nxsem_post(&pstate->acpt_sem);

      /* Stop any further callbacks */

      listener->accept_private = NULL;
      listener->accept         = NULL;
      ret                      = OK;
    }

  return ret;
}

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

/****************************************************************************
 * Name: tcp_accept
 *
 * Description:
 *   This function implements accept() for TCP/IP sockets.  See the
 *   description of accept() for further information.
 *
 * Input Parameters:
 *   psock    The listening TCP socket structure
 *   addr     Receives the address of the connecting client
 *   addrlen  Input: allocated size of 'addr', Return: returned size of
 *            'addr'
 *   newconn  The new, accepted TCP connection structure
 *
 * Returned Value:
 *   Returns zero (OK) on success or a negated errno value on failure.
 *   See the description of accept of the possible errno values in the
 *   description of accept().
 *
 * Assumptions:
 *   Network is locked.
 *
 ****************************************************************************/

int psock_tcp_accept(FAR struct socket *psock, FAR struct sockaddr *addr,
                     FAR socklen_t *addrlen, FAR void **newconn)
{
  FAR struct tcp_conn_s *conn;
  struct accept_s state;
  int ret;

  /* Check the backlog to see if there is a connection already pending for
   * this listener.
   */

  conn = psock->s_conn;

#ifdef CONFIG_NET_TCPBACKLOG
  state.acpt_newconn = tcp_backlogremove(conn);
  if (state.acpt_newconn)
    {
      /* Yes... get the address of the connected client */

      ninfo("Pending conn=%p\n", state.acpt_newconn);
      accept_tcpsender(conn, state.acpt_newconn, addr, addrlen);
    }

  /* In general, this implementation will not support non-blocking socket
   * operations... except in a few cases:  Here for TCP accept with
   * backlog enabled.  If this socket is configured as non-blocking then
   * return EAGAIN if there is no pending connection in the backlog.
   */

  else if (_SS_ISNONBLOCK(conn->sconn.s_flags))
    {
      return -EAGAIN;
    }
  else
#endif
    {
      /* Perform the TCP accept operation */

      /* Initialize the state structure.  This is done with the network
       * locked because we don't want anything to happen until we are
       * ready.
       */

      state.acpt_addr       = addr;
      state.acpt_addrlen    = addrlen;
      state.acpt_newconn    = NULL;
      state.acpt_result     = OK;

      nxsem_init(&state.acpt_sem, 0, 0);

      /* Set up the callback in the connection */

      conn->accept_private  = (FAR void *)&state;
      conn->accept          = accept_eventhandler;

      /* Wait for the send to complete or an error to occur:  NOTES:
       * net_sem_wait will also terminate if a signal is received.
       */

      ret = net_sem_wait(&state.acpt_sem);

      /* Make sure that no further events are processed */

      conn->accept_private = NULL;
      conn->accept         = NULL;

      nxsem_destroy(&state.acpt_sem);

      /* Check for a errors.  Errors are signalled by negative errno values
       * for the send length.
       */

      if (state.acpt_result != 0)
        {
          DEBUGASSERT(state.acpt_result > 0);
          return -state.acpt_result;
        }

      /* If net_sem_wait failed, then we were probably reawakened by a
       * signal.  In this case, net_sem_wait will have returned negated
       * errno appropriately.
       */

      if (ret < 0)
        {
          return ret;
        }
    }

  *newconn = (FAR void *)state.acpt_newconn;
  return OK;
}

#endif /* CONFIG_NET_TCP */