#!/pkg/bin/perl
#
# Created by Richard Wood, July 2015
#
# Viking lightweight mac retrieval script.
#
# Copyright (c) 2015-2017 by cisco Systems, Inc. 
# All rights reserved.
#----------------------------------------------------------------------
$VKG_L2FIB_LOCAL_XID_TRIDENT_RANGE =(32*1024 - 256);
$VKG_L2FIB_LOCAL_XID_SFP_MASK =(0x0007ffff);
$VKG_L2FIB_GLOBAL_XID_OFFSET =((1 << 15) - 1);
$VKG_L2FIB_GLOBAL_XID_BIT = (1 << 31);
$VKG_L2FIB_GLOBAL_XID_TYPHOON_SIZE = (384*1024); 
$VKG_L2FIB_RESERVED_LOCAL_XID_SIZE =(255);
$VKG_L2VPN_RSI_BIT_SHIFT = (19);
$VKG_L2VPN_R_BITS_SHIFT = (9);
$VKG_L2VPN_S_BITS_SHIFT = (4);
$VKG_L2VPN_I_BITS_SHIFT = (0);


sub getopts {
    my @in_args = @{$_[0]};
    my %out_args;

    $out_args{a} = "";

    foreach my $i (0 .. $#in_args) {
        if ($in_args[$i] =~ /-b/) {
            if ($in_args[($i+1)] !~ /^-[a-z]$/) {
                $out_args{b} = $in_args[($i+1)];
            } else {
                $out_args{b} = "";
            }
        }
        if ($in_args[$i] =~ /-n/) {
            $out_args{n} = $in_args[($i+1)];
        }
        if ($in_args[$i] =~ /-i/) {
            $out_args{i} = $in_args[($i+1)];
        }
        if ($in_args[$i] =~ /-x/) {
            $out_args{x} = $in_args[($i+1)];
        }
        if ($in_args[$i] =~ /-l/) {
            $out_args{l} = $in_args[($i+1)];
        }
        if ($in_args[$i] =~ /-a/) {
            $out_args{a} = substr($in_args[($i+1)], 2, 4) . 
                           substr($in_args[($i+2)], 2, 4) . 
                           substr($in_args[($i+3)], 2, 4);
        }
    }

    return %out_args;
}

sub retrieve_fq_nodeid {
    my $location=$_[0];
    my $fq_nodeid = '';
    my $found = 0;
    my $cmd = "sdr_show_info -l";
    my @sdr_out = `$cmd`;
    chomp @sdr_out;
    
    @node_list = grep(/IOS/, @sdr_out);

    foreach my $node (@node_list) {
        if ($node =~ "$location") {
            $found = 1;
            $tmp = `node_conversion -N $location`;
            $fq_nodeid = `node_conversion -i $tmp`;
        }
    }

    if (!$found) {
        print "ERROR: Invalid node provided. $location was not found.\n";
        exit();
    }

    return $fq_nodeid;
}


sub string_hex_16b_byteswap {
    my $hex_bdid=$_[0];

    @a = ();
    push(@a, substr("$hex_bdid",0,1));
    push(@a, substr("$hex_bdid",1,1));
    push(@a, substr("$hex_bdid",2,1));
    push(@a, substr("$hex_bdid",3,1));

    if ( $a[0] eq '' ) {
        $a[3]="0";
        $a[2]="0";
        $a[1]="0";
        $a[0]="0";
    } elsif ( $a[1] eq '' ) {
        $a[3]="$a[0]";
        $a[2]="0";
        $a[1]="0";
        $a[0]="0";
    } elsif ( $a[2] eq '' ) {
        $a[3]="$a[1]";
        $a[2]="$a[0]";
        $a[1]="0";
        $a[0]="0";
    } elsif ( $a[3] eq '' ) {
        $a[3]="$a[2]";
        $a[2]="$a[1]";
        $a[1]="$a[0]";
        $a[0]="0";
    }

    $bdid_byteswapped= join('', $a[2], $a[3], $a[0], $a[1]);

    return $bdid_byteswapped;
}

sub string_hex_24b_byteswap {
    my $in_hex=$_[0];
    
    $byteswapped = string_hex_32b_byteswap($in_hex);

    $byteswapped_out = substr("$byteswapped",0,6);
    
    return $byteswapped_out;
}

