# SNMP::Info::Layer2::Trapeze
#
# Copyright (c) 2012 Eric Miller
#
# 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::Trapeze;

use strict;
use Exporter;
use SNMP::Info;
use SNMP::Info::Bridge;
use SNMP::Info::LLDP;

@SNMP::Info::Layer2::Trapeze::ISA
    = qw/SNMP::Info SNMP::Info::Bridge SNMP::Info::LLDP Exporter/;
@SNMP::Info::Layer2::Trapeze::EXPORT_OK = qw//;

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

$VERSION = '3.08';

%MIBS = (
    %SNMP::Info::MIBS,
    %SNMP::Info::Bridge::MIBS,
    %SNMP::Info::LLDP::MIBS,
    'TRAPEZE-NETWORKS-REGISTRATION-DEVICES-MIB' => 'wirelessLANController',
    'TRAPEZE-NETWORKS-AP-STATUS-MIB'            => 'trpzApStatNumAps',
    'TRAPEZE-NETWORKS-CLIENT-SESSION-MIB'       => 'trpzClSessTotalSessions',
    'TRAPEZE-NETWORKS-SYSTEM-MIB'               => 'trpzSysCpuAverageLoad',
    'TRAPEZE-NETWORKS-BASIC-MIB'                => 'trpzVersionString',
);

%GLOBALS = (
    %SNMP::Info::GLOBALS,
    %SNMP::Info::Bridge::GLOBALS,
    %SNMP::Info::LLDP::GLOBALS,
    'os_ver' => 'trpzVersionString',
    'serial' => 'trpzSerialNumber',
    'mac'    => 'dot1dBaseBridgeAddress',
);

%FUNCS = (
    %SNMP::Info::FUNCS,
    %SNMP::Info::Bridge::FUNCS,
    %SNMP::Info::LLDP::FUNCS,
    # TRAPEZE-NETWORKS-AP-STATUS-MIB::trpzApStatApStatusTable
    'trapeze_ap_mac'      => 'trpzApStatApStatusBaseMac',
    'trapeze_ap_name'     => 'trpzApStatApStatusApName',
    'trapeze_ap_ip'       => 'trpzApStatApStatusIpAddress',
    #'trapeze_ap_loc'      => 'bsnAPLocation',
    'trapeze_ap_sw'       => 'trpzApStatApStatusSoftwareVer',
    'trapeze_ap_fw'       => 'trpzApStatApStatusBootVer',
    'trapeze_ap_model'    => 'trpzApStatApStatusModel',
    'trapeze_ap_type'     => 'trpzApStatApStatusModel',
    'trapeze_ap_status'   => 'trpzApStatApStatusApState',
    'trapeze_ap_vendor'   => 'trpzApStatApStatusManufacturerId',
    'trapeze_ap_num'      => 'trpzApStatApStatusApNum',
    'trapeze_ap_dapnum'   => 'trpzApStatApStatusPortOrDapNum',

    # TRAPEZE-NETWORKS-AP-STATUS-MIB::trpzApStatRadioStatusTable
    'trapeze_apif_mac'    => 'trpzApStatRadioStatusBaseMac',
    'trapeze_apif_type'   => 'trpzApStatRadioStatusRadioPhyType',
    'trapeze_apif_ch_num' => 'trpzApStatRadioStatusCurrentChannelNum',
    'trapeze_apif_power'  => 'trpzApStatRadioStatusCurrentPowerLevel',
    'trapeze_apif_admin'  => 'trpzApStatRadioStatusRadioMode',

    # TRAPEZE-NETWORKS-AP-STATUS-MIB::trpzApStatRadioServiceTable
    'trapeze_apif_prof'   => 'trpzApStatRadioServServiceProfileName',

    # TRAPEZE-NETWORKS-AP-CONFIG-MIB::trpzApConfServiceProfileTable
    'trapeze_ess_bcast'   => 'trpzApConfServProfBeaconEnabled',

    # TRAPEZE-NETWORKS-AP-CONFIG-MIB::trpzApConfRadioConfigTable
    'trapeze_apcr_txpwr'  => 'trpzApConfRadioConfigTxPower',
    'trapeze_apcr_ch'     => 'trpzApConfRadioConfigChannel',
    'trapeze_apcr_mode'   => 'trpzApConfRadioConfigRadioMode',

    # TRAPEZE-NETWORKS-AP-CONFIG-MIB::trpzApConfApConfigTable
    'trapeze_apc_descr'   => 'trpzApConfApConfigDescription',
    'trapeze_apc_loc'     => 'trpzApConfApConfigLocation',
    'trapeze_apc_name'    => 'trpzApConfApConfigApName',
    'trapeze_apc_model'   => 'trpzApConfApConfigApModelName',
    'trapeze_apc_serial'  => 'trpzApConfApConfigApSerialNum',

    # TRAPEZE-NETWORKS-CLIENT-SESSION-MIB::trpzClSessClientSessionTable
    'trapeze_sta_slot'    => 'trpzClSessClientSessRadioNum',
    'trapeze_sta_serial'  => 'trpzClSessClientSessApSerialNum',
    'trapeze_sta_ssid'    => 'trpzClSessClientSessSsid',
    'trapeze_sta_ip'      => 'trpzClSessClientSessIpAddress',

    # TRAPEZE-NETWORKS-AP-STATUS-MIB::trpzApStatRadioServiceTable
    'trapeze_apif_bssid'  => 'trpzApStatRadioServBssid',

    # TRAPEZE-NETWORKS-CLIENT-SESSION-MIB::trpzClSessClientSessionStatisticsTable
    # Pretend to have the CISCO-DOT11-MIB for signal strengths, etc.
    'cd11_sigstrength' => 'trpzClSessClientSessStatsLastRssi',
    'cd11_sigqual'     => 'trpzClSessClientSessStatsLastSNR',
    'cd11_txrate'      => 'trpzClSessClientSessStatsLastRate',
    # These are supposed to be there...
    'cd11_rxbyte'      => 'trpzClSessClientSessStatsUniOctetIn',
    'cd11_txbyte'      => 'trpzClSessClientSessStatsUniOctetOut',
    'cd11_rxpkt'       => 'trpzClSessClientSessStatsUniPktIn',
    'cd11_txpkt'       => 'trpzClSessClientSessStatsUniPktOut',
);

