/******************************************************************************
*                                                                             *
*   Copyright 2005 University of Cambridge Computer Laboratory.               *
*                                                                             *
*   This file is part of Nprobe.                                              *
*                                                                             *
*   Nprobe 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.                                       *
*                                                                             *
*   Nprobe 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 Nprobe; if not, write to the Free Software                     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
*                                                                             *
******************************************************************************/


#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/socket.h>
#ifdef __alpha__
#include <sys/mbuf.h>
#endif
#include <net/route.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>

#ifdef __linux__
#define __FAVOR_BSD
#endif

#ifdef __linux__
#endif
#include <netinet/ip.h>


#include <netinet/tcp.h>
#ifdef __alpha__
#include <net/if_llc.h>
#endif
#include <netinet/if_fddi.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>

#ifdef __linux__
#include "if_llc.h"
#endif

#include <linux/limits.h>

#include "list.h"
#include "pkt.h"
#include "interface.h"
#include "tcpdump_patch.h"
#include "flows.h"
#include "http.h"
#include "tcp.h"
# include "np_file.h"
#include "udp.h"
#include "service.h"
#include "tcp_other.h"
#include "udp_other.h"
#include "udp_ns.h"
#include "timeouts.h"

#ifdef PRINT_OUT
#include "print_util.h"
#include "cprintf.h"
#endif

#include "report.h"
#include "output.h"
#include "if_nprobe.h"
#include "pool.h"
#include "writer.h"
#include "print_util.h"
#include "interesting.h"
#include "sundry_records.h"

#include "tcp_test.h"

#include "config.h"




#define SWALLOW(cp)   \
MACRO_BEGIN    \
while (*cp == ' ' || *cp == '=')  \
     cp++;   \
MACRO_END


typedef struct pspec 
{
  struct pspec *next;
  unsigned char flags;
  us_clock_t send_off;
  int plen;
  unsigned int seq;
} pspec_t;

char *prog;

void 
usage(void)
{
  fprintf(stderr, "%s: usage %s [-w outfile] [-r infile] specfile\n", prog, prog);
  exit (1);
}

void 
error(char *msg, char *pmsg)
{
    
#ifdef PRINT_OUT
  fprintf(stderr, "%s: FATAL ERROR %s: \n", prog, msg);
  if (pmsg)
    perror(pmsg);
  exit (1);
#else
  fprintf(stderr, "FATAL ERROR %s: %s %s\n", msg, pmsg, strerror(errno));
  exit (1);
#endif
}

void
swap_hdr(struct file_header *hp)
{
	hp->version_major = SWAPSHORT(hp->version_major);
	hp->version_minor = SWAPSHORT(hp->version_minor);
	hp->thiszone = SWAPLONG(hp->thiszone);
	hp->sigfigs = SWAPLONG(hp->sigfigs);
	hp->snaplen = SWAPLONG(hp->snaplen);
	hp->linktype = SWAPLONG(hp->linktype);
}


FILE * 
open_tcpdumpfile(char *fnm, struct file_header *hdr, int *sf_swapped)
{
  FILE *f;

  if ((f = fopen(fnm, "r")) == NULL)
    error("open_tcpdumpfile", fnm);

  clearerr(f);
	
  if (fread((char *)hdr, sizeof(struct file_header), 1, f) != 1)
    error("open_tcpdumpfile: read header", fnm);

  if (hdr->magic != TCPDUMP_MAGIC) 
    {
      if (SWAPLONG(hdr->magic) != TCPDUMP_MAGIC)
	error("open_tcpdumpfile: bad format", NULL);
      *sf_swapped = 1;
      swap_hdr(hdr);
    }
  if (hdr->version_major < VERSION_MAJOR)
    error("open_tcpdumpfile: bad tcpdump version", NULL);

  
#ifdef ZFLAG
  if(hflag)
    {
      printf("Collecting tcpdump: version  %hu.%hu timezone %d sigfigs %u snaplen %u linktype %u\n\n", hdr->version_major, hdr->version_minor, hdr->thiszone, hdr->sigfigs, hdr->snaplen, hdr->linktype);
    }
#endif
  
  return  f;
}

FILE * 
open_file(char *fnm, char *mode)
{
  FILE *f;

  if ((f = fopen(fnm, mode)) == NULL)
    error("open_file() fail", fnm);
  
  return f;
  
}

