/****************************************************************************
 * net/usrsock/usrsock_devif.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_USRSOCK)

#include <sys/types.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>

#include <nuttx/random.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/net/net.h>
#include <nuttx/net/usrsock.h>

#include "usrsock/usrsock.h"

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

struct usrsock_req_s
{
  mutex_t  lock;              /* Request mutex (only one outstanding
                               * request) */
  sem_t    acksem;            /* Request acknowledgment notification */
  uint32_t newxid;            /* New transcation Id */
  uint32_t ackxid;            /* Exchange id for which waiting ack */
  uint16_t nbusy;             /* Number of requests blocked from different
                               * threads */

  /* Connection instance to receive data buffers. */

  FAR struct usrsock_conn_s *datain_conn;
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* only support 1 usrsock network interface for the moment,
 * define it into array or construct a list
 * if multiple usrsock network interfaces are needed in the future
 */

static struct usrsock_req_s g_usrsock_req =
{
  NXMUTEX_INITIALIZER,
  SEM_INITIALIZER(0),
  0,
  0,
  0,
  NULL
};

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

/****************************************************************************
 * Name: usrsock_iovec_do() - copy to/from iovec from/to buffer.
 ****************************************************************************/

static ssize_t usrsock_iovec_do(FAR void *srcdst, size_t srcdstlen,
                                FAR struct iovec *iov, int iovcnt,
                                size_t pos, bool from_iov, FAR bool *done)
{
  FAR uint8_t *ioout = srcdst;
  FAR uint8_t *iovbuf;
  ssize_t total = 0;
  size_t srclen = 0;

  /* Rewind to correct position. */

  while (pos >= 0 && iovcnt > 0)
    {
      if (iov->iov_len <= pos)
        {
          pos -= iov->iov_len;
          iov++;
          iovcnt--;
        }
      else
        {
          break;
        }
    }

  if (iovcnt == 0)
    {
      /* Position beyond iovec. */

      total = -EINVAL;
      goto out;
    }

  iovbuf = iov->iov_base;
  srclen = iov->iov_len;
  iovbuf += pos;
  srclen -= pos;
  iov++;
  iovcnt--;

  while ((srclen > 0 || iovcnt > 0) && srcdstlen > 0)
    {
      size_t clen = srclen;

      if (srclen == 0)
        {
          /* Skip empty iovec. */

          iovbuf = iov->iov_base;
          srclen = iov->iov_len;
          iov++;
          iovcnt--;

          continue;
        }

      if (clen > srcdstlen)
        {
          clen = srcdstlen;
        }

      if (from_iov)
        {
          memmove(ioout, iovbuf, clen);
        }
      else
        {
          memmove(iovbuf, ioout, clen);
        }

      ioout += clen;
      srcdstlen -= clen;
      iovbuf += clen;
      srclen -= clen;
      total += clen;

      if (srclen == 0)
        {
          if (iovcnt == 0)
            {
              break;
            }

          iovbuf = iov->iov_base;
          srclen = iov->iov_len;
          iov++;
          iovcnt--;
        }
    }

out:
  if (done)
    {
      *done = !srclen && !iovcnt;
    }

  return total;
}

/****************************************************************************
 * Name: usrsock_handle_event
 ****************************************************************************/

static ssize_t usrsock_handle_event(FAR const void *buffer, size_t len)
{
  FAR const struct usrsock_message_common_s *common = buffer;

  switch (common->msgid)
    {
    case USRSOCK_MESSAGE_SOCKET_EVENT:
      {
        FAR const struct usrsock_message_socket_event_s *hdr = buffer;
        FAR struct usrsock_conn_s *conn;
        int ret;

        if (len < sizeof(*hdr))
          {
            nerr("message too short, %zu < %zu.\n", len, sizeof(*hdr));
            return -EINVAL;
          }

        len = sizeof(*hdr);

        /* Get corresponding usrsock connection. */

        conn = usrsock_active(hdr->usockid);
        if (!conn)
          {
            nerr("no active connection for usockid=%d, events=%x.\n",
                 hdr->usockid, hdr->head.events);

            /* We may receive event after socket close if message from lower
             * layer is out of order, but it's OK to ignore such error.
             */

            return len;
          }

#ifdef CONFIG_DEV_RANDOM
        /* Add randomness. */

        add_sw_randomness((hdr->head.events << 16) - hdr->usockid);
#endif

        /* Handle event. */

        conn->resp.events = hdr->head.events & ~USRSOCK_EVENT_INTERNAL_MASK;
        ret = usrsock_event(conn);
        if (ret < 0)
          {
            return ret;
          }
      }
      break;

    default:
      nerr("Unknown event type: %d\n", common->msgid);
      return -EINVAL;
    }

  return len;
}

