/* 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 <sys/types.h>
#include <sys/time.h>

#include "b_packet.h"
#include "btp_node.h"
#include "btp_debug.h"
#include "util.h"


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


BPacket* 
b_packet_new (BConn* conn, guint8 type, guint16 length)
{
  BPacket* packet;

  packet = (BPacket*) g_new0 (guchar, sizeof(BPacket) + length);

  packet->protocol  = B_PACKET_PROTOCOL;
  packet->version   = B_PACKET_VERSION;
  packet->type      = type;
  packet->length    = g_htons (length);

  if (conn)
    {
      if (conn->group)
	{
	  packet->seq = g_htons(conn->group->seq_num++);
	  packet->source_id = g_htonl(conn->group->source_id);
	}
      packet->id = conn->out_conn_id;
    }

  BTPP (1, "b_packet_new %p\n", packet);

  return packet;
}


BPacket* 
b_packet_clone (BPacket* pkt)
{
  BPacket* new_pkt;
  gint size;

  g_return_val_if_fail (pkt, NULL);

  size = sizeof(BPacket) + g_ntohs(pkt->length);

  new_pkt = (BPacket*) g_malloc (size);
  memcpy (new_pkt, pkt, size);

  return new_pkt;
}


void
b_packet_delete (BPacket* packet)
{
  BTPP (1, "b_packet_delete %p\n", packet);

  if (packet)
    g_free (packet);
}


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

gboolean
b_packet_parse_addr (BPacket* pkt, BAddress** addrp)
{
  guint len;
  guint slen;
  
  g_return_val_if_fail (pkt, TRUE);
  g_return_val_if_fail (addrp, TRUE);

  *addrp = NULL;

  len = g_ntohs (pkt->length);
  if (len == 0) return TRUE;
  slen = strnlen (pkt->data, len);
  if (slen != (len - 3)) return TRUE;
  *addrp = g_new (BAddress, 1);
  (*addrp)->hostname = g_strdup (pkt->data);
  (*addrp)->port = ((unsigned char) pkt->data[slen+1] << 8) |
    (unsigned char) pkt->data[slen+2];

  return FALSE;
}


gboolean
b_packet_parse_addrs (BPacket* pkt, GSList** listp)
{
  GSList* i;
  guint len;
  gchar* p;
  guint slen;

  g_return_val_if_fail (pkt, TRUE);
  g_return_val_if_fail (listp, TRUE);

  *listp = NULL;

  len = g_ntohs (pkt->length);
  p = pkt->data;

  while (len > 0)
    {
      BAddress* addr;

      slen = strnlen (p, len);
      if (slen == len) goto error;
      addr = g_new (BAddress, 1);
      addr->hostname = g_strdup (p);
      p   += (slen + 1);
      len -= (slen + 1);
      if (len < 2) goto error;
      addr->port = ((unsigned char) p[0] << 8) | (unsigned char) p[1];
      p   += 2;
      len -= 2;
      *listp = g_slist_prepend (*listp, addr);
    }
  
  *listp = g_slist_reverse (*listp);

  return FALSE;

 error:
  for (i = *listp; i != NULL; i = i->next)
    {
      BAddress* addr = (BAddress*) i->data;
      g_free (addr->hostname);
      g_free (addr);
    }

  g_slist_free (*listp);
  *listp = NULL;

  return TRUE;
}


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


/* Return bytes parsed, non-zero if error */
gint
b_packet_read (BConn* conn, const gchar* buffer, gint buflen, BPacket** packetp)
{
  BPacket* pkt;
  guint16  data_len;
  BPacket* new_pkt;
  gint     new_pkt_len = 0;

  guint16  copy_len;


  g_return_val_if_fail (buffer, TRUE);
  g_return_val_if_fail (buflen, TRUE);
  g_return_val_if_fail (packetp, TRUE);

  /* Initialize return values */
  *packetp = NULL;


  /* Read any remaining bytes for a previous variable length packet */
  if (conn->cur_pkt)
    {
      pkt = conn->cur_pkt;
      data_len = g_ntohs (pkt->length);
      copy_len = MIN(data_len - conn->cur_read, buflen);
      g_assert ((conn->cur_read + copy_len) <= data_len);

      /* Copy the data */
      memcpy (&pkt->data[conn->cur_read], buffer, copy_len);

      /* Update total read */
      conn->cur_read += copy_len;

      /* Set packet if done */
      if (conn->cur_read == data_len)
	{
	  *packetp = conn->cur_pkt;	/* pkt == conn->cur_pkt */
	  conn->cur_pkt = NULL;
	  conn->cur_read = 0;
	}
      /* Otherwise, there is more data to be received.  We have read
         all we can. */
      else 
	g_assert (buflen == copy_len);

      return copy_len;
    }


  /* Return if amount here smaller than a header */
  if (buflen < sizeof(BPacket))
    return 0;

  /* Get the packet and length */
  pkt      = (BPacket*) buffer;
  data_len = g_ntohs (pkt->length);

  /* Make sure the protocol is right */
  if (pkt->protocol != B_PACKET_PROTOCOL)
    {
      g_warning ("Received packet with bad protocol (%d)\n", 
		 pkt->protocol);
      return -1;
    }

  /* Make sure the version is right */
  if (pkt->version != B_PACKET_VERSION)
    {
      g_warning ("Received packet with bad version (%d)\n", 
		 pkt->version);
      return -1;
    }

  /* Create a new packet */
  new_pkt_len = sizeof(BPacket) + data_len;
  new_pkt     = (BPacket*) g_malloc (new_pkt_len);

  /* Copy the header */
  memcpy ((void*) new_pkt, buffer, sizeof(BPacket));
	    
  /* Copy the data */
  copy_len = MIN(data_len, buflen - sizeof(BPacket));
  memcpy (new_pkt->data, pkt->data, copy_len);

  /* Save packet and break if we're expecting more data */
  if (copy_len < data_len)
    {
      g_assert ((sizeof(BPacket) + copy_len) == buflen);

      conn->cur_pkt  =  new_pkt;
      conn->cur_read =  copy_len;

      return (sizeof(BPacket) + copy_len);
    }
  /* Otherwise, we have read the whole packet */
  else
    {
      *packetp = new_pkt;

      return new_pkt_len;
    }
}


