# SNMP::Info::Layer3::Passport
# $Id$
#
# Copyright (c) 2008 Eric Miller
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the University of California, Santa Cruz nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

package SNMP::Info::Layer3::Passport;

use strict;
use Exporter;
use SNMP::Info::SONMP;
use SNMP::Info::RapidCity;
use SNMP::Info::Layer3;

@SNMP::Info::Layer3::Passport::ISA
    = qw/SNMP::Info::SONMP SNMP::Info::RapidCity
    SNMP::Info::Layer3 Exporter/;
@SNMP::Info::Layer3::Passport::EXPORT_OK = qw//;

use vars qw/$VERSION %GLOBALS %FUNCS %MIBS %MUNGE/;

$VERSION = '2.06';

%MIBS = (
    %SNMP::Info::Layer3::MIBS, %SNMP::Info::RapidCity::MIBS,
    %SNMP::Info::SONMP::MIBS,
);

%GLOBALS = (
    %SNMP::Info::Layer3::GLOBALS, %SNMP::Info::RapidCity::GLOBALS,
    %SNMP::Info::SONMP::GLOBALS,
);

%FUNCS = (
    %SNMP::Info::Layer3::FUNCS, %SNMP::Info::RapidCity::FUNCS,
    %SNMP::Info::SONMP::FUNCS,
);

%MUNGE = (
    %SNMP::Info::Layer3::MUNGE, %SNMP::Info::RapidCity::MUNGE,
    %SNMP::Info::SONMP::MUNGE,
);

sub model {
    my $passport = shift;
    my $id       = $passport->id();

    unless ( defined $id ) {
        print
            " SNMP::Info::Layer3::Passport::model() - Device does not support sysObjectID\n"
            if $passport->debug();
        return;
    }

    my $model = &SNMP::translateObj($id);

    return $id unless defined $model;

    $model =~ s/^rcA//i;
    return $model;
}

sub vendor {
    return 'nortel';
}

sub os {
    return 'passport';
}

sub os_ver {
    my $passport = shift;
    my $descr    = $passport->description();
    return unless defined $descr;

    #ERS / Passport
    if ( $descr =~ m/(\d+\.\d+\.\d+\.\d+)/ ) {
        return $1;
    }

    #Accelar
    if ( $descr =~ m/(\d+\.\d+\.\d+)/ ) {
        return $1;
    }
    return;
}

sub i_index {
    my $passport = shift;
    my $partial  = shift;

    my $i_index = $passport->orig_i_index($partial);
    my $model   = $passport->model();

    my %if_index;
    foreach my $iid ( keys %$i_index ) {
        my $index = $i_index->{$iid};
        next unless defined $index;

        $if_index{$iid} = $index;
    }

    # Get VLAN Virtual Router Interfaces
    if (!defined $partial
        or (defined $model
            and (  ( $partial > 2000 and $model =~ /(86|83|81|16)/ )
                or ( $partial > 256 and $model =~ /(105|11[05]0|12[05])/ ) )
        )
        )
    {

        my $vlan_index = $passport->rc_vlan_if() || {};

        foreach my $vid ( keys %$vlan_index ) {
            my $v_index = $vlan_index->{$vid};
            next unless defined $v_index;
            next if $v_index == 0;
            next if ( defined $partial and $v_index !~ /^$partial$/ );

            $if_index{$v_index} = $v_index;
        }
    }

    if ( defined $model and $model =~ /(86)/ ) {

        my $cpu_index = $passport->rc_cpu_ifindex($partial) || {};
        my $virt_ip = $passport->rc_virt_ip();

        # Get CPU Ethernet Interfaces
        foreach my $cid ( keys %$cpu_index ) {
            my $c_index = $cpu_index->{$cid};
            next unless defined $c_index;
            next if $c_index == 0;

            $if_index{$c_index} = $c_index;
        }

        # Check for Virtual Mgmt Interface
        unless ( $virt_ip eq '0.0.0.0' ) {

            # Make up an index number, 1 is not reserved AFAIK
            $if_index{1} = 1;
        }
    }
    return \%if_index;
}

