/*
 * tcpdmerge - merge tcpdump files by timestamp
 */

/*
 * Copyright (C) 2001 KANAMARU Akira.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* 
 * Version 1.0 (Alpha)
 */

/* if FreeBSD, define __STDC__
 * if Solaris, undef  __STDC__
 *
 * for debug, define DEBUG
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pcap.h>
#include <strings.h>
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <sys/time.h>

#undef DEBUG

char version[] = "1.0";

char *program_name;

void error(const char *fmt, ...);
void usage(void)__attribute__((volatile));
void merge_files(char file1_file_name[], char file2_file_name[],
                 char write_file_name[]);
void make_tmp_file( char filename[], pcap_t **pcap, 
                      struct pcap_pkthdr *hdr, const u_char **pkt );
void dump_remain_packet( pcap_dumper_t **dumper, pcap_t **file1_pcap, 
                         struct pcap_pkthdr *hdr, const u_char **pkt );
int  compare_timestamp_less_than( struct timeval *t1, struct timeval *t2 );


extern  char *optarg;
extern  int optind;

int
main(int argc, char **argv)
{
        int op;
        register char *cp;
        char *write_file_name = "-";    /* default is stdout */

        if ((cp = strrchr(argv[0], '/')) != NULL)
                program_name = cp + 1;
        else
                program_name = argv[0];

        while ((op = getopt(argc, argv, "w:")) != EOF)
                switch (op) {

                case 'w':
                        write_file_name = optarg;
                        break;

                default:
                        usage();
                        /* NOTREACHED */
                }

        if (! (optind + 2 == argc) )
                error("two input file must be given");

#ifdef DEBUG
        printf("file1 file %s, last file %s\n", 
                  argv[optind], argv[optind+1]);
#endif /* DEBUG */

        if ( ! strcmp( write_file_name, "-" ) && isatty( fileno(stdout) ) )
                error("stdout is a terminal; redirect or use -w");

        /* start merge process */
        merge_files(argv[optind], argv[optind+1], write_file_name);

        return 0;
}

void
merge_files(char file1_file_name[], char file2_file_name[], 
            char write_file_name[])
{
        struct pcap_pkthdr file1_hdr, file2_hdr, write_hdr;
        pcap_t *file1_pcap, *file2_pcap, *write_pcap;
        const u_char *file1_pkt, *file2_pkt, *write_pkt;
        pcap_dumper_t *dumper;
        char tmp_file_name[] = ".tcpdmerge.tmp";
        char errbuf[PCAP_ERRBUF_SIZE];

        /* open file1 dump file */
        file1_pcap  = pcap_open_offline(file1_file_name, errbuf);
        if (! file1_pcap)
                error( "bad tcpdump file %s: %s", file1_file_name, errbuf );

        /* open file2 dump file */
        file2_pcap = pcap_open_offline(file2_file_name, errbuf);
        if (! file2_pcap)
                error( "bad tcpdump file %s: %s", file2_file_name, errbuf );

        /* make "temporary dump file" which is include only one packet.
         *
         * pcap_open_offline() require real dump file,
         * so make "temporary dump file" and re-open. 
         */
        file1_pkt  = pcap_next( file1_pcap,  &file1_hdr );
        file2_pkt = pcap_next( file2_pcap, &file2_hdr );

        if ( compare_timestamp_less_than( &file1_hdr.ts, &file2_hdr.ts ) )
                {
                make_tmp_file( tmp_file_name, &file1_pcap, &file1_hdr, &file1_pkt );
                file1_pkt  = pcap_next( file1_pcap,  &file1_hdr );
                }
        else
                {
                make_tmp_file( tmp_file_name, &file2_pcap, &file2_hdr, &file2_pkt );
                file2_pkt  = pcap_next( file2_pcap,  &file2_hdr );
                }

        /* re-open "temporary write dump file" */
        write_pcap = pcap_open_offline(tmp_file_name, errbuf);
        if (! write_pcap)
                error( "bad tcpdump file %s: %s", write_file_name, errbuf );

        /* open dumper */
        dumper = pcap_dump_open(write_pcap, write_file_name);
        if ( ! dumper )
                error( "error creating output file %s: ",
                write_file_name, pcap_geterr( write_pcap ) );

        /* dump packet which is included "temporary write dump file" */
        write_pkt  = pcap_next( write_pcap,  &write_hdr );
        pcap_dump((u_char *) dumper, &write_hdr, write_pkt);

        for( ; ; )
                {

#ifdef DEBUG
                printf("file1 %ld.%ld, file2 %ld.%ld\n", 
                        (file1_hdr.ts).tv_sec,  (file1_hdr.ts).tv_usec,
                        (file2_hdr.ts).tv_sec, (file2_hdr.ts).tv_usec);
#endif /* DEBUG */

                if ( file1_pkt == 0 )
                        {
                        if ( file2_pkt != 0 )
                                {
#ifdef DEBUG
                                printf("write remain packets which is included file2_file\n");
#endif /* DEBUG */
                                dump_remain_packet( &dumper, &file2_pcap, &file2_hdr, &file2_pkt );
                                }
                        break;
                        }

                if ( file2_pkt == 0 )
                        {
                        if ( file1_pkt != 0 )
                                {
#ifdef DEBUG
                                printf("write remain packets which is included file1_file\n");
#endif /* DEBUG */
                                dump_remain_packet( &dumper, &file1_pcap, &file1_hdr, &file1_pkt );
                                }
                        break;
                        }



                if ( compare_timestamp_less_than( &file1_hdr.ts, &file2_hdr.ts ) )
                        {
#ifdef DEBUG
                        printf("write packet which is included file1\n");
#endif /* DEBUG */

                        pcap_dump((u_char *) dumper, &file1_hdr, file1_pkt);

                        file1_pkt  = pcap_next( file1_pcap,  &file1_hdr );

                        }
                else
                        {
#ifdef DEBUG
                        printf("write packet which is included file2\n");
#endif /* DEBUG */
                        pcap_dump((u_char *) dumper, &file2_hdr, file2_pkt);

                        file2_pkt = pcap_next( file2_pcap, &file2_hdr );
                }

        }

        pcap_dump_close( dumper );

        pcap_close( file1_pcap );
        pcap_close( file2_pcap );
}

