//-----------------------------------------------------------------------------
// Copyright © 2004 - Philip Howard - All rights reserved
//
// 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 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.
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// program	tcprelayout
//
// purpose	Read the capture file from tcprelay and output it in one of
//		a few formats.
//
// syntax	tcprelayout [options] < capturefile
//
//		-e	show escape formatted data
//		-h	show help
//		-r	show received data
//		-s	show sent data
//		-t	show timestamps
//		-V	show version
//
// compile	gcc -O2 -o tcprelayout tcprelayout.c -lh
//-----------------------------------------------------------------------------
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#define _GNU_SOURCE
#include <getopt.h>
extern char *optarg;
extern int optind, opterr, optopt;

#include <libh/string.h>
#include <libh/time.h>
#include <libh/vrb.h>


static const char *	program_name	;

//-----------------------------------------------------------------------------
// function	show_help
//-----------------------------------------------------------------------------
int
show_help ()
{
    fprintf( stderr,
	     "\n"
	     "syntax:  %s  [options] < capturefile\n"
	     "\n"
	     "options: -e               show escape formatted data\n"
	     "	       -h		show this help\n"
	     "	       -r               show received data\n"
	     "	       -s               show sent data\n"
	     "	       -t               show timestamps\n"
	     "	       -V               show version\n"
	     "\n",
	     program_name );
    return 1;
}