sub interfaces {
    my $passport = shift;
    my $partial  = shift;

    my $i_index      = $passport->i_index($partial);
    my $model        = $passport->model();
    my $index_factor = $passport->index_factor();
    my $port_offset  = $passport->port_offset();
    my $vlan_index   = {};
    my %reverse_vlan;
    my $vlan_id = {};

    if (!defined $partial
        or (defined $model
            and (  ( $partial > 2000 and $model =~ /(86|83|81|16)/ )
                or ( $partial > 256 and $model =~ /(105|11[05]0|12[05])/ ) )
        )
        )
    {
        $vlan_index   = $passport->rc_vlan_if() || {};
        %reverse_vlan = reverse %$vlan_index;
        $vlan_id      = $passport->rc_vlan_id();
    }

    my %if;
    foreach my $iid ( keys %$i_index ) {
        my $index = $i_index->{$iid};
        next unless defined $index;

        if ( ( $index == 1 ) and ( $model =~ /(86)/ ) ) {
            $if{$index} = 'Cpu.Virtual';
        }

        elsif ( ( $index == 192 ) and ( $model eq '8603' ) ) {
            $if{$index} = 'Cpu.3';
        }

        elsif ( ( $index == 320 ) and ( $model =~ /(8606|8610|8610co)/ ) ) {
            $if{$index} = 'Cpu.5';
        }

        elsif ( ( $index == 384 ) and ( $model =~ /(8606|8610|8610co)/ ) ) {
            $if{$index} = 'Cpu.6';
        }

        elsif (( $index > 2000 and $model =~ /(86|83|81|16)/ )
            or ( $index > 256 and $model =~ /(105|11[05]0|12[05])/ ) )
        {

            my $v_index = $reverse_vlan{$iid};
            my $v_id    = $vlan_id->{$v_index};
            next unless defined $v_id;
            my $v_port = 'Vlan' . "$v_id";
            $if{$index} = $v_port;
        }

        else {
            my $port = ( $index % $index_factor ) + $port_offset;
            my $slot = int( $index / $index_factor );

            my $slotport = "$slot.$port";
            $if{$iid} = $slotport;
        }

    }

    return \%if;
}

sub i_mac {
    my $passport = shift;
    my $partial  = shift;

    my $i_mac = $passport->orig_i_mac($partial) || {};
    my $model = $passport->model();

    my %if_mac;
    foreach my $iid ( keys %$i_mac ) {
        my $mac = $i_mac->{$iid};
        next unless defined $mac;

        $if_mac{$iid} = $mac;
    }

    # Get VLAN Virtual Router Interfaces
    if (!defined $partial
        or (defined $model
            and (  ( $partial > 2000 and $model =~ /(86|83|81|16)/ )
                or ( $partial > 256 and $model =~ /(105|11[05]0|12[05])/ ) )
        )
        )
    {

        my $vlan_index = $passport->rc_vlan_if()  || {};
        my $vlan_mac   = $passport->rc_vlan_mac() || {};

        foreach my $iid ( keys %$vlan_mac ) {
            my $v_mac = $vlan_mac->{$iid};
            next unless defined $v_mac;
            my $v_id = $vlan_index->{$iid};
            next unless defined $v_id;
            next if ( defined $partial and $v_id !~ /^$partial$/ );

            $if_mac{$v_id} = $v_mac;
        }
    }

    if ( defined $model and $model =~ /(86)/ ) {

        my $cpu_mac = $passport->rc_cpu_mac($partial) || {};
        my $virt_ip = $passport->rc_virt_ip()         || '0.0.0.0';

        # Get CPU Ethernet Interfaces
        foreach my $iid ( keys %$cpu_mac ) {
            my $mac = $cpu_mac->{$iid};
            next unless defined $mac;

            $if_mac{$iid} = $mac;
        }

        # Check for Virtual Mgmt Interface
        unless ( ( $virt_ip eq '0.0.0.0' )
            or ( defined $partial and $partial ne "1" ) )
        {
            my $chassis_base_mac = $passport->rc_base_mac();
            if ( defined $chassis_base_mac ) {
                my @virt_mac = split /:/, $chassis_base_mac;
                $virt_mac[0] = hex( $virt_mac[0] );
                $virt_mac[1] = hex( $virt_mac[1] );
                $virt_mac[2] = hex( $virt_mac[2] );
                $virt_mac[3] = hex( $virt_mac[3] );
                $virt_mac[4] = hex( $virt_mac[4] ) + 0x03;
                $virt_mac[5] = hex( $virt_mac[5] ) + 0xF8;

                my $mac = join( ':', map { sprintf "%02x", $_ } @virt_mac );

                $if_mac{1} = $mac;
            }
        }
    }
    return \%if_mac;
}

sub i_description {
    my $passport = shift;
    my $partial  = shift;

    my $i_descr = $passport->orig_i_description($partial) || {};
    my $model = $passport->model();

    my %descr;
    foreach my $iid ( keys %$i_descr ) {
        my $if_descr = $i_descr->{$iid};
        next unless defined $if_descr;

        $descr{$iid} = $if_descr;
    }

    # Get VLAN Virtual Router Interfaces
    if (!defined $partial
        or (defined $model
            and (  ( $partial > 2000 and $model =~ /(86|83|81|16)/ )
                or ( $partial > 256 and $model =~ /(105|11[05]0|12[05])/ ) )
        )
        )
    {

        my $v_descr    = $passport->v_name();
        my $vlan_index = $passport->rc_vlan_if();

        foreach my $vid ( keys %$v_descr ) {
            my $vl_descr = $v_descr->{$vid};
            next unless defined $vl_descr;
            my $v_id = $vlan_index->{$vid};
            next unless defined $v_id;
            next if ( defined $partial and $v_id !~ /^$partial$/ );

            $descr{$v_id} = $vl_descr;
        }
    }
    return \%descr;
}

