/****************************************************************************
 * net/tcp/tcp_recvwindow.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 <stdint.h>
#include <stdbool.h>
#include <debug.h>

#include <net/if.h>

#include <nuttx/mm/iob.h>
#include <nuttx/net/netconfig.h>
#include <nuttx/net/netdev.h>
#include <nuttx/net/tcp.h>

#include "tcp/tcp.h"

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

/****************************************************************************
 * Name: tcp_calc_rcvsize
 *
 * Description:
 *   Calculate the possible max TCP receive buffer size for the connection.
 *
 * Input Parameters:
 *   conn     - The TCP connection.
 *   recvwndo - The TCP receive window size
 *
 * Returned Value:
 *   The value of the TCP receive buffer size.
 *
 ****************************************************************************/

static uint32_t tcp_calc_rcvsize(FAR struct tcp_conn_s *conn,
                                 uint32_t recvwndo)
{
#if CONFIG_NET_RECV_BUFSIZE > 0
  uint32_t recvsize;
  uint32_t desire;

  recvsize = conn->readahead ? conn->readahead->io_pktlen : 0;
  if (conn->rcv_bufs > recvsize)
    {
      desire = conn->rcv_bufs - recvsize;
      if (recvwndo > desire)
        {
          recvwndo = desire;
        }
    }
  else
    {
      recvwndo = 0;
    }
#endif

  return recvwndo;
}

/****************************************************************************
 * Name: tcp_maxrcvwin
 *
 * Description:
 *   Calculate the possible max TCP receive window for the connection.
 *
 * Input Parameters:
 *   conn - The TCP connection.
 *
 * Returned Value:
 *   The value of the TCP receive window.
 ****************************************************************************/

static uint32_t tcp_maxrcvwin(FAR struct tcp_conn_s *conn)
{
  uint32_t recvwndo;

  /* Calculate the max possible window size for the connection.
   * This needs to be in sync with tcp_get_recvwindow().
   */

  recvwndo = tcp_calc_rcvsize(conn, (CONFIG_IOB_NBUFFERS -
                                     CONFIG_IOB_THROTTLE) *
                                     CONFIG_IOB_BUFSIZE);
#ifdef CONFIG_NET_TCP_WINDOW_SCALE
  recvwndo >>= conn->rcv_scale;
#endif

  if (recvwndo > UINT16_MAX)
    {
      recvwndo = UINT16_MAX;
    }

#ifdef CONFIG_NET_TCP_WINDOW_SCALE
  recvwndo <<= conn->rcv_scale;
#endif

  return recvwndo;
}

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

/****************************************************************************
 * Name: tcp_get_recvwindow
 *
 * Description:
 *   Calculate the TCP receive window for the specified device.
 *
 * Input Parameters:
 *   dev  - The device whose TCP receive window will be updated.
 *   conn - The TCP connection structure holding connection information.
 *
 * Returned Value:
 *   The value of the TCP receive window to use.
 *
 ****************************************************************************/

