%PDF- %PDF-
Direktori : /proc/thread-self/root/usr/share/perl5/vendor_perl/Net/SNMP/ |
Current File : //proc/thread-self/root/usr/share/perl5/vendor_perl/Net/SNMP/Message.pm |
# -*- mode: perl -*- # ============================================================================ package Net::SNMP::Message; # $Id: Message.pm,v 3.1 2010/09/10 00:01:22 dtown Rel $ # Object used to represent a SNMP message. # Copyright (c) 2001-2010 David M. Town <dtown@cpan.org> # All rights reserved. # This program is free software; you may redistribute it and/or modify it # under the same terms as the Perl 5 programming language system itself. # ============================================================================ use strict; use bytes; use Math::BigInt(); ## Version of the Net::SNMP::Message module our $VERSION = v3.0.1; ## Handle importing/exporting of symbols use base qw( Exporter ); our @EXPORT_OK = qw( TRUE FALSE DEBUG_INFO ); our %EXPORT_TAGS = ( generictrap => [ qw( COLD_START WARM_START LINK_DOWN LINK_UP AUTHENTICATION_FAILURE EGP_NEIGHBOR_LOSS ENTERPRISE_SPECIFIC ) ], msgFlags => [ qw( MSG_FLAGS_NOAUTHNOPRIV MSG_FLAGS_AUTH MSG_FLAGS_PRIV MSG_FLAGS_REPORTABLE MSG_FLAGS_MASK ) ], securityLevels => [ qw( SECURITY_LEVEL_NOAUTHNOPRIV SECURITY_LEVEL_AUTHNOPRIV SECURITY_LEVEL_AUTHPRIV ) ], securityModels => [ qw( SECURITY_MODEL_ANY SECURITY_MODEL_SNMPV1 SECURITY_MODEL_SNMPV2C SECURITY_MODEL_USM ) ], translate => [ qw( TRANSLATE_NONE TRANSLATE_OCTET_STRING TRANSLATE_NULL TRANSLATE_TIMETICKS TRANSLATE_OPAQUE TRANSLATE_NOSUCHOBJECT TRANSLATE_NOSUCHINSTANCE TRANSLATE_ENDOFMIBVIEW TRANSLATE_UNSIGNED TRANSLATE_ALL ) ], types => [ qw( INTEGER INTEGER32 OCTET_STRING NULL OBJECT_IDENTIFIER SEQUENCE IPADDRESS COUNTER COUNTER32 GAUGE GAUGE32 UNSIGNED32 TIMETICKS OPAQUE COUNTER64 NOSUCHOBJECT NOSUCHINSTANCE ENDOFMIBVIEW GET_REQUEST GET_NEXT_REQUEST GET_RESPONSE SET_REQUEST TRAP GET_BULK_REQUEST INFORM_REQUEST SNMPV2_TRAP REPORT ) ], utilities => [ qw( asn1_ticks_to_time asn1_itoa ) ], versions => [ qw( SNMP_VERSION_1 SNMP_VERSION_2C SNMP_VERSION_3 ) ], ); Exporter::export_ok_tags( qw( generictrap msgFlags securityLevels securityModels translate types utilities versions ) ); $EXPORT_TAGS{ALL} = [ @EXPORT_OK ]; ## ASN.1 Basic Encoding Rules type definitions sub INTEGER { 0x02 } # INTEGER sub INTEGER32 { 0x02 } # Integer32 - SNMPv2c sub OCTET_STRING { 0x04 } # OCTET STRING sub NULL { 0x05 } # NULL sub OBJECT_IDENTIFIER { 0x06 } # OBJECT IDENTIFIER sub SEQUENCE { 0x30 } # SEQUENCE sub IPADDRESS { 0x40 } # IpAddress sub COUNTER { 0x41 } # Counter sub COUNTER32 { 0x41 } # Counter32 - SNMPv2c sub GAUGE { 0x42 } # Gauge sub GAUGE32 { 0x42 } # Gauge32 - SNMPv2c sub UNSIGNED32 { 0x42 } # Unsigned32 - SNMPv2c sub TIMETICKS { 0x43 } # TimeTicks sub OPAQUE { 0x44 } # Opaque sub COUNTER64 { 0x46 } # Counter64 - SNMPv2c sub NOSUCHOBJECT { 0x80 } # noSuchObject - SNMPv2c sub NOSUCHINSTANCE { 0x81 } # noSuchInstance - SNMPv2c sub ENDOFMIBVIEW { 0x82 } # endOfMibView - SNMPv2c sub GET_REQUEST { 0xa0 } # GetRequest-PDU sub GET_NEXT_REQUEST { 0xa1 } # GetNextRequest-PDU sub GET_RESPONSE { 0xa2 } # GetResponse-PDU sub SET_REQUEST { 0xa3 } # SetRequest-PDU sub TRAP { 0xa4 } # Trap-PDU sub GET_BULK_REQUEST { 0xa5 } # GetBulkRequest-PDU - SNMPv2c sub INFORM_REQUEST { 0xa6 } # InformRequest-PDU - SNMPv2c sub SNMPV2_TRAP { 0xa7 } # SNMPv2-Trap-PDU - SNMPv2c sub REPORT { 0xa8 } # Report-PDU - SNMPv3 ## SNMP RFC version definitions sub SNMP_VERSION_1 { 0x00 } # RFC 1157 SNMPv1 sub SNMP_VERSION_2C { 0x01 } # RFC 1901 Community-based SNMPv2 sub SNMP_VERSION_3 { 0x03 } # RFC 3411 SNMPv3 ## RFC 1157 - generic-trap definitions sub COLD_START { 0 } # coldStart(0) sub WARM_START { 1 } # warmStart(1) sub LINK_DOWN { 2 } # linkDown(2) sub LINK_UP { 3 } # linkUp(3) sub AUTHENTICATION_FAILURE { 4 } # authenticationFailure(4) sub EGP_NEIGHBOR_LOSS { 5 } # egpNeighborLoss(5) sub ENTERPRISE_SPECIFIC { 6 } # enterpriseSpecific(6) ## RFC 3412 - msgFlags::=OCTET STRING sub MSG_FLAGS_NOAUTHNOPRIV { 0x00 } # Means noAuthNoPriv sub MSG_FLAGS_AUTH { 0x01 } # authFlag sub MSG_FLAGS_PRIV { 0x02 } # privFlag sub MSG_FLAGS_REPORTABLE { 0x04 } # reportableFlag sub MSG_FLAGS_MASK { 0x07 } ## RFC 3411 - SnmpSecurityLevel::=TEXTUAL-CONVENTION sub SECURITY_LEVEL_NOAUTHNOPRIV { 1 } # noAuthNoPriv sub SECURITY_LEVEL_AUTHNOPRIV { 2 } # authNoPriv sub SECURITY_LEVEL_AUTHPRIV { 3 } # authPriv ## RFC 3411 - SnmpSecurityModel::=TEXTUAL-CONVENTION sub SECURITY_MODEL_ANY { 0 } # Reserved for 'any' sub SECURITY_MODEL_SNMPV1 { 1 } # Reserved for SNMPv1 sub SECURITY_MODEL_SNMPV2C { 2 } # Reserved for SNMPv2c sub SECURITY_MODEL_USM { 3 } # User-Based Security Model (USM) ## Translation masks sub TRANSLATE_NONE { 0x00 } # Bit masks used to determine sub TRANSLATE_OCTET_STRING { 0x01 } # if a specific ASN.1 type is sub TRANSLATE_NULL { 0x02 } # translated into a "human sub TRANSLATE_TIMETICKS { 0x04 } # readable" form. sub TRANSLATE_OPAQUE { 0x08 } sub TRANSLATE_NOSUCHOBJECT { 0x10 } sub TRANSLATE_NOSUCHINSTANCE { 0x20 } sub TRANSLATE_ENDOFMIBVIEW { 0x40 } sub TRANSLATE_UNSIGNED { 0x80 } sub TRANSLATE_ALL { 0xff } ## Truth values sub TRUE { 0x01 } sub FALSE { 0x00 } ## Package variables our $DEBUG = FALSE; # Debug flag our $AUTOLOAD; # Used by the AUTOLOAD method ## Initialize the request-id/msgID. our $ID = int rand((2**16) - 1) + ($^T & 0xff); # [public methods] ----------------------------------------------------------- sub new { my ($class, %argv) = @_; # Create a new data structure for the object my $this = bless { '_buffer' => q{}, # Serialized message buffer '_error' => undef, # Error message '_index' => 0, # Buffer index '_leading_dot' => FALSE, # Prepend leading dot on OIDs '_length' => 0, # Buffer length '_security' => undef, # Security Model object '_translate' => TRANSLATE_NONE, # Translation mode '_transport' => undef, # Transport Layer object '_version' => SNMP_VERSION_1, # SNMP version }, $class; # Validate the passed arguments for (keys %argv) { if (/^-?callback$/i) { $this->callback($argv{$_}); } elsif (/^-?debug$/i) { $this->debug($argv{$_}); } elsif (/^-?leadingdot$/i) { $this->leading_dot($argv{$_}); } elsif (/^-?msgid$/i) { $this->msg_id($argv{$_}); } elsif (/^-?requestid$/i) { $this->request_id($argv{$_}); } elsif (/^-?security$/i) { $this->security($argv{$_}); } elsif (/^-?translate$/i) { $this->translate($argv{$_}); } elsif (/^-?transport$/i) { $this->transport($argv{$_}); } elsif (/^-?version$/i) { $this->version($argv{$_}); } else { $this->_error('The argument "%s" is unknown', $_); } if (defined $this->{_error}) { return wantarray ? (undef, $this->{_error}) : undef; } } return wantarray ? ($this, q{}) : $this; } { my $prepare_methods = { INTEGER, \&_prepare_integer, OCTET_STRING, \&_prepare_octet_string, NULL, \&_prepare_null, OBJECT_IDENTIFIER, \&_prepare_object_identifier, SEQUENCE, \&_prepare_sequence, IPADDRESS, \&_prepare_ipaddress, COUNTER, \&_prepare_counter, GAUGE, \&_prepare_gauge, TIMETICKS, \&_prepare_timeticks, OPAQUE, \&_prepare_opaque, COUNTER64, \&_prepare_counter64, NOSUCHOBJECT, \&_prepare_nosuchobject, NOSUCHINSTANCE, \&_prepare_nosuchinstance, ENDOFMIBVIEW, \&_prepare_endofmibview, GET_REQUEST, \&_prepare_get_request, GET_NEXT_REQUEST, \&_prepare_get_next_request, GET_RESPONSE, \&_prepare_get_response, SET_REQUEST, \&_prepare_set_request, TRAP, \&_prepare_trap, GET_BULK_REQUEST, \&_prepare_get_bulk_request, INFORM_REQUEST, \&_prepare_inform_request, SNMPV2_TRAP, \&_prepare_v2_trap, REPORT, \&_prepare_report }; sub prepare { # my ($this, $type, $value) = @_; return $_[0]->_error() if defined $_[0]->{_error}; if (!defined $_[1]) { return $_[0]->_error('The ASN.1 type is not defined'); } if (!exists $prepare_methods->{$_[1]}) { return $_[0]->_error('The ASN.1 type "%s" is unknown', $_[1]); } return $_[0]->${\$prepare_methods->{$_[1]}}($_[2]); } } { my $process_methods = { INTEGER, \&_process_integer32, OCTET_STRING, \&_process_octet_string, NULL, \&_process_null, OBJECT_IDENTIFIER, \&_process_object_identifier, SEQUENCE, \&_process_sequence, IPADDRESS, \&_process_ipaddress, COUNTER, \&_process_counter, GAUGE, \&_process_gauge, TIMETICKS, \&_process_timeticks, OPAQUE, \&_process_opaque, COUNTER64, \&_process_counter64, NOSUCHOBJECT, \&_process_nosuchobject, NOSUCHINSTANCE, \&_process_nosuchinstance, ENDOFMIBVIEW, \&_process_endofmibview, GET_REQUEST, \&_process_get_request, GET_NEXT_REQUEST, \&_process_get_next_request, GET_RESPONSE, \&_process_get_response, SET_REQUEST, \&_process_set_request, TRAP, \&_process_trap, GET_BULK_REQUEST, \&_process_get_bulk_request, INFORM_REQUEST, \&_process_inform_request, SNMPV2_TRAP, \&_process_v2_trap, REPORT, \&_process_report }; sub process { # my ($this, $expected, $found) = @_; # XXX: If present, $found is updated as a side effect. return $_[0]->_error() if defined $_[0]->{_error}; return $_[0]->_error() if !defined (my $type = $_[0]->_buffer_get(1)); $type = unpack 'C', $type; if (!exists $process_methods->{$type}) { return $_[0]->_error('The ASN.1 type 0x%02x is unknown', $type); } # Check to see if a specific ASN.1 type was expected. if ((@_ > 1) && (defined $_[1]) && ($type != $_[1])) { return $_[0]->_error( 'Expected %s, but found %s', asn1_itoa($_[1]), asn1_itoa($type) ); } # Update the found ASN.1 type, if the argument is present. if (@_ == 3) { $_[2] = $type; } return $_[0]->${\$process_methods->{$type}}($type); } } sub context_engine_id { my ($this, $engine_id) = @_; # RFC 3412 - contextEngineID::=OCTET STRING if (@_ == 2) { if (!defined $engine_id) { return $this->_error('The contextEngineID value is not defined'); } $this->{_context_engine_id} = $engine_id; } if (exists $this->{_context_engine_id}) { return $this->{_context_engine_id} || q{}; } elsif (defined $this->{_security}) { return $this->{_security}->engine_id() || q{}; } return q{}; } sub context_name { my ($this, $name) = @_; # RFC 3412 - contextName::=OCTET STRING if (@_ == 2) { if (!defined $name) { return $this->_error('The contextName value is not defined'); } $this->{_context_name} = $name; } return exists($this->{_context_name}) ? $this->{_context_name} : q{}; } sub msg_flags { my ($this, $flags) = @_; # RFC 3412 - msgFlags::=OCTET STRING (SIZE(1)) # NOTE: The stored value is not an OCTET STRING. if (@_ == 2) { if (!defined $flags) { return $this->_error('The msgFlags value is not defined'); } $this->{_msg_flags} = $flags; } if (exists $this->{_msg_flags}) { return $this->{_msg_flags}; } return MSG_FLAGS_NOAUTHNOPRIV; } sub msg_id { my ($this, $msg_id) = @_; # RFC 3412 - msgID::=INTEGER (0..2147483647) if (@_ == 2) { if (!defined $msg_id) { return $this->_error('The msgID value is not defined'); } if (($msg_id < 0) || ($msg_id > 2147483647)) { return $this->_error( 'The msgId %d is out of range (0..2147483647)', $msg_id ); } $this->{_msg_id} = $msg_id; } if (exists $this->{_msg_id}) { return $this->{_msg_id}; } elsif (exists $this->{_request_id}) { return $this->{_request_id}; } return 0; } sub msg_max_size { my ($this, $size) = @_; # RFC 3412 - msgMaxSize::=INTEGER (484..2147483647) if (@_ == 2) { if (!defined $size) { return $this->_error('The msgMaxSize value is not defined'); } if (($size < 484) || ($size > 2147483647)) { return $this->_error( 'The msgMaxSize %d is out of range (484..2147483647)', $size ); } $this->{_msg_max_size} = $size; } return $this->{_msg_max_size} || 484; } sub msg_security_model { my ($this, $model) = @_; # RFC 3412 - msgSecurityModel::=INTEGER (1..2147483647) if (@_ == 2) { if (!defined $model) { return $this->_error('The msgSecurityModel value is not defined'); } if (($model < 1) || ($model > 2147483647)) { return $this->_error( 'The msgSecurityModel %d is out of range (1..2147483647)', $model ); } $this->{_security_model} = $model; } if (exists $this->{_security_model}) { return $this->{_security_model}; } elsif (defined $this->{_security}) { return $this->{_security}->security_model(); } else { if ($this->{_version} == SNMP_VERSION_1) { return SECURITY_MODEL_SNMPV1; } elsif ($this->{_version} == SNMP_VERSION_2C) { return SECURITY_MODEL_SNMPV2C; } elsif ($this->{_version} == SNMP_VERSION_3) { return SECURITY_MODEL_USM; } } return SECURITY_MODEL_ANY; } sub request_id { my ($this, $request_id) = @_; # request-id::=INTEGER if (@_ == 2) { if (!defined $request_id) { return $this->_error('The request-id value is not defined'); } $this->{_request_id} = $request_id; } return exists($this->{_request_id}) ? $this->{_request_id} : 0; } sub security_level { my ($this, $level) = @_; # RFC 3411 - SnmpSecurityLevel::=INTEGER { noAuthNoPriv(1), # authNoPriv(2), # authPriv(3) } if (@_ == 2) { if (!defined $level) { return $this->_error('The securityLevel value is not defined'); } if (($level < SECURITY_LEVEL_NOAUTHNOPRIV) || ($level > SECURITY_LEVEL_AUTHPRIV)) { return $this->_error( 'The securityLevel %d is out of range (%d..%d)', $level, SECURITY_LEVEL_NOAUTHNOPRIV, SECURITY_LEVEL_AUTHPRIV ); } $this->{_security_level} = $level; } if (exists $this->{_security_level}) { return $this->{_security_level}; } elsif (defined $this->{_security}) { return $this->{_security}->security_level(); } return SECURITY_LEVEL_NOAUTHNOPRIV; } sub security_name { my ($this, $name) = @_; if (@_ == 2) { if (!defined $name) { return $this->_error('The securityName value is not defined'); } # No length checks due to no limits by RFC 1157 for community name. $this->{_security_name} = $name; } if (exists $this->{_security_name}) { return $this->{_security_name}; } elsif (defined $this->{_security}) { return $this->{_security}->security_name(); } return q{}; } sub version { my ($this, $version) = @_; if (@_ == 2) { if (($version == SNMP_VERSION_1) || ($version == SNMP_VERSION_2C) || ($version == SNMP_VERSION_3)) { $this->{_version} = $version; } else { return $this->_error('The SNMP version %d is not supported', $version); } } return $this->{_version}; } sub error_status { return 0; # noError(0) } sub error_index { return 0; } sub var_bind_list { return undef; } sub var_bind_names { return []; } sub var_bind_types { return undef; } # # Security Model accessor methods # sub security { my ($this, $security) = @_; if (@_ == 2) { if (defined $security) { $this->{_security} = $security; } else { $this->_error_clear(); return $this->_error('The Security Model object is not defined'); } } return $this->{_security}; } # # Transport Domain accessor methods # sub transport { my ($this, $transport) = @_; if (@_ == 2) { if (defined $transport) { $this->{_transport} = $transport; } else { $this->_error_clear(); return $this->_error('The Transport Domain object is not defined'); } } return $this->{_transport}; } sub hostname { my ($this) = @_; if (defined $this->{_transport}) { return $this->{_transport}->dest_hostname(); } return q{}; } sub dstname { require Carp; Carp::croak( sprintf '%s::dstname() is obsolete, use hostname() instead', ref $_[0] ); # Never get here. return shift->hostname(@_); } sub max_msg_size { my ($this, $size) = @_; if (!defined $this->{_transport}) { return 0; } if (@_ == 2) { $this->_error_clear(); if (defined ($size = $this->{_transport}->max_msg_size($size))) { return $size; } return $this->_error($this->{_transport}->error()); } return $this->{_transport}->max_msg_size(); } sub retries { return defined($_[0]->{_transport}) ? $_[0]->{_transport}->retries() : 0; } sub timeout { return defined($_[0]->{_transport}) ? $_[0]->{_transport}->timeout() : 0; } sub send { my ($this) = @_; $this->_error_clear(); if (!defined $this->{_transport}) { return $this->_error('The Transport Domain object is not defined'); } DEBUG_INFO('transport address %s', $this->{_transport}->dest_taddress()); $this->_buffer_dump(); if (defined (my $bytes = $this->{_transport}->send($this->{_buffer}))) { return $bytes; } return $this->_error($this->{_transport}->error()); } sub recv { my ($this) = @_; $this->_error_clear(); if (!defined $this->{_transport}) { return $this->_error('The Transport Domain object is not defined'); } my $name = $this->{_transport}->recv($this->{_buffer}); if (defined $name) { $this->{_length} = CORE::length($this->{_buffer}); DEBUG_INFO('transport address %s', $this->{_transport}->peer_taddress()); $this->_buffer_dump(); return $name; } return $this->_error($this->{_transport}->error()); } # # Data representation methods # sub translate { return (@_ == 2) ? $_[0]->{_translate} = $_[1] : $_[0]->{_translate}; } sub leading_dot { return (@_ == 2) ? $_[0]->{_leading_dot} = $_[1] : $_[0]->{_leading_dot}; } # # Callback handler methods # sub callback { my ($this, $callback) = @_; if (@_ == 2) { if (ref($callback) eq 'CODE') { $this->{_callback} = $callback; } elsif (!defined $callback) { $this->{_callback} = undef; } else { DEBUG_INFO('unexpected callback format'); } } return $this->{_callback}; } sub callback_execute { my ($this) = @_; if (!defined $this->{_callback}) { DEBUG_INFO('no callback'); return TRUE; } # Protect ourselves from user error. eval { $this->{_callback}->($this); }; # We clear the callback in case it was a closure which might hold # up the reference count of the calling object. $this->{_callback} = undef; return ($@) ? $this->_error($@) : TRUE; } sub status_information { my $this = shift; if (@_) { $this->{_error} = (@_ > 1) ? sprintf(shift(@_), @_) : $_[0]; if ($this->debug()) { printf "error: [%d] %s(): %s\n", (caller 0)[2], (caller 1)[3], $this->{_error}; } $this->callback_execute(); } return $this->{_error} || q{}; } sub process_response_pdu { goto &callback_execute; } sub timeout_id { return (@_ == 2) ? $_[0]->{_timeout_id} = $_[1] : $_[0]->{_timeout_id}; } # # Buffer manipulation methods # sub index { my ($this, $index) = @_; if ((@_ == 2) && ($index >= 0) && ($index <= $this->{_length})) { $this->{_index} = $index; } return $this->{_index}; } sub length { return $_[0]->{_length}; } sub prepend { goto &_buffer_put; } sub append { goto &_buffer_append; } sub copy { return $_[0]->{_buffer}; } sub reference { return \$_[0]->{_buffer}; } sub clear { my ($this) = @_; $this->{_index} = 0; $this->{_length} = 0; return substr $this->{_buffer}, 0, CORE::length($this->{_buffer}), q{}; } sub dump { goto &_buffer_dump; } # # Debug/error handling methods # sub error { my $this = shift; if (@_) { if (defined $_[0]) { $this->{_error} = (@_ > 1) ? sprintf(shift(@_), @_) : $_[0]; if ($this->debug()) { printf "error: [%d] %s(): %s\n", (caller 0)[2], (caller 1)[3], $this->{_error}; } } else { $this->{_error} = undef; } } return $this->{_error} || q{}; } sub debug { return (@_ == 2) ? $DEBUG = ($_[1]) ? TRUE : FALSE : $DEBUG; } sub AUTOLOAD { my ($this) = @_; return if $AUTOLOAD =~ /::DESTROY$/; $AUTOLOAD =~ s/.*://; if (ref $this) { $this->_error_clear(); return $this->_error('The method "%s" is not supported', $AUTOLOAD); } else { require Carp; Carp::croak(sprintf 'The function "%s" is not supported', $AUTOLOAD); } # Never get here. return; } # [private methods] ---------------------------------------------------------- # # Basic Encoding Rules (BER) prepare methods # sub _prepare_type_length { # my ($this, $type, $value) = @_; if (!defined $_[1]) { return $_[0]->_error('The ASN.1 type is not defined'); } my $length = CORE::length($_[2]); if ($length < 0x80) { return $_[0]->_buffer_put(pack('C2', $_[1], $length) . $_[2]); } elsif ($length <= 0xff) { return $_[0]->_buffer_put(pack('C3', $_[1], 0x81, $length) . $_[2]); } elsif ($length <= 0xffff) { return $_[0]->_buffer_put(pack('CCn', $_[1], 0x82, $length) . $_[2]); } return $_[0]->_error('Unable to prepare the ASN.1 length'); } sub _prepare_integer { my ($this, $value) = @_; if (!defined $value) { return $this->_error('The INTEGER value is not defined'); } if ($value !~ /^-?\d+$/) { return $this->_error( 'The INTEGER value "%s" is expected in numeric format', $value ); } if ($value < -2147483648 || $value > 4294967295) { return $this->_error( 'The INTEGER value "%s" is out of range (-2147483648..4294967295)', $value ); } return $this->_prepare_integer32(INTEGER, $value); } sub _prepare_unsigned32 { my ($this, $type, $value) = @_; if (!defined $value) { return $this->_error('The %s value is not defined', asn1_itoa($type)); } if ($value !~ /^\d+$/) { return $this->_error( 'The %s value "%s" is expected in positive numeric format', asn1_itoa($type), $value ); } if ($value < 0 || $value > 4294967295) { return $this->_error( 'The %s value "%s" is out of range (0..4294967295)', asn1_itoa($type), $value ); } return $this->_prepare_integer32($type, $value); } sub _prepare_integer32 { my ($this, $type, $value) = @_; # Determine if the value is positive or negative my $negative = ($value < 0); # Check to see if the most significant bit is set, if it is we # need to prefix the encoding with a zero byte. my $size = 4; # Assuming 4 byte integers my $prefix = FALSE; my $bytes = q{}; if ((($value & 0xff000000) & 0x80000000) && (!$negative)) { $size++; $prefix = TRUE; } # Remove occurances of nine consecutive ones (if negative) or zeros # from the most significant end of the two's complement integer. while ((((!($value & 0xff800000))) || ((($value & 0xff800000) == 0xff800000) && ($negative))) && ($size > 1)) { $size--; $value <<= 8; } # Add a zero byte so the integer is decoded as a positive value if ($prefix) { $bytes = pack 'x'; $size--; } # Build the integer while ($size-- > 0) { $bytes .= pack 'C*', (($value & 0xff000000) >> 24); $value <<= 8; } # Encode ASN.1 header return $this->_prepare_type_length($type, $bytes); } sub _prepare_octet_string { my ($this, $value) = @_; if (!defined $value) { return $this->_error('The OCTET STRING value is not defined'); } return $this->_prepare_type_length(OCTET_STRING, $value); } sub _prepare_null { return $_[0]->_prepare_type_length(NULL, q{}); } sub _prepare_object_identifier { my ($this, $value) = @_; if (!defined $value) { return $this->_error('The OBJECT IDENTIFIER value not defined'); } # The OBJECT IDENTIFIER is expected in dotted notation. if ($value !~ m/^\.?\d+(?:\.\d+)* *$/) { return $this->_error( 'The OBJECT IDENTIFIER value "%s" is expected in dotted decimal ' . 'notation', $value ); } # Break it up into sub-identifiers. my @subids = split /\./, $value; # If there was a leading dot on _any_ OBJECT IDENTIFIER passed to # a prepare method, return a leading dot on _all_ of the OBJECT # IDENTIFIERs in the process methods. if ($subids[0] eq q{}) { DEBUG_INFO('leading dot present'); $this->{_leading_dot} = TRUE; shift @subids; } # RFC 2578 Section 3.5 - "...there are at most 128 sub-identifiers in # a value, and each sub-identifier has a maximum value of 2^32-1..." if (@subids > 128) { return $this->_error( 'The OBJECT IDENTIFIER value "%s" contains more than the maximum ' . 'of 128 sub-identifiers allowed', $value ); } if (grep { $_ < 0 || $_ > 4294967295; } @subids) { return $this->_error( 'The OBJECT IDENTIFIER value "%s" contains a sub-identifier which ' . 'is out of range (0..4294967295)', $value ); } # ISO/IEC 8825 - Specification of Basic Encoding Rules for Abstract # Syntax Notation One (ASN.1) dictates that the first two sub-identifiers # are encoded into the first identifier using the the equation: # subid = ((first * 40) + second). Pad the OBJECT IDENTIFIER to at # least two sub-identifiers. while (@subids < 2) { push @subids, 0; } # The first sub-identifiers are limited to ccitt(0), iso(1), and # joint-iso-ccitt(2) as defined by RFC 2578. if ($subids[0] > 2) { return $this->_error( 'The OBJECT IDENTIFIER value "%s" must begin with either 0 ' . '(ccitt), 1 (iso), or 2 (joint-iso-ccitt)', $value ); } # If the first sub-identifier is 0 or 1, the second is limited to 0 - 39. if (($subids[0] < 2) && ($subids[1] >= 40)) { return $this->_error( 'The second sub-identifier in the OBJECT IDENTIFIER value "%s" ' . 'must be less than 40', $value ); } elsif ($subids[1] >= (4294967295 - 80)) { return $this->_error( 'The second sub-identifier in the OBJECT IDENTIFIER value "%s" ' . 'must be less than %u', $value, (4294967295 - 80) ); } # Now apply: subid = ((first * 40) + second) $subids[1] += (shift(@subids) * 40); # Encode each sub-identifier in base 128, most significant digit first, # with as few digits as possible. Bit eight (the high bit) is set on # each byte except the last. # Encode the ASN.1 header return $this->_prepare_type_length(OBJECT_IDENTIFIER, pack 'w*', @subids); } sub _prepare_sequence { return $_[0]->_prepare_implicit_sequence(SEQUENCE, $_[1]); } sub _prepare_implicit_sequence { my ($this, $type, $value) = @_; if (defined $value) { return $this->_prepare_type_length($type, $value); } # If the passed value is undefined, we assume that the value of # the IMPLICIT SEQUENCE is the data currently in the serial buffer. if ($this->{_length} < 0x80) { return $this->_buffer_put(pack 'C2', $type, $this->{_length}); } elsif ($this->{_length} <= 0xff) { return $this->_buffer_put(pack 'C3', $type, 0x81, $this->{_length}); } elsif ($this->{_length} <= 0xffff) { return $this->_buffer_put(pack 'CCn', $type, 0x82, $this->{_length}); } return $this->_error('Unable to prepare the ASN.1 SEQUENCE length'); } sub _prepare_ipaddress { my ($this, $value) = @_; if (!defined $value) { return $this->_error('IpAddress is not defined'); } if ($value !~ /^\d+\.\d+\.\d+\.\d+$/) { return $this->_error( 'The IpAddress value "%s" is expected in dotted decimal notation', $value ); } my @octets = split /\./, $value; if (grep { $_ > 255; } @octets) { return $this->_error('The IpAddress value "%s" is invalid', $value); } return $this->_prepare_type_length(IPADDRESS, pack 'C4', @octets); } sub _prepare_counter { return $_[0]->_prepare_unsigned32(COUNTER, $_[1]); } sub _prepare_gauge { return $_[0]->_prepare_unsigned32(GAUGE, $_[1]); } sub _prepare_timeticks { return $_[0]->_prepare_unsigned32(TIMETICKS, $_[1]); } sub _prepare_opaque { my ($this, $value) = @_; if (!defined $value) { return $this->_error('The Opaque value is not defined'); } return $this->_prepare_type_length(OPAQUE, $value); } sub _prepare_counter64 { my ($this, $value) = @_; # Validate the SNMP version if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The Counter64 type is not supported in SNMPv1'); } # Validate the passed value if (!defined $value) { return $this->_error('The Counter64 value is not defined'); } if ($value !~ /^\+?\d+$/) { return $this->_error( 'The Counter64 value "%s" is expected in positive numeric format', $value ); } $value = Math::BigInt->new($value); if ($value eq 'NaN') { return $this->_error('The Counter64 value "%s" is invalid', $value); } # Make sure the value is no more than 8 bytes long if ($value->bcmp('18446744073709551615') > 0) { return $this->_error( 'The Counter64 value "%s" is out of range (0..18446744073709551615)', $value ); } my ($quotient, $remainder, @bytes); # Handle a value of zero if ($value == 0) { unshift @bytes, 0x00; } while ($value > 0) { ($quotient, $remainder) = $value->bdiv(256); $value = Math::BigInt->new($quotient); unshift @bytes, $remainder; } # Make sure that the value is encoded as a positive value if ($bytes[0] & 0x80) { unshift @bytes, 0x00; } return $this->_prepare_type_length(COUNTER64, pack 'C*', @bytes); } sub _prepare_nosuchobject { my ($this) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The noSuchObject type is not supported in SNMPv1'); } return $this->_prepare_type_length(NOSUCHOBJECT, q{}); } sub _prepare_nosuchinstance { my ($this) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error( 'The noSuchInstance type is not supported in SNMPv1' ); } return $this->_prepare_type_length(NOSUCHINSTANCE, q{}); } sub _prepare_endofmibview { my ($this) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The endOfMibView type is not supported in SNMPv1'); } return $this->_prepare_type_length(ENDOFMIBVIEW, q{}); } sub _prepare_get_request { return $_[0]->_prepare_implicit_sequence(GET_REQUEST, $_[1]); } sub _prepare_get_next_request { return $_[0]->_prepare_implicit_sequence(GET_NEXT_REQUEST, $_[1]); } sub _prepare_get_response { return $_[0]->_prepare_implicit_sequence(GET_RESPONSE, $_[1]); } sub _prepare_set_request { return $_[0]->_prepare_implicit_sequence(SET_REQUEST, $_[1]); } sub _prepare_trap { my ($this, $value) = @_; if ($this->{_version} != SNMP_VERSION_1) { return $this->_error('The Trap-PDU is only supported in SNMPv1'); } return $this->_prepare_implicit_sequence(TRAP, $value); } sub _prepare_get_bulk_request { my ($this, $value) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error( 'The GetBulkRequest-PDU is not supported in SNMPv1' ); } return $this->_prepare_implicit_sequence(GET_BULK_REQUEST, $value); } sub _prepare_inform_request { my ($this, $value) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The InformRequest-PDU is not supported in SNMPv1'); } return $this->_prepare_implicit_sequence(INFORM_REQUEST, $value); } sub _prepare_v2_trap { my ($this, $value) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The SNMPv2-Trap-PDU is not supported in SNMPv1'); } return $this->_prepare_implicit_sequence(SNMPV2_TRAP, $value); } sub _prepare_report { my ($this, $value) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The Report-PDU is not supported in SNMPv1'); } return $this->_prepare_implicit_sequence(REPORT, $value); } # # Basic Encoding Rules (BER) process methods # sub _process_length { my ($this) = @_; return $this->_error() if defined $this->{_error}; my $length = $this->_buffer_get(1); if (!defined $length) { return $this->_error(); } $length = unpack 'C', $length; if (!($length & 0x80)) { # "Short" length return $length; } my $byte_cnt = $length & 0x7f; if ($byte_cnt == 0) { return $this->_error('Indefinite ASN.1 lengths are not supported'); } elsif ($byte_cnt > 4) { return $this->_error( 'The ASN.1 length is too long (%u bytes)', $byte_cnt ); } if (!defined($length = $this->_buffer_get($byte_cnt))) { return $this->_error(); } return unpack 'N', ("\000" x (4 - $byte_cnt) . $length); } sub _process_integer32 { my ($this, $type) = @_; # Decode the length return $this->_error() if !defined(my $length = $this->_process_length()); # Return an error if the object length is zero? if ($length < 1) { return $this->_error('The %s length is equal to zero', asn1_itoa($type)); } # Retrieve the whole byte stream outside of the loop. return $this->_error() if !defined(my $bytes = $this->_buffer_get($length)); my @bytes = unpack 'C*', $bytes; my $negative = FALSE; my $int32 = 0; # Validate the length of the Integer32 if (($length > 5) || (($length > 4) && ($bytes[0] != 0x00))) { return $this->_error( 'The %s length is too long (%u bytes)', asn1_itoa($type), $length ); } # If the first bit is set, the Integer32 is negative if ($bytes[0] & 0x80) { $int32 = -1; $negative = TRUE; } # Build the Integer32 map { $int32 = (($int32 << 8) | $_) } @bytes; if ($negative) { if (($type == INTEGER) || (!($this->{_translate} & TRANSLATE_UNSIGNED))) { return unpack 'l', pack 'l', $int32; } else { DEBUG_INFO('translating negative %s value', asn1_itoa($type)); return unpack 'L', pack 'l', $int32; } } return unpack 'L', pack 'L', $int32; } sub _process_octet_string { my ($this, $type) = @_; # Decode the length return $this->_error() if !defined(my $length = $this->_process_length()); # Get the string return $this->_error() if !defined(my $s = $this->_buffer_get($length)); # Set the translation mask my $mask = ($type == OPAQUE) ? TRANSLATE_OPAQUE : TRANSLATE_OCTET_STRING; # # Translate based on the definition of a DisplayString in RFC 2579. # # DisplayString ::= TEXTUAL-CONVENTION # # - the graphics characters (32-126) are interpreted as # US ASCII # - NUL, LF, CR, BEL, BS, HT, VT and FF have the special # meanings specified in RFC 854 # - the sequence 'CR x' for any x other than LF or NUL is # illegal. # if ($this->{_translate} & $mask) { $type = asn1_itoa($type); if ($s =~ m{ # The values other than NUL, LF, CR, BEL, BS, HT, VT, FF, # and the graphic characters (32-126) trigger translation. [\x01-\x06\x0e-\x1f\x7f-\xff]| # The sequence 'CR x' for any x other than LF or NUL # also triggers translation. \x0d(?![\x00\x0a]) }x) { DEBUG_INFO( 'translating %s to hexadecimal formatted DisplayString', $type ); return sprintf '0x%s', unpack 'H*', $s; } else { DEBUG_INFO( 'not translating %s, all octets are allowed in a DisplayString', $type ); } } return $s; } sub _process_null { my ($this) = @_; # Decode the length return $this->_error() if !defined(my $length = $this->_process_length()); return $this->_error('NULL length is not equal to zero') if ($length != 0); if ($this->{_translate} & TRANSLATE_NULL) { DEBUG_INFO(q{translating NULL to 'NULL' string}); return 'NULL'; } return q{}; } sub _process_object_identifier { my ($this) = @_; # Decode the length return $this->_error() if !defined(my $length = $this->_process_length()); # Return an error if the length is equal to zero? if ($length < 1) { return $this->_error('The OBJECT IDENTIFIER length is equal to zero'); } # Retrieve the whole byte stream (by Niilo Neuvo). return $this->_error() if !defined(my $bytes = $this->_buffer_get($length)); my @oid = ( 0, eval { unpack 'w129', $bytes } ); # RFC 2578 Section 3.5 - "...there are at most 128 sub-identifiers in # a value, and each sub-identifier has a maximum value of 2^32-1..." if ($@ || (grep { $_ > 4294967295; } @oid)) { return $this->_error( 'The OBJECT IDENTIFIER contains a sub-identifier which is out of ' . 'range (0..4294967295)' ); } if (@oid > 128) { return $this->_error( 'The OBJECT IDENTIFIER contains more than the maximum of 128 ' . 'sub-identifiers allowed' ); } # The first two sub-identifiers are encoded into the first identifier # using the the equation: subid = ((first * 40) + second). if ($oid[1] == 0x2b) { # Handle the most common case $oid[0] = 1; # first [iso(1).org(3)] $oid[1] = 3; } elsif ($oid[1] < 40) { $oid[0] = 0; } elsif ($oid[1] < 80) { $oid[0] = 1; $oid[1] -= 40; } else { $oid[0] = 2; $oid[1] -= 80; } # Return the OID in dotted notation (optionally with a # leading dot if one was passed to the prepare routine). if ($this->{_leading_dot}) { DEBUG_INFO('adding leading dot'); unshift @oid, q{}; } return join q{.}, @oid; } sub _process_sequence { # Return the length, instead of the value goto &_process_length; } sub _process_ipaddress { my ($this) = @_; # Decode the length return $this->_error() if !defined(my $length = $this->_process_length()); if ($length != 4) { return $this->_error('The IpAddress length of %d is invalid', $length); } if (defined(my $ipaddress = $this->_buffer_get(4))) { return sprintf '%vd', $ipaddress; } return $this->_error(); } sub _process_counter { goto &_process_integer32; } sub _process_gauge { goto &_process_integer32; } sub _process_timeticks { my ($this) = @_; if (defined(my $ticks = $this->_process_integer32(TIMETICKS))) { if ($this->{_translate} & TRANSLATE_TIMETICKS) { DEBUG_INFO('translating %u TimeTicks to time', $ticks); return asn1_ticks_to_time($ticks); } else { return $ticks; } } return $this->_error(); } sub _process_opaque { goto &_process_octet_string; } sub _process_counter64 { my ($this, $type) = @_; # Verify the SNMP version if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The Counter64 type is not supported in SNMPv1'); } # Decode the length return $this->_error() if !defined(my $length = $this->_process_length()); # Return an error if the object length is zero? if ($length < 1) { return $this->_error('The Counter64 length is equal to zero'); } # Retrieve the whole byte stream outside of the loop. return $this->_error() if !defined(my $bytes = $this->_buffer_get($length)); my @bytes = unpack 'C*', $bytes; my $negative = FALSE; # Validate the length of the Counter64 if (($length > 9) || (($length > 8) && ($bytes[0] != 0x00))) { return $_[0]->_error( 'The Counter64 length is too long (%u bytes)', $length ); } # If the first bit is set, the integer is negative if ($bytes[0] & 0x80) { $bytes[0] ^= 0xff; $negative = TRUE; } # Build the Counter64 my $int64 = Math::BigInt->new(shift @bytes); map { if ($negative) { $_ ^= 0xff; } $int64 *= 256; $int64 += $_; } @bytes; # If the value is negative the other end incorrectly encoded # the Counter64 since it should always be a positive value. if ($negative) { $int64 = Math::BigInt->new('-1') - $int64; if ($this->{_translate} & TRANSLATE_UNSIGNED) { DEBUG_INFO('translating negative Counter64 value'); $int64 += Math::BigInt->new('18446744073709551616'); } } # Perl 5.6.0 (force to string or substitution does not work). $int64 .= q{}; # Remove the plus sign (or should we leave it to imply Math::BigInt?) $int64 =~ s/^\+//; return $int64; } sub _process_nosuchobject { my ($this) = @_; # Verify the SNMP version if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The noSuchObject type is not supported in SNMPv1'); } # Decode the length return $this->_error() if !defined(my $length = $this->_process_length()); if ($length != 0) { return $this->_error('The noSuchObject length is not equal to zero'); } if ($this->{_translate} & TRANSLATE_NOSUCHOBJECT) { DEBUG_INFO(q{translating noSuchObject to 'noSuchObject' string}); return 'noSuchObject'; } # XXX: Releases greater than v5.2.0 longer set the error-status. # $this->{_error_status} = NOSUCHOBJECT; return q{}; } sub _process_nosuchinstance { my ($this) = @_; # Verify the SNMP version if ($this->{_version} == SNMP_VERSION_1) { return $this->_error( 'The noSuchInstance type is not supported in SNMPv1' ); } # Decode the length return $this->_error() if !defined(my $length = $this->_process_length()); if ($length != 0) { return $this->_error('The noSuchInstance length is not equal to zero'); } if ($this->{_translate} & TRANSLATE_NOSUCHINSTANCE) { DEBUG_INFO(q{translating noSuchInstance to 'noSuchInstance' string}); return 'noSuchInstance'; } # XXX: Releases greater than v5.2.0 longer set the error-status. # $this->{_error_status} = NOSUCHINSTANCE; return q{}; } sub _process_endofmibview { my ($this) = @_; # Verify the SNMP version if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The endOfMibView type is not supported in SNMPv1'); } # Decode the length return $this->_error() if !defined(my $length = $this->_process_length()); if ($length != 0) { return $this->_error('The endOfMibView length is not equal to zero'); } if ($this->{_translate} & TRANSLATE_ENDOFMIBVIEW) { DEBUG_INFO(q{translating endOfMibView to 'endOfMibView' string}); return 'endOfMibView'; } # XXX: Releases greater than v5.2.0 longer set the error-status. # $this->{_error_status} = ENDOFMIBVIEW; return q{}; } sub _process_pdu_type { my ($this, $type) = @_; # Generic methods used to process the PDU type. The ASN.1 type is # returned by the method as passed by the generic process routine. return defined($this->_process_length()) ? $type : $this->_error(); } sub _process_get_request { goto &_process_pdu_type; } sub _process_get_next_request { goto &_process_pdu_type; } sub _process_get_response { goto &_process_pdu_type; } sub _process_set_request { goto &_process_pdu_type; } sub _process_trap { my ($this) = @_; if ($this->{_version} != SNMP_VERSION_1) { return $this->_error('The Trap-PDU is only supported in SNMPv1'); } goto &_process_pdu_type; } sub _process_get_bulk_request { my ($this) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The GetBulkRequest-PDU is not supported in SNMPv1'); } goto &_process_pdu_type; } sub _process_inform_request { my ($this) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The InformRequest-PDU is not supported in SNMPv1'); } goto &_process_pdu_type; } sub _process_v2_trap { my ($this) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The SNMPv2-Trap-PDU is not supported in SNMPv1'); } goto &_process_pdu_type; } sub _process_report { my ($this) = @_; if ($this->{_version} == SNMP_VERSION_1) { return $this->_error('The Report-PDU is not supported in SNMPv1'); } goto &_process_pdu_type; } # # Abstract Syntax Notation One (ASN.1) utility functions # { my $types = { INTEGER, 'INTEGER', OCTET_STRING, 'OCTET STRING', NULL, 'NULL', OBJECT_IDENTIFIER, 'OBJECT IDENTIFIER', SEQUENCE, 'SEQUENCE', IPADDRESS, 'IpAddress', COUNTER, 'Counter', GAUGE, 'Gauge', TIMETICKS, 'TimeTicks', OPAQUE, 'Opaque', COUNTER64, 'Counter64', NOSUCHOBJECT, 'noSuchObject', NOSUCHINSTANCE, 'noSuchInstance', ENDOFMIBVIEW, 'endOfMibView', GET_REQUEST, 'GetRequest-PDU', GET_NEXT_REQUEST, 'GetNextRequest-PDU', GET_RESPONSE, 'GetResponse-PDU', SET_REQUEST, 'SetRequest-PDU', TRAP, 'Trap-PDU', GET_BULK_REQUEST, 'GetBulkRequest-PDU', INFORM_REQUEST, 'InformRequest-PDU', SNMPV2_TRAP, 'SNMPv2-Trap-PDU', REPORT, 'Report-PDU' }; sub asn1_itoa { my ($type) = @_; return q{??} if (@_ != 1); if (!exists $types->{$type}) { return sprintf '?? [0x%02x]', $type; } return $types->{$type}; } } sub asn1_ticks_to_time { my $ticks = shift || 0; my $days = int($ticks / (24 * 60 * 60 * 100)); $ticks %= (24 * 60 * 60 * 100); my $hours = int($ticks / (60 * 60 * 100)); $ticks %= (60 * 60 * 100); my $minutes = int($ticks / (60 * 100)); $ticks %= (60 * 100); my $seconds = ($ticks / 100); if ($days != 0){ return sprintf '%d day%s, %02d:%02d:%05.02f', $days, ($days == 1 ? q{} : 's'), $hours, $minutes, $seconds; } elsif ($hours != 0) { return sprintf '%d hour%s, %02d:%05.02f', $hours, ($hours == 1 ? q{} : 's'), $minutes, $seconds; } elsif ($minutes != 0) { return sprintf '%d minute%s, %05.02f', $minutes, ($minutes == 1 ? q{} : 's'), $seconds; } else { return sprintf '%04.02f second%s', $seconds, ($seconds == 1 ? q{} : 's'); } } # # Error handlers # sub _error { my $this = shift; if (!defined $this->{_error}) { $this->{_error} = (@_ > 1) ? sprintf(shift(@_), @_) : $_[0]; if ($this->debug()) { printf "error: [%d] %s(): %s\n", (caller 0)[2], (caller 1)[3], $this->{_error}; } } return; } sub _error_clear { return $_[0]->{_error} = undef; } # # Buffer manipulation methods # sub _buffer_append { # my ($this, $value) = @_; return $_[0]->_error() if defined $_[0]->{_error}; # Always reset the index when the buffer is modified $_[0]->{_index} = 0; # Update our length $_[0]->{_length} += CORE::length($_[1]); # Append to the current buffer return $_[0]->{_buffer} .= $_[1]; } sub _buffer_get { my ($this, $requested) = @_; return $this->_error() if defined $this->{_error}; # Return the number of bytes requested at the current index or # clear and return the whole buffer if no argument is passed. if (@_ == 2) { if (($this->{_index} += $requested) > $this->{_length}) { $this->{_index} -= $requested; if ($this->{_length} >= $this->max_msg_size()) { return $this->_error( 'The message size exceeded the buffer maxMsgSize of %d', $this->max_msg_size() ); } return $this->_error('Unexpected end of message buffer'); } return substr $this->{_buffer}, $this->{_index} - $requested, $requested; } # Always reset the index when the buffer is modified $this->{_index} = 0; # Update our length to 0, the whole buffer is about to be cleared. $this->{_length} = 0; return substr $this->{_buffer}, 0, CORE::length($this->{_buffer}), q{}; } sub _buffer_put { # my ($this, $value) = @_; return $_[0]->_error() if defined $_[0]->{_error}; # Always reset the index when the buffer is modified $_[0]->{_index} = 0; # Update our length $_[0]->{_length} += CORE::length($_[1]); # Add the prefix to the current buffer substr $_[0]->{_buffer}, 0, 0, $_[1]; return $_[0]->{_buffer}; } sub _buffer_dump { my ($this) = @_; return $DEBUG if (!$DEBUG); DEBUG_INFO('%d byte%s', $this->{_length}, $this->{_length} != 1 ? 's' : q{}); my ($offset, $hex, $text) = (0, q{}, q{}); while ($this->{_buffer} =~ /(.{1,16})/gs) { $hex = unpack 'H*', ($text = $1); $hex .= q{ } x (32 - CORE::length($hex)); $hex = sprintf '%s %s %s %s ' x 4, unpack 'a2' x 16, $hex; $text =~ s/[\x00-\x1f\x7f-\xff]/./g; printf "[%04d] %s %s\n", $offset, uc($hex), $text; $offset += 16; } return $DEBUG; } sub DEBUG_INFO { return $DEBUG if (!$DEBUG); return printf sprintf('debug: [%d] %s(): ', (caller 0)[2], (caller 1)[3]) . ((@_ > 1) ? shift(@_) : '%s') . "\n", @_; } # ============================================================================ 1; # [end Net::SNMP::Message]