sub i_name {
    my $passport = shift;
    my $partial  = shift;

    my $model      = $passport->model();
    my $i_index    = $passport->i_index($partial) || {};
    my $rc_alias   = $passport->rc_alias($partial) || {};
    my $i_name2    = $passport->orig_i_name($partial) || {};
    my $v_name     = {};
    my $vlan_index = {};
    my %reverse_vlan;

    if (!defined $partial
        or (defined $model
            and (  ( $partial > 2000 and $model =~ /(86|83|81|16)/ )
                or ( $partial > 256 and $model =~ /(105|11[05]0|12[05])/ ) )
        )
        )
    {
        $v_name     = $passport->v_name()     || {};
        $vlan_index = $passport->rc_vlan_if() || {};
        %reverse_vlan = reverse %$vlan_index;
    }

    my %i_name;
    foreach my $iid ( keys %$i_index ) {

        if ( ( $iid == 1 ) and ( $model =~ /(86)/ ) ) {
            $i_name{$iid} = 'CPU Virtual Management IP';
        }

        elsif ( ( $iid == 192 ) and ( $model eq '8603' ) ) {
            $i_name{$iid} = 'CPU 3 Ethernet Port';
        }

        elsif ( ( $iid == 320 ) and ( $model =~ /(8606|8610|8610co)/ ) ) {
            $i_name{$iid} = 'CPU 5 Ethernet Port';
        }

        elsif ( ( $iid == 384 ) and ( $model =~ /(8606|8610|8610co)/ ) ) {
            $i_name{$iid} = 'CPU 6 Ethernet Port';
        }

        elsif (
            ( $iid > 2000 and defined $model and $model =~ /(86|83|81|16)/ )
            or (    $iid > 256
                and defined $model
                and $model =~ /(105|11[05]0|12[05])/ )
            )
        {
            my $vlan_index = $reverse_vlan{$iid};
            my $vlan_name  = $v_name->{$vlan_index};
            next unless defined $vlan_name;

            $i_name{$iid} = $vlan_name;
        }

        else {
            my $name  = $i_name2->{$iid};
            my $alias = $rc_alias->{$iid};
            $i_name{$iid}
                = ( defined $alias and $alias !~ /^\s*$/ )
                ? $alias
                : $name;
        }
    }

    return \%i_name;
}

sub ip_index {
    my $passport = shift;
    my $partial  = shift;

    my $model = $passport->model();
    my $ip_index = $passport->orig_ip_index($partial) || {};

    my %ip_index;
    foreach my $ip ( keys %$ip_index ) {
        my $iid = $ip_index->{$ip};
        next unless defined $iid;

        $ip_index{$ip} = $iid;
    }

    # Only 8600 has CPU and Virtual Management IP
    if ( defined $model and $model =~ /(86)/ ) {

        my $cpu_ip = $passport->rc_cpu_ip($partial) || {};
        my $virt_ip = $passport->rc_virt_ip($partial);

        # Get CPU Ethernet IP
        foreach my $cid ( keys %$cpu_ip ) {
            my $c_ip = $cpu_ip->{$cid};
            next unless defined $c_ip;

            $ip_index{$c_ip} = $cid;
        }

        # Get Virtual Mgmt IP
        $ip_index{$virt_ip} = 1 if ( defined $virt_ip );
    }

    return \%ip_index;
}

sub ip_netmask {
    my $passport = shift;
    my $partial  = shift;

    my $model = $passport->model();
    my $ip_mask = $passport->orig_ip_netmask($partial) || {};

    my %ip_index;
    foreach my $iid ( keys %$ip_mask ) {
        my $mask = $ip_mask->{$iid};
        next unless defined $mask;

        $ip_index{$iid} = $mask;
    }

    # Only 8600 has CPU and Virtual Management IP
    if ( defined $model and $model =~ /(86)/ ) {

        my $cpu_ip    = $passport->rc_cpu_ip($partial)    || {};
        my $cpu_mask  = $passport->rc_cpu_mask($partial)  || {};
        my $virt_ip   = $passport->rc_virt_ip($partial);
        my $virt_mask = $passport->rc_virt_mask($partial) || {};

        # Get CPU Ethernet IP
        foreach my $iid ( keys %$cpu_mask ) {
            my $c_ip = $cpu_ip->{$iid};
            next unless defined $c_ip;
            my $c_mask = $cpu_mask->{$iid};
            next unless defined $c_mask;

            $ip_index{$c_ip} = $c_mask;
        }

        # Get Virtual Mgmt IP
        $ip_index{$virt_ip} = $virt_mask
            if ( defined $virt_mask and defined $virt_ip );
    }

    return \%ip_index;
}

