/*
  This file is part of libmicrohttpd
  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
/**
 * @file lib/upgrade_process.c
 * @brief function to process upgrade activity (over TLS)
 * @author Christian Grothoff
 */
#include "internal.h"
#include "upgrade_process.h"


#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
/**
 * Performs bi-directional forwarding on upgraded HTTPS connections
 * based on the readiness state stored in the @a urh handle.
 * @remark To be called only from thread that process
 * connection's recv(), send() and response.
 *
 * @param urh handle to process
 */
void
MHD_upgrade_response_handle_process_ (struct MHD_UpgradeResponseHandle *urh)
{
  /* Help compiler to optimize:
   * pointers to 'connection' and 'daemon' are not changed
   * during this processing, so no need to chain dereference
   * each time. */
  struct MHD_Connection *const connection = urh->connection;
  struct MHD_Daemon *const daemon = connection->daemon;
  /* Prevent data races: use same value of 'was_closed' throughout
   * this function. If 'was_closed' changed externally in the middle
   * of processing - it will be processed on next iteration. */
  bool was_closed;
  struct MHD_TLS_Plugin *tls = daemon->tls_api;

  if (daemon->shutdown)
  {
    /* Daemon shutting down, application will not receive any more data. */
#ifdef HAVE_MESSAGES
    if (! urh->was_closed)
    {
      MHD_DLOG (daemon,
                MHD_SC_DAEMON_ALREADY_SHUTDOWN,
                _ (
                  "Initiated daemon shutdown while \"upgraded\" connection was not closed.\n"));
    }
#endif
    urh->was_closed = true;
  }
  was_closed = urh->was_closed;
  if (was_closed)
  {
    /* Application was closed connections: no more data
     * can be forwarded to application socket. */
    if (0 < urh->in_buffer_used)
    {
#ifdef HAVE_MESSAGES
      MHD_DLOG (daemon,
                MHD_SC_UPGRADE_FORWARD_INCOMPLETE,
                _ (
                  "Failed to forward to application "
                  MHD_UNSIGNED_LONG_LONG_PRINTF \
                  " bytes of data received from remote side: application shut down socket.\n"),
                (MHD_UNSIGNED_LONG_LONG) urh->in_buffer_used);
#endif

    }
    /* If application signaled MHD about socket closure then
     * check for any pending data even if socket is not marked
     * as 'ready' (signal may arrive after poll()/select()).
     * Socketpair for forwarding is always in non-blocking mode
     * so no risk that recv() will block the thread. */if (0 != urh->out_buffer_size)
      urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY;
    /* Discard any data received form remote. */
    urh->in_buffer_used = 0;
    /* Do not try to push data to application. */
    urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
    /* Reading from remote client is not required anymore. */
    urh->in_buffer_size = 0;
    urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
    connection->tls_read_ready = false;
  }

  /* On some platforms (W32, possibly Darwin) failed send() (send() will always
   * fail after remote disconnect was detected) may discard data in system
   * buffers received by system but not yet read by recv().
   * So, before trying send() on any socket, recv() must be performed at first
   * otherwise last part of incoming data may be lost. *//* If disconnect or error was detected - try to read from socket
   * to dry data possibly pending is system buffers. */if (0 != (MHD_EPOLL_STATE_ERROR & urh->app.celi))
    urh->app.celi |= MHD_EPOLL_STATE_READ_READY;
  if (0 != (MHD_EPOLL_STATE_ERROR & urh->mhd.celi))
    urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY;

  /*
   * handle reading from remote TLS client
   */
  if ( ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) ||
         (connection->tls_read_ready) ) &&
       (urh->in_buffer_used < urh->in_buffer_size) )
  {
    ssize_t res;
    size_t buf_size;

    buf_size = urh->in_buffer_size - urh->in_buffer_used;
    if (buf_size > SSIZE_MAX)
      buf_size = SSIZE_MAX;

    connection->tls_read_ready = false;
    res = tls->recv (tls->cls,
                     connection->tls_cs,
                     &urh->in_buffer[urh->in_buffer_used],
                     buf_size);
    if (0 >= res)
    {
      // FIXME: define GNUTLS-independent error codes!
      if (GNUTLS_E_INTERRUPTED != res)
      {
        urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
        if (GNUTLS_E_AGAIN != res)
        {
          /* Unrecoverable error on socket was detected or
           * socket was disconnected/shut down. */
          /* Stop trying to read from this TLS socket. */
          urh->in_buffer_size = 0;
        }
      }
    }
    else   /* 0 < res */
    {
      urh->in_buffer_used += res;
      if (buf_size > (size_t) res)
        urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
      else if (0 < tls->check_record_pending (tls->cls,
                                              connection->tls_cs))
        connection->tls_read_ready = true;
    }
    if (MHD_EPOLL_STATE_ERROR ==
        ((MHD_EPOLL_STATE_ERROR | MHD_EPOLL_STATE_READ_READY) & urh->app.celi))
    {
      /* Unrecoverable error on socket was detected and all
       * pending data was read from system buffers. */
      /* Stop trying to read from this TLS socket. */
      urh->in_buffer_size = 0;
    }
  }

  /*
   * handle reading from application
   */
  if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) &&
       (urh->out_buffer_used < urh->out_buffer_size) )
  {
    ssize_t res;
    size_t buf_size;

    buf_size = urh->out_buffer_size - urh->out_buffer_used;
    if (buf_size > MHD_SCKT_SEND_MAX_SIZE_)
      buf_size = MHD_SCKT_SEND_MAX_SIZE_;

    res = MHD_recv_ (urh->mhd.socket,
                     &urh->out_buffer[urh->out_buffer_used],
                     buf_size);
    if (0 >= res)
    {
      const int err = MHD_socket_get_error_ ();
      if ((0 == res) ||
          ((! MHD_SCKT_ERR_IS_EINTR_ (err)) &&
           (! MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err))))
      {
        urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
        if ((0 == res) ||
            (was_closed) ||
            (0 != (MHD_EPOLL_STATE_ERROR & urh->mhd.celi)) ||
            (! MHD_SCKT_ERR_IS_EAGAIN_ (err)))
        {
          /* Socket disconnect/shutdown was detected;
           * Application signaled about closure of 'upgraded' socket;
           * or persistent / unrecoverable error. */
          /* Do not try to pull more data from application. */
          urh->out_buffer_size = 0;
        }
      }
    }
    else   /* 0 < res */
    {
      urh->out_buffer_used += res;
      if (buf_size > (size_t) res)
        urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
    }
    if ( (0 == (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) &&
         ( (0 != (MHD_EPOLL_STATE_ERROR & urh->mhd.celi)) ||
           (was_closed) ) )
    {
      /* Unrecoverable error on socket was detected and all
       * pending data was read from system buffers. */
      /* Do not try to pull more data from application. */
      urh->out_buffer_size = 0;
    }
  }

  /*
   * handle writing to remote HTTPS client
   */
  if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) &&
       (urh->out_buffer_used > 0) )
  {
    ssize_t res;
    size_t data_size;

    data_size = urh->out_buffer_used;
    if (data_size > SSIZE_MAX)
      data_size = SSIZE_MAX;

    res = tls->send (tls->cls,
                     connection->tls_cs,
                     urh->out_buffer,
                     data_size);
    if (0 >= res)
    {
      // FIXME: define GNUTLS-independent error codes!
      if (GNUTLS_E_INTERRUPTED != res)
      {
        urh->app.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
        if (GNUTLS_E_INTERRUPTED != res)
        {
          /* TLS connection shut down or
           * persistent / unrecoverable error. */
#ifdef HAVE_MESSAGES
          MHD_DLOG (daemon,
                    MHD_SC_UPGRADE_FORWARD_INCOMPLETE,
                    _ (
                      "Failed to forward to remote client "
                      MHD_UNSIGNED_LONG_LONG_PRINTF \
                      " bytes of data received from application: %s\n"),
                    (MHD_UNSIGNED_LONG_LONG) urh->out_buffer_used,
                    tls->strerror (tls->cls,
                                   res));
#endif
          /* Discard any data unsent to remote. */
          urh->out_buffer_used = 0;
          /* Do not try to pull more data from application. */
          urh->out_buffer_size = 0;
          urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
        }
      }
    }
    else   /* 0 < res */
    {
      const size_t next_out_buffer_used = urh->out_buffer_used - res;
      if (0 != next_out_buffer_used)
      {
        memmove (urh->out_buffer,
                 &urh->out_buffer[res],
                 next_out_buffer_used);
        if (data_size > (size_t) res)
          urh->app.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
      }
      urh->out_buffer_used = next_out_buffer_used;
    }
    if ( (0 == urh->out_buffer_used) &&
         (0 != (MHD_EPOLL_STATE_ERROR & urh->app.celi)) )
    {
      /* Unrecoverable error on socket was detected and all
       * pending data was sent to remote. */
      /* Do not try to send to remote anymore. */
      urh->app.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
      /* Do not try to pull more data from application. */
      urh->out_buffer_size = 0;
      urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
    }
  }

  /*
   * handle writing to application
   */
  if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) &&
       (urh->in_buffer_used > 0) )
  {
    ssize_t res;
    size_t data_size;

    data_size = urh->in_buffer_used;
    if (data_size > MHD_SCKT_SEND_MAX_SIZE_)
      data_size = MHD_SCKT_SEND_MAX_SIZE_;

    res = MHD_send_ (urh->mhd.socket,
                     urh->in_buffer,
                     data_size);
    if (0 >= res)
    {
      const int err = MHD_socket_get_error_ ();
      if ( (! MHD_SCKT_ERR_IS_EINTR_ (err)) &&
           (! MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err)) )
      {
        urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
        if (! MHD_SCKT_ERR_IS_EAGAIN_ (err))
        {
          /* Socketpair connection shut down or
           * persistent / unrecoverable error. */
#ifdef HAVE_MESSAGES
          MHD_DLOG (daemon,
                    MHD_SC_UPGRADE_FORWARD_INCOMPLETE,
                    _ (
                      "Failed to forward to application "
                      MHD_UNSIGNED_LONG_LONG_PRINTF \
                      " bytes of data received from remote side: %s\n"),
                    (MHD_UNSIGNED_LONG_LONG) urh->in_buffer_used,
                    MHD_socket_strerr_ (err));
#endif
          /* Discard any data received form remote. */
          urh->in_buffer_used = 0;
          /* Reading from remote client is not required anymore. */
          urh->in_buffer_size = 0;
          urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
          connection->tls_read_ready = false;
        }
      }
    }
    else   /* 0 < res */
    {
      const size_t next_in_buffer_used = urh->in_buffer_used - res;
      if (0 != next_in_buffer_used)
      {
        memmove (urh->in_buffer,
                 &urh->in_buffer[res],
                 next_in_buffer_used);
        if (data_size > (size_t) res)
          urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
      }
      urh->in_buffer_used = next_in_buffer_used;
    }
    if ( (0 == urh->in_buffer_used) &&
         (0 != (MHD_EPOLL_STATE_ERROR & urh->mhd.celi)) )
    {
      /* Do not try to push data to application. */
      urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
      /* Reading from remote client is not required anymore. */
      urh->in_buffer_size = 0;
      urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
      connection->tls_read_ready = false;
    }
  }

  /* Check whether data is present in TLS buffers
   * and incoming forward buffer have some space. */
  if ( (connection->tls_read_ready) &&
       (urh->in_buffer_used < urh->in_buffer_size) &&
       (MHD_TM_THREAD_PER_CONNECTION != daemon->threading_mode) )
    daemon->data_already_pending = true;

  if ( (daemon->shutdown) &&
       ( (0 != urh->out_buffer_size) ||
         (0 != urh->out_buffer_used) ) )
  {
    /* Daemon shutting down, discard any remaining forward data. */
#ifdef HAVE_MESSAGES
    if (0 < urh->out_buffer_used)
      MHD_DLOG (daemon,
                MHD_SC_UPGRADE_FORWARD_INCOMPLETE,
                _ (
                  "Failed to forward to remote client "
                  MHD_UNSIGNED_LONG_LONG_PRINTF \
                  " bytes of data received from application: daemon shut down.\n"),
                (MHD_UNSIGNED_LONG_LONG) urh->out_buffer_used);
#endif
    /* Discard any data unsent to remote. */
    urh->out_buffer_used = 0;
    /* Do not try to sent to remote anymore. */
    urh->app.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
    /* Do not try to pull more data from application. */
    urh->out_buffer_size = 0;
    urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
  }
}


#endif /* HTTPS_SUPPORT  && UPGRADE_SUPPORT */

/* end of upgrade_process.c */
