/***************************************************************************
 *
 * Copyright (c) 2000, 2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: stream.c,v 1.29.2.1.2.3 2001/09/19 19:22:17 bazsi Exp $
 *
 * Author  : Bazsi
 * Auditor : kisza
 * Last version: 1.9
 * Notes:
 *   - comments
 *
 ***************************************************************************/

#include <zorp/stream.h>
#include <zorp/log.h>

#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>

#if ENABLE_PACKET_TRACE
void
z_data_dump(char *prefix, char *buf, guint len)
{
  guint i, j;
  gchar line[1024];
  
  i = 0;
  while (i < len)
    {
      char *end = line;
      
      for (j = 0; j < 16 && (i + j < len); j++)
        {
          g_snprintf(end, sizeof(line) - (end - line), "%02X ", (unsigned char) buf[i+j]);
          end += 3;
        }
      g_snprintf(end, sizeof(line) - (end - line), " ");
      end++;
      for (j = 0; j < 16 && (i + j < len) && sizeof(line) > (end - line); j++)
        {
          *end = buf[i + j] > 31 ? buf[i + j] : '.';
          end++;
        }
      *end='\0';
          
      i += j;
      
      /*NOLOG*/
      z_log(prefix, CORE_DEBUG, 9, "data line: %s", line);
    }
}
#endif

typedef ZStream ZRealStream;

gboolean
z_stream_wait_fd(ZStream *stream, guint cond, gint timeout)
{
  struct pollfd pfd;
  gint res;

  if (timeout == -2)
    return TRUE;
  errno = 0;
  pfd.fd = stream->fd;
  pfd.events = cond;
  pfd.revents = 0;
  do
    {
      res = poll(&pfd, 1, timeout);
      if (res == 1)
        {
          return TRUE;
        }
    }
  while (res == -1 && errno == EINTR);
  errno = ETIMEDOUT;
  return FALSE;
}

static void 
z_stream_shutdown_fd(ZStream *s, int i)
{
  int res, attempt = 1;
  
  /*LOG
    This debug message indicates that shutdown is to be initiated on
    the given fd.
   */
  z_log(s->name, CORE_DEBUG, 7, "Shutdown channel; fd='%d', mode='%d'", s->fd, i);
  
  errno = 0;
  do
    {
      res = shutdown(s->fd, i);
      if (res == -1)
        {
          if (errno != EINTR)
            {
              /*LOG
                This message indicates that the shutdown on the given fd was
                not successful.
               */
              z_log(s->name, CORE_ERROR, 4, "Shutdown failed; attempt='%d', error='%m'", attempt++);
            }
        }
    }
  while (errno == EINTR);
}

static GIOError
z_stream_read_method(GIOChannel *channel,
		     gchar *buf,
		     guint count,
		     guint *bytes_read)
{
  ZRealStream *self = (ZRealStream *) channel;
  GIOError res;
  int attempt = 1;

  do
    { 
      if (!(self->wait)(self, G_IO_IN | G_IO_HUP, self->timeout))
	{
	  /*LOG
	    This message indicates that reading from the channel timed out.
	   */
	  z_log(self->name, CORE_ERROR, 1, "Channel read timed out; fd='%d'", self->fd);
	  return G_IO_ERROR_UNKNOWN;
	}
      res = g_io_channel_read(self->channel, buf, count, bytes_read);
      if (res != G_IO_ERROR_NONE)
        {
          if (errno != EINTR && errno != EAGAIN)
            {
              /*LOG
                This message indicates that read() failed because of the given
                reason.
               */
              z_log(self->name, CORE_ERROR, 1, "Channel read failed; attempt='%d', error='%m'", attempt++);
            }
        }
    }
  while (res == G_IO_ERROR_AGAIN && errno == EINTR);
  self->bytes_recvd += *bytes_read;

  if (res != G_IO_ERROR_AGAIN)
    {
      /*LOG
        This message reports the number of bytes read from the given fd.
       */
      z_log(self->name, CORE_DEBUG, 7, "Reading channel; fd='%d', count='%d'", self->fd, *bytes_read);
    }
    
#if ENABLE_PACKET_TRACE
  if (z_log_enabled(CORE_DEBUG, 9))
    z_data_dump(self->name, buf, *bytes_read);
#endif
  return res;
}

