#!/usr/bin/perl -w

use strict;
use NF2::RegressLib;
use NF2::PacketLib;
use RegressRouterLib;

use reg_defines_reference_router;

use constant NUM_TBL_ENTRIES => 32;

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

# Reset the NF2 device
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";

# clear LPM table
for (my $i = 0; $i < NUM_TBL_ENTRIES; $i++)
{
        nftest_invalidate_LPM_table_entry('nf2c0', $i);
}

# clear ARP table
for (my $i = 0; $i < NUM_TBL_ENTRIES; $i++)
{
        nftest_invalidate_ARP_table_entry('nf2c0', $i);
}

######### 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);

# PHY in loop back 
nftest_phy_loopback('nf2c2');
nftest_phy_loopback('nf2c3');

`sleep 1`;

# set the oq sram boundaries 
nftest_regwrite("nf2c0", OQ_QUEUE_0_ADDR_HI_REG(), 0x3ff); #1024 words * 8 byte/word = 8KB
nftest_regwrite("nf2c0", OQ_QUEUE_0_ADDR_LO_REG(), 0x0);
nftest_regwrite("nf2c0", OQ_QUEUE_0_CTRL_REG(), 1<<OQ_INITIALIZE_OQ_BIT_NUM()); 

nftest_regwrite("nf2c0", OQ_QUEUE_1_ADDR_HI_REG(), 0x105ff); #1.5K words * 8 byte/word = 12KB
nftest_regwrite("nf2c0", OQ_QUEUE_1_ADDR_LO_REG(), 0x10000);
nftest_regwrite("nf2c0", OQ_QUEUE_1_CTRL_REG(), 1<<OQ_INITIALIZE_OQ_BIT_NUM()); 

nftest_regwrite("nf2c0", OQ_QUEUE_2_ADDR_HI_REG(), 0x203ff);#1024 words * 8 byte/word
nftest_regwrite("nf2c0", OQ_QUEUE_2_ADDR_LO_REG(), 0x20000);
nftest_regwrite("nf2c0", OQ_QUEUE_2_CTRL_REG(), 1<<OQ_INITIALIZE_OQ_BIT_NUM()); 

nftest_regwrite("nf2c0", OQ_QUEUE_3_ADDR_HI_REG(), 0x305ff); #1.5K words * 8 byte/word
nftest_regwrite("nf2c0", OQ_QUEUE_3_ADDR_LO_REG(), 0x30000);
nftest_regwrite("nf2c0", OQ_QUEUE_3_CTRL_REG(), 1<<OQ_INITIALIZE_OQ_BIT_NUM()); 

nftest_regwrite("nf2c0", OQ_QUEUE_4_ADDR_HI_REG(), 0x403ff);#1024 words * 8 byte/word
nftest_regwrite("nf2c0", OQ_QUEUE_4_ADDR_LO_REG(), 0x40000);
nftest_regwrite("nf2c0", OQ_QUEUE_4_CTRL_REG(), 1<<OQ_INITIALIZE_OQ_BIT_NUM()); 

nftest_regwrite("nf2c0", OQ_QUEUE_5_ADDR_HI_REG(), 0x505ff); #1.5K words * 8 byte/word 
nftest_regwrite("nf2c0", OQ_QUEUE_5_ADDR_LO_REG(), 0x50000);
nftest_regwrite("nf2c0", OQ_QUEUE_5_CTRL_REG(), 1<<OQ_INITIALIZE_OQ_BIT_NUM()); 

nftest_regwrite("nf2c0", OQ_QUEUE_6_ADDR_HI_REG(), 0x603ff);#1024 words * 8 byte/word
nftest_regwrite("nf2c0", OQ_QUEUE_6_ADDR_LO_REG(), 0x60000);
nftest_regwrite("nf2c0", OQ_QUEUE_6_CTRL_REG(), 1<<OQ_INITIALIZE_OQ_BIT_NUM()); 

nftest_regwrite("nf2c0", OQ_QUEUE_7_ADDR_HI_REG(), 0x705ff); #1.5K words * 8 byte/word
nftest_regwrite("nf2c0", OQ_QUEUE_7_ADDR_LO_REG(), 0x70000);
nftest_regwrite("nf2c0", OQ_QUEUE_7_CTRL_REG(), 1<<OQ_INITIALIZE_OQ_BIT_NUM()); 

