####################################################################
#
# SnmpAgent module for use with the code generated by mib2c with the 
# embedded perl configuration file mib2c.perl.conf
#
# Copyright Tripleplay Services Limited 2005
# All rights reserved.
#
# Use is subject to license terms specified in the COPYING file
# distributed with the Net-SNMP package.
#
####################################################################

package NetSNMP::agent::Support;
require Exporter;

use NetSNMP::OID (':all');
use NetSNMP::agent (':all');
use NetSNMP::ASN (':all');
use NetSNMP::default_store (':all');
use Data::Dumper;


use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);

@ISA       = qw(Exporter getLeaf);
@EXPORT    = qw(registerAgent getOidElement setOidElement);
@EXPORT_OK = qw();
$VERSION = '5.05';

use strict;
use warnings;

BEGIN {
    print STDERR "Module SnmpAgent.pm loaded ok\n";
}

# set to 1 to get extra debugging information
my $debugging = 0;

# if we're not embedded, this will get auto-set below to 1
my $subagent = 0;

################################################################
# The oidtable is used for rapid random access to elements with known
# oids, such as when doing gets/sets. It is not so good for
# ordered access. For that we build a tree (see below) and setup 
# next links in the oidtable to aid in evaluating where to go next
# for GETNEXT requests.
################################################################
my $oidtable;
my $oidtree;

# oidroot holds the top most oid record in the table. 
my $oidroot = "";

################################################################
# Build a tree for handling getnext requests
# Some parts borrowed from the perl cookbook
################################################################
sub buildTree {
    my ($new_oidtable) = @_;
    foreach my $key (keys %$new_oidtable) {
	insert($oidtree, $key);
    }
}

sub printTree {
    my ($tree) = @_;
    return unless $tree;
    printTree($tree->{LEFT});
    print $tree->{VALUE}. "\n";
    printTree($tree->{RIGHT});
}

my $prev="";
##############################################################
# Walk the sorted oid tree and set the 'next' links in the 
# oidtable.
##############################################################
sub doLinks {
    my ($tree) = @_;
    return unless $tree;
    doLinks($tree->{LEFT});
    if($oidroot eq "") {
	$oidroot = $tree->{VALUE};
#	print "Setting oidroot to $oidroot\n";
    }
    if($prev ne "") {
	$oidtable->{$prev}->{next} = $tree->{VALUE};
    }
    $prev = $tree->{VALUE};
    my $node = $oidtable->{$tree->{VALUE}};
    doLinks($tree->{RIGHT});
}

################################################################
# Insert a node into the tree
################################################################
sub insert {
    my ($tree, $value) = @_;
    unless ($tree) {
	$tree = {};  # Allocate memory
	$tree->{VALUE} = $value;
	$tree->{LEFT} = undef;
	$tree->{RIGHT} = undef;
	$_[0] = $tree;
	return ;
    }
    my $tv = new NetSNMP::OID($tree->{VALUE});
    my $mv = new NetSNMP::OID($value);
    if ($tv > $mv) {
	insert($tree->{LEFT}, $value);
    } elsif ($tv < $mv) {
	insert($tree->{RIGHT}, $value);
    } else {
	print "ERR: Duplicate insert of $value\n";
    }
}

#################################################################
## Main interface. 
## registerAgent(oid);
#################################################################
sub registerAgent {
    my $agent = shift;
    my $regat = shift;
    my $new_oidtable = shift;

    foreach (keys %$new_oidtable) { $oidtable->{$_} = $new_oidtable->{$_}; }

    # This is necessary to avoid problems traversing trees where the
    # IID index is range-limited to not include .0, which is used as a 
    # placeholder in the oidtable.
     netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, 
                            NETSNMP_DS_LIB_DONT_CHECK_RANGE, 1);

    print STDERR "Building OID tree\n";
    buildTree($new_oidtable);    
    doLinks($oidtree);
 
#    print Dumper($oidtable);

    # Debug. Print the list of oids in their next ordering
    my $node = $oidroot;
    while($node) {
#	print $node . "\n";
	$node = $oidtable->{$node}->{next};
    }

# where we are going to hook onto
    my $regoid = new NetSNMP::OID($regat);
    print "registering at ",$regoid,"\n" if ($debugging);
    