gint
b_packet_write (BPacket* packet, gchar** bufferp)
{
  gint length;

  g_return_val_if_fail (packet, 0);
  g_return_val_if_fail (bufferp, 0);

  length = B_PACKET_LENGTH(packet);
  *bufferp = g_malloc (length);
  memcpy (*bufferp, (void*) packet, length);

  return length;
}



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



BPacket*
b_packet_new_ok (BConn* conn, BPacketOkSubtype subtype)
{
  BPacket* packet;

  g_return_val_if_fail (conn != NULL, NULL);

  packet = b_packet_new (conn, B_PACKET_TYPE_OK, 0);
  packet->subtype = subtype;

  return packet;
}



BPacket*
b_packet_new_error (BConn* conn, BPacketErrorSubtype subtype)
{
  BPacket* packet;

  g_return_val_if_fail (conn != NULL, NULL);

  packet = b_packet_new (conn, B_PACKET_TYPE_ERROR, 0);
  packet->subtype = subtype;

  return packet;
}



BPacket*
b_packet_new_hello (BConn* bconn)
{
  BPacket* pkt;
  gchar* url_str;
  guint length;
  gint bytes_packed;


  g_return_val_if_fail (bconn, NULL);
  g_return_val_if_fail (bconn->conn, NULL);
  g_return_val_if_fail (bconn->conn->hostname, NULL);
  g_return_val_if_fail (bconn->group, NULL);

  /* Send: "ssH"
       URL
       My hostname:port
  */

  url_str = gnet_url_get_nice_string (bconn->group->url);
  g_return_val_if_fail (url_str, NULL);

  length = gnet_calcsize ("!ssH", url_str, bconn->group->peer->hostname);
  g_return_val_if_fail (length > 0, NULL);

  pkt = b_packet_new (bconn, B_PACKET_TYPE_HELLO, length);

  bytes_packed = gnet_pack ("!ssH", pkt->data, length, url_str, 
			    bconn->group->peer->hostname,
			    bconn->group->peer->port);
  g_return_val_if_fail (length == bytes_packed, NULL);

  pkt->id = bconn->in_conn_id;	/* We send out id in first */

  g_free (url_str);

  return pkt;
}


gboolean	/* Return FALSE if OK */ 
b_packet_parse_hello (BPacket* pkt, GURL** urlp, 
		      gchar** hostnamep, gint* portp)
{
  gchar* hostname;
  gchar* portstr;
  gchar* end;

  g_return_val_if_fail (pkt,       TRUE);
  g_return_val_if_fail (urlp, 	   TRUE);
  g_return_val_if_fail (hostnamep, TRUE);
  g_return_val_if_fail (portp, 	   TRUE);

  *urlp =      NULL;
  *hostnamep = NULL;
  *portp =     0;

  end = pkt->data + g_htons (pkt->length);

  /* Get hostname */
  hostname = pkt->data;
  while (hostname < end && *hostname) ++hostname;
  ++hostname;
  if (hostname >= end) return TRUE;

  /* Get port string */
  portstr = hostname;
  while (portstr < end && *portstr) ++portstr;
  ++portstr;
  if ((portstr + 2) != end) return TRUE;

  /* Copy data */
  *urlp = gnet_url_new (pkt->data);
  if (!*urlp) return TRUE;
  *hostnamep = g_strdup (hostname);
  *portp = ((unsigned char) portstr[0] << 8) | (unsigned char) portstr[1];

  return FALSE;
}




