/****************************************************************************
    Program:     tcpmerge.c
    Author:      Rob Beverly <rbeverly \at\ mit \d0t\ edu>
    Date:        October 19, 2002
    Updated:     November 4, 2002
    Updated:     June 10, 2004 Fixed bug identified by 
                 Peter Van Epp <vanepp@sfu.ca>, added Peter's proper DLT
                 options for dealing with ethernet link layers.
    Description: Merges two libpcap traces with respect to time.  Useful to
                 merge dump from e.g. each card of OCxMON into a single 
                 ordered file.
****************************************************************************/

#define VERSION "0.4"
#define SNAPLEN 1500
#define MEGA 1000000

/* Specify pcap Default Output Data Link Format */
#define DEFAULT_DLT_TYPE DLT_RAW
/* Choose Default DLT, or Adopt Link Layer Encap of Input 1 */
#define USE_DEFAULT_DLT 0 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pcap.h>

#define TRUE 1
#ifdef TRUE
# define FALSE !TRUE
#endif

void usage(char *prog) {
    printf("%s v%s - Merge libpcap traces, align in time.\n", prog, VERSION);
    printf("usage: %s [-v] -1<infile1> -2<infile2> -o<outfile>\n", prog);
    exit(-1);
}


int main (int argc, char **argv) {
  pcap_t *pcap1, *pcap2, *pcapout;
  pcap_dumper_t *popen;
  struct pcap_pkthdr header1, header2;
  const u_char *packet1, *packet2;
  char errbuf[256];
  double ts1, ts2;
  double begin_s1, begin_s2;
  char *output = NULL;
  char *input1 = NULL;
  char *input2 = NULL;
  int ch;
  int end_stream1 = FALSE;
  int end_stream2 = FALSE;
  int packets_s1 = 0;
  int packets_s2 = 0;
  int verbosity = FALSE;

  if (argc < 3) usage(argv[0]);

  printf("%s v.%s.\n", argv[0], VERSION);

  /* Parse the command-line. */
  while ((ch = getopt(argc, argv, "hvo:1:2:")) != EOF) {
      switch ((char) ch) {
      case 'o':
          output = optarg;
          break;
      case 'h':
          usage(argv[0]);
          break;
      case 'v':
          verbosity++;
          break;
      case '1':
          input1 = optarg;
          break;
      case '2':
          input2 = optarg;
          break;
      }
  }
 
  /* Sanity check */
  if (input1 == NULL) { printf("No input 1 specified.\n"); exit(-1); }
  if (input2 == NULL) { printf("No input 2 specified.\n"); exit(-1); }
  if (output == NULL) { printf("No output specified.\n"); exit(-1); }

  /* Open pcap input files */
  printf("Opening input 1: (%s).\n", input1);
  if ((pcap1 = pcap_open_offline(input1, errbuf)) == NULL) {
     printf("Error: %s\n", errbuf);
     exit(-1);
  }
  printf("Opening input 2: (%s).\n", input2);
  if ((pcap2 = pcap_open_offline(input2, errbuf)) == NULL) {
     printf("Error: %s\n", errbuf);
     exit(-1);
  }

  /* Open pcap output file */
  printf("Opening output: (%s).\n", output);
  if ((pcapout = pcap_open_dead(DEFAULT_DLT_TYPE, SNAPLEN)) == NULL) {
      printf("Error: %s\n", errbuf);
      exit(-1);
  } 

#if USE_DEFAULT_DLT
  popen = pcap_dump_open(pcapout, output);
#else
  popen = pcap_dump_open(pcap1, output);
#endif
  if (popen == NULL) {
      printf("Error! %s\n", pcap_geterr(pcapout));
      exit(-1);
  }

  /* Bootstrap */
  packet1 = pcap_next(pcap1, &header1);
  if (packet1 == NULL) {
      printf("Error: no packets in capture file %s\n", input1);
      exit(-1);
  }
  packets_s1++;
  ts1 = (double) header1.ts.tv_usec/MEGA + header1.ts.tv_sec;
  begin_s1 = ts1;

  packet2 = pcap_next(pcap2, &header2);
  if (packet2 == NULL) {
      printf("Error: no packets in capture file %s\n", input2);
      exit(-1);
  }
  packets_s2++;
  ts2 = (double) header2.ts.tv_usec/MEGA + header2.ts.tv_sec;
  begin_s2 = ts2;
  printf("Initial Timestamp Stream 1: %2.6f Stream 2: %2.6f\n", begin_s1, begin_s2);
 
  /* Loop 'til death do us part */
  while (!end_stream1 && !end_stream2) {
    while ((ts1 < ts2) || end_stream2) {
       if (verbosity) printf("Merging Stream 1 TS: %2.6f\n", ts1); 
       pcap_dump((u_char *)popen, &header1, packet1);
       packet1 = pcap_next(pcap1, &header1);
       if (packet1 != NULL) {
           packets_s1++;
           ts1 = (double) header1.ts.tv_usec/MEGA + header1.ts.tv_sec;
       } else {
           if (verbosity) printf("Reached end of stream 1.\n");
           end_stream1 = TRUE;
           break;
       }
    }
    while ((ts2 <= ts1) || end_stream1) {
       if (verbosity) printf("Merging Stream 2 TS: %2.6f\n", ts2); 
       pcap_dump((u_char *)popen, &header2, packet2);
       packet2 = pcap_next(pcap2, &header2);
       if (packet2 != NULL) {
           packets_s2++;
           ts2 = (double) header2.ts.tv_usec/MEGA + header2.ts.tv_sec;
       } else {
           if (verbosity) printf("Reached end of stream 2.\n");
           end_stream2 = TRUE;
           break;
       }
    }
  }
  
  /* Buh-bye */
  printf("\nProcessed both streams, (%d) packets.\n", packets_s1 + packets_s2);
  printf("Final TS Stream 1:\t%2.6f Stream 2: %2.6f\n", ts1, ts2);
  printf("Duration (S1/S2):\t%2.3f/%2.3f s\n", ts1 - begin_s1, ts2 - begin_s2);
  printf("Packets (S1/S2):\t%d/%d\n", packets_s1, packets_s2);
  printf("Pkt Rate (S1/S2):\t%2.1f/%2.1f pps\n", packets_s1/(ts1 - begin_s1),
        packets_s2/(ts2 - begin_s2));
  pcap_dump_close(popen);
  pcap_close(pcap1);
  pcap_close(pcap2);
  pcap_close(pcapout);
  exit(0);
}
