//-----------------------------------------------------------------------------
// 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_int
//
// purpose	Convert a range of IPv4 addresses, in host byte order,
//		into a group of CIDR block addresses, returned one at a time.
//		This variety of the function uses addresses in int format.
//		See also ipv4_range_to_cidr_in_addr() for in_addr format.
//
// arguments	1 (ipv4_t *) pointer to range first address
//		2 (ipv4_t *) pointer to range last address
//		3 (ipv4_t *) where to store CIDR block address
//		4 (ipv4_t *) where to store CIDR block netmask
//		5 (ipv4_t *) 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_int (
    ipv4_t *		arg_range_first
    ,
    ipv4_t *		arg_range_last
    ,
    ipv4_t *		arg_cidr_addr
    ,
    ipv4_t *		arg_cidr_mask
    ,
    ipv4_t *		arg_cidr_size
    )
__PROTO_END__
{
    ipv4_t		range_addr	;
    ipv4_t		range_last	;
    ipv4_t		range_size	;
    ipv4_t		cidr_size	;
    int			prefix_len	;

    //----------------------------------------------
    // Get first and last addresses and check range.
    //----------------------------------------------
    range_addr = * arg_range_first;
    range_last = * arg_range_last;
    if ( range_addr > range_last ) return -1;

    //------------------------------------
    // Handle special case of whole space.
    //------------------------------------
    if ( range_addr == 0 && range_last == 0xffffffffU ) {
	* arg_range_first = 0xffffffffU;
	* arg_range_last = 0;
	if ( arg_cidr_addr ) * arg_cidr_addr = 0U;
	if ( arg_cidr_mask ) * arg_cidr_mask = 0U;
	if ( arg_cidr_size ) * arg_cidr_size = 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 to remove this CIDR block from the range.
    //------------------------------------------------------------
    * arg_range_first = range_addr + cidr_size;
    if ( arg_cidr_addr ) * arg_cidr_addr = range_addr;
    if ( arg_cidr_mask ) * arg_cidr_mask = ~ ( cidr_size - 1U );
    if ( arg_cidr_size ) * arg_cidr_size = 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;
}