int 
next_pkt(FILE *fp, struct packet_header *h, unsigned char *buff, int swapped, int version_minor, int snaplen)
{

  /* read the stamp */
  if (fread((char *)h, sizeof(struct packet_header), 1, fp) != 1) 
    {
      if (feof(fp))
	return EOF;
      else
	error("next_pkt", "header read");
    }

  if (swapped) 
    {
      /* these were written in opposite byte order */
      h->cap_len = SWAPLONG(h->cap_len);
      h->len = SWAPLONG(h->len);
      h->ts.tv_sec = SWAPLONG(h->ts.tv_sec);
      h->ts.tv_usec = SWAPLONG(h->ts.tv_usec);
    }
  
  /* 
   * Courtesy of tcpdump-3.4 + local hack so that we can read stuff 
   * collected on linux boxes 
   */
  /*
   * "We interchanged the caplen and len fields at version 2.3,
   * in order to match the bpf header layout.  But unfortunately
   * some files were written with version 2.3 in their headers
   * but without the interchanged fields."
   */
  if (version_minor > 3 ||
      (version_minor == 3 && h->cap_len > h->len)) {
    int t = h->cap_len;
    h->cap_len = h->len;
    h->len = t;
  }
  
  if (h->cap_len > snaplen)
    error("next_pkt: reported pkt. length too great", NULL);
  
  /* read the packet itself */
  
  if (fread(buff, h->cap_len, 1, fp) != 1)
    {
      if (feof(fp))
	return EOF;
      else
	error("next_pkt", "pkt read");
    }
 
  return 0;
}

unsigned char 
get_flags(char *cp)
{
  unsigned char flags = 0x00;
  SWALLOW(cp);

  if (*cp != ':')
    goto done;
  else
    cp++;

  while (*cp != '\0')
    {
      switch (*cp)
	{
	case 'S': flags |= TH_SYN; break;
	case 'R': flags |= TH_RST; break;
	case 'F': flags |= TH_FIN; break;
	case 'A': flags |= TH_ACK; break;
	default: break;
	}
      cp++;
    }
 done:
  return flags;
} 

void 
report(struct ip *ipp, struct tcphdr *tcpp, pspec_t *next)
{
  struct timeval tv;

  printf("Inserted:-\n");
  
  while (next != NULL)
    {
      tv.tv_sec = next->send_off/1000000;
      tv.tv_usec = next->send_off%1000000;

      printf("+%s %s:%u > ", ts_string(&tv), inet_ntoa(ipp->ip_src), ntohs(tcpp->th_sport));
      printf("%s:%u ", inet_ntoa(ipp->ip_dst), ntohs(tcpp->th_dport));
      printf("%lu - %lu ", next->seq, next->seq + next->plen);
      print_tcp_flags(next->flags, stdout);
      printf("\n");

      next = next->next;
    }

  fflush(stdout);

  return;
}
	     

#define LINE_LEN 100

