//-----------------------------------------------------------------------------
// Copyright © 2003 - 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.
//-----------------------------------------------------------------------------
// package	libh/net
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------

#include "net_lib.h"

//-----------------------------------------------------------------------------
// file		ipv4_str_to_addr.c
//-----------------------------------------------------------------------------

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	ipv4_str_to_addr
//
// purpose	Convert an IPv4 address or network string in dotted decimal or
//		cetal (hexadecimal) format in one of these forms:
//			address[:port]
//			address-address[:port]
//			address/netmask[:port]
//			address/prefix[:port]
//			address#count[:port]
//		into corresponding numeric values stored to the caller and
//		return the type of format.
//
//		Combinations of these forms can also be handled with different
//		addresses stored in the the appropriate place depending on what
//		format designation it was found with.  The returned value is a
//		combination of flags.
//
// arguments	1 (const char *) string to be converted
//		2 (char * *) where to store pointer to end char
//		3 (ipv4_t *) where to store 1st address
//		4 (ipv4_t *) where to store 2nd address
//		5 (ipv4_t *) where to store netmask
//		6 (unsigned int *) where to store prefix
//		7 (unsigned int *) where to store count
//		8 (unsigned int *) where to store port
//
// return	(int) < 0 : error
//		(int)   0 : nothing found
//		(int)   1 : valid 1st IPv4 address
//		(int)   2 : valid 2nd IPv4 address
//		(int)   4 : valid count
//		(int)   8 : valid port
//		(int)  16 : valid netmask
//		(int)  32 : valid prefix
//-----------------------------------------------------------------------------
//	#define IPV4_STR_TO_ADDR_ADDR		0x00000001
//	#define IPV4_STR_TO_ADDR_ADDR2		0x00000002
//	#define IPV4_STR_TO_ADDR_COUNT		0x00000004
//	#define IPV4_STR_TO_ADDR_PORT		0x00000008
//	#define IPV4_STR_TO_ADDR_PREFIX		0x00000010
//	#define IPV4_STR_TO_ADDR_NETMASK	0x00000020
//
//	#define IPV4_STR_TO_ADDR_BAD_ADDR	0x00010000
//	#define IPV4_STR_TO_ADDR_BAD_ADDR2	0x00020000
//	#define IPV4_STR_TO_ADDR_BAD_COUNT	0x00040000
//	#define IPV4_STR_TO_ADDR_BAD_PORT	0x00080000
//	#define IPV4_STR_TO_ADDR_BAD_PREFIX	0x00100000
//
//	#define IPV4_STR_TO_ADDR_BAD_CHAR	0x40000000
//
//	#define IPV4_STR_TO_ADDR_ERROR		0x80000000
//-----------------------------------------------------------------------------

int
ipv4_str_to_addr (
    const char *	arg_str
    ,
    char * *		arg_endp_p
    ,
    ipv4_t *		arg_addr_p
    ,
    ipv4_t *		arg_addr2_p
    ,
    ipv4_t *		arg_netmask_p
    ,
    unsigned int *	arg_prefix_p
    ,
    unsigned int *	arg_count_p
    ,
    unsigned int *	arg_port_p
    )