sub root_ip {
    my $passport        = shift;
    my $model           = $passport->model();
    my $rc_ip_addr      = $passport->rc_ip_addr();
    my $rc_ip_type      = $passport->rc_ip_type();
    my $virt_ip         = $passport->rc_virt_ip();
    my $router_ip       = $passport->router_ip();
    my $sonmp_topo_port = $passport->sonmp_topo_port();
    my $sonmp_topo_ip   = $passport->sonmp_topo_ip();

    # Only 8600 and 1600 have CLIP or Management Virtual IP
    if ( defined $model and $model =~ /(86|16)/ ) {

        # Return CLIP (CircuitLess IP)
        foreach my $iid ( keys %$rc_ip_type ) {
            my $ip_type = $rc_ip_type->{$iid};
            next
                unless ( ( defined $ip_type )
                and ( $ip_type =~ /circuitLess/i ) );
            my $ip = $rc_ip_addr->{$iid};
            next unless defined $ip;

            return $ip if $passport->snmp_connect_ip($ip);
        }

        # Return Management Virtual IP address
        if ( ( defined $virt_ip ) and ( $virt_ip ne '0.0.0.0' ) ) {
            return $virt_ip if $passport->snmp_connect_ip($virt_ip);
        }
    }

    # Return OSPF Router ID
    if ( ( defined $router_ip ) and ( $router_ip ne '0.0.0.0' ) ) {
        foreach my $iid ( keys %$rc_ip_addr ) {
            my $ip = $rc_ip_addr->{$iid};
            next unless $router_ip eq $ip;
            return $router_ip if $passport->snmp_connect_ip($router_ip);
        }
    }

    # Otherwise Return SONMP Advertised IP Address
    foreach my $entry ( keys %$sonmp_topo_port ) {
        my $port = $sonmp_topo_port->{$entry};
        next unless $port == 0;
        my $ip = $sonmp_topo_ip->{$entry};
        return $ip
            if (( defined $ip )
            and ( $ip ne '0.0.0.0' )
            and ( $passport->snmp_connect_ip($ip) ) );
    }
    return;
}

# Required for SNMP::Info::SONMP
sub index_factor {
    my $passport     = shift;
    my $model        = $passport->model();
    my $index_factor = 64;

    # Older Accelar models use base 16 instead of 64
    $index_factor = 16
        if ( defined $model and $model =~ /(105|11[05]0|12[05])/ );
    return $index_factor;
}

sub slot_offset {
    return 0;
}

sub port_offset {
    return 1;
}

# Bridge MIB does not map Bridge Port to ifIndex correctly
sub bp_index {
    my $passport = shift;
    my $partial  = shift;

    my $if_index = $passport->i_index($partial) || {};

    my %bp_index;
    foreach my $iid ( keys %$if_index ) {
        $bp_index{$iid} = $iid;
    }
    return \%bp_index;
}

# Pseudo ENTITY-MIB methods

sub e_index {
    my $passport = shift;

    my $model = $passport->model();
    my $rc_ps_t = $passport->rc_ps_type() || {};

    # We're going to hack an index: Slot/Mda/Postion
    # We're going to put chassis and power supplies in a slot
    # which doesn't exist
    my %rc_e_index;

    # Make up a chassis index
    $rc_e_index{1} = 1;

    # Power supplies are common, handle them first
    foreach my $idx ( keys %$rc_ps_t ) {
        next unless $idx;

        # We should never have 90 slots, they will also
        # sort numerically at the bottom
        my $index = $idx + 90 . "0000";
        $rc_e_index{$index} = $index;
    }

    # Older Accelars use RAPID-CITY::rcCardTable
    if ( defined $model and $model =~ /(105|11[05]0|12[05])/ ) {
        my $rc_c_t = $passport->rc_c_type() || {};
        foreach my $idx ( keys %$rc_c_t ) {
            next unless $idx;

            my $index = "$idx" . "0000";
            $rc_e_index{$index} = $index;
            $index++;
            $rc_e_index{$index} = $index;
        }
    }

    # All newer models use RAPID-CITY::rc2kCardTable
    else {
        my $rc2_c_t = $passport->rc2k_c_ftype()  || {};
        my $rc2_m_t = $passport->rc2k_mda_type() || {};

        foreach my $idx ( keys %$rc2_c_t ) {
            next unless $idx;

            my $index = "$idx" . "0000";
            for ( 0 .. 2 ) {
                $rc_e_index{$index} = $index;
                $index++;
            }
        }
        foreach my $idx ( keys %$rc2_m_t ) {
            next unless $idx;
            next if $idx == 0;

            my ( $slot, $mda ) = split /\./, $idx;
            $mda = sprintf( "%02d", $mda );

            my $index = "$idx" . "$mda" . "00";
            $rc_e_index{$index} = $index;
            $index++;
            $rc_e_index{$index} = $index;
        }
    }
    return \%rc_e_index;
}

