#!/usr/bin/perl -w
#author: Jad Naous
#
# Test that the IP destination filter table works
#
use strict;
use NF2::RegressLib;
use NF2::PacketLib;
use RegressRouterLib;

use reg_defines_reference_router;

use constant NUM_PKTS => 40;

my @interfaces = ("nf2c0", "nf2c1", "nf2c2", "nf2c3", "eth1", "eth2");
nftest_init(\@ARGV,\@interfaces,);
nftest_start(\@interfaces,);

nftest_fpga_reset('nf2c0');
`sleep 1`;

my $routerMAC0 = "00:ca:fe:00:00:01";
my $routerMAC1 = "00:ca:fe:00:00:02";
my $routerMAC2 = "00:ca:fe:00:00:03";
my $routerMAC3 = "00:ca:fe:00:00:04";

my $routerIP0 = "192.168.0.40";
my $routerIP1 = "192.168.1.40";
my $routerIP2 = "192.168.2.40";
my $routerIP3 = "192.168.3.40";

my $dstIP0 = "192.168.0.50";
my $dstIP1 = "192.168.1.50";
my $dstIP2 = "192.168.2.50";
my $dstIP3 = "192.168.3.50";

my $dstMac0 = "aa:bb:cc:dd:ee:01";
my $dstMac1 = "aa:bb:cc:dd:ee:02";
my $dstMac2 = "aa:bb:cc:dd:ee:03";
my $dstMac3 = "aa:bb:cc:dd:ee:04";

my $ALLSPFRouters = "224.0.0.5";

######### You should skip this section for tests with router SCONE
# Write the mac and IP addresses doesn't matter which of the nf2c0..3 you write to.
nftest_add_dst_ip_filter_entry ('nf2c0', 0, $routerIP0);
nftest_add_dst_ip_filter_entry ('nf2c0', 1, $routerIP1);
nftest_add_dst_ip_filter_entry ('nf2c0', 2, $routerIP2);
nftest_add_dst_ip_filter_entry ('nf2c0', 3, $routerIP3);
nftest_add_dst_ip_filter_entry ('nf2c0', 4, $ALLSPFRouters);

# For these it does matter which interface you write to
nftest_set_router_MAC ('nf2c0', $routerMAC0);
nftest_set_router_MAC ('nf2c1', $routerMAC1);
nftest_set_router_MAC ('nf2c2', $routerMAC2);
nftest_set_router_MAC ('nf2c3', $routerMAC3);
#########

# Put the two ports in loopback mode. Pkts going out will come back in on
# the same port
nftest_phy_loopback('nf2c2');
nftest_phy_loopback('nf2c3');

nftest_regread_expect('nf2c0', MDIO_PHY_0_CONTROL_REG(), 0x1140);
nftest_regread_expect('nf2c0', MDIO_PHY_1_CONTROL_REG(), 0x1140);
nftest_regread_expect('nf2c0', MDIO_PHY_2_CONTROL_REG(), 0x5140);
nftest_regread_expect('nf2c0', MDIO_PHY_3_CONTROL_REG(), 0x5140);

# create mac header
my $ippkt = new NF2::IP_pkt(
	DA => $routerMAC0,
	SA => $dstMac3,
	src_ip => $dstIP3,
	dst_ip => $dstIP0,
	ttl => 64
	);

my $num_precreated = 100;

# precreate random sized packets
my @precreated0;
$ippkt->set( DA  => $routerMAC0, dst_ip => $dstIP0);
push @precreated0, nftest_precreate_pkts($num_precreated, $ippkt->packed);

$ippkt->set( DA  => $routerMAC0, dst_ip => $dstIP1);
push @precreated0, nftest_precreate_pkts($num_precreated, $ippkt->packed);

$ippkt->set( DA  => $routerMAC0, dst_ip => $dstIP2);
push @precreated0, nftest_precreate_pkts($num_precreated, $ippkt->packed);