/****************************************************************************
 * Name: usrsock_handle_response
 ****************************************************************************/

static ssize_t usrsock_handle_response(FAR struct usrsock_conn_s *conn,
                                       FAR const void *buffer,
                                       size_t len)
{
  FAR const struct usrsock_message_req_ack_s *hdr = buffer;

  if (USRSOCK_MESSAGE_REQ_IN_PROGRESS(hdr->head.flags))
    {
      /* In-progress response is acknowledgment that response was
       * received.
       */

      conn->resp.inprogress = true;

      /* This branch indicates successful processing and waiting
       * for USRSOCK_EVENT_CONNECT_READY event.
       */

      conn->resp.result = 0;
    }
  else
    {
      conn->resp.inprogress = false;
      conn->resp.xid = 0;

      /* Get result for common request. */

      conn->resp.result = hdr->result;

      /* Done with request/response. */

      usrsock_event(conn);
    }

  return sizeof(*hdr);
}

/****************************************************************************
 * Name: usrsock_handle_datareq_response
 ****************************************************************************/

static ssize_t
usrsock_handle_datareq_response(FAR struct usrsock_conn_s *conn,
                                FAR const void *buffer,
                                size_t len)
{
  FAR const struct usrsock_message_datareq_ack_s *datahdr = buffer;
  FAR const struct usrsock_message_req_ack_s *hdr = &datahdr->reqack;
  FAR struct usrsock_req_s *req = &g_usrsock_req;
  int num_inbufs;
  int iovpos;

  if (USRSOCK_MESSAGE_REQ_IN_PROGRESS(hdr->head.flags))
    {
      if (datahdr->reqack.result > 0)
        {
          ninfo("error: request in progress, and result > 0.\n");
          return -EINVAL;
        }
      else if (datahdr->valuelen > 0)
        {
          ninfo("error: request in progress, and valuelen > 0.\n");
          return -EINVAL;
        }

      /* In-progress response is acknowledgment that response was
       * received.
       */

      conn->resp.inprogress = true;

      /* This branch indicates successful processing and waiting
       * for USRSOCK_EVENT_CONNECT_READY event.
       */

      conn->resp.result = 0;

      return sizeof(*datahdr);
    }

  conn->resp.inprogress = false;
  conn->resp.xid = 0;

  /* Prepare to read buffers. */

  conn->resp.result = hdr->result;
  conn->resp.valuelen = datahdr->valuelen;
  conn->resp.valuelen_nontrunc = datahdr->valuelen_nontrunc;

  if (conn->resp.result < 0)
    {
      /* Error, valuelen must be zero. */

      if (datahdr->valuelen > 0 || datahdr->valuelen_nontrunc > 0)
        {
          nerr("error: response result negative, and valuelen or "
               "valuelen_nontrunc non-zero.\n");
          return -EINVAL;
        }

      /* Done with request/response. */

      usrsock_event(conn);
      return sizeof(*datahdr);
    }

  /* Check that number of buffers match available. */

  num_inbufs = (hdr->result > 0) + 1;

  if (conn->resp.datain.iovcnt < num_inbufs)
    {
      nwarn("not enough recv buffers (need: %d, have: %d).\n", num_inbufs,
            conn->resp.datain.iovcnt);
      return -EINVAL;
    }

  /* Adjust length of receiving buffers. */

  conn->resp.datain.total = 0;
  iovpos = 0;

  /* Value buffer is always the first */

  if (conn->resp.datain.iov[iovpos].iov_len < datahdr->valuelen)
    {
      nwarn("%dth buffer not large enough (need: %d, have: %zu).\n",
            iovpos, datahdr->valuelen,
            conn->resp.datain.iov[iovpos].iov_len);
      return -EINVAL;
    }

  /* Adjust read size. */

  conn->resp.datain.iov[iovpos].iov_len = datahdr->valuelen;
  conn->resp.datain.total += conn->resp.datain.iov[iovpos].iov_len;
  iovpos++;

  if (hdr->result > 0)
    {
      /* Value buffer is always the first */

      if (conn->resp.datain.iov[iovpos].iov_len < hdr->result)
        {
          nwarn("%dth buffer not large enough "
                "(need: %" PRId32 ", have: %zu).\n",
                iovpos, hdr->result,
                conn->resp.datain.iov[iovpos].iov_len);
          return -EINVAL;
        }

      /* Adjust read size. */

      conn->resp.datain.iov[iovpos].iov_len = hdr->result;
      conn->resp.datain.total += conn->resp.datain.iov[iovpos].iov_len;
      iovpos++;
    }

  DEBUGASSERT(num_inbufs == iovpos);

  conn->resp.datain.iovcnt = num_inbufs;

  /* Next written buffers are redirected to data buffers. */

  req->datain_conn = conn;
  return sizeof(*datahdr);
}

