/* BTP library - Banana Tree Protocol
 * Copyright (C) 1999-2001  The Regents of the University of Michigan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 */

#include <string.h>

#include "b_conn_io.h"
#include "b_packet.h"

#include "btp_debug.h"
#include "util.h"


static gchar* flags2str (guint flags);

static gboolean demux_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data);
static gboolean read_cb  (GIOChannel* iochannel, GIOCondition condition, gpointer data);
static gboolean write_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data);
static gboolean error_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data);



/* ************************************************************ */

static
gchar* flags2str (guint flags)
{
  gchar* str = NULL;

  str = g_strdup ("");
  if (flags & G_IO_IN)
    {
      gchar* oldstr = str;
      str = g_strconcat (oldstr, "IN|", NULL);
      g_free (oldstr);
    }
  if (flags & G_IO_OUT)
    {
      gchar* oldstr = str;
      str = g_strconcat (oldstr, "OUT|", NULL);
      g_free (oldstr);
    }
  if (flags & G_IO_PRI)
    {
      gchar* oldstr = str;
      str = g_strconcat (oldstr, "PRI|", NULL);
      g_free (oldstr);
    }
  if (flags & G_IO_ERR)
    {
      gchar* oldstr = str;
      str = g_strconcat (oldstr, "ERR|", NULL);
      g_free (oldstr);
    }
  if (flags & G_IO_HUP)
    {
      gchar* oldstr = str;
      str = g_strconcat (oldstr, "HUP|", NULL);
      g_free (oldstr);
    }
  if (flags & G_IO_NVAL)
    {
      gchar* oldstr = str;
      str = g_strconcat (oldstr, "NVAL|", NULL);
      g_free (oldstr);
    }

  if (*str != '\0')
    str[strlen(str) - 1] = '\0';  /* wipe out last | */

  return str;
}



gboolean
b_conn_watch_update (BConn* conn, guint new_flags)
{
  g_return_val_if_fail (conn, FALSE);

  {
    gchar* str  = flags2str(conn->watch_flags);
    gchar* str2 = flags2str(new_flags);
    BTPP (1, "b_conn_watch_update: %s -> %s (%d)\n", 
	  str, str2, conn->watch);
    g_free (str);
    g_free (str2);
  }

  if (conn->watch_flags == new_flags)
    return FALSE;

  conn->watch_changed = 1;
  if (conn->watch)
    {
      g_source_remove (conn->watch);
      conn->watch = 0;
    }

  if (new_flags && conn->conn->iochannel)
    conn->watch = g_io_add_watch (conn->conn->iochannel, new_flags, 
				  demux_cb, conn);
  conn->watch_flags = new_flags;

  return TRUE;
}



/* ************************************************************ */

/**

   demux_cb
   
   Called when any IO activity.  This is a work around Glib's poll
   problem.

 */