%MUNGE = (
    %SNMP::Info::MUNGE,
    %SNMP::Info::Bridge::MUNGE,
    %SNMP::Info::LLDP::MUNGE,
    'trapeze_apif_mac'      => \&SNMP::Info::munge_mac,
    'trapeze_apif_bssid'    => \&SNMP::Info::munge_mac,
);

sub layers {
    return '00000111';
}

sub os {
    return 'trapeze';
}

sub vendor {
    return 'juniper';
}

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

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

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

    return $id unless defined $model;

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

sub _ap_serial {
    my $trapeze = shift;
    my $partial  = shift;

    my $names = $trapeze->trapeze_ap_name($partial) || {};

    my %ap_serial;
    foreach my $iid ( keys %$names ) {
        next unless $iid;

        my $serial = join( '', map { sprintf "%c", $_ } split /\./, $iid );
	# Remove any control characters to include nulls
        $serial =~ s/[\c@-\c_]//g;

        $ap_serial{$iid} = "$serial";
    }
    return \%ap_serial;
}

# Wireless switches do not support ifMIB requirements to get MAC
# and port status

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

    my $i_index  = $trapeze->orig_i_index($partial)      || {};
    my $ap_index = $trapeze->trapeze_apif_mac($partial) || {};

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

        $if_index{$iid} = $index;
    }

    # Get Attached APs as Interfaces
    foreach my $ap_id ( keys %$ap_index ) {
	
	my $mac = $ap_index->{$ap_id};
        next unless ($mac);

        $if_index{$ap_id} = $mac;
    }

    return \%if_index;
}

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

    my $i_index      = $trapeze->i_index($partial)              || {};
    my $descriptions = $trapeze->SUPER::i_description($partial) || {};

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

        $if{$iid} = $desc;
    }

    return \%if;
}

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

    my $i_index = $trapeze->i_index($partial)            || {};
    my $i_desc  = $trapeze->orig_i_description($partial) || {};
    my $ap_name = $trapeze->trapeze_ap_name($partial)   || {};

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

        if ( $index =~ /^\d+$/ ) {
            my $name = $i_desc->{$iid};
            next unless defined $name;
            $i_name{$iid} = $name;
        }

        elsif ( $index =~ /(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}/ ) {
            my $idx = $iid;
            $idx =~ s/\.(\d+)$//;
	    my $radio = $1;
	    $radio--;
            my $name = $ap_name->{$idx};
            next unless defined $name;
            $i_name{$iid} = "Radio-$radio: $name";
        }

        else {
            $i_name{$iid} = $index;
        }
    }
    return \%i_name;
}

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

    return $trapeze->i_description($partial);
}

