#!/usr/bin/perl
#
# $Id: ScanComparison.pm,v 1.14 2001/11/24 02:55:56 levine Exp $
#
# Copyright (c) 2000  James D. Levine (jdl@vinecorp.com)
#
#
#   This program is free software; you can redistribute it and/or
#   modify it under the terms of the GNU General Public License
#   as published by the Free Software Foundation; either version 2
#   of the License, or (at your option) any later version.
# 
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
# 
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 
#   02111-1307, USA.
#
####################################################################

#
# Encapsulates the notion of differences between two scans (ScanSet instances)
# 


use PortScan::SetOps;

package PortScan::ScanComparison;

use vars qw( @ISA );

@ISA = qw( Exporter );


sub new
{
    my ($type, $baseline, $observed, $all_scanned_ports) = @_;
    my $self = 
    {
	baseline => $baseline,	# the baseline ScanSet
	observed => $observed,	# the observed ScanSet
	all_scanned_ports => $all_scanned_ports, # union of baseling/observed ports
	to_open => {},		# hash of ports which changed to state 'open'
	to => 0,		# flags some ports changed to open
	to_closed => {},        # hash of ports which changed to state 'closed' 
	tc => 0,		# flags some ports changed to closed
	to_filtered => {},      # hash of ports which changed to state 'filtered'
	tf => 0,                # flags some ports changed to filtered
	to_unfiltered => {},    # hash of ports which changed to state 'unfiltered'
	tu => 0,                # flags some ports changed to unfiltered
	to_unknown => {},       # hash of ports which changed to state 'unknown'
	tunk >= 0,              # flags some ports changed to unknown
	differs => 0            # flags that the two scans differ
    };

    bless $self, $type;

    $self->compare();

    return $self;
}

# generic field accessor method
sub set_or_get
{
    my $field = shift;
    my $self = shift;
    my $val = shift;

    $self->{$field} = $val if defined $val;

    return $self->{$field};
}


# specific field accessors
# only call to query from outside, set for internal use only

# returns boolean
sub differs { set_or_get('differs', @_); }

# returns hash of ports which change to state 'open'
sub to_open { set_or_get('to_open', @_); }

# returns hash of ports which change to state 'closed'
sub to_closed { set_or_get('to_closed', @_); }

# returns hash of ports which change to state 'closed'
sub to_filtered { set_or_get('to_filtered', @_); }

# returns hash of ports which change to state 'unfiltered'
sub to_unfiltered { set_or_get('to_unfiltered', @_); }

# returns hash of ports which change to state 'unknown'
sub to_unknown { set_or_get('to_unknown', @_); }

# returns boolean: true if any ports changed to state 'open'
sub to { set_or_get('to', @_); }

# returns boolean: true if any ports changed to state 'closed'
sub tc { set_or_get('tc', @_); }

# returns boolean: true if any ports changed to state 'filtered'
sub tf { set_or_get('tf', @_); }

# returns boolean: true if any ports changed to state 'unfiltered'
sub tu { set_or_get('tu', @_); }

# returns boolean: true if any ports changed to state 'unknown'
sub tunk { set_or_get('tu', @_); }

# returns the baseline ScanSet instance 
sub baseline { set_or_get('baseline', @_); }

# returns the observed ScanSet instance 
sub observed { set_or_get('observed', @_); }

# returns the union of baseline/observed scanned ports sets (TODO: list? hash?)
sub all_scanned_ports { set_or_get('all_scanned_ports', @_); }

# compare the two ScanSets and set internal state as appropriate
sub compare
{
    my $self = shift;

    $self->{differs} = 0;

    my($baseline, $observed) = ($self->baseline(), $self->observed());


    my $baseline_ports = $baseline->all_ports();
    my $observed_ports = $observed->all_ports();

    my $to_open = $self->to_open();
    my $to_closed = $self->to_closed();
    my $to_filtered = $self->to_filtered();
    my $to_unfiltered = $self->to_unfiltered();
    my $to_unknown = $self->to_unknown();

#    print "before list_union \n";
#    my $all_port_list = PortScan::SetOps::list_union(
#						     [keys %$baseline_ports],
#						     [keys %$observed_ports]
#						     );
#    print "after list_union \n";

    my $all_ports_list = [];


    if ($baseline->default_state() eq $observed->default_state())
#if (1 == 0)
    {
	# if the Ignored State of both scans is the same, need only iterate
	# over the union of interesting ports of both scans...

#    print "before list_union \n";
	$all_port_list = PortScan::SetOps::list_union(
						      [ keys %{$baseline->port_specs()} ], 
						      [ keys %{$observed->port_specs()} ]
						      );
#    print "after list_union \n";
    }
    else
    {
#print "all_scanned_ports() called \n";
	# otherwise need to iterate over the entire list of scanned ports
	# and evaluate each, much slower

	$all_port_list = $self->all_scanned_ports();
    }
# print "all_port_list is $all_port_list \n";

  PORT:
    foreach $pk (@$all_port_list)
    {
#	print "checking $pk \n";
	my $bp = $baseline_ports->{$pk};
	my $op = $observed_ports->{$pk};

	next PORT if (!defined($bp) && !defined($op)); # no info available

	if (!defined $bp)	# not defined in baseline set, so create an unknown entry
	{
	    $bp = $op->clone();
	    $bp->state("unknown");
	}
	my $bs = $bp->state();

	if (!defined $op)	# not defined in observed set, so create an unknown entry
	{
	    $op = $bp->clone();
	    $op->state("unknown");
	}
	my $os = $op->state();

#	print "$pk -  $bs -> $os \n";

	if ($bs ne $os) 
	{
	    $self->{differs} = 1;

	    ($os eq "open")       && ($to_open->{$pk}       = [$bp, $op], $self->to(1), next PORT);
	    ($os eq "closed")     && ($to_closed->{$pk}     = [$bp, $op], $self->tc(1), next PORT);
	    ($os eq "filtered")   && ($to_filtered->{$pk}   = [$bp, $op], $self->tf(1), next PORT);
	    ($os eq "unfiltered") && ($to_unfiltered->{$pk} = [$bp, $op], $self->tu(1), next PORT);
	    ($os eq "unknown")    && ($to_unknown->{$pk}    = [$bp, $op], $self->tunk(1), next PORT);

	    # you are here?? <=== OOPS! port has no recognized state!!!!
	}
    }
#    print "after foreach \n";
}





1;







