# 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::Nmsg::Handle;

use strict;
use warnings;
use Carp;

use Net::Nmsg::Util qw( :io :sniff );

use IO::File;
use IO::Socket::INET;

use constant FILE_IO        => 'IO::File';
use constant SOCKET_IPV4_IO => 'IO::Socket::INET';
use constant SOCKET_IPV6_IO => 'IO::Socket::INET6';
use constant PCAP_IO        => 'Net::Nmsg::IO::Pcap';
use constant CALLBACK_IO    => 'Net::Nmsg::IO::Callback';

###

sub open_input_file  { shift->_open_file(r => @_) }
sub open_output_file { shift->_open_file(w => @_) }

sub _open_file {
  my $class = shift;
  my($mode, $spec) = @_;
  $mode ||= 'r';
  my $fh = $class->FILE_IO->new;
  if (defined (my $fd = fileno($spec))) {
    $fh->fdopen($fd, $mode) || die $!;
  }
  else {
    $fh->open($spec, $mode) || die $!;
  }
  $fh;
}

###

sub _sock_spec_to_opt {
  my $class = shift;
  return unless @_;
  my($host, $port);
  if (@_ % 2) {
    ($host, $port) = parse_socket_spec(shift);
  }
  else {
    ($host, $port) = splice(@_, 0, 2);
  }
  return($host, $port, @_);
}

sub _make_sock {
  my $class = shift;
  return unless @_;
  my %opt = @_;
  my $s = $class->SOCKET_IPV4_IO->new(%opt);
  if (!$s) {
    die "Problem creating socket: $!\n" unless $! =~ /invalid\s+arg/i;
    my $msg = $!;
    my $ipv6_class = $class->SOCKET_IPV6_IO;
    eval "use $ipv6_class";
    die "No fallback from \"$msg\" : $@" if $@;
    $s = $ipv6_class->new(%opt) or die "Problem creating socket: $!\n";
  }
  $s;
}

sub open_input_sock {
  my $class = shift;
  die "spec required" unless @_;
  my $spec = shift;
  if (! is_socket($spec)) {
    my($host, $port, %opt) = $class->_sock_spec_to_opt($spec, @_);
    my %sopt = (
      LocalAddr => $host,
      LocalPort => $port,
      Proto     => 'udp',
      Type      => SOCK_DGRAM,
      ReuseAddr => 1,
      ReusePort => 1,
    );
    eval { $spec = $class->_make_sock(%sopt) };
    if ($@) {
      #print STDERR "TRY AGAIN minus ReusePort\n";
      delete $sopt{ReusePort};
      $spec = $class->_make_sock(%sopt);
    }
    #print STDERR "SOCK RESULT: ", $spec || 'undef', "\n";
    $spec || return;
    my $rcvbuf = delete $opt{rcvbuf} || NMSG_DEFAULT_SO_RCVBUF;
    eval { $spec->sockopt(SO_RCVBUF => $rcvbuf) };
  }
  #print STDERR "open_input_sock() returns $spec\n";
  $spec;
}

sub open_output_sock {
  my $class = shift;
  die "spec required" unless @_;
  my $spec = shift;
  if (! is_socket($spec)) {
    my($host, $port, %opt) = $class->_sock_spec_to_opt($spec, @_);
    my %sopt = (
      PeerAddr  => $host,
      PeerPort  => $port,
      Proto     => 'udp',
      Type      => SOCK_DGRAM,
      Broadcast => $opt{broadcast} ? 1 : 0,
    );
    $spec = $class->_make_sock(%sopt) || return;
    my $sndbuf = delete $opt{sndbuf} || NMSG_DEFAULT_SO_SNDBUF;
    eval { $spec->sockopt(SO_SNDBUF => $sndbuf) };
  }
  $spec;
}

###

sub open_input_pcap_file {
  my $class = shift;
  my($spec, %opt) = @_;
  $class->PCAP_IO->open_file($spec, bpf => $opt{bpf});
}

sub open_input_pcap_iface {
  my $class = shift;
  my($spec, %opt) = @_;
  $class->PCAP_IO->open_iface(
    $spec,
    bpf     => $opt{bpf},
    snaplen => $opt{snaplen},
    promisc => $opt{promisc},
  );
}

###

sub open_output_cb { shift->CALLBACK_IO->open(shift) }

######## IO::Handle mockups

package Net::Nmsg::IO::Pcap;

use strict;
use warnings;
use Carp;

