# SNMP::Info::Layer2::C1900
#
# Copyright (c) 2008 Max Baker changes from version 0.8 and beyond.
#
# Copyright (c) 2002,2003 Regents of the University of California
# 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::Layer2::C1900;

use strict;
use warnings;
use Exporter;
use SNMP::Info::CDP;
use SNMP::Info::CiscoStats;
use SNMP::Info::CiscoConfig;
use SNMP::Info::CiscoStpExtensions;
use SNMP::Info::CiscoAgg;
use SNMP::Info::Layer2;

@SNMP::Info::Layer2::C1900::ISA = qw/SNMP::Info::CDP SNMP::Info::CiscoStats
    SNMP::Info::CiscoConfig SNMP::Info::CiscoStpExtensions SNMP::Info::CiscoAgg SNMP::Info::Layer2
    Exporter/;
@SNMP::Info::Layer2::C1900::EXPORT_OK = qw//;

our ($VERSION, %FUNCS, %GLOBALS, %MIBS, %MUNGE);

$VERSION = '3.74';

%GLOBALS = (
    %SNMP::Info::Layer2::GLOBALS,
    %SNMP::Info::CiscoAgg::GLOBALS,
    %SNMP::Info::CiscoStpExtensions::GLOBALS,
    %SNMP::Info::CiscoConfig::GLOBALS,
    %SNMP::Info::CiscoStats::GLOBALS,
    %SNMP::Info::CDP::GLOBALS,
    'c1900_flash_status' => 'upgradeFlashBankStatus',
);

%FUNCS = (
    %SNMP::Info::Layer2::FUNCS,
    %SNMP::Info::CiscoAgg::FUNCS,
    %SNMP::Info::CiscoStpExtensions::FUNCS,
    %SNMP::Info::CiscoConfig::FUNCS,
    %SNMP::Info::CiscoStats::FUNCS,
    %SNMP::Info::CDP::FUNCS,

    # ESSWITCH-MIB
    'c1900_p_index'        => 'swPortIndex',
    'c1900_p_ifindex'      => 'swPortIfIndex',
    'c1900_p_duplex'       => 'swPortDuplexStatus',
    'c1900_p_duplex_admin' => 'swPortFullDuplex',
    'c1900_p_name'         => 'swPortName',
    'c1900_p_up_admin'     => 'swPortAdminStatus',
    'c1900_p_type'         => 'swPortMediaCapability',
    'c1900_p_media'        => 'swPortConnectorType',
);

%MIBS = (
    %SNMP::Info::Layer2::MIBS,
    %SNMP::Info::CiscoAgg::MIBS,
    %SNMP::Info::CiscoStpExtensions::MIBS,
    %SNMP::Info::CiscoConfig::MIBS,
    %SNMP::Info::CiscoStats::MIBS,
    %SNMP::Info::CDP::MIBS,

    # Also known as the ESSWITCH-MIB
    'STAND-ALONE-ETHERNET-SWITCH-MIB' => 'series2000'
);

%MUNGE = (
    %SNMP::Info::Layer2::MUNGE,             %SNMP::Info::CiscoAgg::MUNGE,
    %SNMP::Info::CiscoStpExtensions::MUNGE, %SNMP::Info::CiscoConfig::MUNGE,
    %SNMP::Info::CiscoStats::MUNGE,         %SNMP::Info::CDP::MUNGE,
);

sub bulkwalk_no         { return 1; }
sub cisco_comm_indexing { return 1; }

sub vendor {
    return 'cisco';
}

sub os {
    return 'catalyst';
}

sub os_ver {
    my $c1900 = shift;

    # Check for superclass one
    my $os_ver = $c1900->SUPER::os_ver();
    return $os_ver if defined $os_ver;

    my $c1900_flash_status = $c1900->c1900_flash_status();
    return unless defined $c1900_flash_status;

    if ( $c1900_flash_status =~ m/V(\d+\.\d+(\.\d+)?)/ ) {
        return $1;
    }
    return;
}

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

    my $i_descr = $c1900->i_description($partial) || {};

    foreach my $iid ( keys %$i_descr ) {
        $i_descr->{$iid} =~ s/\s*$//;
    }
    return $i_descr;
}

sub i_duplex {
    my $c1900   = shift;
    my $partial = shift;

    my $c1900_p_duplex = $c1900->c1900_p_duplex($partial) || {};

    my %i_duplex;
    foreach my $if ( keys %$c1900_p_duplex ) {
        my $duplex = $c1900_p_duplex->{$if};
        next unless defined $duplex;

        $duplex = 'half' if $duplex =~ /half/i;
        $duplex = 'full' if $duplex =~ /full/i;
        $i_duplex{$if} = $duplex;
    }
    return \%i_duplex;
}