BPacket*
b_packet_new_ok_hello (BConn* conn)
{
  BPacket* packet;

  g_return_val_if_fail (conn != NULL, NULL);

  packet = b_packet_new (conn, B_PACKET_TYPE_OK, sizeof(guint32));
  packet->subtype = B_PACKET_OK_SUBTYPE_HELLO;
  *(guint32*) packet->data = conn->in_conn_id;

  return packet;
}


BPacket*
b_packet_new_bye (BConn* conn)
{
  BPacket* packet;

  g_return_val_if_fail (conn != NULL, NULL);

  packet = b_packet_new (conn, B_PACKET_TYPE_BYE, 0);

  return packet;
}



BPacket* 
b_packet_new_ucast (BConn* conn, const void* buffer, guint16 length)
{
  BPacket* packet;

  packet = b_packet_new (conn, B_PACKET_TYPE_UCAST, length);
  memcpy (packet->data, buffer, length);

  return packet;
}



BPacket* 
b_packet_new_mcast (BConn* conn, const void* buffer, guint16 length)
{
  BPacket* packet;

  packet = b_packet_new (conn, B_PACKET_TYPE_MCAST, length);
  memcpy (packet->data, buffer, length);

  return packet;
}



BPacket* 
b_packet_new_info_request_ping (BConn* conn)
{
  BPacket* packet;

  packet = b_packet_new (conn, B_PACKET_TYPE_INFO_REQUEST, sizeof(struct timeval));
  packet->subtype = B_PACKET_INFO_SUBTYPE_PING;
  /* We will set the timeval just before we send it */

  return packet;
}



BPacket* 
b_packet_new_info_reply_ping (BConn* conn, BPacket* ping)
{
  BPacket* packet;
  guint length;

  g_return_val_if_fail (conn != NULL, NULL);
  g_return_val_if_fail (ping != NULL, NULL);

  length = g_ntohs (ping->length);

  packet = b_packet_new (conn, B_PACKET_TYPE_INFO_REPLY, length);
  packet->subtype = B_PACKET_INFO_SUBTYPE_PING;
  memcpy (packet->data, ping->data, length);

  return packet;
}


BPacket*
b_packet_new_info_request_neighbors (BtpNode* node)
{
  BPacket* packet;

  g_return_val_if_fail (node, NULL);
  g_return_val_if_fail (node->conn, NULL);

  packet = b_packet_new (node->conn, B_PACKET_TYPE_INFO_REQUEST, 0);
  packet->subtype = B_PACKET_INFO_SUBTYPE_NEIGHBORS;

  return packet;
}



BPacket*
b_packet_new_info_reply_neighbors (BtpNode* node, BtpNode* parent, GSList* children)
{
  GSList* i;
  guint len = 0;
  gchar* p;
  BPacket* pkt;

  g_return_val_if_fail (node, NULL);
  g_return_val_if_fail (node->conn, NULL);

  /* Calculate string length */
  if (parent)
    len += strlen (parent->hostname);
  len += 1 + 2;

  for (i = children; i != NULL; i = i->next)
    {
      BtpNode* child = (BtpNode*) i->data;
      len += strlen (child->hostname) + 1;
      len += 2;
    }

  /* Create packet */
  pkt = b_packet_new (node->conn, B_PACKET_TYPE_INFO_REPLY, len);
  pkt->subtype = B_PACKET_INFO_SUBTYPE_NEIGHBORS;
  
  /* Copy in strings */
  p = pkt->data;
  if (parent)
    {
      strcpy (p, parent->hostname);
      p += strlen (parent->hostname) + 1;
      *p++ = ((guint) parent->port & 0xFF00) >> 8;
      *p++ =  parent->port & 0x00FF;
    }
  else
    {
      *p++ = '\0'; *p++ = '\0'; *p++ = '\0';
    }

  for (i = children; i != NULL; i = i->next)
    {
      BtpNode* child = (BtpNode*) i->data;

      strcpy (p, child->hostname);
      p += strlen (child->hostname) + 1;
      *p++ = ((guint) child->port & 0xFF00) >> 8;
      *p++ =  child->port & 0x00FF;
    }

  return pkt;
}


