//-----------------------------------------------------------------------------
// file		ipv4addr.c
//
// program	ipv4addr, ipv4class, ipv4cidr, ipv4mask, ipv4range
//
// purpose	Convert set of addresses or address ranges into one of the
//		following forms:
//
//		ipv4addr	Individual addresses
//		ipv4addrv6	Individual addresses as v4 in v6
//		ipv4cidr	CIDR with prefix length
//		ipv4class	Class blocks
//		ipv4in-addr	Class blocks as in-addr
//		ipv4mask	CIDR with netmask
//		ipv4range	Address ranges
//
// usage	Shell command
//
// syntax	ipv4addr    [ options ] [ addrform ... ]
//		ipv4addrv6  [ options ] [ addrform ... ]
//		ipv4cidr    [ options ] [ addrform ... ]
//		ipv4class   [ options ] [ addrform ... ]
//		ipv4in-addr [ options ] [ addrform ... ]
//		ipv4mask    [ options ] [ addrform ... ]
//		ipv4range   [ options ] [ addrform ... ]
//
// addrform	addr | addr1-addr2 | addr/prefix | addr/netmask | addr#count
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include <libh/net.h>
#include <libh/string.h>

int
main (
    int		argc
    ,
    char * *	argv
)
{
    char * *		argp	;
    char *		addrptr	;
    char *		endptr	;
    ipv4_t		addr1	;
    ipv4_t		addr2	;
    ipv4_t		base	;
    ipv4_t		mask	;
    ipv4_t		count	;
    unsigned int	port	;
    unsigned int	prefix	;
    int			sprefix	;
    int			argn	;
    int			errors	;
    int			rc	;
    int			specs	;
    int			v6	;

    enum { USE_ADDR, USE_CLASS, USE_CIDR, USE_IN_ADDR, USE_MASK, USE_RANGE } format;


    //------------------------------------
    // Check which name is being executed.
    //------------------------------------
    addrptr = str_tail_one( argv[0], '/' );
    if ( ( * addrptr == 'i' || * addrptr == 'I' ) && * ++ addrptr &&
	 ( * addrptr == 'p' || * addrptr == 'P' ) && * ++ addrptr &&
	 ( * addrptr == 'v' || * addrptr == 'V' ) && * ++ addrptr &&
	 ( * addrptr == '4' ) ) ++ addrptr;
    if ( * addrptr == 'c' || * addrptr == 'C' ) {
	++ addrptr;
	if ( * addrptr == 'l' || * addrptr == 'L' ) format = USE_CLASS;
	else format = USE_CIDR;
    }
    else if ( * addrptr == 'i' || * addrptr == 'I' ) format = USE_IN_ADDR;
    else if ( * addrptr == 'm' || * addrptr == 'M' ) format = USE_MASK;
    else if ( * addrptr == 'r' || * addrptr == 'R' ) format = USE_RANGE;
    else format = USE_ADDR;

    v6 = ! ! strchr( addrptr, '6' );

    //-----------------------------------------------------------
    // Scan all address/network specifications for errors, first.
    //-----------------------------------------------------------
    errors = 0;
    argn = argc;
    argp = argv;
    while ( -- argn ) {
	addrptr = * ++ argp;

	//-----------------------------------------------
	// Parse the address network range specification.
	//-----------------------------------------------
	rc = ipv4_str_to_addr( addrptr, & endptr, & addr1, & addr2, & mask, & prefix, & count, & port );

	//--------------------------------------------------
	// If there are any parsing errors, report them now.
	//--------------------------------------------------
	if ( rc < 0 ) {
	    fprintf( stderr, "error in address \"%s\"\n", addrptr );
	    fputs( "  encountered at  ", stderr );
	    while ( addrptr ++ < endptr ) fputc( '-', stderr );
	    fputs( "^\n", stderr );
	    if ( rc & IPV4_STR_TO_ADDR_BAD_CHAR   ) fputs( "  bad character in conversion\n", stderr );
	    if ( rc & IPV4_STR_TO_ADDR_BAD_ADDR   ) fputs( "  bad 1st address\n", stderr );
	    if ( rc & IPV4_STR_TO_ADDR_BAD_ADDR2  ) fputs( "  bad 2nd address\n", stderr );
	    if ( rc & IPV4_STR_TO_ADDR_BAD_PREFIX ) fputs( "  bad prefix or netmask\n", stderr );
	    if ( rc & IPV4_STR_TO_ADDR_BAD_COUNT  ) fputs( "  bad count\n", stderr );
	    if ( rc & IPV4_STR_TO_ADDR_BAD_PORT   ) fputs( "  bad port\n", stderr );
	    ++ errors;
	}

	//------------------------------------------------
	// Otherwise check for a consistent specification.
	//------------------------------------------------
	else {
	    specs = 0;

	    //---------------------------------------------
	    // If a second address is given, just count it.
	    //---------------------------------------------
	    if ( rc & IPV4_STR_TO_ADDR_ADDR2   ) {
		++ specs;
	    }

	    //------------------------------------
	    // If a netmask is given, validate it.
	    //------------------------------------
	    if ( rc & IPV4_STR_TO_ADDR_NETMASK ) {
		uint32_t maska = mask;
		uint32_t maskb = mask;
	    
		if ( ( maska & 0x0000ffff ) == 0x0000ffff ) maska >>= 16;
		if ( ( maska & 0x000000ff ) == 0x000000ff ) maska >>=  8;
		if ( ( maska & 0x0000000f ) == 0x0000000f ) maska >>=  4;
		if ( ( maska & 0x00000003 ) == 0x00000003 ) maska >>=  2;
		if ( ( maska & 0x00000001 ) == 0x00000001 ) maska >>=  1;

		if ( ( maskb & 0xffff0000 ) == 0xffff0000 ) maskb <<= 16;
		if ( ( maskb & 0xff000000 ) == 0xff000000 ) maskb <<=  8;
		if ( ( maskb & 0xf0000000 ) == 0xf0000000 ) maskb <<=  4;
		if ( ( maskb & 0x30000000 ) == 0x30000000 ) maskb <<=  2;
		if ( ( maskb & 0x10000000 ) == 0x10000000 ) maskb <<=  1;

		if ( maska != 0 && maskb != 0 ) {
		    fprintf( stderr, "error in address \"%s\"\n", addrptr );
		    fputs( "  netmask is invalid\n", stderr );
		    ++ errors;
		}
		else if ( maskb == 0 ) {
		    mask = ~ mask;
		}
		++ specs;
	    }

	    //----------------------------------------------------
	    // If a prefix is given, check it for validity (1-32).
	    //----------------------------------------------------
	    if ( rc & IPV4_STR_TO_ADDR_PREFIX  ) {
		if ( prefix < 1 || prefix > 32 ) {
		    fprintf( stderr, "error in address \"%s\"\n", addrptr );
		    fprintf( stderr, "prefix value %u in \"%s\" must be from 1 to 32.\n", prefix, addrptr );
		    ++ errors;
		}
		++ specs;
	    }

	    //--------------------------------------------------
	    // If an address count is given, check for validity.
	    //--------------------------------------------------
	    if ( rc & IPV4_STR_TO_ADDR_COUNT   ) {
		if ( count == 0 ) {
		    fprintf( stderr, "error in address \"%s\"\n", addrptr );
		    fputs( "  count must be larger than 0\n", stderr );
		    ++ errors;
		}
		else if ( ( addr1 + count ) < addr1 ) {
		    fprintf( stderr, "error in address \"%s\"\n", addrptr );
		    fputs( "  count %u is too large for address\n", stderr );
		    ++ errors;
		}
		++ specs;
	    }

	    //-----------------------------------------------------------
	    // If more than one specification is given, that is an error.
	    //-----------------------------------------------------------
	    if ( specs > 1 ) {
		fprintf( stderr, "error in address \"%s\"\n", addrptr );
		fputs( "  redundant range specs\n", stderr );
		++ errors;
	    }
	}
    }
    if ( errors > 0 ) return 1;

    //----------------------------------------------------
    // Scan all address/network specifications for output.
    //----------------------------------------------------
    argn = argc;
    argp = argv;
    while ( -- argn ) {
	addrptr = * ++ argp;

	//---------------------------------------------------
	// Parse the address and network range specification.
	//---------------------------------------------------
	rc = ipv4_str_to_addr( addrptr, & endptr, & addr1, & addr2, & mask, & prefix, & count, & port );

	//--------------------------------------------------
	// If a second address is given, check for reversal.
	//--------------------------------------------------
	if ( rc & IPV4_STR_TO_ADDR_ADDR2   ) {
	    if ( addr1 > addr2 ) {
		uint32_t tmp;
		tmp = addr1;
		addr1 = addr2;
		addr2 = addr1;
	    }
	}

	//---------------------------------------------------------------
	// if a netmask is given, calculate both start and end addresses.
	//---------------------------------------------------------------
	else if ( rc & IPV4_STR_TO_ADDR_NETMASK ) {
	    if ( mask >= 0x80000000U ) mask = ~ mask;
	    addr1 &= ~ mask;
	    addr2 = addr1 + mask;
	}

	//--------------------------------------------------------------
	// If a prefix is given, calculate both start and end addresses.
	//--------------------------------------------------------------
	else if ( rc & IPV4_STR_TO_ADDR_PREFIX ) {
	    mask = ( prefix < 32 ) ? ( 0xffffffffU >> prefix ) : 0;
	    addr1 &= ~ mask;
	    addr2 = addr1 + mask;
	}

	//-----------------------------------------------------
	// If a count is given, calculate just the end address.
	//-----------------------------------------------------
	else if ( rc & IPV4_STR_TO_ADDR_COUNT ) {
	    addr2 = addr1 + count - 1U;
	}

	//----------------------------------
	// Otherwise it is a single address.
	//----------------------------------
	else {
	    addr2 = addr1;
	}

	switch ( format ) {

	case USE_RANGE:
	    printf( v6
		    ? "::ffff:%02x%02x:%02x%02x-::ffff:%02x%02x:%02x%02x\n"
		    : "%u.%u.%u.%u-%u.%u.%u.%u\n",
		    (unsigned int) ( ( addr1 >> 24 ) & 255 ),
		    (unsigned int) ( ( addr1 >> 16 ) & 255 ),
		    (unsigned int) ( ( addr1 >>  8 ) & 255 ),
		    (unsigned int) ( ( addr1       ) & 255 ),
		    (unsigned int) ( ( addr2 >> 24 ) & 255 ),
		    (unsigned int) ( ( addr2 >> 16 ) & 255 ),
		    (unsigned int) ( ( addr2 >>  8 ) & 255 ),
		    (unsigned int) ( ( addr2       ) & 255 ) );
	    break;

	case USE_CIDR:
	    for (;;) {
		sprefix = ipv4_range_to_cidr_int( & addr1, & addr2, & base, & mask, & count );
		if ( sprefix < 0 ) break;
		if ( v6 ) sprefix += 96;
		printf( v6
			? "::ffff:%02x%02x:%02x%02x/%u\n"
			: "%u.%u.%u.%u/%d\n",
			(unsigned int) ( ( base >> 24 ) & 255 ),
			(unsigned int) ( ( base >> 16 ) & 255 ),
			(unsigned int) ( ( base >>  8 ) & 255 ),
			(unsigned int) ( ( base       ) & 255 ),
			sprefix );
	    }
	    break;

	case USE_MASK:
	    for (;;) {
		sprefix = ipv4_range_to_cidr_int( & addr1, & addr2, & base, & mask, & count );
		if ( sprefix < 0 ) break;
		printf( v6
			? "::ffff:%02x%02x:%02x%02x/::%02x%02x:%02x%02x\n"
			: "%u.%u.%u.%u/%u.%u.%u.%u\n",
			(unsigned int) ( ( base >> 24 ) & 255 ),
			(unsigned int) ( ( base >> 16 ) & 255 ),
			(unsigned int) ( ( base >>  8 ) & 255 ),
			(unsigned int) ( ( base       ) & 255 ),
			(unsigned int) ( ( mask >> 24 ) & 255 ),
			(unsigned int) ( ( mask >> 16 ) & 255 ),
			(unsigned int) ( ( mask >>  8 ) & 255 ),
			(unsigned int) ( ( mask       ) & 255 ) );
	    }
	    break;

	case USE_CLASS:
	    for (;;) {
		sprefix = ipv4_range_to_class_int( & addr1, & addr2, & base, & mask, & count );
		if ( sprefix == 32 ) {
		    printf( "%u.%u.%u.%u\n",
			    (unsigned int) ( ( base >> 24 ) & 255 ),
			    (unsigned int) ( ( base >> 16 ) & 255 ),
			    (unsigned int) ( ( base >>  8 ) & 255 ),
			    (unsigned int) ( ( base       ) & 255 ) );
		}
		else if ( sprefix == 24 ) {
		    printf( "%u.%u.%u\n",
			    (unsigned int) ( ( base >> 24 ) & 255 ),
			    (unsigned int) ( ( base >> 16 ) & 255 ),
			    (unsigned int) ( ( base >>  8 ) & 255 ) );
		}
		else if ( sprefix == 16 ) {
		    printf( "%u.%u\n",
			    (unsigned int) ( ( base >> 24 ) & 255 ),
			    (unsigned int) ( ( base >> 16 ) & 255 ) );
		}
		else if ( sprefix == 8 ) {
		    printf( "%u\n",
			    (unsigned int) ( ( base >> 24 ) & 255 ) );
		}
		else break;
	    }
	    break;

	case USE_IN_ADDR:
	    for (;;) {
		sprefix = ipv4_range_to_class_int( & addr1, & addr2, & base, & mask, & count );
		if ( sprefix == 32 ) {
		    printf( "%u.%u.%u.%u.in-addr.arpa\n",
			    (unsigned int) ( ( base       ) & 255 ),
			    (unsigned int) ( ( base >>  8 ) & 255 ),
			    (unsigned int) ( ( base >> 16 ) & 255 ),
			    (unsigned int) ( ( base >> 24 ) & 255 ) );
		}
		else if ( sprefix == 24 ) {
		    printf( "%u.%u.%u.in-addr.arpa\n",
			    (unsigned int) ( ( base >>  8 ) & 255 ),
			    (unsigned int) ( ( base >> 16 ) & 255 ),
			    (unsigned int) ( ( base >> 24 ) & 255 ) );
		}
		else if ( sprefix == 16 ) {
		    printf( "%u.%u.in-addr.arpa\n",
			    (unsigned int) ( ( base >> 16 ) & 255 ),
			    (unsigned int) ( ( base >> 24 ) & 255 ) );
		}
		else if ( sprefix == 8 ) {
		    printf( "%u.in-addr.arpa\n",
			    (unsigned int) ( ( base >> 24 ) & 255 ) );
		}
		else break;
	    }
	    break;

	case USE_ADDR:
	default:
	    for ( base = addr1; base <= addr2; ++ base ) {
		printf( v6
			? "::ffff:%02x%02x:%02x%02x\n"
			: "%u.%u.%u.%u\n",
			(unsigned int) ( ( base >> 24 ) & 255 ),
			(unsigned int) ( ( base >> 16 ) & 255 ),
			(unsigned int) ( ( base >>  8 ) & 255 ),
			(unsigned int) ( ( base       ) & 255 ) );
	    }
	}

	fflush( stdout );
    }

    return 0;
}