static gboolean 
demux_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data)
{
  BConn* conn = (BConn*) data;
  gboolean rv;

  g_return_val_if_fail (conn, FALSE);
  g_return_val_if_fail (!conn->in_upcall, FALSE);
  g_return_val_if_fail (condition & conn->watch_flags, FALSE);

  {
    gchar* str;
    str = flags2str (condition);
    BTPP (1, "demux_cb %s\n", str);
    g_free (str);
  }

  conn->in_upcall = 1;
  conn->watch_changed = 0;

  if (condition & conn->watch_flags & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
    {
      rv = error_cb (iochannel, condition, data);
      if (conn->should_destroy)
	goto destroy;
      if (!rv)
	b_conn_watch_update (conn, conn->watch_flags & 
			     ~(G_IO_ERR | G_IO_HUP | G_IO_NVAL));
    }

  if (condition & conn->watch_flags & G_IO_OUT)
    {
      rv = write_cb (iochannel, condition, data);
      if (conn->should_destroy)
	goto destroy;
      if (!rv)
	b_conn_watch_update (conn, conn->watch_flags & ~G_IO_OUT);
    }

  if (condition & conn->watch_flags & G_IO_IN)
    {
      rv = read_cb (iochannel, condition, data);
      if (conn->should_destroy)
	goto destroy;
      if (!rv)
	b_conn_watch_update (conn, conn->watch_flags & ~G_IO_IN);
    }

  conn->in_upcall = 0;

  /* Update flags */
  if (conn->watch_changed)
    {
      conn->watch_changed = 0;
      BTPP (0, "demux_cb: return FALSE\n");
      return FALSE;
    }

  BTPP (0, "demux_cb: return TRUE\n");
  return TRUE;	/* otherwise, watch did not change */

 destroy:
  conn->in_upcall = 0;
  b_conn_delete (conn);
  return FALSE;
}


/**

   read_cb

   Called when an iochannel is readable.  Read from the iochannel.
   Loop parsing the packet and sending the packet up.

   Last checked: 	2001-4-30 DAH

 */
static gboolean
read_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data)
{
  BConn* conn = (BConn*) data;
  gchar* buf;
  gint len;
  GIOError error;
  guint bytes_read;
  guint bytes_parsed = 0;
  BPacket* packet;
  

  g_return_val_if_fail (conn, FALSE);

  BTPP (1, "read_cb: buffer_offset = %d\n", conn->buffer_offset);

  /* Read stuff into our buffer */
  buf = &conn->buffer[conn->buffer_offset];
  len = sizeof(conn->buffer) - conn->buffer_offset;

  error = g_io_channel_read (iochannel, buf, len, &bytes_read);

  /* If we read EOF or got an error, then the connection has failed
     (we should have received a BYE) */
  if ((error == G_IO_ERROR_NONE && bytes_read == 0) ||
      (error != G_IO_ERROR_NONE && error != G_IO_ERROR_AGAIN))
    {
      BTPP (0, "read_cb: error %d, bytes_read %d\n", error, bytes_read);
      goto error;
    }

  /* Update buffer length */
  conn->buffer_offset += bytes_read;
  BTPP (0, "read_cb: bytes_read = %d\n", bytes_read);

  /* Parse the packets and pass them up to the host */
  while (conn->buffer_offset &&
	 (bytes_parsed = b_packet_read (conn, conn->buffer, conn->buffer_offset, &packet)) > 0)
    {
      /* Move data over based on how much was read */
      g_memmove(conn->buffer, &conn->buffer[bytes_parsed], 
		conn->buffer_offset - bytes_parsed);
      conn->buffer_offset -= bytes_parsed;
      BTPP (0, "read_cb: bytes_parsed = %d\n", bytes_parsed);

      /* If there is no packet (it may only be part of a packet, break */
      if (!packet)
	break;

      BTPP (2, "READ %s\n", b_packet_type_to_string(packet->type));

      /* The packet should have the right ID (unless we just accepted
         it) */
      if (!b_conn_is_accepted(conn) && packet->id != conn->in_conn_id)
	{
	  g_warning ("Packet has wrong id (is %d, should be %d)\n",
		     packet->id, conn->in_conn_id);
	}

      /* Dispatch the packet */
      b_conn_handler_dispatch (conn, packet);
      b_packet_delete (packet);

      /* If the connection has been destroyed, then return FALSE. */
      if (conn->should_destroy)
	return FALSE; 
    }

  /* Dump the connection if there is no more buffer space */
  if (sizeof(conn->buffer) == conn->buffer_offset)
    {
      g_warning ("Maximum buffer length reached for connection\n");
      goto error;
    }

  /* Dump the connection if there was a parsing error. */
  if (bytes_parsed < 0)
    {
      BTPP (0, "read_cb: parse failed\n");
      goto error;
    }

  return TRUE;

 error:
  b_conn_func_fail (conn);
  return FALSE;
}



/**

   write_cb

   Called when an iochannel is writable.  If there are no packets to
   send, do an upcall.  If there's a packet to send, send it.
   Otherwise, we don't want any more callbacks.

   Last checked: 	2001-4-30 DAH

 */
gboolean 
write_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data)
{
  BConn* conn = (BConn*) data;
  BPacket* packet;

  g_return_val_if_fail (conn, FALSE);

  BTPP (1, "write_cb %p\n", conn);


  /* If there are no data packets to send, call the write callbacks -
     they may give us a data packet. */
  if (b_conn_is_connected (conn) && 
      !(b_conn_has_packet (conn, B_PACKET_TYPE_MCAST) ||
	b_conn_has_packet (conn, B_PACKET_TYPE_UCAST)) )
    {
      b_conn_func_writeable (conn);
    }

  
  /* If we have a packet, send it */
  if (conn->pkt_queue && (packet = b_conn_pop_packet (conn)) != NULL)
    {
      gchar* buffer;
      guint length;
      guint bytes_writen;
      GIOError error;

      /* Get the next packet from the conn.  We may not be ready to
         send a packet because we're awaiting a response. */
      
      if (!packet) 
	return FALSE;

      BTPP (3, "WRITE %s\n", b_packet_type_to_string(packet->type));

      /* Convert it to bytes */
      length = b_packet_write (packet, &buffer);
      g_return_val_if_fail (buffer, FALSE);
      g_return_val_if_fail (length > 0, FALSE);

      /* Write it */
      error = gnet_io_channel_writen (conn->conn->iochannel, buffer, length,
				      &bytes_writen);
      BTPP (1, "write_cb: bytes_writen = %d, length = %d\n", bytes_writen, length);

      if (error != G_IO_ERROR_NONE || length != bytes_writen)
	{
	  b_conn_func_fail (conn);
	  return FALSE;
	}

      /* Delete the packet and buffer */
      b_packet_delete (packet);
      g_free (buffer);

      /* Return TRUE.  If there were no more packets, this would
	 result in a call to writeable, which may generate more
	 packets. */
      return TRUE;
    }

  /* Otherwise, if the host is being closed, close it */
  else if (b_conn_is_closing (conn))
    b_conn_func_close (conn);

  /* Otherwise, we just don't have any packets to send */

  return FALSE;
}




/* 

   error_cb

   Called when a socket has an ERR or HUP.

   Last checked: 	2001-4-30 DAH

 */
gboolean
error_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data)
{
  BConn* conn = (BConn*) data;

  BTPP (4, "ERROR %d\n", condition);

  b_conn_func_fail (conn);

  return FALSE;
}



syntax highlighted by Code2HTML, v. 0.9.1