/**************************************************************************** 
**
** File: tcp.c
**
** Author: Mike Borella
**
** Comments: Dump TCP header information. 
**
** $Id: tcp.c,v 1.26 2006/11/21 07:47:34 farooq-i-azam Exp $
**
** 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "global.h"
#include "ip_services.h"
#include "tcp.h"
#include "payload.h"

#define HOLDER_SIZE 64

/* 
 * TCP option map 
 */

strmap_t tcp_option_map [] =
{
  { TCP_OPTION_EOL,     "end of options" },
  { TCP_OPTION_NOP,     "no op" },
  { TCP_OPTION_MSS,     "maximum segment size" },
  { TCP_OPTION_WS,      "window scale" },
  { TCP_OPTION_SACKOK,  "SACK permitted" },
  { TCP_OPTION_SACK,    "SACK" },
  { TCP_OPTION_TS,      "timestamp" },
  { 192,                "cwnd" },
  { 0, "" }
};

extern struct arg_t *my_args;
extern strmap_t port_map[];

/*----------------------------------------------------------------------------
**
** dump_tcp_options()
**
** Parse TCP options 
**
**----------------------------------------------------------------------------
*/

void dump_tcp_options(packet_t *pkt, u_int8_t length)
{
  u_int8_t option_bytes_read;
  char holder[HOLDER_SIZE];
  u_int8_t option_length;
  u_int8_t kind;
  u_int16_t mss;
  u_int32_t ts1, ts2;

#ifdef DEBUG
  printf("\nEntering TCP options\n");
#endif

  option_bytes_read = 0;
  while(option_bytes_read < length)
    {
      /* read the kind of option */
      if (get_packet_bytes((u_int8_t *) &kind, pkt, 1) == 0)
	return;
      option_bytes_read ++;
      
      /* take care of the 1-byte options */
      if (kind == TCP_OPTION_EOL || kind == TCP_OPTION_NOP)
	{
	  if (!my_args->m)
	    {
	      snprintf(holder, HOLDER_SIZE, "%d (%s)", kind, 
		       map2str(tcp_option_map, kind));
	      display_string("Option", holder);
	    }
	  continue;
	}

      /* display the option's name */
      if (!my_args->m)
	{
	  snprintf(holder, HOLDER_SIZE, "%d (%s)", kind, 
		   map2str(tcp_option_map, kind));
	  display_string("Option", holder);
	}
      else 
	{
	  display_minimal_string("<");	  
	  display_minimal_string(map2str(tcp_option_map, kind));
	}
          
      /* get and display the length field */
      if (get_packet_bytes((u_int8_t *) &option_length, pkt, 1) == 0)
	return;
      if (!my_args->m)
	display("  Length", (u_int8_t *) &option_length, 1, DISP_DEC);
      option_bytes_read += 1;

      /* read the 'length'-2 bytes because the field counts kind and itself */
      if (get_packet_bytes((u_int8_t *) holder, pkt, option_length-2) == 0)
	return;      
      option_bytes_read += (option_length - 2);
      
      /* read the field */
      switch(kind)
	{
	case TCP_OPTION_EOL:
	case TCP_OPTION_NOP:
	  /* these should be taken care of above... */
	  break;

	case TCP_OPTION_MSS:
	  memcpy((void *) &mss, holder, 2);
	  mss = ntohs(mss);
	  if (!my_args->m)
	    display("  MSS", (u_int8_t *) &mss, 2, DISP_DEC);
	  else
	    {
	      display_minimal_string(" ");
	      display_minimal((u_int8_t *) &mss, 2, DISP_DEC);
	      display_minimal_string(">");
	    }
	  break;
	  
	case TCP_OPTION_WS:
	  if (!my_args->m)
	    display("  Shift count", (u_int8_t *) holder, 1, DISP_DEC);
	  else
	    {
	      display_minimal_string(" ");
	      display_minimal((u_int8_t *) holder, 1, DISP_DEC);
	      display_minimal_string(">");
	    }
	  break;

	case TCP_OPTION_TS:
	  memcpy((void *) &ts1, holder, 4);
	  ts1 = ntohl(ts1);
	  memcpy((void *) &ts2, holder+4, 4);
	  ts2 = ntohl(ts2);
	  if (!my_args->m)
	    {
	      display("  Timestamp value", (u_int8_t *) &ts1, 4, 
		      DISP_DEC);
	      display("  Timestamp reply", (u_int8_t *) &ts2, 4, 
		      DISP_DEC);
	    }
	  else
	    {
	      display_minimal_string(" ");
	      display_minimal((u_int8_t *) &ts1, 4, DISP_DEC);
	      display_minimal_string(" ");
	      display_minimal((u_int8_t *) &ts2, 4, DISP_DEC);
	      display_minimal_string(">");
	    }
	  break;

	  /*
	case 64:
	  display_minimal_string(" ");
	  memcpy((void *) &ts1, holder, 4);
	  ts1 = ntohl(ts1);
	  display_minimal((u_int8_t *) &ts1, 4, DISP_DEC);
	  display_minimal_string(">");
	  break;
	  */

	default:
	  if (my_args->m)
	    display_minimal_string(">");
	  break;

	}
    } /* while */
  
  /*
   * Trailing space for minimal mode
   */

  if (my_args->m)
    display_minimal_string(" ");

#ifdef DEBUG
  printf("\nLeaving TCP options\n");
#endif

}