# If we're not running embedded within the agent, then try to start
# our own subagent instead.
    if (! $agent) {
	$agent = new NetSNMP::agent('Name' => 'test', # reads test.conf
			    'AgentX' => 1);   # make us a subagent
	$subagent = 1;
	print STDERR "started us as a subagent ($agent)\n"
	}
    
# we register ourselves with the master agent we're embedded in.  The
# global $agent variable is how we do this:
    print Dumper($agent) if ($debugging);
    $agent->register('myname',$regoid, \&my_snmp_handler);
}
 
   
######################################################################
# The subroutine to handle the incoming requests to our
# part of the OID tree.  This subroutine will get called for all
# requests within the OID space under the registration oid made above.
######################################################################
sub my_snmp_handler {
    my ($handler, $registration_info, $request_info, $requests) = @_;

    my $request;
    my $reqnum=1;

#    print STDERR "refs: ",join(", ", ref($handler), ref($registration_info), 
	#		       ref($request_info), ref($requests)),"\n";

    print "==============================================\n" if ($debugging);

    print STDERR "processing a request of type " . 
	$request_info->getMode() . "\n" if ($debugging) ;
    #
    # Process each varbind in teh list of requests
    #
    for($request = $requests; $request; $request = $request->next()) {
      my $oid = $request->getOID();
      print STDERR "--  processing request of $oid (request $reqnum) \n"  if ($debugging);

      #
      # Handle the different request types
      #
      my $mode = $request_info->getMode();
      if ($mode == MODE_GET) {
	  getLeaf($oid, $request, $request_info);
      } elsif ($mode == MODE_GETNEXT) {
	  getNextOid($oid, $request, $request_info);
      } else {
	  print STDERR "Request type $mode not supported\n";
      }

      $reqnum++;
    }

    print STDERR "  finished processing\n"
	if ($debugging);
}

##########################################################
# Given an oid see if there is an entry in the oidtable
# and get the record if there is.
#
# Passed the oid as a NetSNMP oid
#
# Returns the record if found
#
##########################################################
sub findOid {
    my $oid = shift;

    # Convert the OID to a string
    # The index items are temporarily set to zero to cater for tables
    my @indexes = $oid->to_array();

    my $idxoffset = $oid->length() - 1;

    # Locate the record in the table if it exists
    # If no match then try setting index values to zero until
    # we have a match of we exhaust the oid
    while($idxoffset) {
	my $oidstr="." . join ".", @indexes;
	my $rec = $oidtable->{$oidstr};

	# Return the record if found and the repaired index array
	if($rec) {
	    print "Found OID $oid ($oidstr) in the table\n"  if ($debugging);
	    return ($rec);
	} else {
	    # Not found. Set the next potential index to zero and
	    # try again
	    $indexes[$idxoffset] = 0;
	    $idxoffset--;
	}
    }
    return (0);
}


##########################################################
# Sub to return an element of an OID
# This is commonly used to get an index item from 
# an OID for table accesses.
##########################################################
sub getOidElement {
    my ($oid, $idx) = @_;

    my @idx = $oid->to_array();
    my $len = $oid->length();
    my $val = $idx[$idx];
    return $val;
}
##########################################################
# Sub to set an element of an OID
# Returns a new NetSNMP::OID object
##########################################################
sub setOidElement {
    my ($oid, $offset, $val) = @_;

    my @idx = $oid->to_array();
    $idx[$offset] = $val;
    my $str = "." . join ".", @idx;
    return new NetSNMP::OID($str);;
}


##########################################################
# Given scalar record in the oidtable get the value.
# Passed the record and the request.
##########################################################
sub getScalar {
    my ($rec, $request) = @_;

    # Got a scalar node from the table
    my $type = $rec->{type};
    
    # Call the GET function
    my $val = $rec->{func}();
    
    $request->setValue($type, $val);
}

############################################################
# Given a record in the OID table that is a columnar object
# locate any objects that have the required index.
#
# Passed the record, the oid object and the request
############################################################
sub getColumnar {
    my ($rec, $oid, $request) = @_;

    print "Get Columnar $oid\n"   if ($debugging);

    my $type = $rec->{type};
    my $args = $rec->{args};
    
    # Check the index is in range and exists
    if($rec->{check}($oid)) {
	
	# Call the handler function with the oid
	my $val = $rec->{func}($oid);

	# Set the value found in the request
	$request->setValue($type, $val);
    }
}