//-----------------------------------------------------------------------------
// Main program.
//-----------------------------------------------------------------------------
int
main (
    int		argc
    ,
    char * *	argv
    )
{
    static const struct option	long_opts	[]	= {
	{ "escape",	no_argument,		NULL,	'e' },
	{ "help",	no_argument,		NULL,	'h' },
	{ "recv",	no_argument,		NULL,	'r' },
	{ "recieve",	no_argument,		NULL,	'r' },
	{ "send",	no_argument,		NULL,	's' },
	{ "time",	no_argument,		NULL,	't' },
	{ "version",	no_argument,		NULL,	'V' },
	{ NULL,		0,			NULL,	0 }
    };

    static const char		short_opts	[]	= ":ehrstV";

    long long		chunk_time	;

    size_t		chunk_len	;

    int			chunk_type	;
    int			opt_esc		;
    int			opt_help	;
    int			opt_recv	;
    int			opt_send	;
    int			opt_time	;
    int			opt_version	;


    //----------------------
    // Initialize variables.
    //----------------------
    opt_esc = 0;
    opt_help = 0;
    opt_recv = 0;
    opt_send = 0;
    opt_time = 0;
    program_name = str_tail_one( argv[0], '/' );

    //--------------
    // Scan options.
    //--------------
    for (;;) {
	int opt;

	opt = getopt_long( argc, argv, short_opts, long_opts, NULL );
	if ( opt < 0 ) break;

	switch ( opt ) {
	case 'e':
	    ++ opt_esc;
	    continue;
	case 'h':
	    ++ opt_help;
	    continue;
	case 'r':
	    ++ opt_recv;
	    continue;
	case 's':
	    ++ opt_send;
	    continue;
	case 't':
	    ++ opt_time;
	    continue;
	case 'V':
	    ++ opt_version;
	    continue;
	}
    }

    //---------------------------------------------------
    // If neither send nor receive is set, then set both.
    //---------------------------------------------------
    if ( ! opt_send && ! opt_recv ) opt_send = opt_recv = 1;

    //--------------------------------
    // Loop through entire input file.
    //--------------------------------
    while ( ! feof( stdin ) ) {

	//----------------------
	// Extract chunk header.
	//----------------------
	chunk_time  = 255U & getc( stdin );
	chunk_time <<= 8;
	chunk_time += 255U & getc( stdin );
	chunk_time <<= 8;
	chunk_time += 255U & getc( stdin );
	chunk_time <<= 8;
	chunk_time += 255U & getc( stdin );
	chunk_time <<= 8;
	chunk_time += 255U & getc( stdin );
	chunk_time <<= 8;
	chunk_time += 255U & getc( stdin );
	chunk_time <<= 8;
	chunk_time += 255U & getc( stdin );

	chunk_type = 255 & getc( stdin );

	chunk_len = 255 & getc( stdin );
	chunk_len <<= 8;
	chunk_len |= 255 & getc( stdin );

	//----------------------------------------
	// If end of file at this point, quit now.
	//-----------------------------------------
	if ( feof( stdin ) ) break;

	if ( chunk_len > 65536 ) {
	    fprintf( stderr, "%s: data chunk length (%lu) too large\n", program_name, (unsigned long) chunk_len );
	    return 1;
	}

	//--------------------------------------
	// If this chunk is not wanted, skip it.
	//--------------------------------------
	if ( ( chunk_type == 's' && ! opt_send ) ||
	     ( chunk_type == 'r' && ! opt_recv ) ) {
	    while ( chunk_len -- ) {
		if ( fgetc( stdin ) == EOF ) break;
	    }
	    continue;
	}

	//------------------------------------------
	// If not escape formatted, then output raw.
	//------------------------------------------
	if ( opt_esc == 0 ) {
	    if ( chunk_len ) {
		int ch;
		do {
		    if ( ( ch = fgetc( stdin ) ) == EOF ) break;
		    fputc( ch, stdout );
		} while ( -- chunk_len );
		if ( ch == EOF ) break;
		fflush( stdout );
	    }
	    continue;
	}

	//----------------------------------
	// Handle formatted send or receive.
	//----------------------------------
	if ( opt_time ) {
	    printf( "%10lu.%06lu ",
		    (unsigned long) ( chunk_time / 1000000U ),
		    (unsigned long) ( chunk_time % 1000000U ) );
	}

	if ( chunk_type == 'z' ) {
	    fputs( "END\n", stdout );
	    while ( chunk_len -- && fgetc( stdin ) != EOF );
	    continue;
	}

	//----------------------------------------
	// If this is an address chunk, output it.
	//----------------------------------------
	if ( chunk_type == 'a' || chunk_type == 'c' ) {
	    fputs( chunk_type == 'a' ? "Accept from " : chunk_type == 'c' ? "Connect to  " : "", stdout );
	    if ( chunk_len >= 6 ) {
		unsigned int	addr	[4]	;
		unsigned int	port		;

		addr[0] = fgetc( stdin );
		addr[1] = fgetc( stdin );
		addr[2] = fgetc( stdin );
		addr[3] = fgetc( stdin );

		port = fgetc( stdin );
		port <<= 8;
		port += fgetc( stdin );

		printf( "[%u.%u.%u.%u]:%u\n", addr[0], addr[1], addr[2], addr[3], port );

		chunk_len -= 6;
	    }
	    while ( chunk_len -- && fgetc( stdin ) != EOF );
	    continue;
	}

	//---------------------------
	// Else this is a data chunk.
	//---------------------------
	fputs( chunk_type == 's' ? "Send >" : chunk_type == 'r' ? "< Recv" : "Unkn", stdout );
	fputc( ' ', stdout );

	if ( chunk_len == 0 ) {
	    fputs( " EOF\n", stdout );
	    continue;
	}

	printf( "%4u \"", (unsigned int) chunk_len );
	for ( ; chunk_len; -- chunk_len ) {
	    int ch;

	    ch = fgetc( stdin );
	    if ( ch == EOF ) break;

	    {
		int out;

		out = 0;
		if ( ch == 0 )		out = '0';
		else if ( ch == '\\' )	out = '\\';
		else if ( ch == '"' )	out = '"';
		else if ( ch == '\a' )	out = 'a';
		else if ( ch == '\b' )	out = 'b';
		else if ( ch == '\e' )	out = 'e';
		else if ( ch == '\f' )	out = 'f';
		else if ( ch == '\n' )	out = 'n';
		else if ( ch == '\r' )	out = 'r';
		else if ( ch == '\t' )	out = 't';
		else if ( ch == '\v' )	out = 'v';
		if ( out ) {
		    fputc( '\\', stdout );
		    fputc( out, stdout );
		    continue;
		}
	    }

	    if ( 1 <= ch && ch <= 26 ) {
		fputc( '^', stdout );
		fputc( ch + 96, stdout );
		continue;
	    }

	    if ( ch < 32 || 126 < ch ) {
		fputc( '\\', stdout );
		fputc( '0' + ( ( ch >> 6 ) & 7 ), stdout );
		fputc( '0' + ( ( ch >> 3 ) & 7 ), stdout );
		fputc( '0' + (   ch        & 7 ), stdout );
		continue;
	    }

	    fputc( ch, stdout );
	}

	fputc( '"', stdout );
	fputc( '\n', stdout );
	fflush( stdout );
    }

    return 0;
}

