#!/usr/bin/perl
#
#
# My first perl project ;)
# Elisa Jasinska <elisa.jasinska@ams-ix.net>
# With many thanks to Tobias Engel for his help and support!
#
#
# sFlow.pm - 2006/09/22
#
# Please send comments or bug reports to <sflow@ams-ix.net>
#
#
# sFlow v4 RFC 3176 
# http://www.ietf.org/rfc/rfc3176.txt
# Dataformat: http://jasinska.de/sFlow/sFlowV4FormatDiagram/
#
# sFlow v5 Memo
# http://sflow.org/sflow_version_5.txt
# Dataformat: http://jasinska.de/sFlow/sFlowV5FormatDiagram/
#
#
# Copyright (c) 2006 AMS-IX B.V.
#
# This package is free software and is provided "as is" without express 
# or implied warranty.  It may be used, redistributed and/or modified 
# under the terms of the Perl Artistic License (see
# http://www.perl.com/perl/misc/Artistic.html)
#


package Net::sFlow;


use strict;
use warnings;

require Exporter;

# 64bit integers
use Math::BigInt;


our $VERSION = '0.04X';
our @EXPORT_OK = qw(decode);


# constants

use constant SFLOWv4                        => 4;
use constant SFLOWv5                        => 5;

use constant UNKNOWNIPVERSION               => 0;
use constant IPv4                           => 1;
use constant IPv6                           => 2;

# sFlow v4 constants

use constant FLOWSAMPLE_SFLOWv4             => 1;
use constant COUNTERSAMPLE_SFLOWv4          => 2;

use constant HEADERDATA_SFLOWv4             => 1;
use constant IPv4DATA_SFLOWv4               => 2;
use constant IPv6DATA_SFLOWv4               => 3;

use constant SWITCHDATA_SFLOWv4             => 1;
use constant ROUTERDATA_SFLOWv4             => 2;
use constant GATEWAYDATA_SFLOWv4            => 3;
use constant USERDATA_SFLOWv4               => 4;
use constant URLDATA_SFLOWv4                => 5;

use constant GENERICCOUNTER_SFLOWv4         => 1;
use constant ETHERNETCOUNTER_SFLOWv4        => 2;
use constant TOKENRINGCOUNTER_SFLOWv4       => 3;
use constant FDDICOUNTER_SFLOWv4            => 4;
use constant VGCOUNTER_SFLOWv4              => 5;
use constant WANCOUNTER_SFLOWv4             => 6;
use constant VLANCOUNTER_SFLOWv4            => 7;

# sFlow v5 constants

use constant FLOWSAMPLE_SFLOWv5             => 1;
use constant COUNTERSAMPLE_SFLOWv5          => 2;
use constant EXPANDEDFLOWSAMPLE_SFLOWv5     => 3;
use constant EXPANDEDCOUNTERSAMPLE_SFLOWv5  => 4;

use constant HEADERDATA_SFLOWv5             => 1;
use constant ETHERNETFRAMEDATA_SFLOWv5      => 2;
use constant IPv4DATA_SFLOWv5               => 3;
use constant IPv6DATA_SFLOWv5               => 4;
use constant SWITCHDATA_SFLOWv5             => 1001;
use constant ROUTERDATA_SFLOWv5             => 1002;
use constant GATEWAYDATA_SFLOWv5            => 1003;
use constant USERDATA_SFLOWv5               => 1004;
use constant URLDATA_SFLOWv5                => 1005;
use constant MPLSDATA_SFLOWv5               => 1006;
use constant NATDATA_SFLOWv5                => 1007;
use constant MPLSTUNNEL_SFLOWv5             => 1008;
use constant MPLSVC_SFLOWv5                 => 1009;
use constant MPLSFEC_SFLOWv5                => 1010;
use constant MPLSLVPFEC_SFLOWv5             => 1011;
use constant VLANTUNNEL_SFLOWv5             => 1012;

use constant GENERICCOUNTER_SFLOWv5         => 1;
use constant ETHERNETCOUNTER_SFLOWv5        => 2;
use constant TOKENRINGCOUNTER_SFLOWv5       => 3;
use constant VGCOUNTER_SFLOWv5              => 4;
use constant VLANCOUNTER_SFLOWv5            => 5;
use constant PROCESSORCOUNTER_SFLOWv5       => 1001;

# ethernet header constants

use constant ETH_TYPE_IP                    => '0800';
use constant ETH_TYPE_ARP                   => '0806';
use constant ETH_TYPE_APPLETALK             => '809b';
use constant ETH_TYPE_RARP                  => '8035';
use constant ETH_TYPE_SNMP                  => '814c';
use constant ETH_TYPE_IPv6                  => '86dd';
use constant ETH_TYPE_PPP                   => '880b';