pspec_t *
get_spec(char *specfnm, struct ip *ipp, struct tcphdr *tcpp)
{
  int line = 0;
  int seq_adv;
  int pload_len = 0;
  ulong period = 0UL, period_add, send_off = 0UL;
  unsigned int seq;
  pspec_t psfirst, *pscurr = &psfirst;

  FILE *f;
  char in[LINE_LEN];

  f = open_file(specfnm, "r");

  while (fgets(in, LINE_LEN, f) != NULL)
    {
      char *cp = in;
      char *end;
      int len = pload_len;
      int pkt = 0;
      unsigned char flags = 0;
      
      period_add = 0;
      line++;
      switch (*cp)
	{
	case 'S':
	  cp++;
	  if (*cp == 'A')
	    {
	      cp++;
	      SWALLOW(cp);
	      ipp->ip_src.s_addr = inet_addr(cp);
	    }
	  else if (*cp == 'P')
	    {
	      cp++;
	      SWALLOW(cp);
	      tcpp->th_sport = htons(atoi(cp));
	    }
	  else 
	    {
	      SWALLOW(cp);
	      if (isdigit(*cp))
		{
		  seq = strtoul(cp, NULL, 0);
		}
	      else 
		{
		  goto syn_err;
		}
	    }
	  break;
	case 'D':
	  cp++;
	  if (*cp == 'A')
	    {
	      cp++;
	      SWALLOW(cp);
	      ipp->ip_dst.s_addr = inet_addr(cp);
	    }
	  else if (*cp == 'P')
	    {
	      cp++;
	      SWALLOW(cp);
	      tcpp->th_dport = htons(atoi(cp));
	    }
	  else 
	    {
	      goto syn_err;
	    }
	  break;
	case 'L':
	  cp++;
	  SWALLOW(cp);
	  if (isdigit(*cp))
	    {
	      pload_len = atoi(cp);
	      //pload_len -= pload_len%sizeof(uint32);
	    }
	  else 
	    {
	      goto syn_err;
	    }
	  break;
	case 'P':
	  cp++;
	  SWALLOW(cp);
	  if (isdigit(*cp))
	    {
	      period = strtoul(cp, NULL, 0);
	    }
	  else 
	    {
	      goto syn_err;
	    }
	  break;
	case 'T':
	  period_add = TCP_SEQ_TIMEO_US;
	  break;
	case '+':
	case '-':
	  if (isdigit(*(cp+1)))
	    {
	      seq_adv = atoi(cp);
	      seq += seq_adv*pload_len;
	    }
	  else 
	    {
	      goto syn_err;
	    }
	  break;
	case '#':
	  continue;
	  break;
	default:
	  SWALLOW(cp);
	  if (!isdigit(*cp))
	    {
	      if (strlen(cp) == 0)
		continue;
	      else
		goto syn_err;
	    }
	  else if (*cp == '0')
	    {
	      len = 0;
	      pkt = 1;
	      flags = get_flags(++cp);
	    }
	  else
	    {
	      pkt = (int)strtoul(cp, &end, 0);
	      flags = get_flags(end);
	    }
	  break;      
	}

      while (pkt)
	{
	  pkt--;
	  send_off += period + period_add;
	  if ((pscurr->next = (pspec_t *)malloc(sizeof(pspec_t))) == NULL)
	    error("get_spec: malloc", "");
	  pscurr = pscurr->next;
	  pscurr->next = NULL;
	  pscurr->flags = flags;
	  pscurr->send_off = send_off;
	  pscurr->plen = len;
	  pscurr->seq = seq;
	  seq += len;
	  if (flags & (TH_SYN | TH_FIN))
	    seq++;
	}
	  
    }

  report(ipp, tcpp, psfirst.next);

  return psfirst.next;
  
 syn_err:
  fprintf (stderr, "Spec syntax error line %d \"%s\"\n", 
	   line, in);
  exit (1);
}

void 
pkt_insert(pspec_t *psp, FILE *outf, struct ip *ipp, struct tcphdr *tcpp, int swapped)
{
  struct packet_header h;
  nprobe_hdr_t nh;		/* nprobe `header' */
  char *buf;
  int i;
  unsigned char pl_seq = psp->seq & 0xff;

  unsigned long long sec = psp->send_off/(unsigned int long)1000000;
  unsigned long long usec = psp->send_off%(unsigned int long)1000000;
  

  h.ts.tv_sec = sec;
  h.ts.tv_usec = usec;
  h.len = psp->plen + sizeof(struct ip) + sizeof(struct tcphdr) + sizeof(nprobe_hdr_t);
  h.cap_len = h.len;

  if (swapped)
    {
      h.cap_len = SWAPLONG(h.cap_len);
      h.len = SWAPLONG(h.len);
      h.ts.tv_sec = SWAPLONG(h.ts.tv_sec);
      h.ts.tv_usec = SWAPLONG(h.ts.tv_usec);
    }

  if (fwrite((char *)&h, sizeof(h), 1, outf) != 1)
	error("pkt_insert: write packet header", "");
  
  nh.u.nh.code = TCP_TESTPKT;
  nh.u.nh.etype = ntohs(ETHERTYPE_IP);

  if (fwrite((char *)&nh, sizeof(nh), 1, outf) != 1)
	error("pkt_insert: write nprobe header", "");

  ipp->ip_v = IPVERSION;
  ipp->ip_hl = 5;
  ipp->ip_tos = 0;
  ipp->ip_len = htons(psp->plen + sizeof(struct ip) + sizeof(struct tcphdr));
  ipp->ip_id = 0;
  ipp->ip_off = 0U; 
  ipp->ip_ttl = 16;
  ipp->ip_p = IPPROTO_TCP;
  ipp->ip_sum = htons(-1);

  if (fwrite((char *)ipp, sizeof(struct ip), 1, outf) != 1)
	error("pkt_insert: write ip header", "");

  tcpp->th_seq = htonl(psp->seq);
  tcpp->th_ack = 0UL;
  tcpp->th_off = 5;
  tcpp->th_flags = psp->flags;
  tcpp->th_win = 0U;
  tcpp->th_sum = htons(-1);
  tcpp->th_urp = 0U;

  if (fwrite((char *)tcpp, sizeof(struct tcphdr), 1, outf) != 1)
	error("pkt_insert: write tcp header", "");

  if ((buf = (unsigned char *)malloc(psp->plen)) == NULL)
    error("pkt_insert: buf malloc", "");

  for (i = 0; i < psp->plen; i++)
    buf[i] = pl_seq++;

  if (psp->plen && fwrite((char *)buf, psp->plen, 1, outf) != 1)
	error("pkt_insert: write buf", "");

  return;
}

  