sub i_duplex_admin {
    my $c1900   = shift;
    my $partial = shift;

    my $c1900_p_admin = $c1900->c1900_p_duplex_admin($partial) || {};

    my %i_duplex_admin;
    foreach my $if ( keys %$c1900_p_admin ) {
        my $duplex = $c1900_p_admin->{$if};
        next unless defined $duplex;

        $duplex = 'half' if $duplex =~ /disabled/i;
        $duplex = 'full' if $duplex =~ /flow control/i;
        $duplex = 'full' if $duplex =~ /enabled/i;
        $duplex = 'auto' if $duplex =~ /auto/i;
        $i_duplex_admin{$if} = $duplex;
    }
    return \%i_duplex_admin;
}

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

    my $i_name       = $c1900->orig_i_name($partial)  || {};
    my $c1900_p_name = $c1900->c1900_p_name($partial) || {};

    foreach my $port ( keys %$c1900_p_name ) {
        my $name = $c1900_p_name->{$port};
        next unless defined $name;
        next unless $name !~ /^\s*$/;
        $i_name->{$port} = $name;
    }

    return $i_name;
}

sub set_i_duplex_admin {
    my $c1900 = shift;
    my ( $duplex, $port ) = @_;

    # map a textual duplex to an integer one the switch understands
    my %duplexes = qw/full 1 half 2 auto 3/;

    my $iid = $c1900->c1900_p_ifindex($port);

    return 0 unless $iid->{$port};

    $duplex = lc($duplex);

    return 0 unless defined $duplexes{$duplex};

    return $c1900->set_c1900_p_duplex_admin( $duplexes{$duplex}, $iid );
}

sub i_vlan {
    my $c1900   = shift;
    my $partial = shift;

    # Overlap allows more than one VLAN per port.  Unable to determine default
    my $overlap
        = $c1900->bridgeGroupAllowMembershipOverlap()
        || $c1900->vlanAllowMembershipOverlap()
        || 'disabled';

    if ( $overlap eq 'enabled' ) {
        return {};
    }

    my $member_of = $c1900->bridgeGroupMemberPortOfBridgeGroup()
        || $c1900->vlanMemberPortOfVlan() || {};

    my $i_pvid = {};
    foreach my $idx ( keys %$member_of ) {
        my @values = split( /\./, $idx );
        my ( $vlan, $port ) = @values;
        next unless $vlan;
        next unless $port;
        next if ( defined $partial and $port !~ /^$partial$/ );
        my $value = $member_of->{$idx};
        next if ( $value eq 'false' );

        $i_pvid->{$port} = $vlan;
    }
    return $i_pvid;
}

sub i_vlan_membership {
    my $c1900   = shift;
    my $partial = shift;

    my $member_of = $c1900->bridgeGroupMemberPortOfBridgeGroup()
        || $c1900->vlanMemberPortOfVlan() || {};

    my $i_vlan_membership = {};
    foreach my $idx ( keys %$member_of ) {
        my @values = split( /\./, $idx );
        my ( $vlan, $port ) = @values;
        next unless $vlan;
        next unless $port;
        next if ( defined $partial and $port !~ /^$partial$/ );
        my $value = $member_of->{$idx};
        next if ( $value eq 'false' );

        push( @{ $i_vlan_membership->{$port} }, $vlan );
    }
    return $i_vlan_membership;
}

sub i_vlan_membership_untagged { return {}; }

sub bp_index {
    my $c1900   = shift;
    my $partial = shift;

    my $if_index = $c1900->i_index($partial);
    my $index = $c1900->orig_bp_index($partial) || {};
    foreach my $iid ( keys %$if_index ) {
        $index->{$iid} = $iid if ( !defined $index->{$iid} );
    }
    return $index;
}

1;
__END__

=head1 NAME

SNMP::Info::Layer2::C1900 - SNMP Interface to data from Cisco Catalyst 1900
Network Switches running CatOS

=head1 AUTHOR

Max Baker

=head1 SYNOPSIS

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

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

=head1 DESCRIPTION

Provides abstraction to the configuration information obtainable from a
Catalyst 1900 device through SNMP.  See SNMP::Info for full documentation

Note that most of these devices only talk SNMP version 1, but not all.

=head2 Inherited classes

=over

=item SNMP::Info::CDP

=item SNMP::Info::CiscoStats

=item SNMP::Info::CiscoConfig

=item SNMP::Info::CiscoStpExtensions

=item SNMP::Info::CiscoAgg

=item SNMP::Info::Layer2

=back

=head2 Required MIBs

=over

=item F<STAND-ALONE-ETHERNET-SWITCH-MIB (ESSWITCH-MIB)>

F<ESSWITCH-MIB> is included in the Version 1 MIBs from Cisco.

=back