/* Returns true if timestamp t1 is chronologically less than timestamp t2. */
int
compare_timestamp_less_than( struct timeval *t1, struct timeval *t2 )
{
        return t1->tv_sec < t2->tv_sec ||
               (t1->tv_sec == t2->tv_sec &&
                t1->tv_usec < t2->tv_usec);
}

/* make temporary file which include only one packet */
void
make_tmp_file( char filename[], pcap_t **pcap, struct pcap_pkthdr *hdr, const u_char **pkt )
{

        pcap_dumper_t *dumper;

        /* open dumper */
        dumper = pcap_dump_open(*pcap, filename);
        if ( ! dumper )
                error( "error creating output file %s: ",
                filename, pcap_geterr( *pcap ) );

        pcap_dump((u_char *) dumper, hdr, *pkt);

        pcap_dump_close( dumper );
}

/* dump remain packet */
void
dump_remain_packet( pcap_dumper_t **dumper, pcap_t **pcap, struct pcap_pkthdr *hdr, const u_char **pkt )
{

for( ; ; )
        {
        if ( *pkt == 0 )
                {
#ifdef DEBUG
                printf("finish write remain packets\n");
#endif /* DEBUG */
                break;
                }

#ifdef DEBUG
        printf("file1 %ld.%ld\n", (hdr->ts).tv_sec, (hdr->ts).tv_usec);
#endif /* DEBUG */

        pcap_dump((u_char *) *dumper, hdr, *pkt);
        *pkt = pcap_next( *pcap, hdr );
        }

}

/* print error messages */
void
error(const char *fmt, ...)
{
        va_list ap;

        (void)fprintf(stderr, "tcpdmerge: ");
#if __STDC__
        va_start(ap, fmt);
#else
        va_start(ap);
#endif
        (void)vfprintf(stderr, fmt, ap);
        va_end(ap);
        if (*fmt) {
                fmt += strlen(fmt);
                if (fmt[-1] != '\n')
                        (void)fputc('\n', stderr);
        }
        usage();
        exit(1);
        /* NOTREACHED */
}

/* print usages */
void
usage(void)
{
        extern char version[];

        (void)fprintf(stderr, "Version %s\n", version);
        (void)fprintf(stderr,
"Usage: tcpdmerge [-w file] file1 file2\n");

        exit(-1);
}
