/*  -*- Mode: C;  -*- */

/******************************************************************************
*                                                                             *
*   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 <string.h>
#include <fcntl.h>
#include <sys/time.h>
#include <limits.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/param.h>


#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/time.h>
#ifdef __alpha__
#include <sys/mbuf.h>
#endif
#include <net/route.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#ifdef __alpha__
#include <netinet/ip_var.h>
#endif
#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <signal.h>
#include <assert.h>

#include "cprintf.h"
#include "list.h"
#include "pkt.h"
#include "seq.h"
#include "flows.h"
#include "service.h"
#include "http.h"
#include "tcp.h"
#include "udp.h"
#include "udp_ns.h"
#include "timeouts.h"
#include "print_util.h"
#include "report.h"
#include "output.h"
#include "tcpdump_patch.h"
#include "counters.h"
#include "writer.h"

#include "config.h"
#include "probe_config.h"

#define START 0
#define STOP 1

char *prog;

int rep_fd, dump_fd;
char repfnm[PATH_MAX+1], dumpfnm[PATH_MAX+1];

/* HTTP objects */
char obj_dirnm[PATH_MAX+1];

/* Payloads */
char payload_dirnm[PATH_MAX+1];



char *outp, *dumpp;	/* current address to dump to */
char *dump_start;	/* where current dump started */
int copyback;           /* flags buffer copy-back */
rep_rec_hdr_t rec_hdr;  /* prepended to each record written */


char* o_infnm;
int o_snaplen;


void tvdif_print(char *s, struct timeval *t0, struct timeval *t1)
{
  struct timeval tdiff;
  long us;

  tdiff.tv_sec = t1->tv_sec - t0->tv_sec;
  tdiff.tv_usec = t1->tv_usec - t0->tv_usec;
  if (tdiff.tv_usec < 0)
    tdiff.tv_sec--, tdiff.tv_usec += 1000000;

  us = ((long)tdiff.tv_sec*1000000) + tdiff.tv_usec;

  fprintf(stderr,"%s : %ldms\n", s, us/1000);
}