/****************************************************************************
 * Name: usrsock_handle_req_response
 ****************************************************************************/

static ssize_t usrsock_handle_req_response(FAR const void *buffer,
                                           size_t len, FAR bool *req_done)
{
  FAR const struct usrsock_message_req_ack_s *hdr = buffer;
  FAR struct usrsock_conn_s *conn = NULL;
  FAR struct usrsock_req_s *req = &g_usrsock_req;
  ssize_t (*handle_response)(FAR struct usrsock_conn_s *conn,
                             FAR const void *buffer,
                             size_t len);
  size_t hdrlen;
  ssize_t ret;

  switch (hdr->head.msgid)
    {
    case USRSOCK_MESSAGE_RESPONSE_ACK:
      hdrlen = sizeof(struct usrsock_message_req_ack_s);
      handle_response = &usrsock_handle_response;
      break;

    case USRSOCK_MESSAGE_RESPONSE_DATA_ACK:
      hdrlen = sizeof(struct usrsock_message_datareq_ack_s);
      handle_response = &usrsock_handle_datareq_response;
      break;

    default:
      nerr("unknown message type: %d, flags: %d, xid: %" PRIu32 ", "
           "result: %" PRId32 "\n",
           hdr->head.msgid, hdr->head.flags, hdr->xid, hdr->result);
      return -EINVAL;
    }

  if (len < hdrlen)
    {
      nerr("message too short, %zu < %zu.\n", len, hdrlen);
      return -EINVAL;
    }

  net_lock();

  /* Get corresponding usrsock connection for this transfer */

  while ((conn = usrsock_nextconn(conn)) != NULL &&
         conn->resp.xid != hdr->xid);
  if (!conn)
    {
      /* No connection waiting for this message. */

      nerr("Could find connection waiting for response"
           "with xid=%" PRIu32 "\n", hdr->xid);

      ret = -EINVAL;
      goto unlock_out;
    }

  if (req->ackxid == hdr->xid)
    {
      req->ackxid = 0;
      if (req_done)
        {
          *req_done = true;
        }

      /* Signal that request was received and read by daemon and
       * acknowledgment response was received.
       */

      nxsem_post(&req->acksem);
    }

  conn->resp.events = hdr->head.events | USRSOCK_EVENT_REQ_COMPLETE;
  ret = handle_response(conn, buffer, len);

unlock_out:
  net_unlock();
  return ret;
}

/****************************************************************************
 * Name: usrsock_handle_message
 ****************************************************************************/

static ssize_t usrsock_handle_message(FAR const void *buffer, size_t len,
                                      FAR bool *req_done)
{
  FAR const struct usrsock_message_common_s *common = buffer;

  if (USRSOCK_MESSAGE_IS_EVENT(common->flags))
    {
      return usrsock_handle_event(buffer, len);
    }

  if (USRSOCK_MESSAGE_IS_REQ_RESPONSE(common->flags))
    {
      return usrsock_handle_req_response(buffer, len, req_done);
    }

  nerr("Unknown message flags %" PRIx8 ".\n", common->flags);
  return -EINVAL;
}

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

/****************************************************************************
 * Name: usrsock_response() - handle usrsock request's ack/response
 ****************************************************************************/

