/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
   Copyright (c) 2012, Monty Program Ab

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */

/*
  Note that we can't have assertion on file descriptors;  The reason for
  this is that during mysql shutdown, another thread can close a file
  we are working on.  In this case we should just return read errors from
  the file descriptior.
*/

#include "vio_priv.h"
#include "ssl_compat.h"

PSI_memory_key key_memory_vio_ssl_fd;
PSI_memory_key key_memory_vio;
PSI_memory_key key_memory_vio_read_buffer;

#ifdef HAVE_PSI_INTERFACE
static PSI_memory_info all_vio_memory[]=
{
  {&key_memory_vio_ssl_fd, "ssl_fd", 0},
  {&key_memory_vio, "vio", 0},
  {&key_memory_vio_read_buffer, "read_buffer", 0},
};

void init_vio_psi_keys()
{
  const char* category= "vio";
  int count;

  count= array_elements(all_vio_memory);
  mysql_memory_register(category, all_vio_memory, count);
}
#endif

#ifdef _WIN32

/**
  Stub io_wait method that defaults to indicate that
  requested I/O event is ready.

  Used for named pipe and shared memory VIO types.

  @param vio      Unused.
  @param event    Unused.
  @param timeout  Unused.

  @retval 1       The requested I/O event has occurred.
*/

static int no_io_wait(Vio *vio __attribute__((unused)),
                      enum enum_vio_io_event event __attribute__((unused)),
                      int timeout __attribute__((unused)))
{
  return 1;
}

#endif

static my_bool has_no_data(Vio *vio __attribute__((unused)))
{
  return FALSE;
}

#ifdef _WIN32
int vio_pipe_shutdown(Vio *vio, int how)
{
  vio->shutdown_flag= how;
  return CancelIoEx(vio->hPipe, NULL);
}
#endif

/*
 * Helper to fill most of the Vio* with defaults.
 */

static void vio_init(Vio *vio, enum enum_vio_type type,
                     my_socket sd, uint flags)
{
  DBUG_ENTER("vio_init");
  DBUG_PRINT("enter", ("type: %d  sd: %d  flags: %d", type, (int)sd, flags));

#ifndef HAVE_VIO_READ_BUFF
  flags&= ~VIO_BUFFERED_READ;
#endif
  memset(vio, 0, sizeof(*vio));
  vio->type= type;
  vio->mysql_socket= MYSQL_INVALID_SOCKET;
  mysql_socket_setfd(&vio->mysql_socket, sd);
  vio->localhost= flags & VIO_LOCALHOST;
  vio->read_timeout= vio->write_timeout= -1;
  if ((flags & VIO_BUFFERED_READ) &&
      !(vio->read_buffer= (char*)my_malloc(key_memory_vio_read_buffer,
                                           VIO_READ_BUFFER_SIZE, MYF(MY_WME))))
    flags&= ~VIO_BUFFERED_READ;
#ifdef _WIN32
  if (type == VIO_TYPE_NAMEDPIPE)
  {
    vio->viodelete	=vio_delete;
    vio->vioerrno	=vio_errno;
    vio->read           =vio_read_pipe;
    vio->write          =vio_write_pipe;
    vio->fastsend	=vio_fastsend;
    vio->viokeepalive	=vio_keepalive;
    vio->should_retry	=vio_should_retry;
    vio->was_timeout    =vio_was_timeout;
    vio->vioclose	=vio_close_pipe;
    vio->peer_addr	=vio_peer_addr;
    vio->vioblocking	=vio_blocking;
    vio->is_blocking	=vio_is_blocking;
    vio->io_wait        =no_io_wait;
    vio->is_connected   =vio_is_connected_pipe;
    vio->has_data       =has_no_data;
    vio->shutdown       =vio_pipe_shutdown;
    DBUG_VOID_RETURN;
  }
#endif

#ifdef HAVE_OPENSSL
  if (type == VIO_TYPE_SSL)
  {
    vio->viodelete	=vio_ssl_delete;
    vio->vioerrno	=vio_errno;
    vio->read		=vio_ssl_read;
    vio->write		=vio_ssl_write;
    vio->fastsend	=vio_fastsend;
    vio->viokeepalive	=vio_keepalive;
    vio->should_retry	=vio_should_retry;
    vio->was_timeout    =vio_was_timeout;
    vio->vioclose	=vio_ssl_close;
    vio->peer_addr	=vio_peer_addr;
    vio->vioblocking	=vio_ssl_blocking;
    vio->is_blocking	=vio_is_blocking;
    vio->io_wait        =vio_io_wait;
    vio->is_connected   =vio_is_connected;
    vio->has_data       =vio_ssl_has_data;
    vio->shutdown       =vio_socket_shutdown;
    vio->timeout        =vio_socket_timeout;
    DBUG_VOID_RETURN;
  }
#endif /* HAVE_OPENSSL */
  vio->viodelete        =vio_delete;
  vio->vioerrno         =vio_errno;
  vio->read=            (flags & VIO_BUFFERED_READ) ? vio_read_buff : vio_read;
  vio->write            =vio_write;
  vio->fastsend         =vio_fastsend;
  vio->viokeepalive     =vio_keepalive;
  vio->should_retry     =vio_should_retry;
  vio->was_timeout      =vio_was_timeout;
  vio->vioclose         =vio_close;
  vio->peer_addr        =vio_peer_addr;
  vio->vioblocking	=vio_blocking;
  vio->is_blocking	=vio_is_blocking;
  vio->io_wait          =vio_io_wait;
  vio->is_connected     =vio_is_connected;
  vio->shutdown         =vio_socket_shutdown;
  vio->timeout          =vio_socket_timeout;
  vio->has_data         = ((flags & VIO_BUFFERED_READ) ?
                           vio_buff_has_data : has_no_data);
  DBUG_VOID_RETURN;
}