void 
make_output_dir(char *fnm_base)
{
  int res;

  umask(S_IWOTH);

  res = mkdir(fnm_base, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
  if (res != 0 && errno != EEXIST)
    error("make output dir: error creating log directory", fnm_base);

  if (http_ob_dump_len != 0)
    {
      sprintf(obj_dirnm, "%s/%s", fnm_base, "objects");
      assert(strlen(obj_dirnm) < 512 - 32);
      res = mkdir(obj_dirnm, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
      if (res != 0 && errno != EEXIST)
	error("stats_init: error creating object directory", obj_dirnm);
      strcat(obj_dirnm, "/");
    }

  if (dumping_payloads != 0)
    {
      sprintf(payload_dirnm, "%s/%s", fnm_base, "payloads");
      assert(strlen(payload_dirnm) < 512 - 32);
      res = mkdir(payload_dirnm, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
      if (res != 0 && errno != EEXIST)
	error("stats_init: error creating object directory", payload_dirnm);
      strcat(payload_dirnm, "/");
    }
  

  return;
}

void 
make_obj_dir_offline(char *fnm_p)
{
  int res;
  strcpy(obj_dirnm, fnm_p);
  strcat(obj_dirnm, "-objects");
  assert(strlen(obj_dirnm) < 512 - 32);
  res = mkdir(obj_dirnm, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
  if (res != 0 && errno != EEXIST)
    error("stats_init: error creating object directory", obj_dirnm);
  strcat(obj_dirnm, "/");

  return;
}

void 
make_payload_dir_offline(char *fnm_p)
{
  int res;
  strcpy(payload_dirnm, fnm_p);
  strcat(payload_dirnm, "-payloads");
  assert(strlen(payload_dirnm) < 512 - 32);
  res = mkdir(payload_dirnm, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
  if (res != 0 && errno != EEXIST)
    error("stats_init: error creating payload directory", payload_dirnm);
  strcat(payload_dirnm, "/");

  return;
}
  

/*
 * write counters at file end
 */

void 
write_counters(counters_t *cp, struct timeval *time)
{
  rep_rec_hdr_t rec_hdr;

#ifdef DUMP_DEBUG
  rec_hdr.magic = REC_HDR_MAGIC;
#endif

  rec_hdr.type = REC_COUNTERS;
  rec_hdr.len = sizeof(counters_t);
  rec_hdr.indx = counters.nrecords++;
  DUMP_STRUCT(outp, &rec_hdr, rep_rec_hdr_t);

#ifdef PROBE_FED
  cp->stop = *time;
#else
  cp->stop = curr_tm_tv;
#endif

  DUMP_STRUCT(outp, cp, counters_t);

  return;
}

  

/*
 * set up error pkt dump file preamble 
 */

void 
write_dumpfile_preamble(int snaplen)
{
  struct file_header hdr;

  hdr.magic = TCPDUMP_MAGIC;
  hdr.version_major = VERSION_MAJOR;
  hdr.version_minor = VERSION_MINOR;
  
  hdr.thiszone = 0;
  hdr.snaplen = snaplen;
  hdr.sigfigs = 3;
  hdr.linktype = dump_linktype;

  DUMP_STRUCT(dumpp, &hdr, struct file_header);

  return;
}



void 
files_init(int chan, char *infnm)
{
  char *cp;
  char hnm[MAXHOSTNAMELEN];
  char tmpbuf[PATH_MAX+1000];
  char fnm_base[PATH_MAX];
  char *fnm_p;
  char chan_str[5];
  struct timeval tv;

  if (!infnm)
    /* live run - build output file base-name */
    {
      if (chan >= 0)
	sprintf(chan_str, "%c", chan + 'A');
      else
	*chan_str = '\0';  
      
      if (gethostname(hnm, MAXHOSTNAMELEN) != 0)
	{
	  sprintf(tmpbuf, "%s: out_init gethostname() fail", prog);
	  perror(tmpbuf);
	  exit (1);
	}
      
      /* Get rid of all but host name */
      cp = strchr(hnm, '.');
      if (cp)
	*cp = '\0';
      
      if (gettimeofday(&tv, NULL) != 0)
	{
	  sprintf(tmpbuf, "%s: out_init gettimeofday() fail", prog);
	  perror(tmpbuf);
	  exit (1);
	}
      
      sprintf(tmpbuf, "%lu", tv.tv_sec);
      
      if (strlen(log_dir) + strlen(tmpbuf) + strlen(hnm) + CYCLE_STR_LEN + 5 > PATH_MAX)
	{
	  sprintf(tmpbuf, "%s: out_init error file path too long", prog);
	  perror(tmpbuf);
	  exit (1);
	}
      
      sprintf(fnm_base, "%s%s.%s", log_dir, hnm, tmpbuf);

      make_output_dir(fnm_base);
      strcat(fnm_base, "/");
      strcat(fnm_base, chan_str);

      fnm_p = fnm_base;
    
    } /* end if infnm */
  else
    {
      /* running from dump file - use file name as output name base */
      /* fnm_p = infnm; */
      if (log_dir_specified)
	sprintf(fnm_base, "%s/A", log_dir);
      else
	sprintf(fnm_base, "%s.A", infnm);
      fnm_p = fnm_base;

      if (http_ob_dump_len != 0)
	make_obj_dir_offline(fnm_p);

      if (dumping_payloads == 1)
	make_payload_dir_offline(fnm_p);
      
    }

  /* analysis output */
  
  sprintf(repfnm, "%s.rep", fnm_p);

  /* error packets dump */
  
  sprintf(dumpfnm, "%s.dump", fnm_p);

  return;
}

void 
output_init(int snaplen, int chan, char *infnm)
{
  struct timeval start;

  /* unfortunately need to keep these as globals */
  o_snaplen = snaplen;
  o_infnm = infnm;

  /* set up directory and files basename */
  files_init(chan, infnm);

  /* set up output buffers */
  obuffs_init();

  /* write dump file hdr */
  write_dumpfile_preamble(snaplen);

  /* initialise counters and record start time */
  if (gettimeofday(&start, NULL) != 0)
    error("output_init", "gettimeofday");
  counters_init(&counters, &start, WAN_MAGIC);

#ifndef PROFILING
#ifndef OWAN
  /* set up writer thread */
  writer_pid = writer_init();
  fprintf(stderr, "\tWriter started - pid %d\n", writer_pid);

  /* record parent pid for use in error */
  parent_pid = getpid();
#else
  /* open first files */
  open_rep_file(stderr);
  open_dump_file(stderr);
#endif
#endif

  return;
}

void 
output_end(listhdr_t *host_hashtbl)
{
  int wait_ret;

  /* dump data on live connections */
  flow_final_dump(host_hashtbl);

  /* prod file completion */
  repblk_consumed = 0;
  repbuf_manage(CYCLE, NOT_CYCLE_SYNC, FINISHING);
  repblk_used += repblk_consumed;
  
  /* let writer know done and collect */
  output_finished++;

#ifdef OWAN
  writer_loop();
#endif


#ifndef PROFILING
#ifndef OWAN
  wait_ret = waitpid(writer_pid, NULL, 0);

  printf("XXX%d\n", wait_ret);
  if (wait_ret != writer_pid)
    ;
#endif
#endif

  return;
}

/* 
 * Called at start of record dump 
 */
void 
rec_dump_start()
{
  /* room in output buffer? */
  if (repblk_used - repblk_written > N_REPBLKS - N_REP_ORUNBLKS)
    error("rec_dump_start()", "output buffers exhausted");

  dump_start = outp;
  outp += sizeof(rep_rec_hdr_t); /* step over */

  repblk_consumed = 0;
  copyback = 0;

  return;
}

/* 
 * Called at end of record dump 
 */
void 
rec_dump_end(unsigned char type)
{
  int dump_len = (copyback*N_REPBLKS*WRITE_BLKSZ) + outp - dump_start;
  rep_rec_hdr_t rec_hdr;

  counters.rep_bytes_written += dump_len;

  /* write back record header */
#ifdef DUMP_DEBUG
  rec_hdr.magic = REC_HDR_MAGIC;
#endif
  rec_hdr.type = type;
  rec_hdr.indx = counters.nrecords++;
  rec_hdr.len = dump_len;
  DUMP_STRUCT(dump_start, &rec_hdr, rep_rec_hdr_t);

  /* always manage buffer */
  repbuf_manage(counters.rep_bytes_written >= report_file_sz ? CYCLE : NO_CYCLE,
		NOT_CYCLE_SYNC, NOT_FINISHING);

  /* make blocks visible to writer */
  repblk_used += repblk_consumed;

#ifndef PROFILING
#ifdef OWAN
  writer_loop();
#endif
#endif

#ifdef PROFILING
  repblk_written = repblk_used;
#endif
  

  return;
}
  
  

void 
repbuf_manage(int file_cycle, int cycle_sync, int finishing)
{

  /*
   * Writes to the rep buffer must be preceded by a call to rec_dump_start()
   *  and followed by a call to rec_dump_end() - the normal caller of this . 
   *  repbuf_manage() function. Large partial writes (ie > a block size) must 
   *  also call this function - which can not therefore make blocks visible 
   *  to the writer (by incrementing repblk_used) as it may cause a write 
   *  *before* rec_dump_end() writes in the record header. This function 
   *  therefore accumulates a count of written blocks in repblk_consumed which 
   *  must be made visible to the writer by the caller.
   */

  int repblk_map_indx;
  static struct timeval cycle_time;

  /* more buffer blocks completed? */
  repblk_map_indx = (repblk_used + repblk_consumed) % N_REPBLKS;
  while (outp >= rep_blk_map[repblk_map_indx + 1].blk_start)
    {
      repblk_consumed++;
      repblk_map_indx++;
    }

  /* over-run? - copy back to buffer front */
  if (repblk_map_indx >= N_REPBLKS)
    {
      char *cp_start = rep_blk_map[N_REPBLKS].blk_start;
      int count = outp - cp_start;
      copyback += 1;
      outp = rep_blk_map[0].blk_start;
      repblk_map_indx = 0;
      WRITER_TRACE(F_RED, "over-run copying %d bytes from %p to %p ", 
		   count, cp_start, outp);
      DUMP_MEM(outp, cp_start, count);
      while (outp >= rep_blk_map[repblk_map_indx + 1].blk_start)
	{
	  repblk_map_indx++;
	}
      WRITER_TRACE(F_BLUE, "outp now %p indx %d\n", outp,  repblk_map_indx);
    }
 
  /* time for file cycle - write counters and reset for new cycle */
  if (file_cycle)
    {
      WRITER_TRACE(F_BLUE, "repbuf_manage - file cycle\n");
      if (repblk_used - repblk_written > N_REPBLKS - sizeof(counters_t))
	error("repbuf_manage()", "output buffers exhausted");

      accumulate_run_counters(&counters);
      write_counters(&counters, &cycle_time);
      reset_counters(&counters, &cycle_time);
      
      /* more buffer blocks completed? */
      while (outp >= rep_blk_map[repblk_map_indx + 1].blk_start)
	{
	  repblk_consumed++;
	  repblk_map_indx++;
	}

      /* over-run? - copy back to buffer front */
      if (repblk_map_indx >= N_REPBLKS)
	{
	  char *cp_start = rep_blk_map[N_REPBLKS].blk_start;
	  int count = outp - cp_start;
	  copyback += 1;
	  outp = rep_blk_map[0].blk_start;
	  repblk_map_indx = 0;
	  WRITER_TRACE(F_RED, "over-run (cycle)  copying %d bytes from %p to %p", 
		       count, cp_start, outp);
	  DUMP_MEM(outp, cp_start, count);
	  while (outp >= rep_blk_map[repblk_map_indx + 1].blk_start)
	    {
	      repblk_map_indx++;
	    }
	  WRITER_TRACE(F_BLUE, "outp now %p indx %d\n", outp,  
		       repblk_map_indx);
	}    
      
      /* mark position in block where file terminates */
      rep_blk_map[repblk_map_indx].file_end = outp;
      
      /* move on to next block */
      repblk_consumed++;
      repblk_map_indx = (repblk_map_indx + 1) % N_REPBLKS;
      outp = rep_blk_map[repblk_map_indx].blk_start;
#if 0      
      if (!finishing)
	{
	  /* reset output count */
	  rep_bytes_written = 0UL;
	}
#endif      
      /* if not called by dumpbuf_manage synchronise dump file cycle */
      if (!cycle_sync)
	dumpbuf_manage(CYCLE, CYCLE_SYNC, finishing);
    }

  WRITER_TRACE(F_BLUE, "repblk_used %u  +%u = %u\n", repblk_used,  repblk_consumed, repblk_used + repblk_consumed);
  
  return;
}

void 
dumpbuf_manage(int file_cycle, int cycle_sync, int finishing)
{

  /*
   * Writes to dumpbuf are always discrete and followed by a single call 
   *  to dumpbuf_manage - completed blocks can therefore always be made 
   *  immediately visible to the writer (by incrementing dumpblk_used)
   */

  int dumpblk_map_indx;

  /* more buffer blocks completed? */
  dumpblk_map_indx = dumpblk_used % N_DUMPBLKS;
  while (dumpp >= dump_blk_map[dumpblk_map_indx + 1].blk_start)
    {
      dumpblk_used++;
      dumpblk_map_indx++;
    }

  assert(dumpblk_map_indx < N_DUMPBLKS + 1);

  /* over-run? - copy back to buffer front */
  if (dumpblk_map_indx >= N_DUMPBLKS)
    {
      char *cp_start = dump_blk_map[N_DUMPBLKS].blk_start;
      int count = dumpp - cp_start;
      dumpp = dump_blk_map[0].blk_start;
      dumpblk_map_indx = 0;
      DUMP_MEM(dumpp, cp_start, count);
      while (dumpp >= dump_blk_map[dumpblk_map_indx + 1].blk_start)
	{
	  dumpblk_map_indx++;
	}
    }
  
  if (file_cycle)
    {
      /* mark position in block where file terminates */
      dump_blk_map[dumpblk_map_indx].file_end = dumpp;
      
      /* move on to next block */
      dumpblk_used++;
      dumpblk_map_indx = dumpblk_used % N_DUMPBLKS;
      dumpp = dump_blk_map[dumpblk_map_indx].blk_start;
      
      if (!finishing)
	{
	  /* write header for new cycle */
	  write_dumpfile_preamble(o_snaplen); 
	  counters.dump_bytes_written = sizeof(struct file_header);
	}
      
      /* if called after buffer write then synchronise report file cycle */
      if (!cycle_sync)
	{
	  /* room in output buffer? */
	  if (repblk_used - repblk_written > N_REPBLKS - 1)
	    error("dumpbuf_manage", "report output buffers exhausted");
	  
	  repblk_consumed = 0;
	  repbuf_manage(CYCLE, CYCLE_SYNC, finishing);
	  repblk_used += repblk_consumed;
	}
    }

#ifndef PROFILING
#ifdef OWAN
  if (!cycle_sync)
    writer_loop();
#endif
#endif

#ifdef PROFILING
  dumpblk_written = dumpblk_used;
#endif
  
  return;
}
  
  
/*
 * end output.c 
 */
