The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Net::DNS::RR::HIP;
#
# $Id: HIP.pm 979 2012-01-27 08:43:38Z willem $
#
# See RFC 5205 for the specification of this record.

use strict;
use MIME::Base64;
use Data::Dumper;

use vars qw(@ISA $VERSION);

@ISA     = qw(Net::DNS::RR);
$VERSION = (qw$LastChangedRevision: 718 $)[1];






sub new {
	my ($class, $self, $data, $offset) = @_;

        if ($self->{'rdlength'} > 0) {
                my $offset_pkalgorithm  = $offset+1;
                my $offset_pklength     = $offset+2;
                my $offset_hit          = $offset+4;

                $self->{'_hitlength'} = unpack('C', substr($$data, $offset, 1));
		my $offset_pubkey    = $offset_hit + $self->{'_hitlength'};

                $self->{'pkalgorithm'}    = unpack('C', substr($$data, $offset_pkalgorithm, 1));
                $self->{'_pklength'}    = unpack('n', substr($$data, $offset_pklength, 2));
		my $offset_rendezvous    = $offset_pubkey + $self->{'_pklength'};

                $self->{'hitbin'}    = substr($$data, $offset_hit, $self->{'_hitlength'});
		$self->{'hit'}=unpack("H*",$self->{'hitbin'});

		$self->{'pubkeybin'}= substr($$data,$offset_pubkey, $self->{'_pklength'});
		$self->{'pubkey'}=encode_base64($self->{'pubkeybin'},"");
                my $rsoffset    = $offset_pubkey + $self->{'_pklength'};
		$self->{'rendezvousservers'}= [];



		my $i=10;
		while( ($rsoffset-$offset)<$self->{'rdlength'}){
			exit unless $i--;
			my ($name, $nextoffset) = Net::DNS::Packet::dn_expand($data, $rsoffset);
			push (@{$self->{'rendezvousservers'}},$name);
			$rsoffset=$nextoffset;

		}
	}

	return bless $self, $class;
}



sub new_from_string {
	my ($class, $self, $string) = @_;
	# first turn multiline into single line
	$string =~ tr/()//d if $string;
	$string =~ s/\n//mg if $string;

	if ($string && ($string =~ /^\s*(\d+)\s+(\S+)\s+(.*)$/)) {
		@{$self}{qw(pkalgorithm hit)} = ($1, $2);
		$self->{'hitbin'}=pack("H*",$self->{'hit'});
		my $reststring=$3;
		# rest string are the space separated components of the base64 encoded public key
		# appended by fully qualified domain names.
		# We'll chop off the FQDNs
		$self->{'rendezvousservers'}=[];
		while ($reststring =~ s/^(.*)(\s+((\S+\.)(\S+\.?)*))\s*$/$1/s){
			unshift (@{$self->{'rendezvousservers'}},$3);
		}
		$reststring=~s/\s//g;
		return () if (length($reststring) % 4); #base64 length should be mulitple of 4
		$self->{'pubkey'}=$reststring;
		$self->{'pubkeybin'} =  decode_base64( $self->{'pubkey'} );
      	}

	return bless $self, $class;
}




sub rr_rdata {
	my ($self, $packet, $offset) = @_;
	my $rdata = "";

	if (exists $self->{"pubkey"}) {
		# This is for consistency.
		my $hitbin=$self->hitbin();
		my $pubkeybin=$self->pubkeybin();
		$rdata = pack("C", $self->{'_hitlength'});
		$rdata .= pack("C", $self->{'pkalgorithm'});
		$rdata .= pack("n", $self->{'_pklength'});
		$rdata .= $hitbin;
		$rdata .= $pubkeybin;
		foreach my $dname (@{$self->{'rendezvousservers'}}){
			$rdata .= $self->_name2wire ($dname);
		}
	}

	return $rdata;
}



sub rdatastr {
	my $self = shift;
	my $rdatastr='';

	if (exists $self->{"pubkey"}) {
		$rdatastr = $self->pkalgorithm       . ' '   .
		            $self->hit  . ' '  .
		            $self->pubkey       . ' ';

		foreach my $dname ( @{$self->rendezvousservers()} ) {
			$rdatastr .= $dname.". ";
		}
		chop $rdatastr;

	}

	return $rdatastr;
}


