/* 
   hexd.c: a hex-dump program for muLinux, which contains these functions...
   ------------------------------------
   BinToHexText  : show code in hex + ascii.   User can edit the hex code.
   HexTextToBin  : translate hex code derived from BinToHexText to binary code.
   BinToHexOnly  : show code in hex with no spaces or linefeeds.
   BinToTextOnly : show code as text, and dots for what can't print.
   PrintText     : called by the text output functions, shows printable chars


 *
 * Copyright (C) 1999, 2000
 *      Michele Andreoli, andreoli@pisoft.it - Originally Public domain.
 * and	-Apr 25 2000, several bits & tweaks added by A. Costa, who 
 *       hereby GPLs, or "Open Source"s this.
 */

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

#define COLUMNS 17	/* maybe this should vary with the terminal width? */
#define COUNTER_WIDTH 6
#define STDIN 0		/* the C constant 'stdin' is a 'file pointer'
			   while read() only likes 'file descriptors', which 
			   are ints.  */

/* global variables */

static char usage[]="\
hexd  code/decode hex dumps
Usage: hexd OPTION < [FILE1] > [FILE2]

    -c		convert input to a hex code/ASCII text dump
    -d		convert output of '-c' or '-x' back to binary code
    -h		show this help
    -t		convert input to an unspaced ASCII text string
    -x		convert input to an unspaced hex string

hexd reads from standard input and then writes to standard output.\n" ;

#define buf usage	/* synonym of 'usage' */
#define line &usage[3]  /* a line buffer used by HexTextToBin */

/* C Preprocessor code to make sure MAXLINE is a good number.  lineSIZE is
 * used only to define MAXLINE, and nowhere else.  
 */
#define lineSIZE (  ( (sizeof buf) - 3 )  - 1 )
#define MAXLINE ( lineSIZE - ( lineSIZE % 12 ) + 1 )

/* MAXLINE is the length of "line", an input buffer kept big for long lines.  
This buffer holds either formatted text with linefeeds, (very bad to truncate); 
or unspaced hex code which must be parsed two bytes at a time.  

If the unspaced hex code is longer than the buffer, and the hex code isn't
parsed evenly, something like "1A" might get cut in the middle and turned into
"01" and "0A".  So this buffer needs to be an even number of bytes long.

However, MAXLINE has to be an odd number, (not an even), because fgets() fills 
this buffer.  fgets() loads MAXLINE-1 chars, and adds a NULL.  So MAXLINE-1 has
be even, and an extra 1 added to store the NULL makes it odd.

The "% 12" makes MAXLINE-1 divisible by 12, (or rather shortens it to 
a length that's a multiple of 12), which allows spacings of 2, 3, 4, and 6.
*/


/* functions */

BinToHexText ()
{
  unsigned char *j;
  register unsigned char *i;
  unsigned int n, row = 0;

  /* read stdio till it's empty */
  while ( n = read (STDIN, buf, COLUMNS)  ,  n>0 )
      {
      printf ("%0*d: ", COUNTER_WIDTH, row );	/* counter */
      row += COLUMNS; 

      for ( i=buf, j=(buf+n) ;  i < j ;  i++ )	/* hex */
	printf( "%02x ", *i );

						/* any missing spaces */
      printf( "%*s", ( ((COLUMNS*3)+1) - (n*3) ) ,  "[" ); 
      PrintText( n );
      printf( "]\n" );
      }
}


BinToHexOnly ()
{
  int n;
  register unsigned char *i, *j;

  while ( n = read (STDIN, buf, MAXLINE)  , n>0 )
      for ( i=buf, j=(buf+n) ;   i < j ;   i++ )
	printf ( "%02x", *i ); 
}


BinToTextOnly ()
{
  int n;

  while ( n = read (STDIN, buf, MAXLINE)  , n>0 )
    PrintText( n );
}

PrintText( int n )	/* print 'n' chars from the buffer 'buf' */ 
{
  register unsigned char c, *i , *j ;

  for ( i=buf, j=(buf+n) ; i < j ; i++)
    {
    c =  isprint( *i )  ?  *i  :  '.' ;	   /* print a dot, maybe? */
    putchar(c);
    }
}