/*----------------------------------------------------------------------------
**
** dump_tcp()
**
** Parse TCP header and dump fields
**
**----------------------------------------------------------------------------
*/

void dump_tcp(packet_t *pkt)
{
  tcp_header_t tcp;
  u_int8_t hlen, unused;
  char flag_str[8];
  service_func_t app_src;
  service_func_t app_dst;
  char holder[64];

#ifdef DEBUG
  printf("\nEntering TCP\n");
#endif

  /* Set the layer */
  set_layer(LAYER_TRANSPORT);

  /*
   * Stats accounting
   */

  stats_update(STATS_TCP);

  /*
   * Grab the static TCP header
   */

  if (get_packet_bytes((u_int8_t *) &tcp, pkt, 20) == 0)
    return;

  /*
   * Conversions
   */

  tcp.src = ntohs(tcp.src);
  tcp.dst = ntohs(tcp.dst);
  tcp.seq_number = ntohl(tcp.seq_number);
  tcp.ack_number = ntohl(tcp.ack_number);
  hlen = tcp.header_length;
  unused = tcp.unused;
  tcp.window = ntohs(tcp.window);
  tcp.checksum = ntohs(tcp.checksum);
  tcp.urgent = ntohs(tcp.urgent);

  /*
   * Prepare flag string
   */

  flag_str[0] = '\0';
  if (tcp.flags & TCP_FLAG_SYN) 
    strcat(flag_str, "S");
  if (tcp.flags & TCP_FLAG_FIN) 
    strcat(flag_str, "F");
  if (tcp.flags & TCP_FLAG_RST) 
    strcat(flag_str, "R");
  if (tcp.flags & TCP_FLAG_PSH) 
    strcat(flag_str, "P");
  if (tcp.flags & TCP_FLAG_ACK) 
    strcat(flag_str, "A");
  if (tcp.flags & TCP_FLAG_URG) 
    strcat(flag_str, "U");

  /*
   * Minimal mode
   */

  if (my_args->m)
    {
      display_minimal_string("| TCP ");
      display_minimal((u_int8_t *) &tcp.src, 2, DISP_DEC);
      display_minimal_string("->");
      display_minimal((u_int8_t *) &tcp.dst, 2, DISP_DEC);
      display_minimal_string(" (");
      display_minimal(flag_str, strlen(flag_str), DISP_STRING);
      display_minimal_string(",");
      display_minimal((u_int8_t *) &tcp.seq_number, 4, DISP_DEC);      
      display_minimal_string(",");
      display_minimal((u_int8_t *) &tcp.ack_number, 4, DISP_DEC);      
      display_minimal_string(",");
      display_minimal((u_int8_t *) &tcp.window, 2, DISP_DEC);      
      display_minimal_string(") ");
    }
  else
    {
      /* Dump TCP header announcement */
      display_header_banner("TCP Header");
      
      /* port fields */
      display_strmap("Source port", tcp.src, port_map);
      display_strmap("Destination port", tcp.dst, port_map);
      
      /* sequence and acknowledgement */
      display("Sequence number", (u_int8_t *) &tcp.seq_number, 4, 
	      DISP_DEC);
      display("Acknowledgement number", (u_int8_t *) &tcp.ack_number, 4, 
	      DISP_DEC);
      
      /* header length and unused bits */
      snprintf(holder, HOLDER_SIZE, "%d (%d bytes)", hlen, hlen*4);   
      display_string("Header length", holder);
      display("Unused", (u_int8_t *) &unused, 1, DISP_DEC);
      
      /* flags */
      display("Flags", (u_int8_t *) &flag_str, strlen(flag_str), 
	      DISP_STRING);
      
      /* window size, checksum and urgent ptr */
      display("Window size", (u_int8_t *) &tcp.window, 2, DISP_DEC);
      display("Checksum", (u_int8_t *) &tcp.checksum, 2, DISP_DEC);
      display("Urgent", (u_int8_t *) &tcp.urgent, 2, DISP_DEC);
    }
  
  /* Add state */
  state_set_srcport(tcp.src);
  state_set_dstport(tcp.dst);
  
  /*
   * Handle any options. There should be option(s) if the header length
   * is > 20 bytes (5 words).  Pass in the total length of the options.
   */

  if (hlen > 5)
    dump_tcp_options(pkt, hlen*4 - 20);

  /* dump the hex buffer */
  hexbuffer_flush();

  /*
   * If there is something left of the packet, process the application
   * data
   */

  if (get_packet_apparentbytesleft(pkt))
    {
      /* 
       * Let's try to decode the application from the port number.  If both 
       * match a decoder, we choose the lowest, assuming that the higher 
       * one is probably ephemeral.
       */

      app_src = port2func(tcp.src);
      app_dst = port2func(tcp.dst);
      if (app_src && app_dst)
	{
	  if (tcp.src < tcp.dst)
	    app_src(pkt);
	  else
	    app_dst(pkt);
	}
      else
	{
	  if (app_src)
	app_src(pkt);
	  else
	    if (app_dst)
	      app_dst(pkt);
	}
    }

#ifdef DEBUG
  printf("\nLeaving TCP\n");
#endif

  return;
  
}