gboolean
b_packet_parse_info_reply_neighbors (BPacket* pkt, BAddress** parent, GSList** children /* BAddress */)
{
  g_return_val_if_fail (pkt, TRUE);
  g_return_val_if_fail (parent, TRUE);
  g_return_val_if_fail (children, TRUE);

  /* Read addrs into children */
  if (b_packet_parse_addrs (pkt, children))
    return TRUE;

  /* Pop off top and set to parent */
  if (*children)
    {
      *parent = (BAddress*) (*children)->data;
      *children = g_slist_remove (*children, *parent);
    }

  return FALSE;
}


BPacket*  
b_packet_new_info_reply_node (BConn* conn, BtpNode* me)
{
  gint len, rv;
  BPacket* pkt;

  /* Info:
       uint32	delay
       char*	hostname
       uint16	port
   */

  len = gnet_calcsize ("!IsH", 0, me->hostname, me->port);
  g_return_val_if_fail (len > 0, NULL);

  pkt = b_packet_new (conn, B_PACKET_TYPE_INFO_REPLY, len);
  pkt->subtype = B_PACKET_INFO_SUBTYPE_NODE;
  rv = gnet_pack ("!IsH", pkt->data, len, 0, me->hostname, me->port);
  g_return_val_if_fail (rv == len, NULL);

  return pkt;
}


gboolean
b_packet_parse_info_reply_node (BPacket* pkt, guint32* delay, gchar** hostname, guint* port)
{
  gint rv;
  guint16 p;
  guint len;

  g_return_val_if_fail (delay && hostname && port, FALSE);

  len = g_ntohs (pkt->length);
  rv = gnet_unpack ("!IsH", pkt->data, len, delay, hostname, &p);
  if (rv != len) return FALSE;
  *port = p;

  return TRUE;
}



BPacket*
b_packet_new_join (BtpNode* node)
{
  BPacket* packet;

  g_return_val_if_fail (node, NULL);
  g_return_val_if_fail (node->conn, NULL);

  packet = b_packet_new (node->conn, B_PACKET_TYPE_JOIN, 0);

  return packet;
}



BPacket*
b_packet_new_switch (BtpNode* node, BtpNode* parent, BPacketSwitchSubtype sub)
{
  guint slen;
  BPacket* pkt;

  g_return_val_if_fail (node, NULL);
  g_return_val_if_fail (node->conn, NULL);
  g_return_val_if_fail (parent, NULL);

  slen = strlen (parent->hostname);
  pkt = b_packet_new (node->conn, B_PACKET_TYPE_SWITCH, slen + 3);
  pkt->subtype = sub;
  memcpy (pkt->data, parent->hostname, slen);
  pkt->data[slen + 0] = '\0';
  pkt->data[slen + 1] = (parent->port & 0xFF00) >> 8;
  pkt->data[slen + 2] =  parent->port & 0x00FF;

  return pkt;
}


gboolean
b_packet_parse_switch (BPacket* pkt, BAddress** addrp)
{
  g_return_val_if_fail (pkt, TRUE);

  if (pkt->subtype != B_PACKET_SWITCH_SUBTYPE_SIBLING &&
      pkt->subtype != B_PACKET_SWITCH_SUBTYPE_GPARENT)
    {
      g_warning ("Bad SWITCH packet subtype\n");
      return TRUE;
    }

  return b_packet_parse_addr (pkt, addrp);
}




BPacket*
b_packet_new_leave (BtpNode* node)
{
  BPacket* packet;

  g_return_val_if_fail (node, NULL);
  g_return_val_if_fail (node->conn, NULL);

  packet = b_packet_new (node->conn, B_PACKET_TYPE_LEAVE, 0);

  return packet;
}


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




static gchar* type_to_string[] = 
{
  "OK",
  "ERROR",

  "HELLO",
  "BYE",

  "UNICAST",
  "MULTICAST",

  "INFO_REQUEST",
  "INFO_REPLY",

  "JOIN",
  "LEAVE",
  "SWITCH",
  "SHORTCUT_ADD",
  "SHORTCUT_REMOVE"
};


gchar* 	   
b_packet_type_to_string (gint type)
{
  if (type < 0 || type > B_PACKET_TYPE_LAST)
    return "<null>";

  return type_to_string[type];
}


void
b_address_delete (BAddress* addr)
{
  if (addr)
    {
      g_free (addr->hostname);
      g_free (addr);
    }
}


void
b_addresses_delete (GSList* list)
{
  if (list)
    {
      GSList* i;
      
      for (i = list; i != NULL; i = i->next)
	b_address_delete ((BAddress*) i->data);

      g_slist_free (list);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1