sub i_type {
    my $trapeze = shift;
    my $partial  = shift;

    my $i_index   = $trapeze->i_index($partial)     || {};
    my $i_type    = $trapeze->orig_i_type($partial) || {};

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

        if ( $index =~ /^\d+$/ ) {
            my $type = $i_type->{$iid};
            next unless defined $type;
            $i_type{$iid} = $type;
        }

        elsif ( $index =~ /(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}/ ) {
	    # Match to an ifType
            $i_type{$iid} = 'capwapWtpVirtualRadio';
        }

        else {
            next;
        }
    }
    return \%i_type;
}

sub i_up {
    my $trapeze = shift;
    my $partial   = shift;

    return $trapeze->i_up_admin($partial);
}

sub i_up_admin {
    my $trapeze = shift;
    my $partial   = shift;

    my $i_index = $trapeze->i_index($partial)             || {};
    my $i_up    = $trapeze->orig_i_up($partial)           || {};
    my $apif_up = $trapeze->trapeze_apif_admin($partial) || {};

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

        if ( $index =~ /^\d+$/ ) {
            my $stat = $i_up->{$iid};
            next unless defined $stat;
            $i_up_admin{$iid} = $stat;
        }

        elsif ( $index =~ /(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}/ ) {
            my $stat = $apif_up->{$iid};
            next unless defined $stat;
            $i_up_admin{$iid} = $stat;
        }

        else {
            next;
        }
    }
    return \%i_up_admin;
}

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

    my $i_index = $trapeze->i_index($partial)    || {};
    my $i_mac   = $trapeze->orig_i_mac($partial) || {};

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

        if ( $index =~ /^\d+$/ ) {
            my $mac = $i_mac->{$iid};
            next unless defined $mac;
            $i_mac{$iid} = $mac;
        }

        # Don't grab AP MACs - we want the AP to show up on edge switch
        # ports

        else {
            next;
        }
    }
    return \%i_mac;
}

# Wireless switches do not support the standard Bridge MIB for client devices
sub bp_index {
    my $trapeze = shift;
    my $partial  = shift;

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

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

        $bp_index{$index} = $iid;
    }
    return \%bp_index;
}

sub fw_mac {
    my $trapeze = shift;
    my $partial  = shift;
    
    my $serials = $trapeze->trapeze_sta_serial($partial) || {};

    my %fw_mac;
    foreach my $iid ( keys %$serials ) {
        next unless $iid;

        my $mac = join( ':', map { sprintf "%02x", $_ } split /\./, $iid );
        next unless $mac =~ /^([0-9A-F][0-9A-F]:){5}[0-9A-F][0-9A-F]$/i;
	
        $fw_mac{$iid} = $mac;
    }
    return \%fw_mac;    
}

sub fw_port {
    my $trapeze = shift;
    my $partial  = shift;

    my $slots      = $trapeze->trapeze_sta_slot($partial) || {};
    my $serials    = $trapeze->trapeze_sta_serial($partial) || {};
    my $ap_serials = $trapeze->_ap_serial($partial) || {};
    my %serial_iid = reverse %$ap_serials;

    my %fw_port;
    foreach my $iid ( keys %$slots ) {
        my $slot = $slots->{$iid};
        next unless defined $slot;
	$slot =~ s/radio-//i;
	
        my $serial = $serials->{$iid};
        next unless defined $serial;
	my $index = $serial_iid{$serial};
	next unless defined $index;
	
        $fw_port{$iid} = "$index.$slot";
    }
    return \%fw_port;
}

