package Net::DNS::RR::APL::ApItem;
#
# $Id: ApItem.pm 764 2008-12-24 09:36:37Z olaf $
#


use Net::DNS::Resolver::Base;
use Socket;
use strict;
BEGIN { 
    eval { require bytes; }
} 
use vars qw(@ISA $VERSION);

@ISA     = qw(Net::DNS::RR);

$VERSION = (qw$LastChangedRevision: 684 $)[1];


use Data::Dumper;



=head1 NAME

Net::DNS::RR::APL::ApItem - DNS APL  ApItem 


=head1 SYNOPSIS

use Net::DNS::RR::APL::ApItem;  

my $apitem=Net::DNS::RR::APL::ApItem->new();

=head1 DESCRIPTION

This is an object that contains the Address Prefixes with related
information such as address family and possible negation flags for the
APL RR. Please see the RFC3123 for details.


=head1 Methods

=head2 new

Constructor method.

$apitem[0]=Net::DNS::RR::APL::ApItem->new(1:192.168.32.0/21);
$apitem[1]=Net::DNS::RR::APL::ApItem->new(!1:192.168.32.0/21);
$apitem[2]=Net::DNS::RR::APL::ApItem->new(2:FF00:0:0:0:0:0:0:0/8);

$empty=Net::DNS::RR::APL::ApItem();

Returns undef if the argument cannot be parsed.
Returns an object with empty attributes when no argument is supplied.


=cut

sub new {
	my $caller = shift;
	my $class = ref($caller) || $caller;
	my $self={
		  address => '',
		  addressfamily => '',
		  negation => '',
		  prefix => '',
		 };
	my $input=shift;
	bless $self, $class;
	return $self->parse($input) if defined($input);  # returns undef when parse fails
	return $self;
}



#  The textual representation of an APL RR in a DNS zone file is as
#   follows:
#
#   <owner>   IN   <TTL>   APL   {[!]afi:address/prefix}*
#
#   The data consists of zero or more strings of the address family
#   indicator <afi>, immediately followed by a colon ":", an address,
#   immediately followed by the "/" character, immediately followed by a
#   decimal numeric value for the prefix length.  Any such string may be
#   preceded by a "!" character.  The strings are separated by
#   whitespace.  The <afi> is the decimal numeric value of that
#   particular address family.

sub parse {
	my $self=shift;
	my $input=shift;
	if($input=~/(!?)([12])\:(.+)\/(\d{1,3})/){
		my $negation=$1;
		my $addressfamily=$2;
		my $address=$3;
		my $prefix=$4;
		# Sanity check
		return if ( ($addressfamily == 1) && (! Net::DNS::Resolver::Base::_ip_is_ipv4($address) || $prefix > 32 )); 
		return if ( ($addressfamily == 2) && (! Net::DNS::Resolver::Base::_ip_is_ipv6($address) || $prefix > 128 )); 
		return if ( $addressfamily > 2 );

		$self->address($address);
		$self->addressfamily($addressfamily);
		$self->prefix($prefix);
		$self->negation($negation);
		 
		return $self;
	}
	#Pattern match failed.
	return;
}
sub print {	print &string, "\n"; }



=head2 string

Returns a string representation of the object in the  '[!]afi:address/prefix' format
   $apl=$apitem[0]->string;


=head2 print

Prints the textual representation of the object.
   $apitem[0]->print;
   # same functionality as:
   $apl=$apitem[0]->string;
   print $apl;


=cut

sub string {
	my $self=shift;
	my $dat= $self->negation ? "!" : "";
	$dat .= $self->addressfamily() .":" .
	  $self->address."/".$self->prefix;
	return $dat;
}




