package Net::IP::Lite;

use strict;
use warnings;

use Carp qw(croak);
use Scalar::Util qw(blessed);

use base 'Exporter';

our @EXPORT = qw(
	ip2bin ip_validate ip_is_ipv4 ip_is_ipv6 ip_is_ipv6ipv4
	ip_transform ip_equal ip_equal_v4 ip_equal_v6 ip_in_range
);

use constant IPV6IPV4HEAD => '0' x 80 . '1' x 16;

our $VERSION = '0.03';

sub _wrong_ip {
	my $addr = shift;
	$addr = 'UNDEFINED' unless defined $addr;
	return "Wrong IP address: '$addr'";
}

sub _wrong_ipv6ipv4 {
	my $addr = shift;
	$addr = 'UNDEFINED' unless defined $addr;
	return "Failed to convert IPv6 address '$addr' to IPv4 address";
}

sub _wrong_net {
	my $net = shift;
	$net = 'UNDEFINED' unless defined $net;
	return "Wrong network definition: '$net'";
}

sub ip2bin {
	my $addr = shift;
	return '' unless defined $addr;

	my $bin = '';

	if ($addr =~ /:/) {
		# IPv6 address

		return '0' x 128 if $addr eq '::';

		return '' if $addr =~ s/^:// && $addr !~ /^:/;
		return '' if $addr =~ s/:$// && $addr !~ /:$/;

		my @words = split(/:/, $addr, -1);
		my $words_amount = scalar @words;

		# IPv4 representation
		$words_amount++ if $addr =~ /\./;

		my $reduct = 0;
		my $i = 0;
		for my $word (@words) {
			$i++;
			if ($word =~ /\./) {
				# IPv4 representation
				return '' if $i != scalar @words || $bin ne IPV6IPV4HEAD;
				my @octets = split(/\./, $word);
				return '' if scalar @octets != 4;
				for my $octet (@octets) {
					return '' if $octet !~ /^\d+$/ || $octet > 255;
					$bin .= unpack('B8', pack('C', $octet));
				}
				return $bin;
			} elsif (!length $word) {
				return '' if $reduct;
				$reduct = 1;
				my $len = (9 - $words_amount) << 4;
				return '' unless $len;
				$bin .= '0' x ((9 - $words_amount) << 4);
			} elsif ($word =~ /^[0-9a-f]+$/i) {
				$word =~ s/^0+//i;
				return '' if length($word) > 4;
				my $int = hex($word);
				$bin .= unpack('B16', pack('n', $int));
			} else {
				return '';
			}
			return '' if length($bin) > 128;
		}
		return '' if length($bin) < 128;
	} elsif ($addr =~ /\./) {
		# IPv4
		my @octets = split(/\./, $addr, -1);
		return '' if scalar @octets > 4;
		my $i = 0;
		for my $octet (@octets) {
			$i++;
			my $dec;
			if ($octet =~ /^0[0-7]+$/) {
				# Octal octet
				$octet =~ s/^0+//;
				return '' if length($octet) > 3;
				$dec = oct($octet);
			} elsif ($octet =~ /^\d+$/) {
				# Decimal octet
				return '' if length($octet) > 3;
				$dec = $octet;
			} elsif ($octet =~ /^0x[0-9a-f]+$/i) {
				# Hexadecimal octet
				$octet =~ s/^0x0*//i;
				return '' if length($octet) > 2;
				$dec = hex($octet);
			} else {
				return '';
			}
			return '' if $dec > 255;
			if ($i == scalar @octets && $i < 4) {
				# add missed octets
				$bin .= '0' x ((4 - $i) << 3);
			}
			$bin .= unpack('B8', pack('C', $dec));
		}
	} elsif ($addr =~ /^0[0-7]+$/) {
		# Octal IPv4 address
		$addr =~ s/^0+//i;
		return '' if $addr > 37777777777;
		my $int = oct($addr);
		$bin = unpack('B32', pack('N', $int));
	} elsif ($addr =~ /^\d+$/) {
		# Decimal IPv4 address
		return '' if $addr > 4294967295;
		$bin = unpack('B32', pack('N', $addr));
	} elsif ($addr =~ /^0x[0-9a-f]+$/i) {
		# Hexadecimal IPv4 addres
		$addr =~ s/^0x0*//i;
		return '' if length($addr) > 8;
		my $int = hex($addr);
		$bin = unpack('B32', pack('N', $int));
	}
	return $bin;
}

sub _ip_validate {
	return length(shift) > 0;
}

sub ip_validate {
	return _ip_validate(ip2bin(shift)) > 0;
}

sub _ip_is_ipv4 {
	return length(shift) eq 32;
}

sub ip_is_ipv4 {
	return _ip_is_ipv4(ip2bin(shift));
}

sub _ip_type_equal {
	return length(shift) == length(shift);
}

sub _ip_is_ipv6 {
	return length(shift) eq 128;
}

sub ip_is_ipv6 {
	return _ip_is_ipv6(ip2bin(shift));
}

sub _ip_is_ipv6ipv4 {
	return substr(shift, 0, 96) eq IPV6IPV4HEAD;
}