ssize_t usrsock_response(FAR const char *buffer, size_t len,
                         FAR bool *req_done)
{
  FAR struct usrsock_req_s *req = &g_usrsock_req;
  FAR struct usrsock_conn_s *conn;
  size_t origlen = len;
  int ret = 0;

  if (!req->datain_conn)
    {
      /* Start of message, buffer length should be at least size of common
       * message header.
       */

      if (len < sizeof(struct usrsock_message_common_s))
        {
          nerr("message too short, %zu < %zu.\n", len,
               sizeof(struct usrsock_message_common_s));
          return -EINVAL;
        }

      /* Handle message. */

      ret = usrsock_handle_message(buffer, len, req_done);
      if (ret >= 0)
        {
          buffer += ret;
          len -= ret;
          ret = origlen - len;
        }
    }

  if (req->datain_conn)
    {
      conn = req->datain_conn;

      /* Copy data from user-space. */

      if (len != 0)
        {
          ret = usrsock_iovec_put(conn->resp.datain.iov,
                                  conn->resp.datain.iovcnt,
                                  conn->resp.datain.pos, buffer, len);
          if (ret < 0)
            {
              /* Tried writing beyond buffer. */

              conn->resp.result = ret;
              conn->resp.datain.pos = conn->resp.datain.total;
            }
          else
            {
              conn->resp.datain.pos += ret;
              buffer += ret;
              len -= ret;
              ret = origlen - len;
            }
        }

      if (conn->resp.datain.pos == conn->resp.datain.total)
        {
          req->datain_conn = NULL;

          /* Done with data response. */

          usrsock_event(conn);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: usrsock_iovec_get() - copy from iovec to buffer.
 ****************************************************************************/

ssize_t usrsock_iovec_get(FAR void *dst, size_t dstlen,
                          FAR const struct iovec *iov, int iovcnt,
                          size_t pos, FAR bool *done)
{
  return usrsock_iovec_do(dst, dstlen, (FAR struct iovec *)iov, iovcnt,
                          pos, true, done);
}

/****************************************************************************
 * Name: usrsock_iovec_put() - copy to iovec from buffer.
 ****************************************************************************/

ssize_t usrsock_iovec_put(FAR struct iovec *iov, int iovcnt, size_t pos,
                          FAR const void *src, size_t srclen)
{
  return usrsock_iovec_do((FAR void *)src, srclen, iov, iovcnt,
                          pos, false, NULL);
}

/****************************************************************************
 * Name: usrsock_do_request() - finish usrsock's request
 ****************************************************************************/

int usrsock_do_request(FAR struct usrsock_conn_s *conn,
                       FAR struct iovec *iov, unsigned int iovcnt)
{
  FAR struct usrsock_request_common_s *req_head = NULL;
  FAR struct usrsock_req_s *req = &g_usrsock_req;
  int ret;

  /* Get exchange id. */

  req_head = iov[0].iov_base;

  /* Set outstanding request for daemon to handle. */

  net_mutex_lock(&req->lock);
  if (++req->newxid == 0)
    {
      ++req->newxid;
    }

  req_head->xid = req->newxid;

  /* Prepare connection for response. */

  conn->resp.xid = req_head->xid;
  conn->resp.result = -EACCES;

  req->ackxid = req_head->xid;

  ret = usrsock_request(iov, iovcnt);
  if (ret >= 0)
    {
      /* Wait ack for request. */

      ++req->nbusy; /* net_lock held. */
      net_sem_wait_uninterruptible(&req->acksem);
      --req->nbusy; /* net_lock held. */
    }
  else
    {
      nerr("error: usrsock request failed with %d\n", ret);
    }

  /* Free request line for next command. */

  nxmutex_unlock(&req->lock);
  return ret;
}

/****************************************************************************
 * Name: usrsock_abort() - abort all usrsock's operations
 ****************************************************************************/

void usrsock_abort(void)
{
  FAR struct usrsock_req_s *req = &g_usrsock_req;
  FAR struct usrsock_conn_s *conn = NULL;
  int ret;

  net_lock();

  /* Set active usrsock sockets to aborted state. */

  while ((conn = usrsock_nextconn(conn)) != NULL)
    {
      conn->resp.inprogress = false;
      conn->resp.xid = 0;
      conn->resp.events = USRSOCK_EVENT_ABORT;
      usrsock_event(conn);
    }

  do
    {
      /* Give other threads short time window to complete recently completed
       * requests.
       */

      ret = net_mutex_timedlock(&req->lock, 10);
      if (ret < 0)
        {
          if (ret != -ETIMEDOUT && ret != -EINTR)
            {
              ninfo("net_sem_timedwait errno: %d\n", ret);
              DEBUGASSERT(false);
            }
        }
      else
        {
          nxmutex_unlock(&req->lock);
        }

      /* Wake-up pending requests. */

      if (req->nbusy == 0)
        {
          break;
        }

      nxsem_post(&req->acksem);
    }
  while (true);

  net_unlock();
}

#endif /* CONFIG_NET && CONFIG_NET_USRSOCK */