sub string_hex_32b_byteswap {
    my $in_hex=$_[0];

    if (substr("$in_hex",7,1) eq '' ) {
        return (0);
    }

    $p1 = substr("$in_hex",0,4);
    $p2 = substr("$in_hex",4,4);

    $byteswapped_out = join('', string_hex_16b_byteswap("$p2"), string_hex_16b_byteswap("$p1"));

    return $byteswapped_out;
}

sub zero_pad_to_8char {
    my $input=$_[0];

    if (length($input) < 8) {
        $zero_cnt = 8 - length($input);
        $zeros = '0' x $zero_cnt;
        $output = join('', $zeros, $input);
    } else {
        $output = $input;
    }

    return $output;
}


sub parse_l2fib_entries {
    my @l2fib_array = @{$_[0]};
    my $xid_search = $_[1];
    my $fq_nodeid = $_[2];
    my $rack = 0;
    my $slot = 0;

    my $xid;
    my $xid_bswap;
    my @xids = ();
    my $mac;
    my @mac_db = ();
    my @bd_ids = ();
    my @is_bvi = ();
    my $found;
    my $index;
    
    foreach (@l2fib_array) {
        if ($_ =~ "Key:") {
            $mac = substr("$_", 9, 4) . "." . substr("$_", 13, 4) . "." . substr("$_", 18, 4);

            if ($_ =~ "Size: 9") {
                # Handle 24b BD-ID
                $bd_id = hex(string_hex_24b_byteswap(zero_pad_to_8char(
                         join('', substr("$_", 22, 4), substr("$_", 27, 2)))));
            } else {
                # Handle 16b BD-ID
                $bd_id = hex(string_hex_16b_byteswap(substr("$_", 22, 4)));
            }
        }
        
        if ($_ =~ "Result:") {
            if ($mac eq "NULL") {
                print "ERROR: MAC not found in key.\n";
            }

            $xid = substr("$_", 36, 8);
            $xid_bswap = string_hex_32b_byteswap("$xid");

            # Retrieve BVI en bit from l2fib cb2. Subject to change!
            $bvi_nib = substr("$_", 14, 1);
            if (hex($bvi_nib) & (0x2)) {
                $bvi = 1;
            } else {
                $bvi = 0;
            }
          
            $local_lrn_nib = substr("$_", 11, 1);
            if (hex($local_lrn_nib) & (0x2)) {
                $local_lrn = 1;
            } else {
                $local_lrn = 0;
            }

            $xid_binary = sprintf( "%b", hex( $xid_bswap ) );

            $pi_xid = $xid_bswap;

            $found = 0;
            $index = 0;
            foreach my $xid_en (@xids) {
                if ($xid_en eq $pi_xid) {
                    push @{$mac_db[$index]}, $mac;
                    $found = 1;
                    $mac = "NULL";
                    break;
                }
                $index = $index+1;
            }
            if ($found) {
                next;
            }
            
            push(@xids, $pi_xid);
            push @{$mac_db[$index]}, $mac;
            push(@bd_ids, $bd_id);
            push(@is_bvi, $bvi);
            push(@is_local, $local_lrn);
        }
    }

    $index = 0;
    print "Mac Address     Learned from/Filtered on\n";
    print "----------------------------------------\n";

    foreach my $xid_en (@xids) {
        if (!($xid_search eq 0) && !($xid_search eq '')) {
            if (!($xid_search eq $xid_en)) {
                $index = $index+1;
                next;
            }
        }

        my @l2fib_xc = `l2fib_show_client -l $fq_nodeid -xcon 0x$xid_en`;

        $interface = (split(/ /, $l2fib_xc[2]))[0];

        if ($interface =~ "mpls") {
            ($pw_info) = $l2fib_xc[2] =~ /\((.*)\)/;
            @a = split(/\,/, $pw_info);
            $neighbor = $a[0];
            $pw_id = $a[1];

            $interface = "PW: $neighbor, id: $pw_id";
        }

        foreach $mymac (@{$mac_db[$index]}) {
            if ($is_bvi[$index]) {
                print "$mymac    BVI, bd-id: $bd_ids[$index]\n";
            } elsif ($is_local[$index]) {
                print "$mymac    $interface (XID: 0x$xid_en), bd-id: $bd_ids[$index], local\n";
            } else {
                print "$mymac    $interface (XID: 0x$xid_en), bd-id: $bd_ids[$index]\n";
            }
        }

        $index = $index+1;
    }
   
}