sub ip_is_ipv6ipv4 {
	return _ip_is_ipv6ipv4(ip2bin(shift));
}

sub _bin2ipv6 {
	my ($bin, $lead_zeros, $short) = @_;
	my $result = '';
	my @chunks = $bin =~ m/[0..1]{16}/g;

	my $short_len = 0;
	if ($short) {
		my @zero_chunks;
		my $i = 0;
		for my $chunk (@chunks) {
			if ($chunk !~ /1/) {
				$zero_chunks[$i]++;
			} else {
				$i++ if defined $zero_chunks[$i];
			}
		}
		@zero_chunks = sort @zero_chunks;
		$short_len = pop @zero_chunks if scalar @zero_chunks;
	}
	for my $chunk (@chunks) {
		my $word = unpack('H4', pack('B16', $chunk)) . ':';
		$word =~ s/^0{1,3}// unless $lead_zeros;
		$result .= $word;
	}

	$result =~ s/(^|:)(0{1,4}:){$short_len}/::/ if $short_len > 1;
	$result =~ s/:$// if $result !~ /::$/;
	return $result;
}

sub _bin2ipv4 {
	my ($bin, $format, $lead_zeros, $short) = @_;

	$format ||= '';
	my @chunks = $bin =~ m/[0..1]{8}/g;

	if ($format =~ /^(D|O|X)$/) {
		my $result = 0;
		my $i = 0;
		for my $chunk (reverse @chunks) {
			$result += unpack('C', pack('B8', $chunk)) << $i;
			$i +=8;
		}

		return sprintf("%#1o", $result) if $format eq 'O';
		return sprintf("0x%.8x", $result) if $format eq 'X' && $lead_zeros;
		return sprintf("0x%x", $result) if $format eq 'X';
		return $result;
	} else {
		my $result = '';
		my $f = '';
		if ($format eq 'o') {
			$f = '%#.1o';
		} elsif ($format eq 'x') {
			$f = $lead_zeros ? '0x%.2x' : '0x%x' ;
		}

		my $i = 4;
		for my $chunk (reverse @chunks) {
			$i--;
			my $octet = unpack('C', pack('B8', $chunk));
			if ($short) {
				next if (!$octet && $i && $i < 3 );
				$short = 0 if $i == 2;
			}
			if ($format eq 'o') {
				$octet = sprintf($f, $octet);
			} elsif ($format eq 'x') {
				$octet = sprintf($f, $octet);
			}
			$result = "$octet.$result";
		}
		$result =~ s/\.$//;
		return $result;
	} 
}

sub _reverse {
	my $bin = shift;
	my $len = _ip_is_ipv4($bin) ? 8 : 16;
	my @chunks = $bin =~ m/[0..1]{$len}/g;
	return join('', reverse @chunks);
}

sub _ip_transform {
	my ($bin, $opts) = @_;

	my $format = $opts->{format_ipv4} || '';
	my $convert_to = $opts->{convert_to} || '';

	my $ipv6;
	my $ipv4;

	if ($convert_to eq 'ipv4' && _ip_is_ipv6ipv4($bin)) {
		# convert ipv6ipv4 to ipv4
		$bin = substr($bin, 96, 32);
	}

	if (($convert_to eq 'ipv6' || $convert_to eq 'ipv6ipv4') && _ip_is_ipv4($bin)) {
		# convert ipv4 to ipv6
		$bin = IPV6IPV4HEAD . $bin;
	}

	if ($convert_to eq 'ipv6ipv4' && _ip_is_ipv6ipv4($bin)) {
		# convert ipv4 to ipv6ipv4
		$ipv4 = substr($bin, 96, 32);
		$ipv4 = _reverse($ipv4) if $opts->{reverse};
		$bin = IPV6IPV4HEAD;
	} else {
		$bin = _reverse($bin) if $opts->{reverse};
	}

	my $result = '';
	if (length($bin) > 32) {
		# IPv6
		$result = _bin2ipv6($bin, $opts->{lead_zeros}, $opts->{short_ipv6});
		if ($ipv4) {
			$bin = $ipv4;
			$ipv6 = $result;
		}
	}

	if (length($bin) == 32) {
		# IPv4
		if ($ipv6) {
			$result = "$ipv6:" . _bin2ipv4($bin);
		} else {
			$result = _bin2ipv4($bin, $format, $opts->{lead_zeros}, $opts->{short_ipv4});
		}
	}

	return $result;
}

sub ip_transform {
	my ($addr, $opts) = @_;

	$opts ||= {};
	croak 'Options must be a hash' unless ref($opts) eq 'HASH';

	my $bin = ip2bin($addr);
	croak _wrong_ip($addr) unless _ip_validate($bin);
	return _ip_transform($bin, $opts);
}

sub _ip_equal {
	my ($bin1, $bin2) = @_;
	return $bin1 eq $bin2;
}

sub ip_equal {
	my ($addr1, $addr2) = @_;
	my $bin1 = ip2bin($addr1);
	my $bin2 = ip2bin($addr2);

	croak _wrong_ip($addr1) unless _ip_validate($bin1);
	croak _wrong_ip($addr2) unless _ip_validate($bin2);

	return $bin1 eq $bin2;
}