void 
file_copy(char *infnm, char *outfnm, char *specfnm, int report_only)
{
  FILE *inf, *outf;
  int sf_swapped = 0;
  struct file_header fhdr, fhdr_orig;
  unsigned char *pbuf;

  int first = 1;
  int input = 0;
  us_clock_t start, tnow; 

  struct ip iph;
  struct tcphdr tcph;
  pspec_t *psp;

  psp = get_spec(specfnm, &iph, & tcph);
  if (report_only)
    return;

  if (infnm != NULL)
    {
      inf = open_tcpdumpfile(infnm, &fhdr, &sf_swapped);
      input = 1;
    }
  else
    {
      inf = NULL;
      fhdr.magic = TCPDUMP_MAGIC;
      fhdr.version_major = VERSION_MAJOR;
      fhdr.version_minor = VERSION_MINOR;
      
      fhdr.thiszone = 0;
      fhdr.snaplen = 2000;
      fhdr.sigfigs = 3;
      fhdr.linktype = LINK_ETH_10MB;
    }
  
  

  outf = open_file(outfnm, "wb");

  fhdr_orig = fhdr;
  if (sf_swapped)
    swap_hdr(&fhdr_orig);

  if (fwrite((char *)&fhdr_orig, sizeof(fhdr), 1, outf) != 1)
    error("file_copy: write header", outfnm);

  /* allocate buffer for packets */
  if ((pbuf = (unsigned char *)malloc(fhdr.snaplen)) == NULL)
    error("file_copy", "buff malloc");

  for (;;)
    {
      struct packet_header h;

      if (input)
	{
	  if (next_pkt(inf, &h, pbuf, sf_swapped, fhdr.version_minor, fhdr.snaplen) == EOF)
	    break;
	  
	  tnow = (unsigned int long)h.ts.tv_sec*1000000 + (unsigned int long)h.ts.tv_usec;
	}
      else
	{
	  if (psp)
	    {
	      tnow = psp->send_off;
	    }
	  else
	    {
	      break;
	    }
	}
      
      if (first)
	{
	  first = 0;
	  start = tnow;
	  if (psp != NULL)
	    psp->send_off += start;
	}

      while (psp != NULL && psp->send_off <= tnow)
	{
	  pkt_insert(psp, outf, &iph, &tcph, sf_swapped);
	  psp = psp->next;
	  if (psp != NULL)
	    psp->send_off += start;
	}
	 

      if (input)
	{     
	  if (fwrite((char *)&h, sizeof(h), 1, outf) != 1)
	    error("file_copy: write packet header", outfnm);
	  
	  if (fwrite((char *)pbuf, h.cap_len, 1, outf) != 1)
	    error("file_copy: write packet header", outfnm);
	}
      
    }

  return;
}

  

  

int 
main(int argc, char **argv)
{
  char c;
  int report_only = 0;
  char *infnm = NULL, *outfnm = NULL, *specfnm;

  prog = argv[0];
  opterr = 0;


  while ((c = getopt(argc, argv, "Rr:w:")) != EOF)
    switch (c) 
      {
      case 'w': outfnm = optarg; break;
      case 'r': infnm = optarg; break;
      case 'R': report_only++; break;
      default:
	usage();
	/* NOTREACHED */
      }
  
  if (optind == argc)
    {
      fprintf(stderr, "%s: no insert spec file specified\n", prog);
      usage();
      return 1;
    }
  else
    {
      specfnm = argv[optind];
    }

  if (outfnm == NULL)
    {
      if (infnm == NULL)
	{
	  fprintf(stderr, "%s: no input dump file specified\n", prog);
	  usage();
	  return 1;
	}
	  
      if ((outfnm = (char *)malloc(strlen(infnm)+10)) == NULL)
        error(prog, "outfnm malloc");
      strcpy(outfnm, infnm);
      strcat(outfnm, ".insert");
    }

  file_copy(infnm, outfnm, specfnm, report_only);

  return 0;
}