sub e_class {
    my $passport = shift;

    my $rc_e_idx = $passport->e_index() || {};

    my %rc_e_class;
    foreach my $iid ( keys %$rc_e_idx ) {
        if ( $iid == 1 ) {
            $rc_e_class{$iid} = 'chassis';
        }
        elsif ( $iid =~ /^9(\d)/ and length $iid > 5 ) {
            $rc_e_class{$iid} = 'powerSupply';
        }
        elsif ( $iid =~ /0000$/ ) {
            $rc_e_class{$iid} = 'container';
        }
        else {
            $rc_e_class{$iid} = 'module';
        }
    }
    return \%rc_e_class;
}

sub e_descr {
    my $passport = shift;

    my $model = $passport->model();
    my $rc_ps = $passport->rc_ps_detail() || {};
    my $rc_ch = $passport->rcChasType();
    $rc_ch =~ s/a//;

    my %rc_e_descr;

    # Chassis
    $rc_e_descr{1} = $rc_ch;

    # Power supplies are common, handle them first
    foreach my $idx ( keys %$rc_ps ) {
        next unless $idx;
        my $ps = $rc_ps->{$idx};
        next unless $ps;
        my $index = $idx + 90 . "0000";
        $rc_e_descr{$index} = $ps;
    }

    # Older Accelars use RAPID-CITY::rcCardTable
    if ( defined $model and $model =~ /(105|11[05]0|12[05])/ ) {
        my $rc_c_t = $passport->rc_c_type() || {};
        foreach my $idx ( keys %$rc_c_t ) {
            next unless $idx;
            my $type = $rc_c_t->{$idx};
            next unless $type;
            my $index = "$idx" . "0000";
            $rc_e_descr{$index} = "Slot " . "$idx";
            $index++;
            $rc_e_descr{$index} = $type;
        }
    }

    # All newer models use RAPID-CITY::rc2kCardTable
    else {
        my $rc2_cf = $passport->rc2k_c_fdesc()  || {};
        my $rc2_cb = $passport->rc2k_c_bdesc()  || {};
        my $rc2_m  = $passport->rc2k_mda_desc() || {};

        foreach my $idx ( keys %$rc2_cf ) {
            next unless $idx;
            my $cf = $rc2_cf->{$idx};
            next unless $idx;
            my $cb = $rc2_cb->{$idx};

            my $index = "$idx" . "0000";
            $rc_e_descr{$index} = "Slot " . "$idx";
            $index++;
            $rc_e_descr{$index} = $cf;
            $index++;
            $rc_e_descr{$index} = $cb;
        }
        foreach my $idx ( keys %$rc2_m ) {
            next unless $idx;
            my $cm = $rc2_m->{$idx};
            next unless $cm;
            my ( $slot, $mda ) = split /\./, $idx;
            $mda = sprintf( "%02d", $mda );

            my $index = "$idx" . "$mda" . "00";
            $rc_e_descr{$index} = $cm;
        }
    }
    return \%rc_e_descr;
}

sub e_type {
    my $passport = shift;

    my $model = $passport->model();
    my $rc_ps = $passport->rc_ps_type() || {};
    my $rc_ch = $passport->rcChasType();

    my %rc_e_type;

    # Chassis
    $rc_e_type{1} = $rc_ch;

    # Power supplies are common, handle them first
    foreach my $idx ( keys %$rc_ps ) {
        next unless $idx;
        my $ps = $rc_ps->{$idx};
        next unless $ps;
        my $index = $idx + 90 . "0000";
        $rc_e_type{$index} = $ps;
    }

    # Older Accelars use RAPID-CITY::rcCardTable
    if ( defined $model and $model =~ /(105|11[05]0|12[05])/ ) {
        my $rc_c_t = $passport->rc_c_type() || {};
        foreach my $idx ( keys %$rc_c_t ) {
            next unless $idx;
            my $type = $rc_c_t->{$idx};
            next unless $type;
            my $index = "$idx" . "0000";
            $rc_e_type{$index} = "zeroDotZero";
            $index++;
            $rc_e_type{$index} = $type;
        }
    }

    # All newer models use RAPID-CITY::rc2kCardTable
    else {
        my $rc2_cf = $passport->rc2k_c_ftype()  || {};
        my $rc2_cb = $passport->rc2k_c_btype()  || {};
        my $rc2_m  = $passport->rc2k_mda_type() || {};

        foreach my $idx ( keys %$rc2_cf ) {
            next unless $idx;
            my $cf = $rc2_cf->{$idx};
            next unless $idx;
            my $cb = $rc2_cb->{$idx};

            my $index = "$idx" . "0000";
            $rc_e_type{$index} = "zeroDotZero";
            $index++;
            $rc_e_type{$index} = $cf;
            $index++;
            $rc_e_type{$index} = $cb;
        }
        foreach my $idx ( keys %$rc2_m ) {
            next unless $idx;
            my $cm = $rc2_m->{$idx};
            next unless $cm;
            my ( $slot, $mda ) = split /\./, $idx;
            $mda = sprintf( "%02d", $mda );

            my $index = "$idx" . "$mda" . "00";
            $rc_e_type{$index} = $cm;
        }
    }
    return \%rc_e_type;
}