sub _ip_equal_v4 {
	my ($bin1, $bin2) = @_;
	$bin1 = substr($bin1, 96, 32) if _ip_is_ipv6ipv4($bin1);
	$bin2 = substr($bin2, 96, 32) if _ip_is_ipv6ipv4($bin2);
	return _ip_equal($bin1, $bin2);
}

sub ip_equal_v4 {
	my ($addr1, $addr2) = @_;
	my $bin1 = ip2bin($addr1);
	my $bin2 = ip2bin($addr2);

	croak _wrong_ip($addr1) unless _ip_validate($bin1);
	croak _wrong_ip($addr2) unless _ip_validate($bin2);

	if ((_ip_is_ipv6($bin1) && ! _ip_is_ipv6ipv4($bin1))) {
		croak _wrong_ipv6ipv4($addr1);
	}

	if ((_ip_is_ipv6($bin2) && ! _ip_is_ipv6ipv4($bin2))) {
		croak _wrong_ipv6ipv4($addr2);
	}

	return _ip_equal_v4($bin1, $bin2);
}

sub _ip_equal_v6 {
	my ($bin1, $bin2) = @_;
	$bin1 = IPV6IPV4HEAD . $bin1 if _ip_is_ipv4($bin1);
	$bin2 = IPV6IPV4HEAD . $bin2 if _ip_is_ipv4($bin2);
	return _ip_equal($bin1, $bin2);
}

sub ip_equal_v6 {
	my ($addr1, $addr2) = @_;
	my $bin1 = ip2bin($addr1);
	my $bin2 = ip2bin($addr2);

	croak _wrong_ip($addr1) unless _ip_validate($bin1);
	croak _wrong_ip($addr2) unless _ip_validate($bin2);

	return _ip_equal_v6($bin1, $bin2);
}