sub i_ssidlist {
    my $trapeze = shift;
    my $partial  = shift;

    my $apif_bssid = $trapeze->trapeze_apif_bssid($partial) || {};
    my $i_index    = $trapeze->i_index($partial)             || {};

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

	# Skip non-radio interfaces
	next if $iid =~ /^\d+$/;

	foreach my $idx ( keys %$apif_bssid ) {
	    next unless ( $idx =~ /^$iid\./ );
	    my $bssid_mac = $apif_bssid->{$idx};
	    next unless $bssid_mac;

	    # Give the SSID a numeric value based upon tail of BSSID
	    my $id;
	    if ( $bssid_mac =~ /:([0-9A-F]{1,2})$/i ) {
		$id = hex $1;
	    }
	    next unless ( defined $id and $id =~ /\d+/ );
	    my $ssid_oid = $idx;
	    $ssid_oid =~ s/^$iid\.//;

	    my $ssid
		= join( '', map { sprintf "%c", $_ } split /\./, $ssid_oid );

	    # Remove any control characters including nulls
	    $ssid =~ s/[\c@-\c_]//g;
	    $i_ssidlist{"$iid.$id"} = $ssid;
	}
    }
    return \%i_ssidlist;
}


# Can't find in MIB
#
#sub i_ssidbcast {
#
#}

sub i_80211channel {
    my $trapeze = shift;
    my $partial  = shift;

    my $ch_list = $trapeze->trapeze_apif_ch_num($partial) || {};

    my %i_80211channel;
    foreach my $iid ( keys %$ch_list ) {
        my $ch = $ch_list->{$iid};
        next unless $ch =~ /\d+/;
        $i_80211channel{$iid} = $ch;
    }
    return \%i_80211channel;
}

sub dot11_cur_tx_pwr_mw {
    my $trapeze = shift;
    my $partial  = shift;

    my $cur = $trapeze->trapeze_apif_power($partial);
    
    my $dot11_cur_tx_pwr_mw = {};
    foreach my $idx ( keys %$cur ) {
        my $pwr_dbm = $cur->{$idx};
	next unless $pwr_dbm;
	#Convert to milliWatts = 10(dBm/10)
        my $pwr = int (10 ** ($pwr_dbm / 10));
	
        $dot11_cur_tx_pwr_mw->{$idx} = $pwr; 
    }
    return $dot11_cur_tx_pwr_mw;
}

# Pseudo ENTITY-MIB methods

sub e_index {
    my $trapeze = shift;

    # Try new first, fall back to depreciated
    my $ap_num = $trapeze->trapeze_ap_num() || $trapeze->trapeze_ap_dapnum() || {};
  
    my %e_index;

    # Chassis
    $e_index{1} = 1;

    # We're going to hack an index to capture APs
    foreach my $idx ( keys %$ap_num ) {
	my $number = $ap_num->{$idx};
	next unless $number =~ /\d+/;

        $e_index{$idx} = $number;
    }
    return \%e_index;
}

sub e_class {
    my $trapeze = shift;

    my $e_idx = $trapeze->e_index() || {};

    my %e_class;
    foreach my $iid ( keys %$e_idx ) {
        if ( $iid eq "1" ) {
            $e_class{$iid} = 'chassis';
        }

        # This isn't a valid PhysicalClass, but we're hacking this anyway
        else {
            $e_class{$iid} = 'ap';
        }
    }
    return \%e_class;
}

sub e_name {
    my $trapeze = shift;

    my $ap_name = $trapeze->trapeze_ap_name() || {};

    my %e_name;

    # Chassis
    $e_name{1} = 'WLAN Controller';

    # APs
    foreach my $iid ( keys %$ap_name ) {
        $e_name{$iid} = 'AP';
    }
    return \%e_name;
}