sleep 1; 

# Enable all Output Queues. Later on some Output Queues are selectively disabled 
nftest_regwrite("nf2c0", OQ_QUEUE_0_CTRL_REG(), (1 << OQ_ENABLE_SEND_BIT_NUM())); 
nftest_regwrite("nf2c0", OQ_QUEUE_1_CTRL_REG(), (1 << OQ_ENABLE_SEND_BIT_NUM())); 
nftest_regwrite("nf2c0", OQ_QUEUE_2_CTRL_REG(), (1 << OQ_ENABLE_SEND_BIT_NUM())); 
nftest_regwrite("nf2c0", OQ_QUEUE_3_CTRL_REG(), (1 << OQ_ENABLE_SEND_BIT_NUM())); 
nftest_regwrite("nf2c0", OQ_QUEUE_4_CTRL_REG(), (1 << OQ_ENABLE_SEND_BIT_NUM())); 
nftest_regwrite("nf2c0", OQ_QUEUE_5_CTRL_REG(), (1 << OQ_ENABLE_SEND_BIT_NUM())); 
nftest_regwrite("nf2c0", OQ_QUEUE_6_CTRL_REG(), (1 << OQ_ENABLE_SEND_BIT_NUM())); 
nftest_regwrite("nf2c0", OQ_QUEUE_7_CTRL_REG(), (1 << OQ_ENABLE_SEND_BIT_NUM())); 

`sleep 1`; 

# set parameters
my $DA = $routerMAC0;
my $SA = "aa:bb:cc:dd:ee:ff";
my $TTL = 64;
my $DST_IP = "192.168.101.2";
my $SRC_IP = "192.168.100.2";;
my $nextHopMAC = "dd:55:dd:66:dd:77";

# create mac header
my $MAC_hdr = NF2::Ethernet_hdr->new(DA => $DA,
                                     SA => $SA,
                                     Ethertype => 0x800
                                    );

#create IP header
my $IP_hdr = NF2::IP_hdr->new(ttl => $TTL,
                              src_ip => $SRC_IP,
                              dst_ip => $DST_IP
                             );

$IP_hdr->checksum(0);  # make sure its zero before we calculate it.
$IP_hdr->checksum($IP_hdr->calc_checksum);

my $num_precreated = 20;
my $start_val = $MAC_hdr->length_in_bytes() + $IP_hdr->length_in_bytes()+1;

# precreate (1024-8)=1016 byte sized packets
$MAC_hdr->DA($routerMAC0);
my @precreated0 = nftest_precreate_pkts($num_precreated,
                                        $MAC_hdr->packed . $IP_hdr->packed, 1016, 1016);
$MAC_hdr->DA($routerMAC1);
my @precreated1 = nftest_precreate_pkts($num_precreated,
                                        $MAC_hdr->packed . $IP_hdr->packed, 1016, 1016);
$MAC_hdr->DA($routerMAC2);
my @precreated2 = nftest_precreate_pkts($num_precreated,
                                        $MAC_hdr->packed . $IP_hdr->packed, 1016, 1016);
$MAC_hdr->DA($routerMAC3);
my @precreated3 = nftest_precreate_pkts($num_precreated,
                                        $MAC_hdr->packed . $IP_hdr->packed, 1016, 1016);

my $pkt;

print "Start testing CPU OQ sizes \n";

print "Disabling servicing CPU OQs\n";
nftest_regwrite("nf2c0", OQ_QUEUE_1_CTRL_REG(), 0);
nftest_regwrite("nf2c0", OQ_QUEUE_3_CTRL_REG(), 0);
nftest_regwrite("nf2c0", OQ_QUEUE_5_CTRL_REG(), 0);
nftest_regwrite("nf2c0", OQ_QUEUE_7_CTRL_REG(), 0);

# clear counter values for CPU output queues only
nftest_regwrite("nf2c0", OQ_QUEUE_1_NUM_PKTS_DROPPED_REG(), 0);
nftest_regwrite("nf2c0", OQ_QUEUE_3_NUM_PKTS_DROPPED_REG(), 0);
nftest_regwrite("nf2c0", OQ_QUEUE_5_NUM_PKTS_DROPPED_REG(), 0);
nftest_regwrite("nf2c0", OQ_QUEUE_7_NUM_PKTS_DROPPED_REG(), 0);