sub decode {

  my $sFlowDatagramPacked = shift;
  my %sFlowDatagram = ();
  my @sFlowSamples = ();
  my @errors = ();
  my $error = undef;
  my $subProcessed = undef;

  my $offset = 0;

  ($sFlowDatagram{sFlowVersion},
   $sFlowDatagram{AgentIpVersion}) = 
    unpack('NN', $sFlowDatagramPacked);

  $offset += (2 * 4); 

  ($subProcessed, $error) = 
    &_decodeIpAddress(
      \$offset, 
      \$sFlowDatagramPacked, 
      \%sFlowDatagram, 
      undef, 
      \@sFlowSamples, 
      $sFlowDatagram{AgentIpVersion}, 
      'AgentIp', 
      1
    );

  unless ($subProcessed) {
    push @errors, $error; 
    %sFlowDatagram = ();
    return (\%sFlowDatagram, \@sFlowSamples, \@errors);
  }


####### sFlow V4 #######

  if ($sFlowDatagram{sFlowVersion} <= SFLOWv4) {

    (undef,
     $sFlowDatagram{datagramSequenceNumber},
     $sFlowDatagram{agentUptime},
     $sFlowDatagram{samplesInPacket}) = 
      unpack("a$offset N3", $sFlowDatagramPacked);

    $offset += (3 * 4);

    # parse samples
    for my $samplesCount (0 .. $sFlowDatagram{samplesInPacket} - 1) {

      my %sFlowSample = ();
      push @sFlowSamples, \%sFlowSample;

      (undef, 
       $sFlowSample{sampleType}) = 
        unpack("a$offset N", $sFlowDatagramPacked);

      $offset += 4;


      # FLOWSAMPLE
      if ($sFlowSample{sampleType} == FLOWSAMPLE_SFLOWv4) {

        (undef, 
         $sFlowSample{sampleSequenceNumber}) = 
          unpack("a$offset N", $sFlowDatagramPacked);

        $offset += 4;

        my $sourceId = undef;

        (undef,
         $sourceId,
         $sFlowSample{samplingRate},
         $sFlowSample{samplePool},
         $sFlowSample{drops},
         $sFlowSample{inputInterface},
         $sFlowSample{outputInterface},
         $sFlowSample{packetDataType}) = 
          unpack("a$offset N7", $sFlowDatagramPacked);

        $offset += 28;

        $sFlowSample{sourceIdType} = $sourceId >> 24;
        $sFlowSample{sourceIdIndex} = $sourceId & 2 ** 24 - 1;

        # packet data type: header
        if ($sFlowSample{packetDataType} == HEADERDATA_SFLOWv4) {

          ($subProcessed, $error) = 
            &_decodeHeaderData(
              \$offset, 
              \$sFlowDatagramPacked, 
              \%sFlowDatagram, 
              \%sFlowSample, 
              \@sFlowSamples
            ); 

          unless ($subProcessed) {
            push @errors, $error;
          }
        }

        # packet data type: IPv4
        elsif ($sFlowSample{packetDataType} == IPv4DATA_SFLOWv4) {
          &_decodeIPv4Data(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
        }

        # packet data type: IPv6
        elsif ($sFlowSample{packetDataType} == IPv6DATA_SFLOWv4){
          &_decodeIPv6Data(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
        }

        else { 

          $error = "ERROR: [sFlow.pm] <sFlowV4:PacketData> AgentIP: $sFlowDatagram{AgentIp}, "
                 . "Datagram: $sFlowDatagram{datagramSequenceNumber} - Unknown packet data type: "
                 . "$sFlowSample{packetDataType} - rest of the datagram skipped";

          push @errors, $error;
          pop @sFlowSamples; 
		      return (\%sFlowDatagram, \@sFlowSamples, \@errors); 
        }

        (undef, 
         $sFlowSample{extendedDataInSample}) = 
          unpack("a$offset N", $sFlowDatagramPacked);

        $offset += 4;

        for my $extendedDataCount (0 .. $sFlowSample{extendedDataInSample} - 1){

          my $extendedDataType = undef;

          (undef, $extendedDataType) = unpack("a$offset N", $sFlowDatagramPacked);
          $offset += 4;

          # extended data: switch
          if ($extendedDataType == SWITCHDATA_SFLOWv4) {
            &_decodeSwitchData(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
          }

          # extended data: router
          elsif ($extendedDataType == ROUTERDATA_SFLOWv4) {

            ($subProcessed, $error) = 
              &_decodeRouterData(
                \$offset, 
                \$sFlowDatagramPacked, 
                \%sFlowDatagram, 
                \%sFlowSample, 
                \@sFlowSamples
              );

            unless ($subProcessed) {
              push @errors, $error;
              pop @sFlowSamples;
              return (\%sFlowDatagram, \@sFlowSamples, \@errors);
            }

          }

          # extended data: gateway
          elsif ($extendedDataType == GATEWAYDATA_SFLOWv4) {

            ($subProcessed, $error) = 
              &_decodeGatewayData(
                \$offset, 
                \$sFlowDatagramPacked, 
                \%sFlowDatagram, 
                \%sFlowSample, 
                \@sFlowSamples
              );

            unless ($subProcessed) {
              push @errors, $error;
              pop @sFlowSamples;
              return (\%sFlowDatagram, \@sFlowSamples, \@errors);
            }
  
          }

          # extended data: user
          elsif ($extendedDataType == USERDATA_SFLOWv4) {

            &_decodeUserData(
              \$offset, 
              \$sFlowDatagramPacked, 
              \%sFlowDatagram, 
              \%sFlowSample
            );

          }

          # extended data: url
          # added in v.3.
          elsif ($extendedDataType == URLDATA_SFLOWv4) {

            &_decodeUrlData(
              \$offset, 
              \$sFlowDatagramPacked, 
              \%sFlowDatagram, 
              \%sFlowSample
            );

          }

          else { 

            $error = "ERROR: [sFlow.pm] <sFlowV4:ExtendedData> AgentIP: $sFlowDatagram{AgentIp}, "
                   . "Datagram: $sFlowDatagram{datagramSequenceNumber} - Unknown extended data type: "
                   . "$extendedDataType - rest of the datagram skipped";

            push @errors, $error;
            pop @sFlowSamples; 
		        return (\%sFlowDatagram, \@sFlowSamples, \@errors); 
          }

        }

      }

      # COUNTERSAMPLE
      elsif ($sFlowSample{sampleType} == COUNTERSAMPLE_SFLOWv4) {

        my $sourceId = undef;

        (undef,
         $sFlowSample{sampleSequenceNumber},
         $sourceId,
         $sFlowSample{counterSamplingInterval},
         $sFlowSample{countersVersion}) = 
          unpack("a$offset N4", $sFlowDatagramPacked);

        $offset += 16;
      
        $sFlowSample{sourceIdType} = $sourceId >> 24;
        $sFlowSample{sourceIdIndex} = $sourceId & 2 ** 24 - 1;

        # counterstype: generic
        if ($sFlowSample{countersVersion} == GENERICCOUNTER_SFLOWv4) {
          &_decodeCounterGeneric(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
        }

        # counterstype: ethernet
        elsif ($sFlowSample{countersVersion} == ETHERNETCOUNTER_SFLOWv4) {
          &_decodeCounterGeneric(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
          &_decodeCounterEthernet(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
        }

        # counterstype: tokenring
        elsif ($sFlowSample{countersVersion} == TOKENRINGCOUNTER_SFLOWv4) {
          &_decodeCounterGeneric(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
          &_decodeCounterTokenring(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
        }

        # counterstype: fddi
        elsif ($sFlowSample{countersVersion} == FDDICOUNTER_SFLOWv4) {
          &_decodeCounterGeneric(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
        }

        # counterstype: vg
        elsif ($sFlowSample{countersVersion} == VGCOUNTER_SFLOWv4) {
          &_decodeCounterGeneric(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
          &_decodeCounterVg(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
        }

        # counterstype: wan
        elsif ($sFlowSample{countersVersion} == WANCOUNTER_SFLOWv4) {
          &_decodeCounterGeneric(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
        }

        # counterstype: vlan
        elsif ($sFlowSample{countersVersion} == VLANCOUNTER_SFLOWv4) {
          &_decodeCounterVlan(\$offset, \$sFlowDatagramPacked, \%sFlowSample);
        }

        else { 

          $error = "ERROR: [sFlow.pm] <sFlowV4:CountersType> AgentIP: $sFlowDatagram{AgentIp}, "
                 . "Datagram: $sFlowDatagram{datagramSequenceNumber} - Unknown counters type: "
                 . "$sFlowSample{countersVersion} - rest of the datagram skipped";

          push @errors, $error;
          pop @sFlowSamples; 
		      return (\%sFlowDatagram, \@sFlowSamples, \@errors); 
        }

      }

      else { 

        $error = "ERROR: [sFlow.pm] <sFlowV4:SampleType> AgentIP: $sFlowDatagram{AgentIp}, "
               . "Datagram: $sFlowDatagram{datagramSequenceNumber} - Unknown sample type: "
               . "$sFlowSample{sampleType} - rest of the datagram skipped";

        push @errors, $error;
        pop @sFlowSamples; 
		    return (\%sFlowDatagram, \@sFlowSamples, \@errors); 
      }

    }  

  }
  

####### sFlow V5 #######

  elsif ($sFlowDatagram{sFlowVersion} >= SFLOWv5) { 

    # v5 also provides a sub agent id
    (undef, 
     $sFlowDatagram{subAgentId}) = 
      unpack("a$offset N", $sFlowDatagramPacked);

    $offset += 4;

    (undef,
     $sFlowDatagram{datagramSequenceNumber},
     $sFlowDatagram{agentUptime},
     $sFlowDatagram{samplesInPacket}) = 
      unpack("a$offset N3", $sFlowDatagramPacked);

    $offset += 12;

    # parse samples
    for my $samplesCount (0 .. $sFlowDatagram{samplesInPacket} - 1) {

      my %sFlowSample = ();
      push @sFlowSamples, \%sFlowSample;

      my $sampleType = undef;

      (undef, 
       $sampleType,
       $sFlowSample{sampleLength}) = 
        unpack("a$offset NN", $sFlowDatagramPacked);

      $offset += 8;

      $sFlowSample{sampleTypeEnterprise} = $sampleType >> 12;
      $sFlowSample{sampleTypeFormat} = $sampleType & 2 ** 12 - 1;

      my $sourceId = undef;

      if ($sFlowSample{sampleTypeEnterprise} == 0 
          and $sFlowSample{sampleTypeFormat} == FLOWSAMPLE_SFLOWv5) {

        (undef,
         $sFlowSample{sampleSequenceNumber},
         $sourceId,
         $sFlowSample{samplingRate},
         $sFlowSample{samplePool},
         $sFlowSample{drops},
         $sFlowSample{inputInterface},
         $sFlowSample{outputInterface},
         $sFlowSample{flowRecordsCount}) = 
          unpack("a$offset N8", $sFlowDatagramPacked);

        $offset += 32;

        $sFlowSample{sourceIdType} = $sourceId >> 24;
        $sFlowSample{sourceIdIndex} = $sourceId & 2 ** 24 - 1;  

        for my $flowRecords (0 .. $sFlowSample{flowRecordsCount} - 1) {

          ($subProcessed, $error) = 
            &_decodeFlowRecord(
              \$offset, 
              \$sFlowDatagramPacked, 
              \%sFlowDatagram, 
              \%sFlowSample, 
              \@sFlowSamples, 
              \@errors
            );

          unless ($subProcessed) {
            push @errors, $error;
            pop @sFlowSamples;
            return (\%sFlowDatagram, \@sFlowSamples, \@errors); 
          }

        }

      } 

      elsif ($sFlowSample{sampleTypeEnterprise} == 0 
             and $sFlowSample{sampleTypeFormat} == COUNTERSAMPLE_SFLOWv5) {

        (undef,
         $sFlowSample{sampleSequenceNumber},
         $sourceId,
         $sFlowSample{counterRecordsCount}) = 
          unpack("a$offset N3", $sFlowDatagramPacked);

        $offset += 12;

        $sFlowSample{sourceIdType} = $sourceId >> 24;
        $sFlowSample{sourceIdIndex} = $sourceId & 2 ** 24 - 1;

        for my $counterRecords (0 .. $sFlowSample{counterRecordsCount} - 1) {

          ($subProcessed, $error) = 
            &_decodeCounterRecord(
              \$offset, 
              \$sFlowDatagramPacked, 
              \%sFlowDatagram, 
              \%sFlowSample, 
              \@sFlowSamples
            );

          unless ($subProcessed) {
            push @errors, $error;
            pop @sFlowSamples;
            return (\%sFlowDatagram, \@sFlowSamples, \@errors);
          }

        }

      } 

      elsif ($sFlowSample{sampleTypeEnterprise} == 0 
             and $sFlowSample{sampleTypeFormat} == EXPANDEDFLOWSAMPLE_SFLOWv5) {
      
        (undef,
         $sFlowSample{sampleSequenceNumber},
         $sFlowSample{sourceIdType},
         $sFlowSample{sourceIdIndex},
         $sFlowSample{samplingRate},
         $sFlowSample{samplePool},
         $sFlowSample{drops},
         $sFlowSample{inputInterfaceFormat},
         $sFlowSample{inputInterfaceValue},
         $sFlowSample{outputInterfaceFormat},
         $sFlowSample{outputInterfaceValue},
         $sFlowSample{flowRecordsCount}) = 
          unpack("a$offset N11", $sFlowDatagramPacked);

        $offset += 44;

        for my $flowRecords (0 .. $sFlowSample{flowRecordsCount} - 1) {

          ($subProcessed, $error) = 
            &_decodeFlowRecord(
              \$offset, 
              \$sFlowDatagramPacked, 
              \%sFlowDatagram, 
              \%sFlowSample, 
              \@sFlowSamples, 
              \@errors
            );

          unless ($subProcessed) {
            push @errors, $error;
            pop @sFlowSamples;
            return (\%sFlowDatagram, \@sFlowSamples, \@errors);
          }

        } 

      } 

      elsif ($sFlowSample{sampleTypeEnterprise} == 0 
             and $sFlowSample{sampleTypeFormat} == EXPANDEDCOUNTERSAMPLE_SFLOWv5) {

        (undef,
         $sFlowSample{sampleSequenceNumber},
         $sFlowSample{sourceIdType},
         $sFlowSample{sourceIdIndex},
         $sFlowSample{counterRecordsCount}) = 
          unpack("a$offset N4", $sFlowDatagramPacked);

        $offset += 16;
  
        for my $counterRecords (0 .. $sFlowSample{counterRecordsCount} - 1) {

          ($subProcessed, $error) = 
            &_decodeCounterRecord(
              \$offset, 
              \$sFlowDatagramPacked, 
              \%sFlowDatagram, 
              \%sFlowSample, 
              \@sFlowSamples
            ); 

          unless ($subProcessed) {
            push @errors, $error;
            pop @sFlowSamples;
            return (\%sFlowDatagram, \@sFlowSamples, \@errors);
          }

        }

      } 

      else { 

        $error = "ERROR: [sFlow.pm] <sFlowV5:SampleData> AgentIP: $sFlowDatagram{AgentIp} Datagram: "
               . "$sFlowDatagram{datagramSequenceNumber} - Unknown sample enterprise: "
               . "$sFlowSample{sampleTypeEnterprise} or format: $sFlowSample{sampleTypeFormat} "
               . "- rest of the datagram skipped";

        push @errors, $error;
        pop @sFlowSamples; 
		    return (\%sFlowDatagram, \@sFlowSamples, \@errors); 
      }

    }

  }

  else { 

    $error = "ERROR: [sFlow.pm] AgentIP: $sFlowDatagram{AgentIp}, Datagram: "
           . "$sFlowDatagram{datagramSequenceNumber} - Unknown sFlow Version: "
           . "$sFlowDatagram{sFlowVersion}";

    push @errors, $error;
    %sFlowDatagram = ();
		return (\%sFlowDatagram, \@sFlowSamples, \@errors); 
  }
  
  return (\%sFlowDatagram, \@sFlowSamples, \@errors);

}


#################### END sub decode() #######################


sub _bin2ip {

  # _bin2ip is a copy of "to_dotquad" from NetPacket::IP.pm
  # Copyright (c) 2001 Tim Potter.
  # Copyright (c) 2001 Stephanie Wehner.

  my($net) = @_ ;
  my($na, $nb, $nc, $nd);

  $na = $net >> 24 & 255;
  $nb = $net >> 16 & 255;
  $nc = $net >>  8 & 255;
  $nd = $net & 255;

  return ("$na.$nb.$nc.$nd");
}


sub _decodeIpAddress {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;
  my $sFlowSamples = shift;
  my $IpVersion = shift;
  my $keyName = shift;
  my $DatagramOrSampleData = shift;

  my $error = undef;
  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;

  if (defined($DatagramOrSampleData)) {

    if ($IpVersion == IPv4) {  

      (undef, 
       $sFlowDatagram->{$keyName}) = 
        unpack("a$offset N", $sFlowDatagramPacked);

      $sFlowDatagram->{$keyName} = &_bin2ip($sFlowDatagram->{$keyName});
      $offset += 4;
    }

    elsif ($IpVersion == IPv6) { 

      (undef, 
       $sFlowDatagram->{$keyName}) = 
        unpack("a$offset B128", $sFlowDatagramPacked);

      $sFlowDatagram->{$keyName} = 
        join(':', unpack('H4H4H4H4H4H4H4H4', $sFlowDatagram->{$keyName}));

      $offset += 16;
    }
  }

  else {

    if ($IpVersion == IPv4) {

      (undef, 
       $sFlowSample->{$keyName}) = 
        unpack("a$offset B32", $sFlowDatagramPacked);

      $sFlowSample->{$keyName} = &_bin2ip($sFlowSample->{$keyName});
      $offset += 4;
    }

    elsif ($IpVersion == IPv6) {

      (undef, 
       $sFlowSample->{$keyName}) = 
        unpack("a$offset B128", $sFlowDatagramPacked);

      $sFlowSample->{$keyName} = 
        join(':', unpack('H4H4H4H4H4H4H4H4', $sFlowSample->{$keyName}));

      $offset += 16;
    }
  }

  if ($IpVersion != IPv4 and $IpVersion != IPv6) {

    if (defined($DatagramOrSampleData)) { 

      # unknown ip version added in v5 
      if ($IpVersion == UNKNOWNIPVERSION) {

        $error = "ERROR: [sFlow.pm] AgentIP: Unknown agent ip version: "
               . "$IpVersion - rest of the datagram skipped";

        return (undef, $error);
      }

      else {

        $error = "ERROR: [sFlow.pm] AgentIP: Unknown agent ip version: "
               . "$IpVersion - rest of the datagram skipped";

        return (undef, $error);
      }

    }

    else { 
    
      # unknown ip version added in v5 
      if ($IpVersion == UNKNOWNIPVERSION) {

        $error = "ERROR: [sFlow.pm] AgentIP: $sFlowDatagram->{AgentIp}, "
               . "Datagram: $sFlowDatagram->{datagramSequenceNumber}, Sample: "
               . "$sFlowSample->{sampleSequenceNumber} - Unknown ip version: "
               . "$IpVersion - rest of the datagram skipped";

        return (undef, $error);
      }
    
      else {
        $error = "ERROR: [sFlow.pm] AgentIP: $sFlowDatagram->{AgentIp}, "
               . "Datagram: $sFlowDatagram->{datagramSequenceNumber}, Sample: "
               . "$sFlowSample->{sampleSequenceNumber} - Unknown ip version: "
               . "$IpVersion - rest of the datagram skipped";

        return (undef, $error);
      }      

    }

  }

  $$offsetref = $offset;
  return (1, undef);
}


sub _decodeFlowRecord {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;
  my $sFlowSamples = shift;
  my $errors = shift;
  
  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $flowType = undef;
  my $flowDataLength = undef;
  my $error = undef;
  my $subProcessed = undef;
  
  (undef,
   $flowType,
   $flowDataLength) = 
    unpack("a$offset NN", $sFlowDatagramPacked);
  
  $offset += 8;
  
  my $flowTypeEnterprise = $flowType >> 12;
  my $flowTypeFormat = $flowType & 2 ** 12 - 1;

  if ($flowTypeEnterprise == 0) {
  
    if ($flowTypeFormat == HEADERDATA_SFLOWv5) {

      ($subProcessed, $error) = 
        &_decodeHeaderData(
          \$offset, 
          $sFlowDatagramPackedRef, 
          $sFlowDatagram, 
          $sFlowSample, 
          $sFlowSamples
        );

      unless ($subProcessed) {
        push @{$errors}, $error;
      }

    } 

    elsif ($flowTypeFormat == SWITCHDATA_SFLOWv5) {
      &_decodeSwitchData(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($flowTypeFormat == ETHERNETFRAMEDATA_SFLOWv5) {
      &_decodeEthernetFrameData(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($flowTypeFormat == IPv4DATA_SFLOWv5) {
      &_decodeIPv4Data(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($flowTypeFormat == IPv6DATA_SFLOWv5) {
      &_decodeIPv6Data(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 
  
    elsif ($flowTypeFormat == ROUTERDATA_SFLOWv5) {

      ($subProcessed, $error) = 
        &_decodeRouterData(
          \$offset, 
          $sFlowDatagramPackedRef, 
          $sFlowDatagram, 
          $sFlowSample, 
          $sFlowSamples
        );

      unless ($subProcessed) {
        return (undef, $error);
      }

    } 

    elsif ($flowTypeFormat == GATEWAYDATA_SFLOWv5) {

      ($subProcessed, $error) = 
        &_decodeGatewayData(
          \$offset, 
          $sFlowDatagramPackedRef, 
          $sFlowDatagram, 
          $sFlowSample, 
          $sFlowSamples
        );

      unless ($subProcessed) {
        return (undef, $error);
      }

    } 

    elsif ($flowTypeFormat == USERDATA_SFLOWv5) {
      &_decodeUserData(\$offset, $sFlowDatagramPackedRef, $sFlowDatagram, $sFlowSample);
    }   

    elsif ($flowTypeFormat == URLDATA_SFLOWv5) {
      &_decodeUrlData(\$offset, $sFlowDatagramPackedRef, $sFlowDatagram, $sFlowSample);
    } 

    elsif ($flowTypeFormat == MPLSDATA_SFLOWv5) {

      ($subProcessed, $error) = 
        &_decodeMplsData(
          \$offset, 
          $sFlowDatagramPackedRef, 
          $sFlowDatagram, 
          $sFlowSample, 
          $sFlowSamples
        );

      unless ($subProcessed) {
        return (undef, $error);
      }

    } 

    elsif ($flowTypeFormat == NATDATA_SFLOWv5) {

      ($subProcessed, $error) = 
        &_decodeNatData(
          \$offset, 
          $sFlowDatagramPackedRef, 
          $sFlowDatagram, 
          $sFlowSample, 
          $sFlowSamples
        );

      unless ($subProcessed) {
        return (undef, $error);
      }
    
    } 

    elsif ($flowTypeFormat == MPLSTUNNEL_SFLOWv5) {
      &_decodeMplsTunnel(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($flowTypeFormat == MPLSVC_SFLOWv5) {
      &_decodeMplsVc(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    }  

    elsif ($flowTypeFormat == MPLSFEC_SFLOWv5) {
      &_decodeMplsFec(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($flowTypeFormat == MPLSLVPFEC_SFLOWv5) {
      &_decodeMplsLpvFec(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($flowTypeFormat == VLANTUNNEL_SFLOWv5) {
      &_decodeVlanTunnel(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    }
  
    else { 

      $error = "ERROR: [sFlow.pm] <sFlowV5:FlowData> AgentIP: $sFlowDatagram->{AgentIp}, "
             . "Datagram: $sFlowDatagram->{datagramSequenceNumber}, Sample: "
             . "$sFlowSample->{sampleSequenceNumber} - Unknown Flowdata format: "
             . "$flowTypeFormat - rest of the datagram skipped";

		  return (undef, $error); 
    }
  
  }

  else { 

    $error = "ERROR: [sFlow.pm] <sFlowV5:FlowData> AgentIP: $sFlowDatagram->{AgentIp}, "
           . "Datagram: $sFlowDatagram->{datagramSequenceNumber} - Unknown Flowdata enterprise: "
           . "$flowTypeEnterprise - rest of the datagram skipped";

		return (undef, $error); 
  }

  $$offsetref = $offset;
  return (1,undef);
}


sub _decodeCounterRecord {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;
  my $sFlowSamples = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $counterType = undef;
  my $counterDataLength = undef;
  my $error = undef;

  (undef,
   $counterType,
   $sFlowSample->{counterDataLength}) = 
    unpack("a$offset NN", $sFlowDatagramPacked);

  $offset += 8;

  my $counterTypeEnterprise = $counterType >> 12;
  my $counterTypeFormat = $counterType & 2 ** 12 - 1;

  if ($counterTypeEnterprise == 0) {

    if ($counterTypeFormat == GENERICCOUNTER_SFLOWv5) {
      &_decodeCounterGeneric(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    }  

    elsif ($counterTypeFormat == ETHERNETCOUNTER_SFLOWv5) {
      &_decodeCounterEthernet(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($counterTypeFormat == TOKENRINGCOUNTER_SFLOWv5) {
      &_decodeCounterTokenring(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($counterTypeFormat == VGCOUNTER_SFLOWv5) {
      &_decodeCounterVg(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($counterTypeFormat == VLANCOUNTER_SFLOWv5) {
      &_decodeCounterVlan(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    } 

    elsif ($counterTypeFormat == PROCESSORCOUNTER_SFLOWv5) {
      &_decodeCounterProcessor(\$offset, $sFlowDatagramPackedRef, $sFlowSample);
    }
   
    else { 

      $error = "ERROR: [sFlow.pm] <sFlowV5:CounterData> AgentIP: $sFlowDatagram->{AgentIp}, "
             . "Datagram: $sFlowDatagram->{datagramSequenceNumber} - Unknown counter data format: "
             . "$counterTypeFormat - rest of the datagram skipped";
		  return (undef, $error); 
    }

  } 

  else { 

    $error = "ERROR: [sFlow.pm] <sFlowV5:CounterData> AgentIP: $sFlowDatagram->{AgentIp}, "
           . "Datagram: $sFlowDatagram->{datagramSequenceNumber} - Unknown counter data enterprise: "
           . "$counterTypeEnterprise - rest of the datagram skipped";

	  return (undef, $error); 
  }
  
  $$offsetref = $offset;
  return (1, undef);
}


sub _decodeHeaderData {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;
  my $sFlowSamples = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;

  $sFlowSample->{HEADERDATA} = 'HEADERDATA';

  if ($sFlowDatagram->{sFlowVersion} == SFLOWv5) {
  
    (undef,
     $sFlowSample->{HeaderProtocol},
     $sFlowSample->{HeaderFrameLength},
     $sFlowSample->{HeaderStrippedLength},
     $sFlowSample->{HeaderSizeByte}) = 
      unpack("a$offset N4", $sFlowDatagramPacked);

    $offset += 16;
  
  } else {

    (undef,
     $sFlowSample->{HeaderProtocol},
     $sFlowSample->{HeaderFrameLength},
     $sFlowSample->{HeaderSizeByte}) = 
      unpack("a$offset N3", $sFlowDatagramPacked);
  
    $offset += 12;

  }

  # header size in bit
  $sFlowSample->{HeaderSizeBit} = $sFlowSample->{HeaderSizeByte} * 8;

  my $header = substr ($sFlowDatagramPacked, $offset, $sFlowSample->{HeaderSizeByte});

  # we have to cut off a $sFlowSample->{HeaderSizeByte} mod 4 == 0 number of bytes 
  my $tmp = 4 - ($sFlowSample->{HeaderSizeByte} % 4);
  $tmp == 4 and $tmp = 0;

  $offset += ($sFlowSample->{HeaderSizeByte} + $tmp);


  # unpack ethernet header
  # copy of the decode function of NetPacket::Ethernet
  # Copyright (c) 2001 Tim Potter.

  my($sm_lo, $sm_hi, $dm_lo, $dm_hi, $type, $ipdata);

  ($dm_hi, $dm_lo, $sm_hi, $sm_lo, $type, $ipdata) = unpack('NnNnH4a*', $header);

  # Convert MAC addresses to hex string to avoid representation problems

  $sFlowSample->{HeaderEtherSrcMac} = sprintf("%08x%04x", $sm_hi, $sm_lo);
  $sFlowSample->{HeaderEtherDestMac} = sprintf("%08x%04x", $dm_hi, $dm_lo);


  if ($type eq '0800') {

    $sFlowSample->{HeaderVer} = 4;

    (undef, $sFlowSample->{HeaderDatalen}) = unpack('nn', $ipdata);
    # add ethenet header length
    $sFlowSample->{HeaderDatalen} += 14;
  }

  elsif ($type eq '86dd') {

    $sFlowSample->{HeaderVer} = 6;

    (undef, $sFlowSample->{HeaderDatalen}) = unpack('Nn', $ipdata);
    # add v6 header (not included in v6)
    $sFlowSample->{HeaderDatalen} += 40;
    # add ethenet header length
    $sFlowSample->{HeaderDatalen} += 14;
  }

  elsif ($type eq '0806') {
    # ARP 
    $sFlowSample->{HeaderVer} = 1;
  }

  else {
    # unknown
    $sFlowSample->{HeaderVer} = 0;
  }

  $$offsetref = $offset;
  return (1, undef);
  
}


sub _decodeEthernetFrameData {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $EtherSrcMac1 = undef;
  my $EtherSrcMac2 = undef;
  my $EtherDestMac1 = undef;
  my $EtherDestMac2 = undef;
   
  $sFlowSample->{ETHERNETFRAMEDATA} = 'ETHERNETFRAMEDATA';
  
  (undef,
   $sFlowSample->{EtherMacPacketlength},
   $EtherSrcMac1,
   $EtherSrcMac2,
   $EtherDestMac1,
   $EtherDestMac2,
   $sFlowSample->{EtherPackettype}) = 
    unpack("a$offset N6", $sFlowDatagramPacked);
  
  $sFlowSample->{EtherSrcMac} = sprintf("%08x%04x", $EtherSrcMac1, $EtherSrcMac2);
  $sFlowSample->{EtherDestMac} = sprintf("%08x%04x", $EtherDestMac1, $EtherDestMac2);
 
  $offset += 24;
  $$offsetref = $offset;
}


sub _decodeIPv4Data {
  
  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;
  
  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  
  $sFlowSample->{IPv4DATA} = 'IPv4DATA';
  
  (undef,
   $sFlowSample->{IPv4Packetlength},
   $sFlowSample->{IPv4NextHeaderProtocol},
   $sFlowSample->{IPv4srcIp},
   $sFlowSample->{IPv4destIp},
   $sFlowSample->{IPv4srcPort},
   $sFlowSample->{IPv4destPort},
   $sFlowSample->{IPv4tcpFlags},
   $sFlowSample->{IPv4tos}) = 
    unpack("a$offset N2B32B32N4", $sFlowDatagramPacked); 
  
  $sFlowSample->{IPv4srcIp} = &_bin2ip($sFlowSample->{IPv4srcIp});
  $sFlowSample->{IPv4destIp} = &_bin2ip($sFlowSample->{IPv4destIp});
  
  $offset += 32;
  $$offsetref = $offset;
}


sub _decodeIPv6Data {
  
  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;
  
  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  
  $sFlowSample->{IPv6DATA} = 'IPv6DATA';

  (undef,
   $sFlowSample->{IPv6Packetlength},
   $sFlowSample->{IPv6NextHeaderProto},
   $sFlowSample->{IPv6srcIp},
   $sFlowSample->{IPv6destIp},
   $sFlowSample->{IPv6srcPort},
   $sFlowSample->{IPv6destPort},
   $sFlowSample->{IPv6tcpFlags},
   $sFlowSample->{IPv6Priority}) = 
    unpack("a$offset N2B128B128N4", $sFlowDatagramPacked);

  $sFlowSample->{IPv6srcIp} = 
    join(':', unpack('H4H4H4H4H4H4H4H4', $sFlowSample->{IPv6srcIp}));

  $sFlowSample->{IPv6destIp} = 
    join(':', unpack('H4H4H4H4H4H4H4H4', $sFlowSample->{IPv6destIp}));
   
  $offset += 56;
  $$offsetref = $offset;
}


sub _decodeSwitchData {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;

  $sFlowSample->{SWITCHDATA} = 'SWITCHDATA';

  (undef,
   $sFlowSample->{SwitchSrcVlan},
   $sFlowSample->{SwitchSrcPriority},
   $sFlowSample->{SwitchDestVlan},
   $sFlowSample->{SwitchDestPriority}) = 
    unpack("a$offset N4", $sFlowDatagramPacked);
  
  $offset += 16;
  $$offsetref = $offset;
}


sub _decodeRouterData {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;
  my $sFlowSamples = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $subProcessed = undef;
  my $error = undef;

  $sFlowSample->{ROUTERDATA} = 'ROUTERDATA';

  (undef, 
   $sFlowSample->{RouterIpVersionNextHopRouter}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;

  ($subProcessed, $error) = 
    &_decodeIpAddress(
      \$offset, 
      $sFlowDatagramPackedRef, 
      $sFlowDatagram, 
      $sFlowSample, 
      $sFlowSamples,
      $sFlowSample->{RouterIpVersionNextHopRouter},
      'RouterIpAddressNextHopRouter',
      undef
    );
  
  unless ($subProcessed) {
    return (undef, $error);
  }
  
  (undef, 
   $sFlowSample->{RouterSrcMask},
   $sFlowSample->{RouterDestMask}) = 
    unpack("a$offset NN", $sFlowDatagramPacked);

  $offset += 8;

  $$offsetref = $offset;
  return (1, undef);
}


sub _decodeGatewayData {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;
  my $sFlowSamples = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $subProcessed = undef;
  my $error = undef;

  $sFlowSample->{GATEWAYDATA} = 'GATEWAYDATA';

  if ($sFlowDatagram->{sFlowVersion} == SFLOWv5) {

    (undef, 
     $sFlowSample->{GatewayIpVersionNextHopRouter}) = 
      unpack("a$offset N", $sFlowDatagramPacked);

    $offset += 4;

    ($subProcessed, $error) = 
      &_decodeIpAddress(
        \$offset, 
        $sFlowDatagramPackedRef, 
        $sFlowDatagram, 
        $sFlowSample, 
        $sFlowSamples,
        $sFlowSample->{GatewayIpVersionNextHopRouter}, 
        'GatewayIpVersionNextHopRouter', 
        undef
      );

    unless ($subProcessed) {
      return (undef, $error);
    } 
  }

  (undef,
   $sFlowSample->{GatewayAsRouter},
   $sFlowSample->{GatewayAsSource},
   $sFlowSample->{GatewayAsSourcePeer},
   $sFlowSample->{GatewayDestAsPathsCount}) = 
    unpack("a$offset N4", $sFlowDatagramPacked);    

  $offset += 16;

  # array containing the single paths
  my @sFlowAsPaths = ();

  # reference to this array in extended data
  $sFlowSample->{GatewayDestAsPaths} = \@sFlowAsPaths;

  for my $destAsPathCount (0 .. $sFlowSample->{GatewayDestAsPathsCount} - 1) {

    # single path hash
    my %sFlowAsPath = ();

    # reference to this single path hash in the paths array
    push @sFlowAsPaths, \%sFlowAsPath; 
    
    (undef,
     $sFlowAsPath{asPathSegmentType},
     $sFlowAsPath{lengthAsList}) = 
      unpack("a$offset NN", $sFlowDatagramPacked);
    
    $offset += 8;

    # array containing the as numbers of a path
    my @sFlowAsNumber = ();

    # referece to this array in path hash
    $sFlowAsPath{AsPath} = \@sFlowAsNumber;

    for my $asListLength (0 .. $sFlowAsPath{lengthAsList} - 1) {

      (undef, 
       my $asNumber) = 
        unpack("a$offset N", $sFlowDatagramPacked);

      # push as number to array
      push @sFlowAsNumber, $asNumber;
      $offset += 4;
    }

  }
  # communities added in v.4.
  if ($sFlowDatagram->{sFlowVersion} == SFLOWv4) {

    (undef, 
     $sFlowSample->{GatewayLengthCommunitiesList}) = 
      unpack("a$offset N", $sFlowDatagramPacked);

    $offset += 4;

    my @sFlowCommunities = ();
    $sFlowSample->{GatewayCommunities} = \@sFlowCommunities;

    for my $commLength (0 .. $sFlowSample->{GatewayLengthCommunitiesList} - 1) {

      (undef, 
       my $community) = 
        unpack("a$offset N", $sFlowDatagramPacked);

      push @sFlowCommunities, $community;
      $offset += 4;
    }
  }

  (undef, 
   $sFlowSample->{localPref}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;

  $$offsetref = $offset;
  return (1, undef);
}


sub _decodeUserData {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;  

  $sFlowSample->{USERDATA} = 'USERDATA';

  if ($sFlowDatagram->{sFlowVersion} == SFLOWv5) {

    (undef, 
     $sFlowSample->{UserSrcCharset}) = 
      unpack("a$offset N", $sFlowDatagramPacked);

    $offset += 4;
  }

  # xxx - string length "A" ????
  (undef, 
   $sFlowSample->{UserLengthSrcString}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;

  (undef, 
   $sFlowSample->{UserSrcString}) = 
    unpack("a$offset A$sFlowSample->{UserLengthSrcString}", $sFlowDatagramPacked);

  $offset += $sFlowSample->{UserLengthSrcString};

  if ($sFlowDatagram->{sFlowVersion} == SFLOWv5) {

    (undef, 
     $sFlowSample->{UserDestCharset}) = 
      unpack("a$offset N", $sFlowDatagramPacked);

    $offset += 4;
  }

  (undef, 
   $sFlowSample->{UserLengthDestString}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;

  (undef, 
   $sFlowSample->{UserDestString}) = 
    unpack("a$offset A$sFlowSample->{UserLengthDestString}", $sFlowDatagramPacked);

  $offset += $sFlowSample->{UserLengthDestString};
  
  $$offsetref = $offset;
}


sub _decodeUrlData {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  
  $sFlowSample->{URLDATA} = 'URLDATA';

  # xxx - string length "A" ????
  (undef, 
   $sFlowSample->{UrlDirection}, 
   $sFlowSample->{UrlLength}) = 
    unpack("a$offset NN", $sFlowDatagramPacked);

  $offset += 8;

  (undef, 
   $sFlowSample->{Url}) = 
    unpack("a$offset A$sFlowSample->{UrlLength}", $sFlowDatagramPacked);

  $offset += $sFlowSample->{UrlLength};

  if ($sFlowDatagram->{sFlowVersion} == SFLOWv5) {

    (undef, 
     $sFlowSample->{UrlHostLength}) = 
      unpack("a$offset N", $sFlowDatagramPacked);

    $offset += 4;

    (undef, 
     $sFlowSample->{UrlHost}) = 
      unpack("a$offset A$sFlowSample->{UrlHostLength}", $sFlowDatagramPacked);

    $offset += $sFlowSample->{UrlHostLength};
  }
  
  $$offsetref = $offset;
}


sub _decodeMplsData {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;
  my $sFlowSamples = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $subProcessed = undef;
  my $error = undef;
 
  $sFlowSample->{MPLSDATA} = 'MPLSDATA';
 
  (undef, 
   $sFlowSample->{MplsIpVersionNextHopRouter}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;
 
  ($subProcessed, $error) = 
    &_decodeIpAddress(
      \$offset, 
      $sFlowDatagramPackedRef, 
      $sFlowDatagram, 
      $sFlowSample, 
      $sFlowSamples,
      $sFlowSample->{MplsIpVersionNextHopRouter}, 
      'MplsIpVersionNextHopRouter', 
      undef
    );

  unless ($subProcessed) {
    return (undef, $error); 
  }
 
  (undef, 
   $sFlowSample->{MplsInLabelStackCount}) = 
    unpack("a$offset N", $sFlowDatagramPacked); 

  $offset += 4;
  
  my @MplsInLabelStack = ();
  $sFlowSample->{MplsInLabelStack} = \@MplsInLabelStack;
  
  for my $MplsInLabelStackCount (0 .. $sFlowSample->{MplsInLabelStackCount} - 1) {

    (undef, my $MplsInLabel) = unpack("a$offset N", $sFlowDatagramPacked);
    push @MplsInLabelStack, $MplsInLabel;
    $offset += 4;
  }
  
  (undef, $sFlowSample->{MplsOutLabelStackCount}) = unpack("a$offset N", $sFlowDatagramPacked); 
  $offset += 4;
  
  my @MplsOutLabelStack = ();
  $sFlowSample->{MplsOutLabelStack} = \@MplsInLabelStack;

  for my $MplsOutLabelStackCount (0 .. $sFlowSample->{MplsOutLabelStackCount} - 1) {

    (undef, my $MplsOutLabel) = unpack("a$offset N", $sFlowDatagramPacked);
    push @MplsOutLabelStack, $MplsOutLabel;
    $offset += 4;
  }

  $$offsetref = $offset;
  return (1, undef);
}


sub _decodeNatData {
  
  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowDatagram = shift;
  my $sFlowSample = shift;
  my $sFlowSamples = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $subProcessed = undef;
  my $error = undef;

  $sFlowSample->{NATDATA} = 'NATDATA';

  (undef, 
   $sFlowSample->{NatIpVersionSrcAddress}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;

  ($subProcessed, $error) = 
    &_decodeIpAddress(
      \$offset, 
      $sFlowDatagramPackedRef, 
      $sFlowDatagram, 
      $sFlowSample, 
      $sFlowSamples,
      $sFlowSample->{NatIpVersionSrcAddress}, 
      'NatIpVersionSrcAddress', 
      undef
    );

  unless ($subProcessed) {
    return (undef, $error); 
  }
 
  (undef, 
   $sFlowSample->{NatIpVersionDestAddress}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;
 
  ($subProcessed, $error) = 
    &_decodeIpAddress(
      \$offset, 
      $sFlowDatagramPackedRef, 
      $sFlowDatagram, 
      $sFlowSample, 
      $sFlowSamples,
      $sFlowSample->{NatIpVersionDestAddress}, 
      'NatIpVersionDestAddress', 
      undef
    );

  unless ($subProcessed) {
    return (undef, $error); 
  }

  $$offsetref = $offset;
  return (1, undef);
}


sub _decodeMplsTunnel {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  
  $sFlowSample->{MPLSTUNNEL} = 'MPLSTUNNEL';

  (undef, 
   $sFlowSample->{MplsTunnelLength}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;
  
  (undef, 
   $sFlowSample->{MplsTunnelName}) = 
    unpack("a$offset A$sFlowSample->{MplsTunnelLength}", $sFlowDatagramPacked);

  $offset += $sFlowSample->{MplsTunnelLength};
  
  (undef, 
   $sFlowSample->{MplsTunnelId},
   $sFlowSample->{MplsTunnelCosValue}) = 
    unpack("a$offset NN", $sFlowDatagramPacked);

  $offset += 8;
  
  $$offsetref = $offset;
}


sub _decodeMplsVc {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;

  $sFlowSample->{MPLSVC} = 'MPLSVC';

  (undef, 
   $sFlowSample->{MplsVcInstanceNameLength}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;
  
  (undef, 
   $sFlowSample->{MplsVcInstanceName}) = 
    unpack("a$offset A$sFlowSample->{MplsVcInstanceNameLength}", $sFlowDatagramPacked);

  $offset += $sFlowSample->{MplsVcInstanceNameLength};
  
  (undef, 
   $sFlowSample->{MplsVcId},
   $sFlowSample->{MplsVcLabelCosValue}) = 
    unpack("a$offset NN", $sFlowDatagramPacked);

  $offset += 8;
  
  $$offsetref = $offset;
}


sub _decodeMplsFec {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;

  $sFlowSample->{MPLSFEC} = 'MPLSFEC';

  (undef, 
   $sFlowSample->{MplsFtnDescrLength}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;
  
  (undef, 
   $sFlowSample->{MplsFtnDescr}) = 
    unpack("a$offset A$sFlowSample->{MplsFtnDescrLength}", $sFlowDatagramPacked);

  $offset += $sFlowSample->{MplsFtrDescrLength};
  
  (undef, $sFlowSample->{MplsFtnMask}) = unpack("a$offset N", $sFlowDatagramPacked);
  $offset += 4;
  
  $$offsetref = $offset;
}


sub _decodeMplsLpvFec {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  
  $sFlowSample->{MPLSLPVFEC} = 'MPLSLPVFEC';

  (undef, 
   $sFlowSample->{MplsFecAddrPrefixLength}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;

  $$offsetref = $offset;
}


sub _decodeVlanTunnel {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;

  $sFlowSample->{VLANTUNNEL} = 'VLANTUNNEL';

  (undef, 
   $sFlowSample->{VlanTunnelLayerStackCount}) = 
    unpack("a$offset N", $sFlowDatagramPacked);

  $offset += 4;
  
  my @VlanTunnelLayerStack = ();
  $sFlowSample->{VlanTunnelLayerStack} = \@VlanTunnelLayerStack;

  for my $VlanTunnelLayerCount (0 .. $sFlowSample->{VlanTunnelLayerStackCount} - 1) {

    (undef, my $VlanTunnelLayer) = unpack("a$offset N", $sFlowDatagramPacked);
    push @VlanTunnelLayerStack, $VlanTunnelLayer;
    $offset += 4;
  }

  $$offsetref = $offset;
}


sub _decodeCounterGeneric {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $ifSpeed1 = undef; 
  my $ifSpeed2 = undef; 
  my $ifInOctets1 = undef;
  my $ifInOctets2 = undef;
  my $ifOutOctets1 = undef;
  my $ifOutOctets2 = undef;

  $sFlowSample->{COUNTERGENERIC} = 'COUNTERGENERIC';

  (undef,
   $sFlowSample->{ifIndex},
   $sFlowSample->{ifType},
   $ifSpeed1,
   $ifSpeed2,
   $sFlowSample->{ifDirection},
   $sFlowSample->{ifAdminStatus},
   $sFlowSample->{ifOperStatus},
   undef,
   $ifInOctets1,
   $ifInOctets2,
   $sFlowSample->{ifInUcastPkts},
   $sFlowSample->{ifInMulticastPkts},
   $sFlowSample->{ifInBroadcastPkts},
   $sFlowSample->{idInDiscards},
   $sFlowSample->{ifInErrors},
   $sFlowSample->{ifInUnknownProtos},
   $ifOutOctets1,
   $ifOutOctets2,
   $sFlowSample->{ifOutUcastPkts},
   $sFlowSample->{ifOutMulticastPkts},
   $sFlowSample->{ifOutBroadcastPkts},
   $sFlowSample->{ifOutDiscards},
   $sFlowSample->{ifOutErrors},
   $sFlowSample->{ifPromiscuousMode}) = 
    unpack("a$offset N5B1B1B30N16", $sFlowDatagramPacked);

  $offset += 88;

  $sFlowSample->{ifSpeed} = Math::BigInt->new("$ifSpeed1");
  $sFlowSample->{ifSpeed} = $sFlowSample->{ifSpeed} << 32;
  $sFlowSample->{ifSpeed} += $ifSpeed2;
 
  $sFlowSample->{idInOctets} = Math::BigInt->new("$ifInOctets1");
  $sFlowSample->{idInOctets} = $sFlowSample->{idInOctets} << 32;
  $sFlowSample->{idInOctets} += $ifInOctets2;

  $sFlowSample->{ifOutOctets} = Math::BigInt->new("$ifOutOctets1");
  $sFlowSample->{ifOutOctets} = $sFlowSample->{ifOutOctets} << 32;
  $sFlowSample->{ifOutOctets} += $ifOutOctets2;
  
  $$offsetref = $offset;
}


sub _decodeCounterEthernet {
  
  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;

  $sFlowSample->{COUNTERETHERNET} = 'COUNTERETHERNET';

  (undef,
   $sFlowSample->{dot3StatsAlignmentErrors},
   $sFlowSample->{dot3StatsFCSErrors},
   $sFlowSample->{dot3StatsSingleCollisionFrames},
   $sFlowSample->{dot3StatsMultipleCollisionFrames},
   $sFlowSample->{dot3StatsSQETestErrors},
   $sFlowSample->{dot3StatsDeferredTransmissions},
   $sFlowSample->{dot3StatsLateCollisions},
   $sFlowSample->{dot3StatsExcessiveCollisions},
   $sFlowSample->{dot3StatsInternalMacTransmitErrors},
   $sFlowSample->{dot3StatsCarrierSenseErrors},
   $sFlowSample->{dot3StatsFrameTooLongs},
   $sFlowSample->{dot3StatsInternalMacReceiveErrors},
   $sFlowSample->{dot3StatsSymbolErrors}) = 
    unpack("a$offset N13", $sFlowDatagramPacked);

  $offset += 52;
  $$offsetref = $offset;
} 


sub _decodeCounterTokenring {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;

  $sFlowSample->{COUNTERTOKENRING} = 'COUNTERTOKENRING';

  (undef,
   $sFlowSample->{dot5StatsLineErrors},
   $sFlowSample->{dot5StatsBurstErrors},
   $sFlowSample->{dot5StatsACErrors},
   $sFlowSample->{dot5StatsAbortTransErrors},
   $sFlowSample->{dot5StatsInternalErrors},
   $sFlowSample->{dot5StatsLostFrameErrors},
   $sFlowSample->{dot5StatsReceiveCongestions},
   $sFlowSample->{dot5StatsFrameCopiedErrors},
   $sFlowSample->{dot5StatsTokenErrors},
   $sFlowSample->{dot5StatsSoftErrors},
   $sFlowSample->{dot5StatsHardErrors},
   $sFlowSample->{dot5StatsSignalLoss},
   $sFlowSample->{dot5StatsTransmitBeacons},
   $sFlowSample->{dot5StatsRecoverys},
   $sFlowSample->{dot5StatsLobeWires},
   $sFlowSample->{dot5StatsRemoves},
   $sFlowSample->{dot5StatsSingles},
   $sFlowSample->{dot5StatsFreqErrors}) = 
    unpack("a$offset N18", $sFlowDatagramPacked);

  $offset += 72;
  $$offsetref = $offset;
}


sub _decodeCounterVg {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $dot12InHighPriorityOctets1 = undef;
  my $dot12InHighPriorityOctets2 = undef;
  my $dot12InNormPriorityOctets1 = undef;
  my $dot12InNormPriorityOctets2 = undef;
  my $dot12OutHighPriorityOctets1 = undef;
  my $dot12OutHighPriorityOctets2 = undef;
  my $dot12HCInHighPriorityOctets1 = undef;
  my $dot12HCInHighPriorityOctets2 = undef;
  my $dot12HCInNormPriorityOctets1 = undef;
  my $dot12HCInNormPriorityOctets2 = undef;
  my $dot12HCOutHighPriorityOctets1 = undef;
  my $dot12HCOutHighPriorityOctets2 = undef;

  $sFlowSample->{COUNTERVG} = 'COUNTERVG';

  (undef,
   $sFlowSample->{dot12InHighPriorityFrames},
   $dot12InHighPriorityOctets1,
   $dot12InHighPriorityOctets2,
   $sFlowSample->{dot12InNormPriorityFrames},
   $dot12InNormPriorityOctets1,
   $dot12InNormPriorityOctets2,
   $sFlowSample->{dot12InIPMErrors},
   $sFlowSample->{dot12InOversizeFrameErrors},
   $sFlowSample->{dot12InDataErrors},
   $sFlowSample->{dot12InNullAddressedFrames},
   $sFlowSample->{dot12OutHighPriorityFrames},
   $dot12OutHighPriorityOctets1,
   $dot12OutHighPriorityOctets2,
   $sFlowSample->{dot12TransitionIntoTrainings},
   $dot12HCInHighPriorityOctets1,
   $dot12HCInHighPriorityOctets2,
   $dot12HCInNormPriorityOctets1,
   $dot12HCInNormPriorityOctets2,
   $dot12HCOutHighPriorityOctets1,
   $dot12HCOutHighPriorityOctets2) = 
    unpack("a$offset N20", $sFlowDatagramPacked);

  $offset += 80;

  $sFlowSample->{dot12InHighPriorityOctets} = Math::BigInt->new("$dot12InHighPriorityOctets1");
  $sFlowSample->{dot12InHighPriorityOctets} = $sFlowSample->{dot12InHighPriorityOctets} << 32;
  $sFlowSample->{dot12InHighPriorityOctets} += $dot12InHighPriorityOctets2;

  $sFlowSample->{dot12InNormPriorityOctets} = Math::BigInt->new("$dot12InNormPriorityOctets1");
  $sFlowSample->{dot12InNormPriorityOctets} = $sFlowSample->{dot12InNormPriorityOctets} << 32;
  $sFlowSample->{dot12InNormPriorityOctets} += $dot12InNormPriorityOctets2;

  $sFlowSample->{dot12OutHighPriorityOctets} = Math::BigInt->new("$dot12OutHighPriorityOctets1");
  $sFlowSample->{dot12OutHighPriorityOctets} = $sFlowSample->{dot12OutHighPriorityOctets} << 32;
  $sFlowSample->{dot12OutHighPriorityOctets} += $dot12OutHighPriorityOctets2;

  $sFlowSample->{dot12HCInHighPriorityOctets} = Math::BigInt->new("$dot12HCInHighPriorityOctets1");
  $sFlowSample->{dot12HCInHighPriorityOctets} = $sFlowSample->{dot12HCInHighPriorityOctets} << 32;
  $sFlowSample->{dot12HCInHighPriorityOctets} += $dot12HCInHighPriorityOctets2;

  $sFlowSample->{dot12HCInNormPriorityOctets} = Math::BigInt->new("$dot12HCInNormPriorityOctets1");
  $sFlowSample->{dot12HCInNormPriorityOctets} = $sFlowSample->{dot12HCInNormPriorityOctets} << 32;
  $sFlowSample->{dot12HCInNormPriorityOctets} += $dot12HCInNormPriorityOctets2;

  $sFlowSample->{dot12HCOutHighPriorityOctets} = Math::BigInt->new("$dot12HCOutHighPriorityOctets1");
  $sFlowSample->{dot12HCOutHighPriorityOctets} = $sFlowSample->{dot12HCOutHighPriorityOctets} << 32;
  $sFlowSample->{dot12HCOutHighPriorityOctets} += $dot12HCOutHighPriorityOctets2;
  
  $$offsetref = $offset;
}


sub _decodeCounterVlan {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;

  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $octets1 = undef;
  my $octets2 = undef;

  $sFlowSample->{COUNTERVLAN} = 'COUNTERVLAN';

  (undef,
   $sFlowSample->{vlan_id},
   $octets1,
   $octets2,
   $sFlowSample->{ucastPkts},
   $sFlowSample->{multicastPkts},
   $sFlowSample->{broadcastPkts},
   $sFlowSample->{discards}) = 
    unpack("a$offset N7", $sFlowDatagramPacked);
  
  $offset += 28;

  $sFlowSample->{octets} = Math::BigInt->new("$octets1");
  $sFlowSample->{octets} = $sFlowSample->{octets} << 32;
  $sFlowSample->{octets} += $octets2;
   
  $$offsetref = $offset;
}


sub _decodeCounterProcessor {

  my $offsetref = shift;
  my $sFlowDatagramPackedRef = shift;
  my $sFlowSample = shift;
  
  my $sFlowDatagramPacked = $$sFlowDatagramPackedRef;
  my $offset = $$offsetref;
  my $memoryTotal1 = undef;
  my $memoryTotal2 = undef;
  my $memoryFree1 = undef;
  my $memoryFree2 = undef;

  $sFlowSample->{COUNTERPROCESSOR} = 'COUNTERPROCESSOR';

  (undef,
   $sFlowSample->{cpu5s},
   $sFlowSample->{cpu1m},
   $sFlowSample->{cpu5m},
   $memoryTotal1,
   $memoryTotal2,
   $memoryFree1,
   $memoryFree2) = 
    unpack("a$offset N7", $sFlowDatagramPacked);

  $offset += 28;  

  $sFlowSample->{memoryTotal} = Math::BigInt->new("$memoryTotal1");
  $sFlowSample->{memoryTotal} = $sFlowSample->{memoryTotal} << 32;
  $sFlowSample->{memoryTotal} += $memoryTotal2;
  
  $sFlowSample->{memoryFree} = Math::BigInt->new("$memoryFree1");
  $sFlowSample->{memoryFree} = $sFlowSample->{memoryFree} << 32;
  $sFlowSample->{memoryFree} += $memoryFree2;

  $$offsetref = $offset;
} 

1;

__END__

=head1 NAME

Net::sFlow - decode sFlow datagrams.



=head1 SYNOPSIS

  use Net::sFlow;
  use IO::Socket::INET;
  
  my $sock = IO::Socket::INET->new( LocalPort => '6343',
                                    Proto     => 'udp')
                               or die "Can't bind : $@\n";

  while ($sock->recv($packet,1548)) {
    &processPacket($packet);
  }
  die "Socket recv: $!";

  sub processPacket {

    my $sFlowPacket = shift;

    my ($sFlowDatagramRef, $sFlowSamplesRef, $errorsRef) = Net::sFlow::decode($sFlowPacket);

    # print errors
      foreach my $error (@{$errorsRef}) {
      warn "$error";
    }

    # print sflow data
    print "===Datagram===\n";
    print "sFlow version: $sFlowDatagramRef->{sFlowVersion}\n";
    print "datagram sequence number: $sFlowDatagramRef->{datagramSequenceNumber}\n";

    foreach my $sFlowSample (@{$printSamplesRef}) {
      print "\n";
      print "---Sample---\n";
      print "sample sequence number: $sFlowSample->{sampleSequenceNumber}\n";
    }

  }



=head1 DESCRIPTION

The sFlow module provides a mechanism to parse and decode sFlow
datagrams. It supports sFlow version 2/4 (RFC 3176 -
http://www.ietf.org/rfc/rfc3176.txt) and sFlow version 5 (Memo -
http://sflow.org/sflow_version_5.txt).

The module's functionality is provided by a single (exportable)
function, L<decode|/decode>().


=head1 FUNCTIONS

=head2 X<decode>decode( UDP_PAYLOAD )
 
($datagram, $samples, $error) = Net::sFlow::decode($udp_data);

Returns a HASH reference containing the datagram data, 
an ARRAY reference with the sample data (each array element contains a HASH reference for one sample)
and in case of an error a reference to an ARRAY containing the error messages.

=head3 Return Values

=over 4
=item I<$datagram>


A HASH reference containing information about the sFlow datagram, with
the following keys:

  sFlowVersion
  AgentIpVersion
  AgentIp
  datagramSequenceNumber
  agentUptime
  samplesInPacket

In the case of sFlow v5, there is an additional key:

  subAgentId


=item I<$samples>


Reference to a list of HASH references, each one representing one
sample. Depending on the type, the hash contains the following additional keys:


In case of sFlow <= 4:

  sampleType
  sampleSequenceNumber
  sourceIdType
  sourceIdIndex

If it's a sFlow <= 4 flowsample you will get the following additional keys:

  samplingRate
  samplePool
  drops
  inputInterface
  outputInterface
  packetDataType
  extendedDataInSample

If it's a sFlow <= 4 countersample you will get these additional keys:

  counterSamplingInterval
  countersVersion

In case of sFlow >= 5 you will first get enterprise, format and length information:

  sampleTypeEnterprise
  sampleTypeFormat
  sampleLength

In case of a flowsample (enterprise == 0 and format == 1):

  sampleSequenceNumber
  sourceIdType
  sourceIdIndex
  samplingRate
  samplePool
  drops
  inputInterface
  outputInterface
  flowRecordsCount

If it's an expanded flowsample (enterprise == 0 and format == 3)
you will get these additional keys instead of inputInterface and outputInterface:

  inputInterfaceFormat
  inputInterfaceValue
  outputInterfaceFormat
  outputInterfaceValue

In case of a countersample (enterprise == 0 and format == 2) or
an expanded countersample (enterprise == 0 and format == 4):

  sampleSequenceNumber
  sourceIdType
  sourceIdIndex
  counterRecordsCount
  counterDataLength

Depending on what kind of samples the hardware is taking
you will get the following additional keys:


Header data:

  HEADERDATA
  HeaderProtocol
  HeaderFrameLength 
  HeaderStrippedLength
  HeaderSizeByte
  HeaderSizeBit

  HeaderEtherSrcMac
  HeaderEtherDestMac
  HeaderVer
  HeaderDatalen


Ethernet frame data:

  ETHERNETFRAMEDATA
  EtherMacPacketlength
  EtherSrcMac
  EtherDestMac
  EtherPackettype

IPv4 data:

  IPv4DATA
  IPv4Packetlength
  IPv4NextHeaderProtocol
  IPv4srcIp
  IPv4destIp
  IPv4srcPort
  IPv4destPort
  IPv4tcpFlags
  IPv4tos

IPv6 data:

  IPv6DATA
  IPv6Packetlength
  IPv6NextHeaderProto
  IPv6srcIp
  IPv6destIp
  IPv6srcPort
  IPv6destPort
  IPv6tcpFlags
  IPv6Priority

Switch data:

  SWITCHDATA
  SwitchSrcVlan
  SwitchSrcPriority
  SwitchDestVlan
  SwitchDestPriority  

Router data:

  ROUTERDATA
  RouterIpVersionNextHopRouter
  RouterIpAddressNextHopRouter
  RouterSrcMask
  RouterDestMask

Gateway data:

  GATEWAYDATA
  GatewayIpVersionNextHopRouter (only in case of sFlow v5)
  GatewayIpAddressNextHopRouter (only in case of sFlow v5)
  GatewayAsRouter
  GatewayAsSource
  GatewayAsSourcePeer
  GatewayDestAsPathsCount

  GatewayDestAsPaths (arrayreference)
    each enty contains a hashreference:
      asPathSegmentType
      lengthAsList
      AsPath (arrayreference, asNumbers as entries)

  GatewayLengthCommunitiesList (added in sFlow v4)
  GatewayCommunities (arrayreference, added in sFlow v4)
    each enty contains a community (added in sFlow v4)

  localPref

User data:

  USERDATA
  UserSrcCharset (only in case of sFlow v5)
  UserLengthSrcString
  UserSrcString
  UserDestCharset (only in case of sFlow v5)
  UserLengthDestString
  UserDestString

Url data (added in sFlow v3):

  URLDATA
  UrlDirection
  UrlLength
  Url
  UrlHostLength (only in case of sFlow v5)
  UrlHost (only in case of sFlow v5)


The following keys can be only available in sFlow v5:

Mpls data:

  MPLSDATA
  MplsIpVersionNextHopRouter
  MplsIpAddressNextHopRouter
  MplsInLabelStackCount
  MplsInLabelStack (arrayreference containing MplsInLabels)
  MplsOutLabelStackCount
  MplsOutLabelStack (arrayreference containing MplsOutLabels)  

Nat data:

  NATDATA
  NatIpVersionSrcAddress
  NatSrcAddress
  NatIpVersionDestAddress
  NatDestAddress

Mpls tunnel:

  MPLSTUNNEL
  MplsTunnelLength
  MplsTunnelName
  MplsTunnelId
  MplsTunnelCosValue  

Mpls vc:

  MPLSVC
  MplsVcInstanceNameLength
  MplsVcInstanceName
  MplsVcId
  MplsVcLabelCosValue

Mpls fec:

  MPLSFEC
  MplsFtnDescrLength
  MplsFtnDescr
  MplsFtnMask

Mpls lpv fec:

  MPLSLPVFEC
  MplsFecAddrPrefixLength

Vlan tunnel:

  VLANTUNNEL
  VlanTunnelLayerStackCount
  VlanTunnelLayerStack (arrayreference containing VlanTunnelLayer entries)

The following keys are also available in sFlow < 5:

Counter generic:

  COUNTERGENERIC
  ifIndex
  ifType
  ifSpeed
  ifDirection
  ifAdminStatus
  ifOperStatus
  idInOctets
  ifInUcastPkts
  ifInMulticastPkts
  ifInBroadcastPkts
  idInDiscards
  ifInErrors
  ifInUnknownProtos
  ifOutOctets
  ifOutUcastPkts
  ifOutMulticastPkts
  ifOutBroadcastPkts
  ifOutDiscards
  ifOutErrors
  ifPromiscuousMode

Counter ethernet:

  COUNTERETHERNET
  dot3StatsAlignmentErrors
  dot3StatsFCSErrors
  dot3StatsSingleCollisionFrames
  dot3StatsMultipleCollisionFrames
  dot3StatsSQETestErrors
  dot3StatsDeferredTransmissions
  dot3StatsLateCollisions
  dot3StatsExcessiveCollisions
  dot3StatsInternalMacTransmitErrors
  dot3StatsCarrierSenseErrors
  dot3StatsFrameTooLongs
  dot3StatsInternalMacReceiveErrors
  dot3StatsSymbolErrors

Counter tokenring:

  COUNTERTOKENRING
  dot5StatsLineErrors
  dot5StatsBurstErrors
  dot5StatsACErrors
  dot5StatsAbortTransErrors
  dot5StatsInternalErrors
  dot5StatsLostFrameErrors
  dot5StatsReceiveCongestions
  dot5StatsFrameCopiedErrors
  dot5StatsTokenErrors
  dot5StatsSoftErrors
  dot5StatsHardErrors
  dot5StatsSignalLoss
  dot5StatsTransmitBeacons
  dot5StatsRecoverys
  dot5StatsLobeWires
  dot5StatsRemoves
  dot5StatsSingles
  dot5StatsFreqErrors

Counter vg:

  COUNTERVG
  dot12InHighPriorityFrames
  dot12InHighPriorityOctets
  dot12InNormPriorityFrames
  dot12InNormPriorityOctets
  dot12InIPMErrors
  dot12InOversizeFrameErrors
  dot12InDataErrors
  dot12InNullAddressedFrames
  dot12OutHighPriorityFrames
  dot12OutHighPriorityOctets
  dot12TransitionIntoTrainings
  dot12HCInHighPriorityOctets
  dot12HCInNormPriorityOctets
  dot12HCOutHighPriorityOctets

Counter vlan:

  COUNTERVLAN
  vlan_id
  octets
  ucastPkts
  multicastPkts
  broadcastPkts
  discards

Counter processor (only in sFlow v5):

  COUNTERPROCESSOR
  cpu5s
  cpu1m
  cpu5m
  memoryTotal
  memoryFree 


=item I<$error>

Reference to a list of error messages.

=back


=head1 CAVEATS

The L<decode|/decode> function will blindly attempt to decode the data
you provide. There are some tests for the appropriate values at various
places (where it is feasible to test - like enterprises,
formats, versionnumbers, etc.), but in general the GIGO principle still
stands: Garbage In / Garbage Out.


=head1 SEE ALSO

sFlow v4
http://www.ietf.org/rfc/rfc3176.txt

Format Diagram v4:
http://jasinska.de/sFlow/sFlowV4FormatDiagram/

sFlow v5
http://sflow.org/sflow_version_5.txt

Format Diagram v5:
http://jasinska.de/sFlow/sFlowV5FormatDiagram/

Math::BigInt
http://search.cpan.org/~tels/Math-BigInt-1.77/lib/Math/BigInt.pm


=head1 AUTHOR

Elisa Jasinska <elisa.jasinska@ams-ix.net>


=head1 CONTACT

Please send comments or bug reports to <sflow@ams-ix.net>


=head1 COPYRIGHT

Copyright (c) 2006 AMS-IX B.V.

This package is free software and is provided "as is" without express 
or implied warranty.  It may be used, redistributed and/or modified 
under the terms of the Perl Artistic License (see
http://www.perl.com/perl/misc/Artistic.html)

=cut