sub e_name {
    my $passport = shift;

    my $model = $passport->model();
    my $rc_e_idx = $passport->e_index() || {};

    my %rc_e_name;
    foreach my $iid ( keys %$rc_e_idx ) {

        if ( $iid == 1 ) {
            $rc_e_name{$iid} = 'Chassis';
            next;
        }

        my $mod = int( substr( $iid, -4, 2 ) );
        my $slot = substr( $iid, -6, 2 );

        if ( $iid =~ /^9(\d)/ and length $iid > 5 ) {
            $rc_e_name{$iid} = "Power Supply $1";
        }
        elsif ( $iid =~ /(00){2}$/ ) {
            $rc_e_name{$iid} = "Slot $slot";
        }
        elsif ( $iid =~ /(00){1}$/ ) {
            $rc_e_name{$iid} = "Card $slot, MDA $mod";
        }
        elsif ( defined $model
            and $model =~ /(105|11[05]0|12[05])/
            and $iid   =~ /1$/ )
        {
            $rc_e_name{$iid} = "Card $slot";
        }
        elsif ( $iid =~ /1$/ ) {
            $rc_e_name{$iid} = "Card $slot (front)";
        }
        elsif ( $iid =~ /2$/ ) {
            $rc_e_name{$iid} = "Card $slot (back)";
        }
    }
    return \%rc_e_name;
}

sub e_hwver {
    my $passport = shift;

    my $model = $passport->model();
    my $rc_ps = $passport->rc_ps_rev() || {};

    my %rc_e_hwver;

    # Chassis
    $rc_e_hwver{1} = $passport->rc_ch_rev();

    # Power supplies are common, handle them first
    foreach my $idx ( keys %$rc_ps ) {
        next unless $idx;
        my $ps = $rc_ps->{$idx};
        next unless $ps;
        my $index = $idx + 90 . "0000";
        $rc_e_hwver{$index} = $ps;
    }

    # Older Accelars use RAPID-CITY::rcCardTable
    if ( defined $model and $model =~ /(105|11[05]0|12[05])/ ) {
        my $rc_c_t = $passport->rc_c_rev() || {};
        foreach my $idx ( keys %$rc_c_t ) {
            next unless $idx;
            my $type = $rc_c_t->{$idx};
            next unless $type;
            my $index = "$idx" . "0001";
            $rc_e_hwver{$index} = $type;
        }
    }

    # All newer models use RAPID-CITY::rc2kCardTable
    else {
        my $rc2_cf = $passport->rc2k_c_frev()  || {};
        my $rc2_cb = $passport->rc2k_c_brev()  || {};
        my $rc2_m  = $passport->rc2k_mda_rev() || {};

        foreach my $idx ( keys %$rc2_cf ) {
            next unless $idx;
            my $cf = $rc2_cf->{$idx};
            next unless $idx;
            my $cb = $rc2_cb->{$idx};

            my $index = "$idx" . "0001";
            $rc_e_hwver{$index} = $cf;
            $index++;
            $rc_e_hwver{$index} = $cb;
        }
        foreach my $idx ( keys %$rc2_m ) {
            next unless $idx;
            my $cm = $rc2_m->{$idx};
            next unless $cm;
            my ( $slot, $mda ) = split /\./, $idx;
            $mda = sprintf( "%02d", $mda );

            my $index = "$idx" . "$mda" . "00";
            $rc_e_hwver{$index} = $cm;
        }
    }
    return \%rc_e_hwver;
}

sub e_vendor {
    my $passport = shift;

    my $rc_e_idx = $passport->e_index() || {};

    my %rc_e_vendor;
    foreach my $iid ( keys %$rc_e_idx ) {
        $rc_e_vendor{$iid} = 'nortel';
    }
    return \%rc_e_vendor;
}