sub print_help() {
    print "Usage: This script takes the bd-id, the np-id, and location as \n",
          "input arguments and retrieves the mac entries learned against \n",
          "this bd-id from the MAC table. Further isolation can be done by \n",
          "also providing the XID as an argument. \n",
          "  -b bridge-domain name \n",
          "  -x xconnect-id \n",
          "  -n np-id \n",
          "  -l location \n\n",
          "  Examples: \n",
          "    1) l2fib_dump_mac_entries -b bd -x 0x0c600003 -n 1 -l 0/6/CPU0 \n",
          "    2) l2fib_dump_mac_entries -b bd -l 0/1/CPU0 \n";
}


sub main {
    my %args;
    %args = getopts(\@ARGV);

    if (!$args{l}) {
        print "ERROR: No location provided.\n";
        exit(0);
    }

    if (!$args{n}) {
        $args{n} = 0;
    }

    $xc_id = $args{x};
    $interface = $args{i};
    $location = $args{l};

    $np_id = hex($args{n});
    $bd_id = sprintf("%x", $args{b});
    $bd_id = string_hex_16b_byteswap("$bd_id");
    $mac_addr = $args{a};
    $fq_nodeid = retrieve_fq_nodeid($location);
    chomp($fq_nodeid);

    @l2fib_sum = `prm_np_show struct -u 0x12 -y 1`;
    foreach my $line (@l2fib_sum) {
        if ($line =~ "Used Entries") {
            $max_entries = (split(',', (split(': ', $line))[2]))[0];           
        }
    }
    if ($max_entries > 20000) {
        printf "WARNING: High number of macs detected in the system. For improved " .
               "performace at high scale, use the mac-cache CLI\n";
    }

    # If no bridge-domain is provided, search all entries of L2FIB table. 
    # Otherwise, only retrieve macs associated with the provided BD.

    if ($args{b} =~ /^ *$/) {
        # No Bridge-domain name provided.
        if ($args{a} =~ /^ *$/) {
            # No specific mac, no bridge, dump all entries in the system.
            $prm_l2fib_cmd = "prm_np_show struct -u 0x12 -e $np_id -s $fq_nodeid";
        } else {
            # Specific mac provided, no bridge-domain name provided.
            $prm_l2fib_cmd = "prm_np_show struct -u 0x12 -l $mac_addr -e $np_id -s $fq_nodeid";
        }
    } else {
        # Bridge-domain name provided.

        # Enhanced so user can provide bridge-group:bridge-domain, or just bridge-domain
        $bd_name = $args{b};
        if ($bd_name =~ m/:/) {
            $bd_name = (split(/:/, $bd_name))[1];
        }

        # Retrieve bridge-id from bridge-name
        my @l2fib_bd = `l2vpn_show -b $bd_name -r 0x9`;

        $bd_id = (split(/ {2,}/, $l2fib_bd[3]))[1];
        $bd_id = sprintf("%x", $bd_id);
        $bd_id_hex = string_hex_24b_byteswap(zero_pad_to_8char($bd_id));
        # Currently, PRM doesn't support 3B bridge-domain search. Use 2B.
        $bd_id_hex = substr($bd_id_hex, 0, 4);

        if ($args{a} =~ /^ *$/) {
            # No specific mac provided
            $prm_l2fib_cmd = "prm_np_show struct -u 0x12 -l $bd_id_hex -b 0xc -e $np_id -s $fq_nodeid";
        } else {
            # Specific mac provided
            $prm_l2fib_cmd = "prm_np_show struct -u 0x12 -l $mac_addr$bd_id_hex -e $np_id -s $fq_nodeid";
        }
    }

    if (!($xc_id eq 0) && (substr($xc_id, 1, 1) eq 'x')) {
        $xc_id = substr($xc_id, 2);
    }

    my @l2fib_entries = `$prm_l2fib_cmd`;    

    parse_l2fib_entries(\@l2fib_entries, $xc_id, $fq_nodeid, 
                        $rack, $slot);
}


&main();