static GIOError
z_stream_write_method(GIOChannel *channel,
		      gchar *buf,
		      guint count,
		      guint *bytes_written)
{
  ZRealStream *self = (ZRealStream *) channel;
  GIOError res;
  int attempt = 1;

  /* FIXME: todo, datadump */
  
  /*LOG
    This message indicates that the given number of bytes is to be written
    to the given fd.
   */
  z_log(self->name, CORE_DEBUG, 7, "Writing channel; fd='%d', count='%d'", self->fd, count);

  do
    {
      if (!(self->wait)(self, G_IO_OUT | G_IO_HUP, self->timeout))
	{
	  /*LOG
	    This message indicates that writing to the given fd timed out.
	   */
	  z_log(self->name, CORE_ERROR, 1, "Channel write timed out; fd='%d'", self->fd);
	  return G_IO_ERROR_UNKNOWN;
	}
      res = g_io_channel_write(self->channel, buf, count, bytes_written);
      if (res != G_IO_ERROR_NONE)
        {
	  if (errno != EINTR && errno != EAGAIN)
	    {
	      /*LOG
	        This message indicates that some I/O error occurred in
	        the write() system call.
	       */
	      z_log(self->name, CORE_ERROR, 1, "Channel write failed; attempt='%d', error='%m'", attempt++);
	    }
        }
    }
  while (res == G_IO_ERROR_AGAIN && errno == EINTR);

#if ENABLE_PACKET_TRACE
  if (z_log_enabled(CORE_DEBUG, 9))
    z_data_dump(self->name, buf, *bytes_written);
#endif
  self->bytes_sent += *bytes_written;
  return res;
}


GIOError
z_stream_send(ZStream *self, gchar *buf, guint buflen, int flags)
{
  int res, attempt = 1;
  
  errno = 0;
  do
    {
      if (!(self->wait)(self, G_IO_OUT | G_IO_HUP, self->timeout))
	{
	  /*LOG
	    This message indicates that send() timed out.
	   */
	  z_log(self->name, CORE_ERROR, 1, "Send timed out; fd='%d'", self->fd);
	  return G_IO_ERROR_UNKNOWN;
	}
      res = send(self->fd, buf, buflen, flags);
      if (res == -1)
        {
	  if (errno != EINTR && errno != EAGAIN)
	    {
	      /*LOG
	        This message indicates that some I/O error occurred in the
	        send() system call.
	       */
	      z_log(self->name, CORE_ERROR, 1, "Send failed; attempt='%d', error='%m'", attempt++);
	    }
        }
    }
  while (res == -1 && errno == EINTR);
  self->bytes_sent += buflen;
  if (res == 0)
    return G_IO_ERROR_NONE;
    
  if (errno == EAGAIN)
    return G_IO_ERROR_AGAIN;
  else
    return G_IO_ERROR_UNKNOWN;
}


static void
z_stream_close_method(GIOChannel *channel)
{
  ZRealStream *self = (ZRealStream *) channel;

  /*LOG
    This message indicates that the given fd is to be closed.
   */
  z_log(self->name, CORE_DEBUG, 7, "Closing channel; fd='%d'", self->fd);
  g_io_channel_close(self->channel);
}

static void
z_stream_free(GIOChannel *channel)
{
  ZRealStream *self = (ZRealStream *) channel;
  time_t time_close;

  time_close = time(NULL);
  
  /*LOG
    This message contains accounting information on the given channel. It
    reports the number of seconds the fd was open and the number of
    bytes sent/received on this channel.
   */
  z_log(self->name, CORE_ACCOUNTING, 5, "accounting info; duration='%d', sent='%lld', received='%lld'", 
        (int)(time_close - self->time_open), self->bytes_sent, self->bytes_recvd);
  g_io_channel_unref(self->channel);
  g_free(channel);
}

GIOFuncs z_stream_funcs =
{
  z_stream_read_method,
  z_stream_write_method,
  NULL,
  z_stream_close_method,
  NULL,
  z_stream_free
};

/*+

  Allocate and initialize a ZThread instance with the given fd and name.

  Parameters:
    fd        fd to wrap this stream around
    name      name to identify to stream in logs
  
  Returns:
    ZStream instance

  +*/
ZStream *
z_stream_new(gint fd, gchar *name)
{
  ZRealStream *self = g_new0(ZRealStream, 1);

  g_io_channel_init(&self->super);
  self->shutdown = z_stream_shutdown_fd;
  self->wait = z_stream_wait_fd;
  self->fd = fd;
  self->super.funcs = &z_stream_funcs;
  self->timeout = -2;
  self->time_open = time(NULL);
  self->channel = g_io_channel_unix_new(fd);
  strncpy(self->name, name, sizeof(self->name) - 1);
  return (ZStream *) self;
}

