# Use of the Net-Silk library and related source code is subject to the
# terms of the following licenses:
# 
# GNU Public License (GPL) Rights pursuant to Version 2, June 1991
# Government Purpose License Rights (GPLR) pursuant to DFARS 252.227.7013
# 
# NO WARRANTY
# 
# ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER 
# PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY 
# PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN 
# "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY 
# KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT 
# LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE, 
# MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE 
# OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT, 
# SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY 
# TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF 
# WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES. 
# LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF 
# CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON 
# CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE 
# DELIVERABLES UNDER THIS LICENSE.
# 
# Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie 
# Mellon University, its trustees, officers, employees, and agents from 
# all claims or demands made against them (and any related losses, 
# expenses, or attorney's fees) arising out of, or relating to Licensee's 
# and/or its sub licensees' negligent use or willful misuse of or 
# negligent conduct or willful misconduct regarding the Software, 
# facilities, or other rights or assistance granted by Carnegie Mellon 
# University under this License, including, but not limited to, any 
# claims of product liability, personal injury, death, damage to 
# property, or violation of any laws or regulations.
# 
# Carnegie Mellon University Software Engineering Institute authored 
# documents are sponsored by the U.S. Department of Defense under 
# Contract FA8721-05-C-0003. Carnegie Mellon University retains 
# copyrights in all material produced under this contract. The U.S. 
# Government retains a non-exclusive, royalty-free license to publish or 
# reproduce these documents, or allow others to do so, for U.S. 
# Government purposes only pursuant to the copyright license under the 
# contract clause at 252.227.7013.

package Net::Silk::Pmap;

use strict;
use warnings;
use Carp;

use Net::Silk qw( :basic );
use Net::Silk::IPSet;

use overload (
  '""'   => \&_str,
  '%{}'  => \&_me_hash,
  '<>'   => \&_fh_iter,
);

my %Attr;

use constant HASH   => 0;
use constant ITER   => 1;
use constant LABELS => 2;

###

sub load {
  my $class = shift;
  my $self  = $class->_load(@_);
  return $self->_is_ipaddr ? bless $self, SILK_PMAP_IP_CLASS
                           : bless $self, SILK_PMAP_PP_CLASS;
}

sub _fh_iter {
  my $self = shift;
  my $iter = $Attr{$self}{fh_iter} ||= $self->iter;
  if (wantarray) {
    delete $Attr{$self}{fh_iter};
    return $iter->();
  }
  else {
    while ($_ = $iter->()) {
      return $_;
    }
    delete $Attr{$self}{fh_iter};
    return;
  }
}

sub iter {
  my $self = shift;
  my $iter = Net::Silk::Pmap::iter_xs->bind($self);
  my $vals = ($Attr{$self} ||= [])->[LABELS] ||= {};
  my $next = $self->_is_ipaddr ? \&Net::Silk::Pmap::iter_xs::next_ip
                               : \&Net::Silk::Pmap::iter_xs::next_pp;
  sub {
    return unless $iter;
    if (wantarray) {
      my @items;
      while (my @res = $next->($iter)) {
        $res[-1] = $vals->{$res[-1]} ||=
                   Net::Silk::Pmap::get_val($self, $res[-1]);
        push(@items, \@res);
      }
      $iter = undef;
      return @items;
    }
    my @res = $next->($iter);
    if (!@res) {
      $iter = undef;
      return;
    }
    $res[-1] = $vals->{$res[-1]} ||=
               Net::Silk::Pmap::get_val($self, $res[-1]);
    \@res;
  };
}

*iter_ranges = *iter_pmap = \&iter;

sub iter_vals {
  my $self = shift;
  my $max  = $self->val_count - 1;
  my $vals = ($Attr{$self} ||= [])->[LABELS] ||= {};
  my $c    = 0;
  sub {
    if (wantarray) {
      my @items;
      foreach (0 .. $max) {
        push(@items, $vals->{$_} ||= Net::Silk::Pmap::get_val($self, $_));
      }
      return @items;
    }
    while ($c <= $max) {
      my $val = $vals->{$c} ||= Net::Silk::Pmap::get_val($self, $c);
      ++$c;
      return $val;
    }
    $c = 0;
    return;
  };
}

sub iter_keys {
  my $self = shift;
  my $iter = $self->iter;
  sub {
    $iter || return;
    if (wantarray) {
      my @keys;
      while (my $r = $iter->()) {
        pop @$r;
        push(@keys, $r);
      }
      $iter = undef;
      return @keys;
    }
    else {
      while (my $r = $iter->()) {
        pop @$r;
        return $r;
      }
      $iter = undef;
      return;
    }
  };
}

sub invert {
  my $self = shift;
  my $iter = $self->iter;
  my %sets;
  if ($self->_is_ipaddr) {
    while (my $r = $iter->()) {
      my $s = $sets{$r->[-1]} ||= SILK_IPSET_CLASS->new;
      $s->add_range($r->[0], $r->[1]);
    }
  }
  else {
    while (my $r = $iter->()) {
      my $s = $sets{$r->[-1]} ||= [];
      push(@$s, $r);
    }
  }
  wantarray ? %sets : \%sets;
}

sub _str { overload::StrVal($_[0]) }

sub _me_hash {
  my $self = shift;
  my $attr = $Attr{$self} ||= [];
  my $hash = $attr->[HASH];
  if (!$hash) {
    $hash = $attr->[HASH] = {};
    tie(%$hash, $self);
  }
  $hash;
}