/**
  Reinitialize an existing Vio object.

  @remark Used to rebind an initialized socket-based Vio object
          to another socket-based transport type. For example,
          rebind a TCP/IP transport to SSL.

  @param vio    A VIO object.
  @param type   A socket-based transport type.
  @param sd     The socket.
  @param ssl    An optional SSL structure.
  @param flags  Flags passed to vio_init.

  @return Return value is zero on success.
*/

my_bool vio_reset(Vio* vio, enum enum_vio_type type,
                  my_socket sd, void *ssl __attribute__((unused)), uint flags)
{
  int ret= FALSE;
  Vio old_vio= *vio;
  DBUG_ENTER("vio_reset");

  /* The only supported rebind is from a socket-based transport type. */
  DBUG_ASSERT(vio->type == VIO_TYPE_TCPIP || vio->type == VIO_TYPE_SOCKET);

  /*
    Will be reinitialized depending on the flags.
    Nonetheless, already buffered inside the SSL layer.
  */
  my_free(vio->read_buffer);

  vio_init(vio, type, sd, flags);

  /* Preserve perfschema info for this connection */
  vio->mysql_socket.m_psi= old_vio.mysql_socket.m_psi;

#ifdef HAVE_OPENSSL
  vio->ssl_arg= ssl;
#endif

  /*
    Propagate the timeout values. Necessary to also propagate
    the underlying proprieties associated with the timeout,
    such as the socket blocking mode.

    note: old_vio.read_timeout/old_vio.write_timeout is stored in ms
    but vio_timeout() takes seconds as argument, hence the / 1000
  */
  if (old_vio.read_timeout >= 0)
    ret|= vio_timeout(vio, 0, old_vio.read_timeout / 1000);

  if (old_vio.write_timeout >= 0)
    ret|= vio_timeout(vio, 1, old_vio.write_timeout / 1000);

  DBUG_RETURN(MY_TEST(ret));
}


/* Create a new VIO for socket or TCP/IP connection. */