sub e_descr {
    my $trapeze = shift;

    my $ap_model = $trapeze->trapeze_ap_model() || {};
    my $ap_name  = $trapeze->trapeze_ap_name()  || {};

    my %e_descr;

    # Chassis
    $e_descr{1} = $trapeze->model();

    # APs
    foreach my $iid ( keys %$ap_name ) {
        my $name = $ap_name->{$iid};
        next unless defined $name;
        my $model = $ap_model->{$iid} || 'AP';

        $e_descr{$iid} = "$model: $name";
    }
    return \%e_descr;
}

sub e_model {
    my $trapeze = shift;

    my $ap_model = $trapeze->trapeze_ap_model() || {};

    my %e_model;

    # Chassis
    $e_model{1} = $trapeze->model();

    # APs
    foreach my $iid ( keys %$ap_model ) {
        my $model = $ap_model->{$iid};
        next unless defined $model;

        $e_model{$iid} = $model;
    }
    return \%e_model;
}

sub e_type {
    my $trapeze = shift;

    return $trapeze->e_model();
}

sub e_fwver {
    my $trapeze = shift;

    my $ap_fw = $trapeze->trapeze_ap_fw() || {};

    my %e_fwver;
    # APs
    foreach my $iid ( keys %$ap_fw ) {
        my $fw = $ap_fw->{$iid};
        next unless defined $fw;

        $e_fwver{$iid} = $fw;
    }
    return \%e_fwver;
}

sub e_vendor {
    my $trapeze = shift;

    my $vendors = $trapeze->trapeze_ap_vendor() || {};

    my %e_vendor;

    # Chassis
    $e_vendor{1} = 'avaya';

    # APs
    foreach my $iid ( keys %$vendors ) {
        my $vendor = $vendors->{$iid};
        next unless defined $vendor;

        $e_vendor{$iid} = $vendor;
    }
    return \%e_vendor;
}

sub e_serial {
    my $trapeze = shift;

    my $ap_serial = $trapeze->_ap_serial() || {};

    my %e_serial;

    # Chassis
    $e_serial{1} = $trapeze->serial();

    # APs
    foreach my $iid ( keys %$ap_serial ) {
        my $serial = $ap_serial->{$iid};
        next unless defined $serial;

        $e_serial{$iid} = $serial;
    }
    return \%e_serial;
}

sub e_pos {
    my $trapeze = shift;

    my $e_idx = $trapeze->e_index() || {};

    my %e_pos;
    my $pos = 0;
    foreach my $iid ( sort keys %$e_idx ) {
        if ( $iid eq "1" ) {
            $e_pos{$iid} = -1;
            next;
        }
        else {
            $pos++;
            $e_pos{$iid} = $pos;
        }
    }
    return \%e_pos;
}

sub e_swver {
    my $trapeze = shift;

    my $ap_sw = $trapeze->trapeze_ap_sw() || {};

    my %e_swver;

    # Chassis
    $e_swver{1} = $trapeze->os_ver();

    # APs
    foreach my $iid ( keys %$ap_sw ) {
        my $sw = $ap_sw->{$iid};
        next unless defined $sw;

        $e_swver{$iid} = $sw;
    }
    return \%e_swver;
}

sub e_parent {
    my $trapeze = shift;

    my $e_idx = $trapeze->e_index() || {};

    my %e_parent;
    foreach my $iid ( sort keys %$e_idx ) {
        if ( $iid eq "1" ) {
            $e_parent{$iid} = 0;
            next;
        }
        else {
            $e_parent{$iid} = 1;
        }
    }
    return \%e_parent;
}

# arpnip:
#
# This is the controller snooping on the MAC->IP mappings.
# Pretending this is arpnip data allows us to get MAC->IP
# mappings even for stations that only communicate locally.

sub at_paddr {
    my $trapeze = shift;

    my $mac2ip = $trapeze->trapeze_sta_ip();

    my $ret = {};
    foreach my $idx ( keys %$mac2ip ) {
	next if ( $mac2ip->{ $idx } eq '0.0.0.0' );
	my $mac = join( ":", map { sprintf "%02x", $_ } split /\./, $idx );
	$ret->{$idx} = $mac;
    }
    return $ret;
}

