//-----------------------------------------------------------------------------
// 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"

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	ipv4_range_to_cidr_in_addr
//
// purpose	Convert a range of IPv4 addresses, in network byte order,
//		into a group of CIDR block addresses, returned one at a time.
//		This variety of the function uses addresses in struct in_addr
//		format.  See also ipv4_int_range_to_cidr() for int format.
//
// arguments	1 (struct in_addr *) pointer to range first address
//		2 (struct in_addr *) pointer to range last address
//		3 (struct in_addr *) where to store CIDR block address
//		4 (struct in_addr *) where to store CIDR block netmask
//		5 (struct in_addr *) where to store CIDR block size
//
// returns	(int) 1..32 : CIDR block prefix length
//		(int)     0 : the whole IPv4 address space
//		(int)    -1 : no (more) CIDR blocks
//
// note		The locations pointed to are modified in place to reduce the
//		range by removing each CIDR block returned.
//-----------------------------------------------------------------------------
int
ipv4_range_to_cidr_in_addr (
    struct in_addr *	arg_range_addr
    ,
    struct in_addr *	arg_range_last
    ,
    struct in_addr *	arg_cidr_addr
    ,
    struct in_addr *	arg_cidr_mask
    ,
    struct in_addr *	arg_cidr_size
    )
__PROTO_END__
{
    ipv4_t		range_addr	;
    ipv4_t		range_last	;
    ipv4_t		range_size	;
    ipv4_t		cidr_size	;
    int			prefix_len	;

    range_addr = ntohl( arg_range_addr->s_addr );
    range_last = ntohl( arg_range_last->s_addr );

    //-- Handle an invalid range.
    if ( range_addr > range_last ) return -1;

    //-- Handle special case of whole space.
    if ( range_addr == 0 && range_last == 0xffffffffU ) {
	arg_range_addr->s_addr = 0xffffffffU;
	arg_range_last->s_addr = 0;
	if ( arg_cidr_addr ) arg_cidr_addr->s_addr = 0U;
	if ( arg_cidr_mask ) arg_cidr_mask->s_addr = 0U;
	if ( arg_cidr_size ) arg_cidr_size->s_addr = 0U;
	return 0;
    }

    //-- Determine largest possible CIDR size, and range size.
    cidr_size = ( ( range_addr ^ ( range_addr - 1U ) ) >> 1 ) + 1U;
    range_size = range_last - range_addr + 1U;

    //--------------------------------------------
    // Reduce CIDR block size to fit given range.
    // This loop is really O(log(b)) when averaged
    // over all possible different address values.
    //--------------------------------------------
    while ( cidr_size > range_size ) cidr_size >>= 1;

    //-- Store back values.
    arg_range_addr->s_addr = htonl( range_addr + cidr_size );
    if ( arg_cidr_addr ) arg_cidr_addr->s_addr = htonl( range_addr );
    if ( arg_cidr_mask ) arg_cidr_mask->s_addr = htonl( ~ ( cidr_size - 1U ) );
    if ( arg_cidr_size ) arg_cidr_size->s_addr = htonl( cidr_size );

    //-- Determine network prefix length.
    prefix_len = 32;
    if ( cidr_size >= 0x00010000U ) { cidr_size >>= 16; prefix_len -= 16; }
    if ( cidr_size >= 0x00000100U ) { cidr_size >>=  8; prefix_len -=  8; }
    if ( cidr_size >= 0x00000010U ) { cidr_size >>=  4; prefix_len -=  4; }
    if ( cidr_size >= 0x00000004U ) { cidr_size >>=  2; prefix_len -=  2; }
    if ( cidr_size >= 0x00000002U ) {                   prefix_len -=  1; }

    return prefix_len;
}