sub __ip_in_range {
	my ($bin_addr, $bin_net, $bin_mask) = @_;

	my @addr_bits = split(//, $bin_addr);
	my @mask_bits = split(//, $bin_mask);
	my $result = '';

	my $i = 0;
	for my $bit (@addr_bits) {
		$result	.= $bit & $mask_bits[$i];
		$i++;
	}

	return _ip_equal($bin_net, $result);
}

sub _ip_in_range {
	my ($bin_addr, $net, $addr) = @_;

	my $bin_net;
	my $bin_mask;

	if ($net =~ /^(.+)\/(\d+)$/) {
		my $mask = $2;
		$bin_net = ip2bin($1);
		croak _wrong_net($net) unless _ip_validate($bin_net);

		my $mask_len = _ip_is_ipv4($bin_net) ? 32 : 128;
		croak _wrong_net($net) if $mask > $mask_len;

		$bin_mask = '1' x $mask . '0' x ($mask_len - $mask);
	} elsif ($net =~ /^(\S+)\s+(\S+)$/) {
		$bin_net = ip2bin($1);
		$bin_mask = ip2bin($2);
	} else {
		$bin_net = ip2bin($net);
		$bin_mask = '1' x (_ip_is_ipv4($bin_net) ? 32 : 128);
	}

	unless (_ip_validate($bin_net) && _ip_validate($bin_mask) && _ip_type_equal($bin_net, $bin_mask)) {
		croak _wrong_net($net);
	}

	return 0 unless _ip_type_equal($bin_addr, $bin_net);

	return __ip_in_range($bin_addr, $bin_net, $bin_mask);
}

sub ip_in_range {
	my ($addr, $range) = @_;

	croak _wrong_net($range) unless defined $range;

	if (ref($range) eq 'ARRAY') {
		for my $net (@$range) {
			return 1 if ip_in_range($addr, $net);
		}
		return 0;
	} 

	my $bin_addr = ip2bin($addr);
	croak _wrong_ip($addr) unless _ip_validate($bin_addr);

	return _ip_in_range($bin_addr, $range, $addr);
}

sub new {
	my ($class, $addr) = @_;

	my $bin = ip2bin($addr);
	return 0 unless _ip_validate($bin);

	my $self = { 
		bin => $bin,
		addr => $addr
	};

	bless $self, $class;
	return $self;
}

sub _set_binary {
	my ($self, $bin) = @_;
	$self->{bin} = $bin;
	$self->{addr} = $self->transform();
}

sub address {
	my $self = shift;
	return $self->{addr};
}

sub binary {
	my $self = shift;
	return $self->{bin};
}

sub is_ipv4 {
	my $self = shift;
	return _ip_is_ipv4($self->binary);
}

sub is_ipv6 {
	my $self = shift;
	return _ip_is_ipv6($self->binary);
}

sub is_ipv6ipv4 {
	my $self = shift;
	return _ip_is_ipv6ipv4($self->binary);
}

sub transform {
	my ($self, $opts) = @_;
	$opts ||= {};
	croak 'Options must be a hash' unless ref($opts) eq 'HASH';
	return _ip_transform($self->binary, $opts);
}

sub equal {
	my ($self, $addr) = @_;

	if (blessed($addr) && $addr->isa('Net::IP::Lite')) {
		return _ip_equal($self->binary, $addr->binary);
	}

	my $bin2 = ip2bin($addr);
	croak _wrong_ip($addr) unless _ip_validate($bin2);

	return _ip_equal($self->binary, $bin2);
}

sub equal_v4 {
	my ($self, $addr) = @_;

	my $bin2 = (blessed($addr) && $addr->isa('Net::IP::Lite')) ? $addr->binary : ip2bin($addr);
	croak _wrong_ip($addr) unless _ip_validate($bin2);

	if (($self->is_ipv6() && ! $self->is_ipv6ipv4())) {
		croak _wrong_ipv6ipv4($self->address);
	}

	if ((_ip_is_ipv6($bin2) && ! _ip_is_ipv6ipv4($bin2))) {
		croak _wrong_ipv6ipv4($addr);
	}

	return _ip_equal_v4($self->binary, $bin2);
}

sub equal_v6 {
	my ($self, $addr) = @_;

	my $bin2 = (blessed($addr) && $addr->isa('Net::IP::Lite')) ? $addr->binary : ip2bin($addr);

	croak _wrong_ip($addr) unless _ip_validate($bin2);

	return _ip_equal_v6($self->binary, $bin2);
}

sub in_range {
	my ($self, $range) = @_;

	croak _wrong_net($range) unless defined $range;

	if (ref($range) eq 'ARRAY') {
		for my $net (@$range) {
			return 1 if $self->in_range($net);
		}
		return 0;
	} 

	if (blessed($range) && $range->isa('Net::IP::Lite::Net')) {
		return $range->contains($self);
	}

	return _ip_in_range($self->binary, $range, $self->address);
}

package Net::IP::Lite::Net;
use Carp qw(croak);
use Scalar::Util qw(blessed);

use base qw(Net::IP::Lite);

sub new {
	my ($class, $net) = @_;

	return 0 unless defined $net;

	my $self = {};
	my $bin_mask;

	if ($net =~ /^(.+)\/(\d+)$/) {
		my $mask = $2;
		$self = $class->SUPER::new($1);

		my $mask_len = $self->is_ipv4() ? 32 : 128;
		return 0 if $mask > $mask_len;

		$bin_mask = '1' x $mask . '0' x ($mask_len - $mask);
	} elsif ($net =~ /^(\S+)\s+(\S+)$/) {
		$self = $class->SUPER::new($1);
		return 0 unless $self;
		$self->{mask} = Net::IP::Lite->new($2);
	} else {
		$self = $class->SUPER::new($net);
		return 0 unless $self;
		$bin_mask = '1' x ($self->is_ipv4 ? 32 : 128);
	}

	if ($bin_mask) {
		if (length($bin_mask) == 32) {
			$self->{mask} = Net::IP::Lite->new(Net::IP::Lite::_bin2ipv4($bin_mask));
		} else {
			$self->{mask} = Net::IP::Lite->new(Net::IP::Lite::_bin2ipv6($bin_mask));
		}
	}

	return 0 unless $self->{mask};

	my $ipv4_mask = $self->{mask}->is_ipv4;
	return 0 unless (($self->is_ipv4 && $ipv4_mask) || ($self->is_ipv6 && ! $ipv4_mask));

	$self->{net} = $net;
	return $self;
}

sub mask {
	my ($self) = @_;
	return $self->{mask};
}

sub network {
	my ($self) = @_;
	return $self->{net};
}

sub contains {
	my ($self, $addr) = @_;

	if (blessed($addr) && $addr->isa('Net::IP::Lite')) {
		return 0 unless Net::IP::Lite::_ip_type_equal($self->binary, $addr->binary);
		return Net::IP::Lite::__ip_in_range($addr->binary, $self->binary, $self->mask->binary);
	} else {
		my $bin_addr = Net::IP::Lite::ip2bin($addr);
		croak Net::IP::Lite::_wrong_ip($addr) unless Net::IP::Lite::_ip_validate($bin_addr);
		return 0 unless Net::IP::Lite::_ip_type_equal($self->binary, $bin_addr);
		return Net::IP::Lite::__ip_in_range($bin_addr, $self->binary, $self->mask->binary);
	}
}


1;

__END__

=head1 NAME

Net::IP::Lite - Perl extension for manipulating IPv4/IPv6 addresses

=head1 SYNOPSIS

	use Net::IP::Lite;

	print ip2bin('127.0.0.1') . "\n";
	print ip_validate('127.0.0.1') . "\n";

	print ip_is_ipv4('127.0.0.1') . "\n";
	print ip_is_ipv6('::1') . "\n";
	print ip_is_ipv6ipv4('::ffff:7f00:1') . "\n";

	print ip_transform('127.0.0.1', {
		convert_to => 'ipv6ipv4',
		short_ipv6 => 1 }
	) . "\n";

	print ip_equal('0x7f000001', '127.0.0.1') . "\n";
	print ip_equal_v4('0x7f000001', '::ffff:127.0.0.1') . "\n";
	print ip_equal_v6('0x7f000001', '::ffff:127.0.0.1') . "\n";

	print ip_in_range('127.0.0.1', '127.0.0.1/8') . "\n";
	print ip_in_range('127.0.0.1', [
		'127.0.0.1/8',
		'10.0.0.0 255.255.255.255'
	]) . "\n";

	my $ip = Net::IP::Lite->new('127.0.0.1') || die 'Invalid IP address';
	print $ip->binary . "\n";
	print $ip->address . "\n";

	print $ip->is_ipv4('127.0.0.1') . "\n";
	print $ip->is_ipv6('::1') . "\n";
	print $ip->is_ipv6ipv4('::ffff:7f00:1') . "\n";

	print $ip->transform({
		convert_to => 'ipv6ipv4',
		short_ipv6 => 1
	}) . "\n";

	print $ip->equal('0x7f000001', '127.0.0.1') . "\n";
	print $ip->equal('0x7f000001', Net::IP::Lite->new('127.1')) . "\n";
	print $ip->equal_v4('0x7f000001', '::ffff:127.0.0.1') . "\n";
	print $ip->equal_v6('0x7f000001', '::ffff:127.0.0.1') . "\n";

	print $ip->in_range('127.0.0.1', '127.0.0.1/8') . "\n";
	print $ip->in_range('127.0.0.1', [
		'127.0.0.1/8',
		'10.0.0.0 255.0.0.0',
		Net::IP::Lite::Net->new('10.0.0.0/8')
	]) . "\n";

	my $net = Net::IP::Lite::Net->new('10.0.0.0/8') || die ...;
	print $net->address() . "\n";
	print $net->mask->address() . "\n";
	print $net->contains('10.1.1.1') . "\n";

=head1 DESCRIPTION

This is another module to manipulate B<IPv4/IPv6> addresses.
In contrast of NET::IP, it does not require Math::BigInt. Also, it supports
some additional IPv4 formats like 0x7f000001, 2130706433, 017700000001,
0177.0.0.1, 0x7f.0.0.0x1.

The module provides the following capabilities:

=over

=item * validating IP addresses;

=item * converting IP addresses in different format;

=item * comparing IP addresses;

=item * verifying whether an IP is an IPv4 or an IPv6 or an IPv4-embedded IPv6 address;

=item * verifying whether an IP is in a range.

=back

Most subroutines have two implementations, so you can use procedural
or object-oriented approach.

=head1 SUPPORTED FORMATS

You can use any IPv4 and IPv6 formats:

=over

=item * 127.0.0.1, 127.0.1, 127.1 (IPv4 with decimal octets);

=item * 0177.0.0.1, 0177.0.1, 0177.1 (IPv4 with octal octets);

=item * 0x7f.0x0.0x0.0x1, 0x7f.0x0.0x1, 0x7f.0x1 (IPv4 with hexadecimal octets);

=item * 0177.0.0.1, 0x7f.0.1, 0177.0x1 (IPv4 with mixed octets);

=item * 2130706433 (decimal IPv4);

=item * 0x7f000001 (hexadecimal IPv4);

=item * 017700000001 (octal IPv4);

=item * 0:0:0:0:0:0:0:1, ::, ::1 (IPv6);

=item * 0:0:0:0:0:ffff:127.0.0.1, ::ffff:127.0.0.1 (IPv4-embedded IPv6 address).

=back

=head1 PROCEDURAL INTERFACE

=head2 ip2bin

Returns a string that contains binary representation of an IP address.
Returns the empty string if an invalid IP address is specified.

	$ip = ip2bin('127.0.0.1'); # '01111111000000000000000000000001'
	$ip = ip2bin('::1');       # '0' x 127 . '1'
	$ip = ip2bin('::1:');      # ''

=head2 ip_validate

Returns TRUE if the specified IP address is a valid, or FALSE otherwise.

	$ok = ip_validate('127.0.0.1');     # TRUE
	$ok = ip_validate('::1');           # TRUE
	$ok = ip_validate('127.0.0.');      # FALSE
	$ok = ip_validate('127.256');       # FALSE
	$ok = ip_validate('::1:127.0.0.1'); # FALSE

=head2 ip_is_ipv4

Returns TRUE if the specified IP address is an IPv4 address, or FALSE otherwise.

	$ok = ip_is_ipv4('127.0.0.1');        # TRUE
	$ok = ip_is_ipv4('::1');              # FALSE
	$ok = ip_is_ipv4('0::0:');            # FALSE
	$ok = ip_is_ipv4('::ffff:127.0.0.1'); # FALSE

=head2 ip_is_ipv6

Returns TRUE if the specified IP address is an IPv6 address, or FALSE otherwise.

	$ok = ip_is_ipv6('::1');              # TRUE
	$ok = ip_is_ipv6('::ffff:127.0.0.1'); # TRUE
	$ok = ip_is_ipv6('0::0:');            # FALSE
	$ok = ip_is_ipv6('0::0:ffff1');       # FALSE
	$ok = ip_is_ipv6('127.0.0.1');        # FALSE

=head2 ip_is_ipv6ipv4

Returns TRUE if the specified IP address is an IPv4-embedded IPv6 address,
or FALSE otherwise.

	$ok = ip_is_ipv6ipv4('::ffff:127.0.0.1'); # TRUE
	$ok = ip_is_ipv6ipv4('::ffff:7f00:1');    # TRUE
	$ok = ip_is_ipv6ipv4('::fff1:7f00:1');    # FALSE
	$ok = ip_is_ipv6ipv4('127.0.0.1');        # FALSE

=head2 ip_transform

Converts an IP address string to another IP address string (or number).

	$ip = ip_transform($ip, $opts);

Where $opts is a hash that can have the following keys:

=over

=item * short_ipv6 => 1 (return abbreviated IPv6 address);

=item * short_ipv4 => 1 (return abbreviated IPv4 address);

=item * lead_zeros => 1 (add leading zeros to IPv6 address or hexadecimal IPv4 address);

=item * reverse => 1 (return reversed IP address);

=item * convert_to => 'ipv6' (transform IPv4 to IPv6 address);

=item * convert_to => 'ipv4' (transform IPv6-embedded address to IPv4);

=item * convert_to => 'ipv6ipv4' (transform IP address to format ::ffff:xx.xx.xx.xx);

=item * format_ipv4 => 'X' (transform IPv4 address to hexadecimal number);

=item * format_ipv4 => 'D' (transform IPv4 address to decimal number);

=item * format_ipv4 => 'O' (transform IPv4 address to octal number);

=item * format_ipv4 => 'x' (transform IPv4 address to hexadecimal octet format);

=item * format_ipv4 => 'o' (transform IPv4 address to octal number).

=back

	$ip = ip_transform('127.0.1');          # 127.0.0.1
	$ip = ip_transform('::1');              # 0:0:0:0:0:0:0:1
	$ip = ip_transform('::ffff:127.0.0.1'); # 0:0:0:0:0:ffff:7f00:1

	$ip = ip_transform('127.0.0.1', {
		short_ipv4 => 1
	}); # 127.1

	$ip = ip_transform('0:0::1', {
		short_ipv6 => 1
	}); # ::1

	$ip = ip_transform('0:0::1', {
		lead_zeros => 1
	}); # 0000:0000:0000:0000:0000:0000:0000:0001

	$ip = ip_transform('0:0::1', {
		short_ipv6 => 1,
		lead_zeros => 1
	}); # ::0001

	$ip = ip_transform('0:0::1', {
		reverse => 1
	}); # 1:0:0:0:0:0:0:0

	$ip = ip_transform('::ffff:127.0.0.1', {
		reverse => 1,
		short_ipv6 => 1
	}); # 1:7f00:ffff::

	$ip = ip_transform('127.0.0.1', {
		convert_to => 'ipv6'
	}); # 0:0:0:0:0:ffff:7f00:1

	$ip = ip_transform('::ffff:127.0.0.1', {
		convert_to => 'ipv6'
	}); # 0:0:0:0:0:ffff:7f00:1

	$ip = ip_transform('::ffff:7f00:1', {
		convert_to => 'ipv4'
	}); # 127.0.0.1

	$ip = ip_transform('::ffff:127.0.0.1', {
		convert_to => 'ipv4'
	}); # 127.0.0.1

	$ip = ip_transform('::ffff:7f00:1', {
		convert_to => 'ipv6ipv4'
	}); # 0:0:0:0:0:ffff:127.0.0.1

	$ip = ip_transform('::ffff:127.0.0.1', {
		convert_to => 'ipv6ipv4'
	}); # 0:0:0:0:0:ffff:127.0.0.1

	$ip = ip_transform('127.0.0.1', {
		convert_to => 'ipv6ipv4'
	}); # 0:0:0:0:0:ffff:127.0.0.1

	$ip = ip_transform('0.0.0.1', {
		format_ipv4 => 'X'
	}); # 0x1

	$ip = ip_transform('0.0.0.1', {
		format_ipv4 => 'X',
		lead_zeros => 1
	}); # 0x00000001

	$ip = ip_transform('127.0.0.1', {
		format_ipv4 => 'D'
	}); # 2130706433

	$ip = ip_transform('127.0.0.1', {
		format_ipv4 => 'O'
	});
	# 017700000001

	$ip = ip_transform('127.0.0.1', {
		format_ipv4 => 'x'
	}); # 0x7f.0x0.0x0.0x1

	$ip = ip_transform('127.0.0.1', {
		format_ipv4 => 'x',
	short_ipv4 => 1
	}); # 0x7f.0x1

	$ip = ip_transform('127.0.0.1', {
		format_ipv4 => 'x',
		lead_zeros => 1 });
	# 0x7f.0x00.0x00.0x01'

	$ip = ip_transform('127.0.0.1', {
		format_ipv4 => 'o'
	}); # 0177.0.0.01

=head2 ip_equal

Compares two IP addresses.

	$eq = ip_equal('127.0.0.1', '0x7f000001');       # TRUE
	$eq = ip_equal('::', '0:0:0:0:0:0:0:0');         # TRUE
	$eq = ip_equal('::ffff:127.0.0.1', '127.0.0.1'); # FALSE

=head2 ip_equal_v4

Compares two IP addresses as IPv4 addresses.

	$eq = ip_equal_v4('127.0.0.1', '0x7f000001');       # TRUE
	$eq = ip_equal_v4('::ffff:127.0.0.1', '127.0.0.1'); # TRUE
	$eq = ip_equal_v4('::', '127.0.0.1');               # dies

=head2 ip_equal_v6

Compares two IP addresses as IPv6 addresses.

	$eq = ip_equal_v6('127.0.0.1', '0x7f000001');       # TRUE
	$eq = ip_equal_v6('::1', '0:0::1');                 # TRUE
	$eq = ip_equal_v6('::ffff:127.0.0.1', '127.0.0.1'); # TRUE
	$eq = ip_equal_v6('::', '127.0.0.1');               # FALSE

=head2 ip_in_range

Verifies whether the specified IP address in a range.

	$in_range = ip_in_range('127.0.0.1', $range);

Where range can be specified in the following ways:

=over

=item * an IP address and a mask ('192.168.0.1 255.255.255.0');

=item * an IP address with a prefix ('ffff:ffff:1::/48');

=item * an IP address without mask ('129.168.0.1' (equivalent to '192.168.0.1/32'));

=item * as an array ([ '129.168.0.0/16', '172.16.0.0/12', '10.0.0.0 255.0.0.0', '::ffff/96' ]);

=back

	$in = ip_in_range('192.168.0.1', '192.168.0 255.255.255.0');   # TRUE
	$in = ip_in_range('10.10.10.19', [ '127.1', '10.0/8' ]);       # TRUE
	$in = ip_in_range('10.10.10.19', '10.10.10.8/29');             # FALSE
	$in = ip_in_range('a0:a0:a0:a0:1::1', 'a0:a0:a0:a0::/64');     # TRUE
	$in = ip_in_range('::ffff:10.10.10.10', '::ffff:0:0/96');      # TRUE
	$in = ip_in_range('1:2:3::8000:40', '1:2:3::8000:20/123');     # FALSE

=head1 EXPORTS

Net::IP::Lite exports the following functions:

=over

=item * ip2bin

=item * ip_validate

=item * ip_is_ipv4

=item * ip_is_ipv6

=item * ip_is_ipv6ipv4

=item * ip_transform

=item * ip_equal

=item * ip_equal_v4

=item * ip_equal_v6

=item * ip_in_range

=back

=head1 OBJECT-ORIENTED INTERFACE

When you use the object oriented approach, binary representation of IP address
is calculated once (when you create Net::SimpleIO object). Thus, if you are
going to use an IP address (or a range) more than once, you can use once
created object to reduce redundant IP-to-binary conversions.

=head2 Net::IP::Lite object

=head3 constructor

	$ip = Net::IP::Lite->new('10.77.0.77') || die 'Invalid IP address';
	$ip = Net::IP::Lite->new('::1') || die ...

=head3 address

Returns the original IP address that was specified as the constructor argument.

	$ip = Net::IP::Lite->new('10.77.77');
	print $ip->address(); # 10.77.77

=head3 binary

Returns a string that contains binary representation of the specified IP
address.

	$ip = Net::IP::Lite->new('10.77.77');
	print $ip->binary(); # 00001010010011010000000001001101

=head3 is_ipv4

Returns TRUE if the IP address is a IPv4 address, or FALSE otherwise.

	$ip = Net::IP::Lite->new('10.77.77');
	$ipv4 = $ip->is_ipv4(); # TRUE

	$ip = Net::IP::Lite->new('::1');
	$ipv4 = $ip->is_ipv4(); # FALSE

See also: L</"ip_is_ipv4">

=head3 is_ipv6

Returns TRUE if the IP address is a IPv6 address, or FALSE otherwise.

	$ip = Net::IP::Lite->new('::1');
	$ipv6 = $ip->is_ipv6(); # TRUE

	$ip = Net::IP::Lite->new('127.1');
	$ipv6 = $ip->is_ipv6(); # FALSE

See also: L</"ip_is_ipv6">

=head3 is_ipv6ipv4

Returns TRUE if the IP address is a IPv4-Embedded IPv6 address, or FALSE
otherwise.

	$ip = Net::IP::Lite->new('::ffff:127.0.0.1');
	$emb = $ip->is_ipv6ipv4(); # TRUE

	$ip = Net::IP::Lite->new('::ffff:7f00:1');
	$emb = $ip->is_ipv6ipv4(); # TRUE

	$ip = Net::IP::Lite->new('::1');
	$emb = $ip->is_ipv6ipv4(); # FALSE

	$ip = Net::IP::Lite->new('127.1');
	$emb = $ip->is_ipv6ipv4(); # FALSE

See also: L</"ip_is_ipv6ipv4">

=head3 transform

Converts the IP address to an IP address string (or number).

	$ip = Net::IP::Lite->new('0:0:0:0:0:0:0:1');
	print $ip->transform({ short_ipv6 => 1 }); # ::1

See L</"ip_transform"> for all possible values of $opts.

=head3 equal

Compares two IP addresses.

	$ip = Net::IP::Lite->new('0:0:0:0:0:0:0:1');
	$eq = $ip->equal('::1'); # TRUE
	$eq = $ip->equal('::2'); # FALSE

	$ip1 = Net::IP::Lite->new('0:0:0:0:0:0:0:1');
	$ip2 = Net::IP::Lite->new('::1');
	$eq = $ip->equal($ip2); # TRUE

See also: L</"ip_equal">

=head3 equal_v4

Compares two IP addresses as IPv4 addresses.

	$ip = Net::IP::Lite->new('::ffff:127.0.0.1');
	$eq = $ip->equal_v4('127.0.0.1'); # TRUE

	$ip1 = Net::IP::Lite->new('::ffff:7f00:1');
	$ip2 = Net::IP::Lite->new('127.0.0.1');
	$eq = $ip->equal_v4($ip2); # TRUE

	$ip = Net::IP::Lite->new('::7f00:1');
	$eq = $ip->equal_v4('127.0.0.1'); # dies

See also: L</"ip_equal_v4">

=head3 equal_v6

Compares two IP addresses as IPv6 addresses.

	$ip = Net::IP::Lite->new('::ffff:127.0.0.1');
	$eq = $ip->equal_v6('127.0.0.1'); # TRUE

	$ip1 = Net::IP::Lite->new('::ffff:7f00:1');
	$ip2 = Net::IP::Lite->new('127.0.0.1');
	$eq = $ip->equal_v6($ip2); # TRUE

See also: L</"ip_equal_v6">

=head3 in_range

Verifies whether the IP in a range.

	$ip = Net::IP::Lite->new('10.10.10.10');
	$in = $ip->in_range('10.10.10.8/29'); # TRUE
	$in = $ip->in_range([ '192.168.0 255.255.255.0', '10.0/8' ]); # TRUE

See also: L</"ip_in_range">

Apart from string IP addresses you specify Net::IP::Lite::Net object:

	$ip  = Net::IP::Lite->new('10.10.10.10');
	$net = Net::IP::Lite::Net->new('10.0/8') || die ...;
	$in = $ip->in_range($net); # TRUE

	$net = Net::IP::Lite::Net->new('1::/16') || die ...;
	$in = $ip->in_range($net);               # FALSE
	$in = $ip->in_range([ $net, '10.0/8' ]); # TRUE

See also: L</"Net::IP::Lite::Net object">

=head2 Net::IP::Lite::Net object

=head3 constructor

The Net::IP::Lite::Net class is a descendant of Net::IP::Lite. 

	$net = Net::IP::Lite::Net->new('10.0/8') || die ...;
	$net = Net::IP::Lite::Net->new('10.0.0.8 255.255.255.248') || die ...;
	$net = Net::IP::Lite::Net->new('1::/16') || die ...;
	$net = Net::IP::Lite::Net->new('1:: ffff::') || die ...;

Please note: Net::IP::Lite::Net allows you to create an network (without
possible hosts).

For example:

	$net = Net::IP::Lite::Net->new('10.10.10.8/28');

You can use the <L</"contains"> method to check whether there are any possible
hosts or not.

All Net:IP::Lite methods return the same values as if you created
Net::IP::Lite object without a mask.

	$net = Net::IP::Lite::Net->new('1:: ffff::') || die ...;
	print $net->address; # 1::

See also: L</"Net::IP::Lite object">

=head3 mask

Returns Net::IP::Lite instance for the network mask;

	$net = Net::IP::Lite::Net->new('1:: ffff::') || die ...;
	print $net->mask->address(); # ffff::

	$net = Net::IP::Lite::Net->new('1::/32') || die ...;
	print $net->mask->address(); # ffff:ffff:0:0:0:0:0:0

	$net = Net::IP::Lite::Net->new('10.0/8') || die ...;
	print $net->mask->binary(); # 11111111000000000000000000000000

=head3 network

Returns the original network definition that was specified as the constructor
argument.

	my $net = Net::IP::Lite::Net->new('1::/32') || die ...;
	print $net->network(); # 1::/32

=head3 contains

Verifies whether an IP in the net.

	$net = Net::IP::Lite::Net->new('1:: ffff::') || die ...;
	$in = $net->contains('1:ff::1'); # TRUE

Also you can pass an Net::IP::Lite object:

	$ip = Net::IP::Lite::Net->new('1::1') || die ...;
	$net = Net::IP::Lite::Net->new('1::/32') || die ...;
	my $in = $net->contains($ip); # TRUE

This method also can be used to check whether there are possible hosts
in the network or not:

	$net = Net::IP::Lite::Net->new('10.10.10.8/28');
	$ok = $net->contains($net); # FALSE

	$net = Net::IP::Lite::Net->new('10.10.10.8/29');
	$ok = $net->contains($net); # TRUE

See also: L</"ip_in_range">, L</"Net::IP::Lite object">

=head1 SEE ALSO

L<NET::IP>, L<NetAddr::IP>, L<NetAddr::IP::Lite>

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

	perldoc Net::IP::Lite

You can also look for information at:

=over

=item * Code Repository at GitHub

L<http://github.com/alexey-komarov/Net-IP-Lite>

=item * GitHub Issue Tracker

L<http://github.com/alexey-komarov/Net-IP-Lite/issues>

=item * RT, CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-IP-Lite>

=back

=head1 AUTHOR

Alexey A. Komarov E<lt>alexkom@cpan.orgE<gt>

=head1 COPYRIGHT

2013 Alexey A. Komarov

=head1 LICENSE

This library is free software; you may redistribute it and/or modify
it under the same terms as Perl itself.

=cut