__PROTO_END__
{
    static const signed char	cetal_table	[256]	= {
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,
	  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,	 -1,  -1,  -1,	-1,  -1
    };

    const char *		str_p		;
    ipv4_t			accum1		;
    ipv4_t			accum4		;
    unsigned int		sep_ch		;
    int				cetal		;
    int				seen		;
    int				parts		;

    str_p = (const char *) arg_str;
    seen = sep_ch = 0;
    accum1 = accum4 = cetal = parts = 0;

    //-----------------------------------------------
    // All parsing is done in this one stateful loop.
    //-----------------------------------------------
    for (;;) {
	unsigned int ch;

	ch = * (unsigned char *) str_p ++;

	//------------------------------------------------------------
	// If conversion is cetal (hexadecimal), add the cetal digits.
	//------------------------------------------------------------
	if ( cetal && cetal_table[ch] >= 0 ) {
	    accum1 <<= 4;
	    accum1 += cetal_table[ch];
	    continue;
	}

	//--------------------------------------------------
	// If conversion is decimal, add the decimal digits.
	//--------------------------------------------------
	if ( ( ! cetal ) && ( '0' <= ch && ch <= '9' ) ) {
	    accum1 *= 10;
	    accum1 += ch - '0';
	    continue;
	}

	//-----------------------------------------------------------------
	// If cetal (hexadecimal) prefix is encountered, switch conversion.
	//-----------------------------------------------------------------
	if ( ( ! cetal ) && ( accum1 == 0 ) && ( ch == 'x' || ch == 'X' ) ) {
	    cetal = 1;
	    continue;
	}

	//---------------------------------------------------------------------
	// If characters are unrecognized, pretend it is the end of the string.
	//---------------------------------------------------------------------
	if ( ch != '.' && ch != '/' && ch != '-' && ch != '#' && ch != ':' ) {
	    ch = 0;
	}

	//--------------------------------------------
	// Accumulate the address part into the whole.
	//--------------------------------------------
	++ parts;
	accum4 <<= 8;
	accum4 += accum1;

	//-----------------------------------------------------
	// If this is a dot, make sure the data type allows it.
	//-----------------------------------------------------
	if ( ch == '.' ) {

	    //-- If the data type is a port number, no dots allowed.
	    if ( sep_ch == ':' ) {
		seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_PORT;
		break;
	    }

	    //-- If the data type is a count number, no dots allowed.
	    else if ( sep_ch == '#' ) {
		seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_COUNT;
		break;
	    }

	    //-- If too many parts, flag an error for the appropriate type.
	    else if ( parts > 4 ) {
		if ( sep_ch == '/' ) {
		    seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_PREFIX;
		}
		else if ( sep_ch == '-' ) {
		    seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_ADDR2;
		}
		break;
	    }

	    //-- Reset part accumulator and continue with the next character.
	    accum1 = 0;
	    cetal = 0;
	    continue;
	}

	//-----------------------------------------------------------
	// At this point an address or number conversion is complete.
	//-----------------------------------------------------------

	//------------------------------------------------------------------
	// If this is an address conversion, then normalize it and store it.
	//------------------------------------------------------------------
	if ( sep_ch == 0 || sep_ch == '-' ) {
	    if ( parts == 1 ) {
		if ( accum4 < 0x00000100 ) accum4 <<= 24;
	    } else if ( parts == 2 ) {
		if ( accum4 < 0x00010000 ) accum4 <<= 16;
	    } else if ( parts == 3 ) {
		if ( accum4 < 0x01000000 ) accum4 <<= 8;
	    } else if ( parts > 4 ) {
		if ( sep_ch == 0 ) {
		    seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_ADDR;
		} else if ( sep_ch == '-' ) {
		    seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_ADDR2;
		}
		break;
	    }

	    if ( sep_ch == 0 ) {
		seen |= IPV4_STR_TO_ADDR_ADDR;
		if ( arg_addr_p ) * arg_addr_p = accum4;
	    } else if ( sep_ch == '-' ) {
		seen |= IPV4_STR_TO_ADDR_ADDR2;
		if ( arg_addr2_p ) * arg_addr2_p = accum4;
	    }
	}

	//--------------------------------------------------------
	// If this completes a prefix or netmask, determine which.
	//--------------------------------------------------------
	else if ( sep_ch == '/' ) {
	    if ( parts == 1 && accum1 <= 32 ) {
		seen |= IPV4_STR_TO_ADDR_PREFIX;
		if ( arg_prefix_p ) * arg_prefix_p = accum1;
	    }
	    else if ( parts == 4 ) {
		seen |= IPV4_STR_TO_ADDR_NETMASK;
		if ( arg_netmask_p ) * arg_netmask_p = accum4;
	    }
	    else {
		seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_PREFIX;
		break;
	    }
	}

	//-------------------------------------------------------
	// If this completes the count of addresses, validate it.
	//-------------------------------------------------------
	else if ( sep_ch == '#' ) {
	    if ( parts == 1 ) {
		seen |= IPV4_STR_TO_ADDR_COUNT;
		if ( arg_count_p ) * arg_count_p = accum1;
	    }
	    else {
		seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_COUNT;
		break;
	    }
	}

	//------------------------------------------------
	// If this completes the port number, validate it.
	//------------------------------------------------
	else if ( sep_ch == ':' ) {
	    if ( parts == 1 ) {
		seen |= IPV4_STR_TO_ADDR_PORT;
		if ( arg_port_p ) * arg_port_p = accum1;
	    }
	    else {
		seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_PORT;
		break;
	    }
	}

	//-------------------------------------
	// If this ends the string, finish now.
	//-------------------------------------
	if ( ch == 0 ) break;

	//--------------------------------
	// Start a new type of conversion.
	//--------------------------------
	sep_ch = ch;
	accum1 = accum4 = cetal = parts = 0;

	//---------------------------------------------------------------------------
	// If this starts a prefix/netmask, it cannot follow itself or a 2nd address.
	//---------------------------------------------------------------------------
	if ( ch == '/' ) {
	    if ( seen & ( IPV4_STR_TO_ADDR_PREFIX | IPV4_STR_TO_ADDR_NETMASK | IPV4_STR_TO_ADDR_BAD_PREFIX |
			  IPV4_STR_TO_ADDR_ADDR2 | IPV4_STR_TO_ADDR_BAD_ADDR2 ) ) {
		seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_PREFIX;
		break;
	    }
	    continue;
	}

	//-----------------------------------------------------------------------------
	// If this starts a 2nd address in a range, it can only follow the 1st address.
	//-----------------------------------------------------------------------------
	if ( ch == '-' ) {
	    if ( seen & ~ IPV4_STR_TO_ADDR_ADDR ) {
		seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_ADDR2;
		break;
	    }
	    continue;
	}

	//-------------------------------------------------------
	// If this starts an address count, be sure it is unique.
	//-------------------------------------------------------
	if ( ch == '#' ) {
	    if ( seen & ( IPV4_STR_TO_ADDR_COUNT | IPV4_STR_TO_ADDR_BAD_COUNT ) ) {
		seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_COUNT;
		break;
	    }
	    continue;
	}

	//----------------------------------------------------
	// If this starts a port number, be sure it is unique.
	//----------------------------------------------------
	if ( ch == ':' ) {
	    if ( seen & ( IPV4_STR_TO_ADDR_PORT | IPV4_STR_TO_ADDR_BAD_PORT ) ) {
		seen |= IPV4_STR_TO_ADDR_ERROR | IPV4_STR_TO_ADDR_BAD_PORT;
		break;
	    }
	    continue;
	}

	//-------------------------------
	// For anything else, finish now.
	//-------------------------------
	break;
    }

    //------------------------------------------------------------------
    // Store the end pointer back for the caller then return the status.
    //------------------------------------------------------------------
    if ( arg_endp_p ) * ( (const char * *) arg_endp_p ) = str_p - 1;
    return seen;
}