sub at_netaddr {
    my $trapeze = shift;

    my $mac2ip = $trapeze->trapeze_sta_ip();

    my $ret = {};
    foreach my $idx ( keys %$mac2ip ) {
	next if ( $mac2ip->{ $idx } eq '0.0.0.0' );
	$ret->{$idx} = $mac2ip->{ $idx };
    }
    return $ret;
}

# Client MAC
sub cd11_mac {
    my $trapeze = shift;
    my $cd11_sigstrength = $trapeze->cd11_sigstrength();

    my $ret = {};
    foreach my $idx ( keys %$cd11_sigstrength ) {
	my $mac = join( ":", map { sprintf "%02x", $_ } split /\./, $idx );
	$ret->{$idx} = $mac
    }
    return $ret;
}


1;
__END__

=head1 NAME

SNMP::Info::Layer2::Trapeze - SNMP Interface to Juniper (Trapeze) Wireless
Controllers

=head1 AUTHOR

Eric Miller

=head1 SYNOPSIS

    #Let SNMP::Info determine the correct subclass for you.

    my $trapeze = new SNMP::Info(
                          AutoSpecify => 1,
                          Debug       => 1,
                          DestHost    => 'myswitch',
                          Community   => 'public',
                          Version     => 2
                        ) 

    or die "Can't connect to DestHost.\n";

    my $class = $trapeze->class();
    print " Using device sub class : $class\n";

=head1 DESCRIPTION

Provides abstraction to the configuration information obtainable from 
Juniper (Trapeze) Wireless Controllers through SNMP.

This class emulates bridge functionality for the wireless switch. This enables
end station MAC addresses collection and correlation to the thin access point
the end station is using for communication.

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

my $trapeze = new SNMP::Info::Layer2::Trapeze(...);

=head2 Inherited Classes

=over

=item SNMP::Info

=item SNMP::Info::Bridge

=back

=head2 Required MIBs

=over

=item F<TRAPEZE-NETWORKS-REGISTRATION-DEVICES-MIB>

=item F<TRAPEZE-NETWORKS-AP-STATUS-MIB>

=item F<TRAPEZE-NETWORKS-CLIENT-SESSION-MIB>

=item F<TRAPEZE-NETWORKS-SYSTEM-MIB>

=item F<TRAPEZE-NETWORKS-BASIC-MIB>

=back

=head2 Inherited Classes' MIBs

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

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

=head1 GLOBALS

These are methods that return scalar value from SNMP

=over

=item $trapeze->vendor()

Returns 'juniper'

=item $trapeze->os()

Returns 'trapeze'

=item $trapeze->os_ver()

(C<trpzVersionString>)

=item $trapeze->model()

Tries to reference $trapeze->id() to F<TRAPEZE-NETWORKS-REGISTRATION-DEVICES-MIB>

Removes C<'wirelessLANController'> for readability.

=item $trapeze->serial()

(C<trpzSerialNumber>)

=item $trapeze->mac()

(C<dot1dBaseBridgeAddress>)

=back

=head2 Overrides

=over

=item $trapeze->layers()

Returns 00000011.  Class emulates Layer 2 functionality for Thin APs through
proprietary MIBs.

=back

=head2 Global Methods imported from SNMP::Info

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

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

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

=head1 TABLE METHODS

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

=over 

=item $trapeze->i_ssidlist()

Returns reference to hash.  SSID's recognized by the radio interface.

=item $trapeze->i_80211channel()

Returns reference to hash.  Current operating frequency channel of the radio
interface.

=item $trapeze->dot11_cur_tx_pwr_mw()

Returns reference to hash.  Current transmit power, in milliwatts, of the
radio interface.

=item cd11_mac()

Client MAC address.

=back

=head2 AP Status Table  (C<trpzApStatApStatusTable>)

A table describing all the APs currently present and managed by the
controller.

=over

=item $trapeze->trapeze_ap_mac()

(C<trpzApStatApStatusBaseMac>)

=item $trapeze->trapeze_ap_name()

(C<trpzApStatApStatusApName>)

=item $nws2300->trapeze_ap_ip()

(C<trpzApStatApStatusIpAddress>)

=item $nws2300->trapeze_ap_sw()

(C<trpzApStatApStatusSoftwareVer>)

=item $nws2300->trapeze_ap_fw()