######################################################################
#
# If the oid is in range then set the data in the supplied request
# object.
# 
# Tries to get a scalar first then checks the coumnar second
#
# Return 1 if successful or 0 if not
#
#######################################################################
sub getLeaf {
    my $oid          = shift;
    my $request      = shift;
    my $request_info = shift;

    print "getLeaf: $oid\n"  if ($debugging);

    # Find an oid entry in the table
    my ($rec) = findOid($oid);
    if($rec) {

	# Record found. Use the istable flag to pass control to the
	# scalar or coulmnar handler
	if($rec->{istable} == 1) {
	    return getColumnar($rec, $oid, $request);
	} else {
	    return getScalar($rec, $request);
	}
    }
}

#####################################################
#
# getNextOid
#
# The workhorse for GETNEXT.
# Given an OID, locates the next oid and if valid does
# a getLeaf on that OID. It does this by walking the list of
# OIDs. We try to otimise the walk by first looking for an oid
# in the list as follows:
#
# 1. Try to locate an oid match in the table.
#    If that succeeds then look for the next object in the table 
#    using the next attribute and get that object.
#
# 2. If the OID found is a table element then use the table
#    specific index handler to see if there is an item with the
#    next index.This will retutn either an oid which we get, or 0.
#    If there is not then we continue our walk along the tree
#
#  3.If the supplied oid is not found, but is in the range of our  oids
#    then we start at the root oid and walk the list until we either 
#    drop of the end, or we fnd an OID that is greater than the OID supplied.
#    In all cases if we sucessfully find an OID to retrieve, 
#    then we set the next OID in the resposnse.
#
######################################################
sub getNextOid {
    my $oid          = shift;
    my $request      = shift;
    my $request_info = shift;

    my $curoid = new NetSNMP::OID($oidroot); # Current OID in the walk

    # Find the starting position if we can
    my $current = findOid($oid);
#    print Dumper($current);
    if($current) {
	# Found the start in the table
	$curoid = new NetSNMP::OID($oid);
	
        # If the node we found is not a table then start at the
	# next oid in the table
	unless($current->{istable}) {

	    my $nextoid = $current->{next};
	    $curoid = new NetSNMP::OID($nextoid);
#	    print "Not a table so using the next $nextoid\n";
	    $current = $oidtable->{$nextoid};
	}
    }

    # If we cannot find the starting point in the table, then start
    # at the top and 
    # walk along the list until we drop off the end
    # or we get to an oid that is >= to the one we want.
    else {

#	print "Not found so using the top ($oidroot)\n";
	$current = $oidtable->{$oidroot};
	
	while($current && $curoid <= $oid) {
	    my $nextoid = $current->{next};
	    print "Trying $nextoid\n"   if ($debugging);
	    $current = $oidtable->{$nextoid};
	    $curoid = $current ? new NetSNMP::OID($nextoid) : undef;
	}
    }

    ##
    ## Got a starting point
    ## $current points to the node in the table
    ## $curoid is a NetSNMP::OID object with the oid we are trying
    ##
    print "Got a startng point of " . Dumper($current)   if ($debugging);
    while($current) {
	if($current->{istable}) {
	    
	    # Got a table oid. See if the next is valid for the table
#	    print "Trying table\n";

	    my $nextoid = $current->{nextoid}($curoid);

#	    print Dumper($nextoid);
	    if($nextoid) {
		getColumnar($current, $nextoid, $request);
		$request->setOID($nextoid);
		return;
	    }
	    
	    # No not this one so try the next
	    $nextoid = $current->{next};
	    $current = $oidtable->{$nextoid};
	    $curoid = $current ? new NetSNMP::OID($nextoid) : undef;
	    print "Trying next $curoid $nextoid\n"   if ($debugging);
	} else {

	    # Must be the current node
	    if(getScalar($current, $request)) {
		$request->setOID($curoid);
		return 1;
	    }	
	}
    }
}


# Return true from this module
1;