# APL RDATA format
#
#   The RDATA section consists of zero or more items (<apitem>) of the
#   form
#
#      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
#      |                          ADDRESSFAMILY                        |
#      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
#      |             PREFIX            | N |         AFDLENGTH         |
#      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
#      /                            AFDPART                            /
#      |                                                               |
#      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
#
#      ADDRESSFAMILY     16 bit unsigned value as assigned by IANA
#                        (see IANA Considerations)
#      PREFIX            8 bit unsigned binary coded prefix length.
#                        Upper and lower bounds and interpretation of
#                        this value are address family specific.
#      N                 negation flag, indicates the presence of the
#                        "!" character in the textual format.  It has
#                        the value "1" if the "!" was given, "0" else.
#      AFDLENGTH         length in octets of the following address
#                        family dependent part (7 bit unsigned).
#      AFDPART           address family dependent part.  See below.
#
#  @self

sub rdata {
	my $self=shift;
	my $rdata;
	return unless ((exists $self->{'addressfamily'}) &&
		       (exists $self->{'address'}) &&
		       (exists $self->{'prefix'}));
	
	if ($self->{'addressfamily'}==1){
		$rdata=pack ("n",0x0001);
		$rdata.=pack ("C",$self->prefix());
		my $data .= &stripzerobytes(inet_aton($self->address()));
		$rdata.=pack("C", ($self->negation()? (0x80|length($data)):length($data) ));
		$rdata.=$data;
	}elsif ($self->{'addressfamily'}==2){
		$rdata=pack ("n",0x0002);
		$rdata.=pack ("C",$self->prefix());
		my @addr = split(/:/, $self->{"address"});
		my $data=&stripzerobytes(pack("n8", map { hex $_ } @addr ));
		$rdata.=pack("C", ($self->negation()? (0x80|length($data)):length($data) ));
	        $rdata.=$data;
	}else{
		#NOT Defined, return empty A data
		return;
	}
	
	return $rdata;
}


sub stripzerobytes {
	my @a = unpack('C*',shift);
	my @b;
	my $notseenval=1;
	while (defined(my $a=pop( @a))){
		unshift @b,$a unless ($a==0 && $notseenval);
		next unless $notseenval;
		$notseenval= !$a;
	}
	return pack("C*",@b);
}

sub new_from_wire {
	my $caller = shift;
	my $class = ref($caller) || $caller;
	my ($data, $offset) = @_;
	
	my $orgoffset=$offset;
	my $self={
		  address => '',
		  addressfamily => '',
		  negation => '',
		  prefix => '',
		 };
	bless $self, $class;

	$self->addressfamily( unpack('n',substr($data,$offset,2)));
	$offset+=2;
	$self->prefix( unpack('C',substr($data,$offset,1)));
	$offset+=1;
	my $negandlength=unpack('C',substr($data,$offset,1));
	$self->negation( 0x80 == ( $negandlength & 0x80));
	$offset+=1;
	my $length= $negandlength & 0x7f;
	return if (length($data)< $offset+$length);
	if ($self->addressfamily==1){
		# pack(Cy,unpack("C*",$foo)) is a hack to pad with trailing 0s.
		$self->{"address"} = 
		  inet_ntoa(  pack("C4",unpack("C*",substr($data, $offset,$length) )));
	}elsif($self->addressfamily==2){
		my $extracted=substr($data, $offset,$length);
		# pack(Cy,unpack("C*",$foo)) is a hack to pad with trailing 0s.
		my @addr = unpack("n8", pack("C16",unpack("C*",substr($data, $offset,$length) )));
		$self->{"address"} = sprintf("%x:%x:%x:%x:%x:%x:%x:%x", @addr);
	}else{
		#FORMERR
		return;
	}
	$offset+=$length;
	return $self,$offset;
}






=head1 COPYRIGHT

Copyright (c) 2008 Olaf Kolkman (NLnet Labs)

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

=head1 SEE ALSO

L<Net::DNS::RR::APL>,
L<perl(1)>, L<Net::DNS>, L<Net::DNS::Resolver>, L<Net::DNS::Packet>,
L<Net::DNS::Header>, L<Net::DNS::Question>, L<Net::DNS::RR>,
RFC 3123


=cut