use base qw( Net::Nmsg::Layer );

use Net::Nmsg::Util qw( :io );

use constant NMSG_PCAP_XS => 'Net::Nmsg::XS::nmsg_pcap';
use constant PCAP_XS      => 'Net::Nmsg::XS::pcap';

my %Defaults = (
  snaplen => NMSG_DEFAULT_SNAPLEN,
  promisc => 0,
  bpf     => undef,
);

sub _defaults { \%Defaults }

sub get_bpf     { shift->_get_xs_opt('bpf'    ) }
sub get_snaplen { shift->_get_io_opt('snaplen') }
sub get_promisc { shift->_get_io_opt('promisc') }

sub set_bpf     { shift->_set_xs_opt(bpf     => @_) }
sub set_snaplen { shift->_set_io_opt(snaplen => @_) }
sub set_promisc { shift->_set_io_opt(promisc => @_) }

sub open {
  my($self, $spec, $fatal, %opt) = shift->_open_init(@_);
  if (is_file($spec)) {
    $self->open_file($spec, %opt) || ($fatal ? croak $self->error : return);
  }
  elsif (is_interface($spec)) {
    $self->open_iface($spec, %opt) || ($fatal ? croak $self->error : return);
  }
  else {
    $self->error("not a file or interface (got root?)");
    return unless $fatal;
    croak $self->error;
  }
  $self;
}

sub open_file {
  my($self, $spec, $fatal, %opt) = shift->_open_init(@_);
  my $pcap;
  eval { $pcap = $self->PCAP_XS->open_offline($spec) };
  $@ && $self->error($@) && ($fatal ? croak $self->error : return);
  eval { return $self->_open_pcap_handle($pcap, %opt) };
  $@ && $self->error($@) && ($fatal ? croak $self->error : return);
  $self;
}

sub open_iface {
  my($self, $spec, $fatal, %opt) = shift->_open_init(@_);
  my $snaplen = defined $opt{snaplen} ? $opt{snaplen} : NMSG_DEFAULT_SNAPLEN;
  my $promisc = defined $opt{promisc} ? $opt{promisc} : NMSG_DEFAULT_PROMISC;
  my $pcap;
  eval { $pcap = $self->PCAP_XS->open_live($spec, $snaplen, $promisc) };
  $@ && $self->error($@) && ($fatal ? croak $self->error : return);
  eval { $self->_open_pcap_handle($pcap, %opt) };
  $@ && $self->error($@) && ($fatal ? croak $self->error : return);
  $self;
}

sub _open_pcap_handle {
  my $self = shift;
  my $pcap = shift || die "pcap handle required";
  my $nmsg_pcap;
  eval { $nmsg_pcap = $self->NMSG_PCAP_XS->open_input($pcap) }; 
  $@ && croak $@;
  *$self->{_io} = $pcap;
  *$self->{_xs} = $nmsg_pcap;
  $self->_init_opts(@_);
  $self->_dup_io_r($self->fileno);
  $self;
}

### IO layer

sub blocking { }

sub eof { }

sub is_live { (shift->_xs || return)->get_type == NMSG_PCAP_TYPE_LIVE }

sub error {
  my $self = shift;
  *$self->{error} = shift if @_;
  return *$self->{error} if defined *$self->{error};
  ($self->_io || return)->get_error;
}

sub opened {
  my $self = shift;
  $self->is_live || defined($self->fileno);
}

sub fileno {
  my $self = shift;
  $self->is_live
    ? ($self->_io || return)->get_selectable_fd
    : ($self->_io || return)->fileno;
}

sub stat {
  my $self = shift;
  $self->is_live
    ? $self->_fake_stat
    : stat ($self->_io || return)->fileno;
}

########

package Net::Nmsg::IO::Callback;

use strict;
use warnings;
use Carp;

use base qw( Net::Nmsg::Layer );

sub open {
  my($self, $cb, $fatal, %opt) = shift->_open_init(@_);
  if (! ref $cb || ref $cb ne 'CODE') {
    $self->error("not a CODE reference");
    $fatal ? croak $self->error : return;
  }
  *$self->{count} = 0;
  *$self = $cb;
  $self;
}

sub close {
  my $self = shift;
  undef(&{*$self});
  $self->SUPER::close;
}

sub opened { defined *{shift()}->{count} }

sub write { *{shift()}->(@_) }

sub getpos { *{shift()}->{count} }

*tell = \&getpos;

sub stat { shift->_fake_stat }

1;