=head2 Inherited MIBs

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

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

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

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

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

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

=head1 GLOBALS

These are methods that return scalar value from SNMP

=over

=item $c1900->c1900_flash_status()

Usually contains the version of the software loaded in flash.
Used by os_ver()

C<STAND-ALONE-ETHERNET-SWITCH-MIB::upgradeFlashBankStatus>

=item $c1900->os()

Returns 'catalyst'

=item $c1900->os_ver()

Returns CatOS version if obtainable.  First tries to use
SNMP::Info::CiscoStats->os_ver() .  If that fails then it
checks for the presence of $c1900->c1900_flash_status() and culls
the version from there.

=item $c1900->vendor()

Returns 'cisco'

=back

=head2 Overrides

=over

=item $c1900->cisco_comm_indexing()

Returns 1.  Use vlan indexing.

=item $c1900->bulkwalk_no

Return C<1>.  Bulkwalk is turned off for this class.

=back

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

See L<SNMP::Info::CDP/"GLOBALS"> for details.

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

See L<SNMP::Info::CiscoStats/"GLOBALS"> for details.

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

See L<SNMP::Info::CiscoConfig/"GLOBALS"> for details.

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

See L<SNMP::Info::CiscoStpExtensions/"GLOBALS"> for details.

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

See L<SNMP::Info::Layer2/"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 $c1900->interfaces()

Returns reference to the map between IID and physical Port.

=item $c1900->i_duplex()

Returns reference to map of IIDs to current link duplex

=item $c1900->i_duplex_admin()

Returns reference to hash of IIDs to admin duplex setting

=item $c1900->i_name()

Crosses C<ifName> with $c1900->c1900_p_name() and returns the human set port
name if exists.

=item $c1900->i_vlan()

Returns a mapping between the interface and the VLAN / bridge group if overlap
is not enabled.

=item $c1900->i_vlan_membership()

Returns reference to hash of arrays: key = interface, value = array of VLAN /
bridge group IDs.

  Example:
  my $interfaces = $c1900->interfaces();
  my $vlans      = $c1900->i_vlan_membership();

  foreach my $iid (sort keys %$interfaces) {
    my $port = $interfaces->{$iid};
    my $vlan = join(',', sort(@{$vlans->{$iid}}));
    print "Port: $port VLAN: $vlan\n";
  }

=item $c1900->i_vlan_membership_untagged()

Unsupported, returns nothing.

=item $c1900->bp_index()

Returns a bp_index that contains the original bp_index entries and extra
entries for those interfaces listed in if_index, as some C1900 devices do not
return complete bp_indexes.

=back

=head2 F<STAND-ALONE-ETHERNET-SWITCH-MIB> Switch Port Table Entries:

=over

=item $c1900->c1900_p_index()

Maps the Switch Port Table to the IID

C<swPortIfIndex>

=item $c1900->c1900_p_duplex()

Gives Port Duplex Info

(C<swPortDuplexStatus>)

=item $c1900->c1900_p_duplex_admin()

Gives admin setting for Duplex Info

(C<swPortFullDuplex>)

=item $c1900->c1900_p_name()

Gives human set name for port

(C<swPortName>)

=item $c1900->c1900_p_up_admin()

Gives Admin status of port enabled.

(C<swPortAdminStatus>)

=item $c1900->c1900_p_type()

Gives Type of port, i.e. C<"general-ethernet">

(C<swPortMediaCapability>)

=item $c1900->c1900_p_media()

Gives the media of the port , i.e. "C<fiber-sc>"

(C<swPortConnectorType>)

=back

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

See L<SNMP::Info::CDP/"TABLE METHODS"> for details.

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

See L<SNMP::Info::CiscoStats/"TABLE METHODS"> for details.

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

See L<SNMP::Info::CiscoConfig/"TABLE METHODS"> for details.

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

See L<SNMP::Info::CiscoStpExtensions/"TABLE METHODS"> for details.

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

See L<SNMP::Info::CiscoAgg/"TABLE METHODS"> for details.

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

See L<SNMP::Info::Layer2/"TABLE METHODS"> for details.

=head1 SET METHODS

These are methods that provide SNMP set functionality for overridden methods
or provide a simpler interface to complex set operations.  See
L<SNMP::Info/"SETTING DATA VIA SNMP"> for general information on set
operations.

=over

=item $c1900->set_i_duplex_admin(duplex, ifIndex)

Sets port duplex, must be supplied with duplex and port C<ifIndex>.  Speed
choices are 'auto', 'half', 'full'.

  Example:
  my %if_map = reverse %{$c1900->interfaces()};
  $c1900->set_i_duplex_admin('auto', $if_map{'1'})
    or die "Couldn't change port duplex. ",$c1900->error(1);

=back

=cut