my @precreated1;
$ippkt->set( DA  => $routerMAC1, dst_ip => $dstIP0);
push @precreated1, nftest_precreate_pkts($num_precreated, $ippkt->packed);

$ippkt->set( DA  => $routerMAC1, dst_ip => $dstIP1);
push @precreated1, nftest_precreate_pkts($num_precreated, $ippkt->packed);

$ippkt->set( DA  => $routerMAC1, dst_ip => $dstIP2);
push @precreated1, nftest_precreate_pkts($num_precreated, $ippkt->packed);

# add destinations to be filtered to the software
nftest_add_dst_ip_filter_entry ('nf2c0', 5, $dstIP0);
nftest_add_dst_ip_filter_entry ('nf2c0', 6, $dstIP1);
nftest_add_dst_ip_filter_entry ('nf2c0', 7, $dstIP2);

# clear counter
nftest_regwrite("nf2c0", ROUTER_OP_LUT_NUM_FILTERED_PKTS_REG(), 0);

print "Sending now.\n";
my $pkt;

for(my $i=0; $i<NUM_PKTS; $i++){
  $pkt = $precreated0[int(rand(3*$num_precreated))];
  nftest_send('eth1', $pkt);
  nftest_expect('nf2c0', $pkt);

  $pkt = $precreated1[int(rand(3*$num_precreated))];
  nftest_send('eth2', $pkt);
  nftest_expect('nf2c1', $pkt);
}

sleep 1;

# check counter
nftest_regread_expect("nf2c0", ROUTER_OP_LUT_NUM_FILTERED_PKTS_REG(), 2*NUM_PKTS);

# remove dstIP0 from filter table and add it to the forwarding table
nftest_invalidate_dst_ip_filter_entry('nf2c0', 5);
nftest_add_LPM_table_entry ('nf2c0',
                            0,
                            $dstIP0,
                            "255.255.255.0",
                            "0.0.0.0",
                            0x04); # send out MAC1

# add an entry in the ARP table
nftest_add_ARP_table_entry('nf2c0',
                           0,
                           $dstIP0,
                           $dstMac0);

# create expected packets when forwarded
$ippkt->set(
		DA	=> $dstMac0,
		SA	=> $routerMAC1,
		dst_ip	=> $dstIP0,
		ttl	=> 63,
	);
my $hdrlen = $ippkt->length_in_bytes();
my @expected0 = map { $ippkt->packed . substr($_, $hdrlen) } @precreated0[0..($num_precreated-1)];
my @expected1 = map { $ippkt->packed . substr($_, $hdrlen) } @precreated1[0..($num_precreated-1)];

sleep 1;

print "Resending packets.\n";
my $index;
for(my $i=0; $i<NUM_PKTS; $i++){
  $index = int(rand(3*$num_precreated));
  $pkt = $precreated0[$index];
  nftest_send('eth1', $pkt);
	`usleep 500`;
  # if the packet should be forwarded
  if($index < $num_precreated) {
    nftest_expect('eth2', $expected0[$index]);
  }
  else {
    nftest_expect('nf2c0', $pkt);
  }

  $index = int(rand(3*$num_precreated));
  $pkt = $precreated1[$index];
  nftest_send('eth2', $pkt);
	`usleep 500`;
  if($index < $num_precreated) {
    nftest_expect('eth2', $expected1[$index]);
  }
  else {
    nftest_expect('nf2c1', $pkt);
  }
}

`sleep 1`;

nftest_invalidate_LPM_table_entry ('nf2c0', 0);
nftest_invalidate_ARP_table_entry ('nf2c0', 0);

my $unmatched_hoh = nftest_finish();
nftest_reset_phy();

my $total_errors = 0;

print "Checking pkt errors\n";
$total_errors += nftest_print_errors($unmatched_hoh);

my @badReads = nftest_get_badReads();
$total_errors += (0+@badReads);

if ($total_errors==0) {
  print "Test PASSES\n";
  exit 0;
}
else {
  print "Test FAILED: $total_errors errors\n";
  exit 1;
}