sub hitbin {
	my ($self, $new_val) = @_;
	if (defined $new_val) {
		$self->{'hitbin'} = $new_val;
		$self->{'hit'}=unpack("H*",$new_val);
	}
	$self->{'hitbin'}=pack("H*",$self->{'hit'}) unless defined ($self->{'hitbin'});
	$self->{'_hitlength'} =length($self->{'hitbin'});
	return ($self->{'hitbin'});
}


sub hit {
	my ($self, $new_val) = @_;
	if (defined $new_val) {
		$self->{'hitbin'} = $new_val;
		$self->{'hitbin'}=pack("H*",$new_val);
	}
	$self->{'hit'}=unpack("H*",$self->{'hitbin'}) unless defined ($self->{'hit'});
	$self->{'_hitlength'} =length($self->{'hitbin'});
	return ($self->{'hit'});
}




sub pubkeybin {
	my ($self, $new_val) = @_;
	if (defined $new_val) {
		$self->{'pubkeybin'} = $new_val;
		$self->{'pubkey'}=encode_base64($self->{'pubkeybin'},"");

	}
	$self->{'pubkeybin'}= decode_base64($self->{'pubkey'}) unless defined ($self->{'pubkeybin'});
	$self->{'_pklength'} =length($self->{'pubkeybin'});
	return ($self->{'pubkeybin'});
}






sub pubkey {
	my ($self, $new_val) = @_;
	if (defined $new_val) {
		$self->{'pubkey'} = $new_val;
		$self->{'pubkeybin'}=decode_base64($self->{'pubkey'});

	}
	$self->{'pubkey'}= encode_base64($self->{'pubkeybin'},"") unless defined ($self->{'pubkey'});
	$self->{'_pklength'} =length($self->{'pubkeybin'});
	return ($self->{'pubkey'});
}





sub _normalize_dnames {
	my $self=shift;
	$self->_normalize_ownername();
	$self->{'rendezvousservers'} ||= [];
	my @dnames = @{$self->{'rendezvousservers'}};
	$self->{'rendezvousservers'}=[];
	foreach my $dname (@dnames){
		push (   @{$self->{'rendezvousservers'}},   Net::DNS::stripdot($dname) )
	}


}


sub rendezvousservers {
	my ($self, $new_val) = @_;

	if ($new_val) {
		$self->{'rendezvousservers'}= $new_val;
	}

	$self->_normalize_dnames();
	return $self->{'rendezvousservers'};

}










=head1 NAME

Net::DNS::RR::HIP - DNS HIP resource record

=head1 SYNOPSIS

C<use Net::DNS::RR>;

=head1 DESCRIPTION

This class implements the HIP RR (RFC5205)


=head1 METHODS

=head2 pkalgorithm

Returns or sets the public key algorithm field

=head2 hit

Returns or sets the hit in base16 representation.

=head2 hitbin

Returns or sets the binary representation of the the hit.

Using hit or hitbin to set the one of these attributes will update both attributes.

=head2 pubkey

Returns or sets the publick key in base64 representation.

=head2 pubkey

Returns or sets the binary representation of the the public key.

Using pubkey or pubkeybin to set the one of these attributes will update both attributes.



=head2 rendezvousservers


      my $rendezvousservers=$hip->rendezvousservers();

Returns a reference to an array of rendezvous servers. The representation is in
Perl's internal storage format i.e. without trailing dot.

     $hip->rendezvousservers( [ qw|example.com  example.net| ] )

With a reference to an array as the argument this method will set the rendezvousservers.



=head1 NOTES

Since (multiline) base64 encoded publik keys may contain spaces string
parsing of the HIP RR depends on rendevous server names containing at
least one . (dot) in their domain name. Failure of string parsing will
return an 'undef'.

The rdatastr method (and hence the string and print methods) return the
rendezvousservers as fully qualified domain names.



=head1 COPYRIGHT

Copyright (c) 2009 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<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 5205


=cut




1;