(C<trpzApStatApStatusBootVer>)

=item $nws2300->trapeze_ap_model()

(C<trpzApStatApStatusModel>)

=item $nws2300->trapeze_ap_type()

(C<trpzApStatApStatusModel>)

=item $nws2300->trapeze_ap_status()

(C<trpzApStatApStatusApState>)

=item $nws2300->trapeze_ap_vendor()

(C<trpzApStatApStatusManufacturerId>)

=item $nws2300->trapeze_ap_num()

(C<trpzApStatApStatusApNum>)

=item $nws2300->trapeze_ap_dapnum()

(C<trpzApStatApStatusPortOrDapNum>)

=back

=head2 AP Radio Status Table  (C<trpzApStatRadioStatusTable>)

A table describing all radios on all the APs currently present and managed
by the controller.

=over

=item $nws2300->trapeze_apif_mac()

(C<trpzApStatRadioStatusBaseMac>)

=item $nws2300->trapeze_apif_type()

(C<trpzApStatRadioStatusRadioPhyType>)

=item $nws2300->trapeze_apif_ch_num()

(C<trpzApStatRadioStatusCurrentChannelNum>)

=item $nws2300->trapeze_apif_power()

(C<trpzApStatRadioStatusCurrentPowerLevel>)

=item $nws2300->trapeze_apif_admin()

(C<trpzApStatRadioStatusRadioMode>)

=back

=head2 AP Radio Status Service Table (C<trpzApStatRadioServiceTable>)

A table describing radio services associated with APs currently present
and managed by the controller.

=over

=item $nws2300->trapeze_apif_bssid()

(C<trpzApStatRadioServBssid>)

=item $nws2300->trapeze_apif_prof()

(C<trpzApStatRadioServServiceProfileName>)

=back

=head2 AP Service Profile Config Table (C<trpzApConfServiceProfileTable>)

=over

=item $nws2300->trapeze_ess_bcast()

(C<trpzApConfServProfBeaconEnabled>)

=back

=head2 AP Radio Config Table (C<trpzApConfRadioConfigTable>)

=over

=item $nws2300->trapeze_apcr_txpwr()

(C<trpzApConfRadioConfigTxPower>)

=item $nws2300->trapeze_apcr_ch()

(C<trpzApConfRadioConfigChannel>)

=item $nws2300->trapeze_apcr_mode()

(C<trpzApConfRadioConfigRadioMode>)

=back

=head2 AP Config Table (C<trpzApConfApConfigTable>)

=over

=item $nws2300->trapeze_apc_descr()

(C<trpzApConfApConfigDescription>)

=item $nws2300->trapeze_apc_loc()

(C<trpzApConfApConfigLocation>)

=item $nws2300->trapeze_apc_name()

(C<trpzApConfApConfigApName>)

=item $nws2300->trapeze_apc_model()

(C<trpzApConfApConfigApModelName>)

=item $nws2300->trapeze_apc_serial()

(C<trpzApConfApConfigApSerialNum>)

=back

=head2 Client Session Table (C<trpzClSessClientSessionTable>)

=over

=item $nws2300->trapeze_sta_slot()

(C<trpzClSessClientSessRadioNum>)

=item $nws2300->trapeze_sta_serial()

(C<trpzClSessClientSessApSerialNum>)

=item $nws2300->trapeze_sta_ssid()

(C<trpzClSessClientSessSsid>)

=item $nws2300->trapeze_sta_ip()

(C<trpzClSessClientSessIpAddress>)

=back

=head2 Client Session Statistics Table (C<trpzClSessClientSessionStatisticsTable>)

These emulate the F<CISCO-DOT11-MIB>

=over

=item $nws2300->cd11_sigstrength()

(C<trpzClSessClientSessStatsLastRssi>)

=item $nws2300->cd11_sigqual()

(C<trpzClSessClientSessStatsLastSNR>)

=item $nws2300->cd11_txrate()

(C<trpzClSessClientSessStatsLastRate>)

=item $nws2300->cd11_rxbyte()

(C<trpzClSessClientSessStatsUniOctetIn>)

=item $nws2300->cd11_txbyte()