sub e_serial {
    my $passport = shift;

    my $model = $passport->model();
    my $rc_ps = $passport->rc_ps_serial() || {};

    my %rc_e_serial;

    # Chassis
    $rc_e_serial{1} = $passport->rc_serial();

    # Power supplies are common, handle them first
    foreach my $idx ( keys %$rc_ps ) {
        next unless $idx;
        my $ps = $rc_ps->{$idx};
        next unless $ps;
        my $index = $idx + 90 . "0000";
        $rc_e_serial{$index} = $ps;
    }

    # Older Accelars use RAPID-CITY::rcCardTable
    if ( defined $model and $model =~ /(105|11[05]0|12[05])/ ) {
        my $rc_c_t = $passport->rc_c_serial() || {};
        foreach my $idx ( keys %$rc_c_t ) {
            next unless $idx;
            my $type = $rc_c_t->{$idx};
            next unless $type;
            my $index = "$idx" . "0001";
            $rc_e_serial{$index} = $type;
        }
    }

    # All newer models use RAPID-CITY::rc2kCardTable
    else {
        my $rc2_cf = $passport->rc2k_c_fserial()  || {};
        my $rc2_cb = $passport->rc2k_c_bserial()  || {};
        my $rc2_m  = $passport->rc2k_mda_serial() || {};

        foreach my $idx ( keys %$rc2_cf ) {
            next unless $idx;
            my $cf = $rc2_cf->{$idx};
            next unless $idx;
            my $cb = $rc2_cb->{$idx};

            my $index = "$idx" . "0001";
            $rc_e_serial{$index} = $cf;
            $index++;
            $rc_e_serial{$index} = $cb;
        }
        foreach my $idx ( keys %$rc2_m ) {
            next unless $idx;
            my $cm = $rc2_m->{$idx};
            next unless $cm;
            my ( $slot, $mda ) = split /\./, $idx;
            $mda = sprintf( "%02d", $mda );

            my $index = "$idx" . "$mda" . "00";
            $rc_e_serial{$index} = $cm;
        }
    }
    return \%rc_e_serial;
}

sub e_pos {
    my $passport = shift;

    my $rc_e_idx = $passport->e_index() || {};

    my %rc_e_pos;
    foreach my $iid ( keys %$rc_e_idx ) {
        next unless $iid;
        if ( $iid == 1 ) {
            $rc_e_pos{$iid} = -1;
            next;
        }
        my $sub = int( substr( $iid, -2, 2 ) );
        my $mod = int( substr( $iid, -4, 2 ) );
        my $slot = substr( $iid, -6, 2 );
        if ( $iid =~ /(00){2}$/ ) {
            $rc_e_pos{$iid} = $slot;
        }
        elsif ( $iid =~ /(00){1}$/ ) {
            $rc_e_pos{$iid} = $mod * 100;
        }
        else {
            $rc_e_pos{$iid} = $sub;
        }
    }
    return \%rc_e_pos;
}

sub e_parent {
    my $passport = shift;

    my $rc_e_idx = $passport->e_index() || {};

    my %rc_e_parent;
    foreach my $iid ( keys %$rc_e_idx ) {
        next unless $iid;
        if ( $iid == 1 ) {
            $rc_e_parent{$iid} = 0;
            next;
        }
        my $slot = substr( $iid, -6, 2 );
        if ( $iid =~ /(00){1,2}$/ ) {
            $rc_e_parent{$iid} = 1;
        }
        else {
            $rc_e_parent{$iid} = "$slot" . "0000";
        }
    }
    return \%rc_e_parent;
}

1;
__END__

=head1 NAME

SNMP::Info::Layer3::Passport - SNMP Interface to modular Nortel Ethernet Routing
Switches (formerly Passport / Accelar)

=head1 AUTHOR

Eric Miller

=head1 SYNOPSIS

 # Let SNMP::Info determine the correct subclass for you. 
 my $passport = new SNMP::Info(
                          AutoSpecify => 1,
                          Debug       => 1,
                          DestHost    => 'myswitch',
                          Community   => 'public',
                          Version     => 2
                        ) 
    or die "Can't connect to DestHost.\n";

 my $class = $passport->class();
 print "SNMP::Info determined this device to fall under subclass : $class\n";

=head1 DESCRIPTION

Abstraction subclass for modular Nortel Ethernet Routing Switches (formerly
Passport and Accelar Series Switches).

These devices have some of the same characteristics as the stackable Nortel 
Ethernet Switches (Baystack).  For example, extended interface information is 
gleaned from F<RAPID-CITY>.

For speed or debugging purposes you can call the subclass directly, but not
after determining a more specific class using the method above. 

 my $passport = new SNMP::Info::Layer3::Passport(...);

=head2 Inherited Classes

=over

=item SNMP::Info::SONMP

=item SNMP::Info::RapidCity

=item SNMP::Info::Layer3

=back

=head2 Required MIBs

=over

=item Inherited Classes' MIBs

See L<SNMP::Info::SONMP/"Required MIBs"> for its own MIB requirements.

See L<SNMP::Info::RapidCity/"Required MIBs"> for its own MIB requirements.

See L<SNMP::Info::Layer3/"Required MIBs"> for its own MIB requirements.

=back

=head1 GLOBALS

These are methods that return scalar value from SNMP

=over

=item $passport->model()

Returns model type.  Checks $passport->id() against the 
F<RAPID-CITY-MIB> and then parses out C<rcA>.

=item $passport->vendor()

Returns 'nortel'

=item $passport->os()

Returns 'passport'

=item $passport->os_ver()