nftest_regwrite("nf2c0", OQ_QUEUE_1_NUM_PKTS_REMOVED_REG(), 0);
nftest_regwrite("nf2c0", OQ_QUEUE_3_NUM_PKTS_REMOVED_REG(), 0);
nftest_regwrite("nf2c0", OQ_QUEUE_5_NUM_PKTS_REMOVED_REG(), 0);
nftest_regwrite("nf2c0", OQ_QUEUE_7_NUM_PKTS_REMOVED_REG(), 0);

`sleep 1`; 

# This is the max number of pkts that a CPU OQ can store  
my $NUM_PKTS_IN_CPU_OQ = 8;  

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

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

  $pkt = $precreated2[int(rand($num_precreated))];
  nftest_send('nf2c2', $pkt);
  nftest_expect('nf2c2', $pkt);

  $pkt = $precreated3[int(rand($num_precreated))];
  nftest_send('nf2c3', $pkt);
  nftest_expect('nf2c3', $pkt);

}

print "CPU OQs should be full. Start to drop received pkts\n"; 

$pkt = $precreated0[int(rand($num_precreated))];
nftest_send('eth1', $pkt);

$pkt = $precreated1[int(rand($num_precreated))];
nftest_send('eth2', $pkt);

$pkt = $precreated2[int(rand($num_precreated))];
nftest_send('nf2c2', $pkt);

$pkt = $precreated3[int(rand($num_precreated))];
nftest_send('nf2c3', $pkt);

`sleep 1`;

print "Verifying that the packets are stored in the output queues\n";
nftest_regread_expect("nf2c0", OQ_QUEUE_1_NUM_PKTS_IN_Q_REG(), $NUM_PKTS_IN_CPU_OQ);
nftest_regread_expect("nf2c0", OQ_QUEUE_3_NUM_PKTS_IN_Q_REG(), $NUM_PKTS_IN_CPU_OQ);
nftest_regread_expect("nf2c0", OQ_QUEUE_5_NUM_PKTS_IN_Q_REG(), $NUM_PKTS_IN_CPU_OQ);
nftest_regread_expect("nf2c0", OQ_QUEUE_7_NUM_PKTS_IN_Q_REG(), $NUM_PKTS_IN_CPU_OQ);

print "Verifying dropped pkts counter\n";
nftest_regread_expect("nf2c0", OQ_QUEUE_1_NUM_PKTS_DROPPED_REG(), 1);
nftest_regread_expect("nf2c0", OQ_QUEUE_3_NUM_PKTS_DROPPED_REG(), 1);
nftest_regread_expect("nf2c0", OQ_QUEUE_5_NUM_PKTS_DROPPED_REG(), 1);
nftest_regread_expect("nf2c0", OQ_QUEUE_7_NUM_PKTS_DROPPED_REG(), 1);

print "Start servicing the CPU OQ again. Packets should be sent out\n";
nftest_regwrite("nf2c0", OQ_QUEUE_1_CTRL_REG(), 1 << OQ_ENABLE_SEND_BIT_NUM());
nftest_regwrite("nf2c0", OQ_QUEUE_3_CTRL_REG(), 1 << OQ_ENABLE_SEND_BIT_NUM());
nftest_regwrite("nf2c0", OQ_QUEUE_5_CTRL_REG(), 1 << OQ_ENABLE_SEND_BIT_NUM());
nftest_regwrite("nf2c0", OQ_QUEUE_7_CTRL_REG(), 1 << OQ_ENABLE_SEND_BIT_NUM());

`sleep 1`;

print "Verifying that the packets are drained in the output queues\n";
nftest_regread_expect("nf2c0", OQ_QUEUE_1_NUM_PKTS_REMOVED_REG(), $NUM_PKTS_IN_CPU_OQ);
nftest_regread_expect("nf2c0", OQ_QUEUE_3_NUM_PKTS_REMOVED_REG(), $NUM_PKTS_IN_CPU_OQ);
nftest_regread_expect("nf2c0", OQ_QUEUE_5_NUM_PKTS_REMOVED_REG(), $NUM_PKTS_IN_CPU_OQ);
nftest_regread_expect("nf2c0", OQ_QUEUE_7_NUM_PKTS_REMOVED_REG(), $NUM_PKTS_IN_CPU_OQ);

print "Done testing CPU OQ sizes \n";
print "\n";

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;
}