(C<trpzClSessClientSessStatsUniOctetOut>)

=item $nws2300->cd11_rxpkt()

(C<trpzClSessClientSessStatsUniPktIn>)

=item $nws2300->cd11_txpkt()

(C<trpzClSessClientSessStatsUniPktOut>)

=back 

=head2 Table Methods imported from SNMP::Info

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

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

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

=head2 Overrides

=over

=item $trapeze->i_index()

Returns reference to map of IIDs to Interface index. 

Extends C<ifIndex> to support thin APs and WLAN virtual interfaces as device
interfaces.

=item $trapeze->interfaces()

Returns reference to map of IIDs to ports.  Thin APs are implemented as device 
interfaces.  The thin AP MAC address and Slot ID trapeze_apif_slot() are
used as the port identifier.

=item $trapeze->i_name()

Returns reference to map of IIDs to interface names.  Returns C<ifName> for
Ethernet interfaces and trapeze_ap_name() for thin AP interfaces.

=item $trapeze->i_description()

Returns reference to map of IIDs to interface types.  Returns C<ifDescr>
for Ethernet interfaces, trapeze_ap_name() for thin AP interfaces.

=item $trapeze->i_type()

Returns reference to map of IIDs to interface descriptions.  Returns
C<ifType> for Ethernet interfaces and C<'capwapWtpVirtualRadio'> for thin AP
interfaces.

=item $trapeze->i_up()

Returns reference to map of IIDs to link status of the interface.  Returns
C<ifOperStatus> for Ethernet interfaces and trapeze_apif_admin() for thin AP
interfaces.

=item $trapeze->i_up_admin()

Returns reference to map of IIDs to administrative status of the interface.
Returns C<ifAdminStatus> for Ethernet interfaces and trapeze_apif_admin()
for thin AP interfaces.

=item $trapeze->i_mac()

Returns reference to map of IIDs to MAC address of the interface.  Returns
C<ifPhysAddress> for Ethernet interfaces.

=item $trapeze->bp_index()

Simulates bridge MIB by returning reference to a hash mapping i_index() to
the interface iid.

=item $trapeze->fw_port()

Returns reference to a hash, value being mac and
trapeze_sta_slot() combined to match the interface iid.  

=item $trapeze->fw_mac()

Extracts the MAC from the trapeze_sta_serial() index.

=back

=head2 Pseudo ARP Cache Entries

The controller snoops on the MAC->IP mappings.  Using this as ARP cache data
allows us to get MAC->IP mappings even for stations that only
communicate locally.  The data is gathered from trapeze_sta_ip().

=over

=item $trapeze->at_paddr()

Returns reference to hash of Pseudo Arp Cache Entries to MAC address

=item $trapeze->at_netaddr()

Returns reference to hash of Pseudo Arp Cache Entries to IP Address

=back

=head2 Pseudo F<ENTITY-MIB> information

These methods emulate F<ENTITY-MIB> Physical Table methods using
F<TRAPEZE-NETWORKS-AP-STATUS-MIB>.  Thin APs are included as subcomponents of
the wireless controller.

=over

=item $trapeze->e_index()

Returns reference to hash.  Key: IID and Value: Integer. The index for APs is
created with an integer representation of the last three octets of the
AP MAC address.

=item $trapeze->e_class()

Returns reference to hash.  Key: IID, Value: General hardware type.  Return ap
for wireless access points.

=item $trapeze->e_descr()

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

=item $trapeze->e_model()

Returns reference to hash.  Key: IID, Value: Model name.

=item $trapeze->e_name()

More computer friendly name of entity.  Name is either 'WLAN Controller' or
'AP'.

=item $trapeze->e_vendor()

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

=item $trapeze->e_serial()

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

=item $trapeze->e_pos()

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

=item $trapeze->e_type()

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

=item $trapeze->e_fwver()

Returns reference to hash.  Key: IID, Value: Firmware revision.

=item $trapeze->e_swver()

Returns reference to hash.  Key: IID, Value: Software revision.

=item $trapeze->e_parent()

Returns reference to hash.  Key: IID, Value: The value of e_index() for the
entity which 'contains' this entity.

=back

=cut