uint32_t tcp_get_recvwindow(FAR struct net_driver_s *dev,
                            FAR struct tcp_conn_s *conn)
{
  uint32_t tailroom;
  uint32_t recvwndo;
  int niob_avail;

  /* Update the TCP received window based on read-ahead I/O buffer
   * and IOB chain availability.
   * The amount of read-ahead
   * data that can be buffered is given by the number of IOBs available
   * (ignoring competition with other IOB consumers).
   */

  if (conn->readahead != NULL)
    {
      tailroom = iob_tailroom(conn->readahead);
    }
  else
    {
      tailroom = 0;
    }

  niob_avail = iob_navail(true);

  /* Is there a a queue entry and IOBs available for read-ahead buffering? */

  if (niob_avail > 0)
    {
      /* The optimal TCP window size is the amount of TCP data that we can
       * currently buffer via TCP read-ahead buffering for the device packet
       * buffer.  This logic here assumes that all IOBs are available for
       * TCP buffering.
       *
       * Assume that all of the available IOBs are can be used for buffering
       * on this connection.
       *
       * REVISIT:  In an environment with multiple, active read-ahead TCP
       * sockets (and perhaps multiple network devices) or if there are
       * other consumers of IOBs (such as for TCP write buffering) then the
       * total number of IOBs will all not be available for read-ahead
       * buffering for this connection.
       */

      recvwndo = tailroom + (niob_avail * CONFIG_IOB_BUFSIZE);
    }
#if CONFIG_IOB_THROTTLE > 0
  else if (conn->readahead == NULL)
    {
      /* Advertise maximum segment size for window edge if here is no
       * available iobs on current "free" connection.
       *
       * Note: hopefully, a single mss-sized packet can be queued by
       * the throttled=false case in tcp_datahandler().
       */

      int niob_avail_no_throttle = iob_navail(false);

      recvwndo = tcp_rx_mss(dev);
      if (recvwndo > niob_avail_no_throttle * CONFIG_IOB_BUFSIZE)
        {
          recvwndo = niob_avail_no_throttle * CONFIG_IOB_BUFSIZE;
        }
    }
#endif
  else /* niob_avail == 0 */
    {
      /* No IOBs are available.
       * Advertise the edge of window to zero.
       *
       * NOTE:  If no IOBs are available, then the next packet will be
       * lost if there is no listener on the connection.
       */

      recvwndo = tailroom;
    }

  recvwndo = tcp_calc_rcvsize(conn, recvwndo);

#ifdef CONFIG_NET_TCP_WINDOW_SCALE
  recvwndo >>= conn->rcv_scale;
#endif

  if (recvwndo > UINT16_MAX)
    {
      recvwndo = UINT16_MAX;
    }

#ifdef CONFIG_NET_TCP_WINDOW_SCALE
  recvwndo <<= conn->rcv_scale;
#endif

  return recvwndo;
}

bool tcp_should_send_recvwindow(FAR struct tcp_conn_s *conn)
{
  FAR struct net_driver_s *dev = conn->dev;
  uint32_t win;
  uint32_t maxwin;
  uint32_t oldwin;
  uint32_t rcvseq;
  uint32_t adv;
  uint16_t mss;

  /* Note: rcv_adv can be smaller than rcvseq.
   * For examples, when:
   *
   * - we shrunk the window
   * - zero window probes advanced rcvseq
   */

  rcvseq = tcp_getsequence(conn->rcvseq);
  if (TCP_SEQ_GT(conn->rcv_adv, rcvseq))
    {
      oldwin = TCP_SEQ_SUB(conn->rcv_adv, rcvseq);
    }
  else
    {
      oldwin = 0;
    }

  win = tcp_get_recvwindow(dev, conn);

  /* If the window doesn't extend, don't send. */

  if (win <= oldwin)
    {
      ninfo("Returning false: "
            "rcvseq=%" PRIu32 ", rcv_adv=%" PRIu32 ", "
            "old win=%" PRIu32 ", new win=%" PRIu32 "\n",
            rcvseq, conn->rcv_adv, oldwin, win);
      return false;
    }

  adv = win - oldwin;

  /* The following conditions are inspired from NetBSD TCP stack.
   *
   * - If we can extend the window by the half of the max possible size,
   *   send it.
   *
   * - If we can extend the window by 2 * mss, send it.
   */

  maxwin = tcp_maxrcvwin(conn);
  if (2 * adv >= maxwin)
    {
      ninfo("Returning true: "
            "adv=%" PRIu32 ", maxwin=%" PRIu32 "\n",
            adv, maxwin);
      return true;
    }

  /* Revisit: the real expected size should be used instead.
   * E.g. consider the path MTU
   */

  mss = tcp_rx_mss(dev);
  if (adv >= 2 * mss)
    {
      ninfo("Returning true: "
            "adv=%" PRIu32 ", mss=%" PRIu16 ", maxwin=%" PRIu32 "\n",
            adv, mss, maxwin);
      return true;
    }

  ninfo("Returning false: "
        "adv=%" PRIu32 ", mss=%" PRIu16 ", maxwin=%" PRIu32 "\n",
        adv, mss, maxwin);
  return false;
}