Vio *mysql_socket_vio_new(MYSQL_SOCKET mysql_socket, enum enum_vio_type type, uint flags)
{
  Vio *vio;
  my_socket sd= mysql_socket_getfd(mysql_socket);
  DBUG_ENTER("mysql_socket_vio_new");
  DBUG_PRINT("enter", ("sd: %d", (int)sd));
  if ((vio = (Vio*) my_malloc(key_memory_vio, sizeof(*vio), MYF(MY_WME))))
  {
    vio_init(vio, type, sd, flags);
    vio->desc= (vio->type == VIO_TYPE_SOCKET ? "socket" : "TCP/IP");
    vio->mysql_socket= mysql_socket;
  }
  DBUG_RETURN(vio);
}

/* Open the socket or TCP/IP connection and read the fnctl() status */

Vio *vio_new(my_socket sd, enum enum_vio_type type, uint flags)
{
  Vio *vio;
  MYSQL_SOCKET mysql_socket= MYSQL_INVALID_SOCKET;
  DBUG_ENTER("vio_new");
  DBUG_PRINT("enter", ("sd: %d", (int)sd));

  mysql_socket_setfd(&mysql_socket, sd);
  vio = mysql_socket_vio_new(mysql_socket, type, flags);

  DBUG_RETURN(vio);
}

#ifdef _WIN32

Vio *vio_new_win32pipe(HANDLE hPipe)
{
  Vio *vio;
  DBUG_ENTER("vio_new_handle");
  if ((vio = (Vio*) my_malloc(PSI_INSTRUMENT_ME, sizeof(Vio),MYF(MY_WME))))
  {
    vio_init(vio, VIO_TYPE_NAMEDPIPE, 0, VIO_LOCALHOST);
    vio->desc= "named pipe";
    /* Create an object for event notification. */
    vio->overlapped.hEvent= CreateEvent(NULL, FALSE, FALSE, NULL);
    if (vio->overlapped.hEvent == NULL)
    {
      my_free(vio);
      DBUG_RETURN(NULL);
    }
    vio->hPipe= hPipe;
  }
  DBUG_RETURN(vio);
}


#endif


/**
  Set timeout for a network send or receive operation.

  @remark A non-infinite timeout causes the socket to be
          set to non-blocking mode. On infinite timeouts,
          the socket is set to blocking mode.

  @remark A negative timeout means an infinite timeout.

  @param vio      A VIO object.
  @param which    Whether timeout is for send (1) or receive (0).
  @param timeout  Timeout interval in seconds.

  @return FALSE on success, TRUE otherwise.
*/

int vio_timeout(Vio *vio, uint which, int timeout_sec)
{
  int timeout_ms;
  my_bool old_mode;

  /*
    Vio timeouts are measured in milliseconds. Check for a possible
    overflow. In case of overflow, set to infinite.
  */
  if (timeout_sec > INT_MAX/1000)
    timeout_ms= -1;
  else
    timeout_ms= (int) (timeout_sec * 1000);

  /* Deduce the current timeout status mode. */
  old_mode= vio->write_timeout < 0 && vio->read_timeout < 0;

  if (which)
    vio->write_timeout= timeout_ms;
  else
    vio->read_timeout= timeout_ms;

  /* VIO-specific timeout handling. Might change the blocking mode. */
  return vio->timeout ? vio->timeout(vio, which, old_mode) : 0;
}


void vio_delete(Vio* vio)
{
  if (!vio)
    return; /* It must be safe to delete null pointers. */

  if (vio->type != VIO_CLOSED)
    vio->vioclose(vio);
  my_free(vio->read_buffer);
  my_free(vio);
}


/*
  Cleanup memory allocated by vio or the
  components below it when application finish

*/
void vio_end(void)
{
#ifdef HAVE_WOLFSSL
  wolfSSL_Cleanup();
#elif defined(HAVE_OPENSSL)
  // This one is needed on the client side
  ERR_remove_state(0);
  ERR_free_strings();
  EVP_cleanup();
  CRYPTO_cleanup_all_ex_data();
#endif
}