sub DESTROY {
  my $self = shift;
  delete $Attr{$self};
  Net::Silk::Pmap::_destroy($self);
}

### tied hash

sub TIEHASH {
  my $class = shift;
  my $self;
  if (ref $class) {
    $self  = $class;
    $class = ref $self;
  }
  else {
    $self = $class->new;
  }
  $self;
}

*FETCH = \&get;

sub EXISTS { defined( shift->get(@_) ) ? 1 : 0 }

sub FIRSTKEY {
  my $self = shift;
  my $attr = $Attr{$self} ||= [];
  $attr->[ITER] = $self->iter_keys();
  $self->NEXTKEY();
}

sub NEXTKEY {
  my $self = shift;
  my $attr = $Attr{$self} ||= [];
  my $iter = $attr->[ITER] ||= $self->iter_keys();
  my $v = $iter->();
  defined $v ? $v : ($attr->[ITER] = undef);
}

###

package Net::Silk::Pmap::IP;

use strict;
use warnings;
use Carp;

use base qw( Net::Silk::Pmap );

sub get {
  my $self = shift;
  croak "invalid ip (multiple arguments)" if @_ > 1;
  $self->SUPER::get(@_);
}

###

package Net::Silk::Pmap::PP;

use strict;
use warnings;
use Carp;

use base qw( Net::Silk::Pmap );

use Scalar::Util qw( looks_like_number );

use Net::Silk qw( :basic );
use Net::Silk::ProtoPort;

sub get {
  my $self = shift;
  my $pp;
  if (@_ == 1 && UNIVERSAL::isa($_[0], SILK_PROTOPORT_CLASS)) {
    $pp = shift;
  }
  else {
    $pp = SILK_PROTOPORT_CLASS->new(@_);
  }
  $self->SUPER::get($pp);
}

*FETCH = \&get;

###

1;

__END__

=head1 NAME

Net::Silk::Pmap - SiLK Prefix Map interface

=head1 SYNOPSIS

  use Net::Silk::Pmap;
  use Net::Silk::IPSet;

  my $addys  = Net::Silk::IPSet->load("netblocks.set");
  my $labels = Net::Silk::Pmap->load("services.pmap");

  while (my $ip = <$addys>) {
    my $svc = $labels{$ip}; # or $labels->get($ip)
    print "$ip $svc" unless $svc eq 'unknown';
  }

  while (my $block = <$labels>) {
    my($lo, $hi, $label) = @$block;
    my $block = Net::Silk::IPSet->new("$lo-$hi");
    if ($block && $addys) {
      print "$lo-$hi $label\n";
    }
  }

=head1 DESCRIPTION

C<Net::Silk::Pmap> objects are an interface to SiLK Prefix Maps. Prefix
maps are an immutable mapping from IP addresses or protocol/port pairs
to labels. Pmaps are created from SiLK prefix map files as created by
L<rwpmapbuild(1)>.

=head1 METHODS

=over

=item load($pmap_file)

Returns a new C<Net::Silk::Pmap::IP> or C<Net::Silk::Pmap::PP> object,
depending on whether the given file uses IP addresses or protocol/port
pairs for keys.

=item get($proto, $port)

=item get($ip_or_pp)

Returns the label for the given IP address or protocol/port pair. For
the single argument version, the key can be a L<Net::Silk::IPAddr>
object or a L<Net::Silk::ProtoPort> object, or a string representation
of either.

=item iter()

Returns a sub ref iterator. Each invocation returns an array reference
containing low, high, and label. Low and high are the range values for
which the label applies, returned as L<Net::Silk::IPAddr> or
L<Net::Silk::ProtoPort> objects depending on the type of pmap file.
Using the IO operator C<E<lt>E<gt>> on the pmap reference will also
iterate over low/high/label values.

=item iter_vals()

Returns a sub ref iterator that returns each label value in the pmap.

=item iter_keys()

Returns a sub ref iterator that returns an array reference containing
the low and high value for each range present in the pmap.

=item invert()

For an IP address pmap, return a hash that maps labels to
L<Net::Silk::IPSet> objects representing the all of the IP ranges
for that label. For a protocol/port pmap, return a hash mapping
labels to a list of all the proto/port ranges for that label.

=back

=head1 OPERATORS

The IO operator C<E<lt>E<gt>> works on C<Net::Silk::Pmap> references,
returning the same results as the iterator returned from the
C<iter()> method.

=head1 TIED HASH

The pmap reference can be treated as a hash reference, so that
C<$pmap-E<gt>{$key}>, C<keys %$pmap>, etc, work as expected.

=head1 SEE ALSO

L<Net::Silk>, L<Net::Silk::RWRec>, L<Net::Silk::IPSet>, L<Net::Silk::IPWildcard>, L<Net::Silk::Bag>, L<Net::Silk::IPAddr>, L<Net::Silk::TCPFlags>, L<Net::Silk::ProtoPort>, L<Net::Silk::File>, L<Net::Silk::Site>, L<silk(7)>

=head1 COPYRIGHT & LICENSE

Copyright (C) 2011-2015 by Carnegie Mellon University

Use of the Net-Silk library and related source code is subject to the
terms of the following licenses:

GNU Public License (GPL) Rights pursuant to Version 2, June 1991
Government Purpose License Rights (GPLR) pursuant to DFARS 252.227.7013

NO WARRANTY

See GPL.txt and LICENSE.txt for more details.

=cut