Returns the software version extracted from C<sysDescr>

=item $passport->serial()

Returns (C<rcChasSerialNumber>)

=item $passport->root_ip()

Returns the primary IP used to communicate with the device.  Returns the first
found:  CLIP (CircuitLess IP), Management Virtual IP (C<rcSysVirtualIpAddr>),
OSPF Router ID (C<ospfRouterId>), SONMP Advertised IP Address.

=back

=head2 Overrides

=over

=item $passport->index_factor()

Required by SNMP::Info::SONMP.  Returns 64 for 8600, 16 for Accelar.

=item $passport->port_offset()

Required by SNMP::Info::SONMP.  Returns 1.

=item $passport->slot_offset()

Required by SNMP::Info::SONMP.  Returns 0.

=back

=head2 Global Methods imported from SNMP::Info::SONMP

See documentation in L<SNMP::Info::SONMP/"GLOBALS"> for details.

=head2 Global Methods imported from SNMP::Info::RapidCity

See documentation in L<SNMP::Info::RapidCity/"GLOBALS"> for details.

=head2 Globals imported from SNMP::Info::Layer3

See documentation in L<SNMP::Info::Layer3/"GLOBALS"> for details.

=head1 TABLE METHODS

These are methods that return tables of information in the form of a reference
to a hash.

=head2 Overrides

=over

=item $passport->i_index()

Returns SNMP IID to Interface index.  Extends (C<ifIndex>) by adding the index
of the CPU virtual management IP (if present), each CPU Ethernet port, and
each VLAN to ensure the virtual router ports are captured.

=item $passport->interfaces()

Returns reference to the map between IID and physical Port.

Slot and port numbers on the Passport switches are determined by the formula:
port = (C<ifIndex % index_factor>) + port_offset,
slot = int(C<ifIndex / index_factor>).

The physical port name is returned as slot.port.  CPU Ethernet ports are
prefixed with CPU and VLAN interfaces are returned as the VLAN ID prefixed
with Vlan.

=item $passport->i_mac()

MAC address of the interface.  Note this is just the MAC of the port, not
anything connected to it.

=item $passport->i_description()

Description of the interface. Usually a little longer single word name that is both
human and machine friendly.  Not always.

=item $passport->i_name()

Crosses rc_alias() (C<rcPortName>) with ifAlias() and returns the human set
port name if exists.

=item $passport->ip_index()

Maps the IP Table to the IID.  Extends (C<ipAdEntIfIndex>) by adding the index of
the CPU virtual management IP (if present) and each CPU Ethernet port.

=item $passport->ip_netmask()

Extends (C<ipAdEntNetMask>) by adding the mask of the CPU virtual management
IP (if present) and each CPU Ethernet port.

=item $passport->bp_index()

Returns reference to hash of bridge port table entries map back to interface
identifier (iid)

Returns (C<ifIndex>) for both key and value since some devices seem to have
problems with F<BRIDGE-MIB>

=back

=head2 Pseudo F<ENTITY-MIB> information

These devices do not support F<ENTITY-MIB>.  These methods emulate Physical
Table methods using the F<RAPID-CITY MIB>.

=over

=item $passport->e_index()

Returns reference to hash.  Key and Value: Integer. The index is created by
combining the slot, module, and position into a five or six digit integer.
Slot can be either one or two digits while the module and position are each
two digits padded with leading zero if required.

=item $passport->e_class()

Returns reference to hash.  Key: IID, Value: General hardware type.  This
class only returns container, module, and power supply types.

=item $passport->e_descr()

Returns reference to hash.  Key: IID, Value: Human friendly name.

=item $passport->e_name()

Returns reference to hash.  Key: IID, Value: Human friendly name.

=item $passport->e_hwver()

Returns reference to hash.  Key: IID, Value: Hardware version.

=item $passport->e_vendor()

Returns reference to hash.  Key: IID, Value: nortel.

=item $passport->e_serial()

Returns reference to hash.  Key: IID, Value: Serial number.

=item $passport->e_pos()

Returns reference to hash.  Key: IID, Value: The relative position among all
entities sharing the same parent.

=item $passport->e_type()

Returns reference to hash.  Key: IID, Value: Type of component/sub-component.

=item $passport->e_parent()

Returns reference to hash.  Key: IID, Value: The value of e_index() for the
entity which 'contains' this entity.  A value of zero indicates	this entity
is not contained in any other entity.

=back

=head2 Table Methods imported from SNMP::Info::SONMP

See documentation in L<SNMP::Info::SONMP/"TABLE METHODS"> for details.

=head2 Table Methods imported from SNMP::Info::RapidCity

See documentation in L<SNMP::Info::RapidCity/"TABLE METHODS"> for details.

=head2 Table Methods imported from SNMP::Info::Layer3

See documentation in L<SNMP::Info::Layer3/"TABLE METHODS"> for details.

=cut