/* Notes on HexTexttoBin()
 *  
 *  This routine allows a variety of input.  It reads in a line of text, 
 *  or else MAXLINE chars of text.  Then it looks for a ':' and a '['.
 *  Anything between those two delimiters is either a hex digit or it is
 *  not.  If a character is not a hex digit, it's treated as whitespace.
 *  Numbers can be one or two digits long.  A single digit number has
 *  to be seperated by whitespace from the next character.
 *
 *  If there's no ':' or '[' to delimit the hex code, it scans the whole
 *  line.
 *
 *  If the input has no linefeeds at all, even that works, but only if 
 *  the spacing is perfectly even... fgets() reads in MAXLINE chars, and it
 *  might break a 2 digit hex number in two if the spacing was wrong.
 *
 *  Examples of inputs which produce the same output (with linefeeds):
 *
 *   plain old '-c' output
 *    000000: 0a 6f 20 44 69 73 63 6c 61 69 6d 65 72 0a 0a 57 69 [.o Disclaimer..Wi]
 *
 *   shorter byte-number field
 *    0: 0a 6f 20 44 69 73 63 6c 61 69 6d 65 72 0a 0a 57 69 [.o Disclaimer..Wi]
 *
 *   hex '0a' without the leading '0'
 *    0: a 6f 20 44 69 73 63 6c 61 69 6d 65 72 a a 57 69 [.o Disclaimer..Wi]
 *
 *   no byte-number field, no text field
 *    a 6f 20 44 69 73 63 6c 61 69 6d 65 72 a a 57 69
 *
 *   more than one space separating numbers
 *    a    6f   20  44 69 73  63 6c 61 69 6d 65 72 a  a    57        69
 *
 *   no spaces separating the two digit numbers
 *    a 6f20446973636c61696d6572 a a 5769
 *
 *   a comma is as good as a space
 *    a,6f20446973636c61696d6572a,a,5769
 *
 *   comments in the text field
 *    a,6f20446973636c61696d6572a,a,5769 [ a comment
 *
 *   comments in the byte-number field
 *    another comment : a,6f20446973636c61696d6572a,a,5769
 *
 *  An example of what's not equivalent:
 *    0f 0f 0f 0f 0f 0f 0f	[ this makes 7 bytes of '0F's
 *    f f f f f f f   		[ same thing
 *    fffffff			[ Not the same, only 4 bytes.
 */
HexTextToBin ()
{
  int c;
  register unsigned char *i, *j ;

  buf[2] = 0;		/*  same result as "char buf[2]"... 
			 *  a 2 char buffer to hold up to 2 hex digits 
			 */
  while( fgets( line, MAXLINE, stdin ) != 0 ) 
    {
    /*
     * 'i' starts out as the 1st char in the 'line' buffer.
     * If 'line' contains a ':', set 'i' to one char past that.
     * Set 'j' to where the '[' is, or else the end of the line.
     */
    i = j = line;
    step1:
	if ( *j == ':' ) { j++ ; i=j ;  goto step2; }
	if ( *j == '['  ||  *j == 0 )   goto fini;
	j++;
	goto step1;
    step2:
	while( *j != '['  &&  *j != 0 )   j++ ;
    fini:

    /* find hex digits and convert those */
    while( i < j )
	{
	if ( isxdigit( *i )  )
	    {
	    buf[0] = *i;
	    i++;

	    /* check if there's a second hex char */
	    buf[1] =  isxdigit( *i )   ?   *i  :  0 ;

	    c=strtol(buf, NULL, 16);  	/* convert buf from hex to bin */
	    putchar(c);
	    }
	i++;
	}
    }
}



main (int argc, char **argv)
{
  char op = 0;

  /* Parsing */

  if (  argc > 1   &&  argv[1][0] == '-')
      op = argv[1][1];

  switch (op)
    {
    case 'h': { fprintf( stderr, usage ) ;  break; }
    case 'c': { BinToHexText() ;	    break; }
    case 'x': {	BinToHexOnly() ;	    break; }
    case 't': {	BinToTextOnly() ;	    break; }
    case 'd': {	HexTextToBin() ; 	    break; }
    default:
      {	fprintf( stderr, usage ) ;  exit (1); }
    }

  exit (0);
}
