Fix password length for utf-8 encoded chars (http://zargony.com/2009/07/...

require 'thread'
require 'forwardable'
require 'pp'
module Net
  module SNMP
    class Session
      # == SNMP Session
      #
      # Provides API for interacting with a host with snmp
      extend Forwardable
      include Net::SNMP::Debug
      attr_accessor :struct, :callback, :requests, :peername, :community
      attr_reader :version
      def_delegator :@struct, :pointer
      #@sessions = []
      @lock = Mutex.new
      @sessions = {}

      class << self
        attr_accessor :sessions, :lock

        # Open a new session.  Accepts a block which yields the session.
        #
        #   Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'public') do |sess|
        #     pdu = sess.get(["sysDescr.0"])
        #     pdu.print
        #   end
        # Options:
        # * +peername+ - hostname
        # * +community+ - snmp community string.  Default is public
        # * +version+ - snmp version.  Possible values include 1, '2c', and 3. Default is 1.
        # * +timeout+ - snmp timeout in seconds
        # * +retries+ - snmp retries.  default = 5
        # * +security_level+ - SNMPv3 only. default = Net::SNMP::Constants::SNMP_SEC_LEVEL_NOAUTH
        # * +auth_protocol+ - SNMPv3 only. default is nil (usmNoAuthProtocol). Possible values include :md5, :sha1, and nil
        # * +priv_protocol+ - SNMPv3 only. default is nil (usmNoPrivProtocol). Possible values include :des, :aes, and nil
        # * +context+ - SNMPv3 only.
        # * +username+ - SNMPv3 only.
        # * +auth_password+ - SNMPv3 only.
        # * +priv_password+ - SNMPv3 only.
        # Returns:
        # Net::SNMP::Session
        def open(options = {})
          session = new(options)
          if Net::SNMP::thread_safe
            Net::SNMP::Session.lock.synchronize {
              Net::SNMP::Session.sessions[session.sessid] = session
            }
          else
            Net::SNMP::Session.sessions[session.sessid] = session
          end
          if block_given?
            yield session
          end
          session
        end
      end

      def initialize(options = {})
        @timeout = options[:timeout] || 1
        @retries = options[:retries] || 5
        @requests = {}
        @peername = options[:peername] || 'localhost'
        @peername = "#{@peername}:#{options[:port]}" if options[:port]
        @community = options[:community] || "public"
        options[:community_len] = @community.length
        @version = options[:version] || 1
        options[:version] ||= Constants::SNMP_VERSION_1
        @version = options[:version] || 1
        #self.class.sessions << self
        @sess = Wrapper::SnmpSession.new(nil)
        Wrapper.snmp_sess_init(@sess.pointer)
        #options.each_pair {|k,v| ptr.send("#{k}=", v)}
        @sess.community = FFI::MemoryPointer.from_string(@community)
        @sess.community_len = @community.length
        @sess.peername = FFI::MemoryPointer.from_string(@peername)
        #@sess.remote_port = options[:port] || 162
        @sess.version = case options[:version].to_s
        when '1'
          Constants::SNMP_VERSION_1
        when '2', '2c'
          Constants::SNMP_VERSION_2c
        when '3'
          Constants::SNMP_VERSION_3
        else
          Constants::SNMP_VERSION_1
        end
        debug "setting timeout = #{@timeout} retries = #{@retries}"
        @sess.timeout = @timeout * 1000000
        @sess.retries = @retries

        if @sess.version == Constants::SNMP_VERSION_3
          @sess.securityLevel = options[:security_level] || Constants::SNMP_SEC_LEVEL_NOAUTH
          @sess.securityAuthProto = case options[:auth_protocol]
              when :sha1
                OID.new("1.3.6.1.6.3.10.1.1.3").pointer
              when :md5
                OID.new("1.3.6.1.6.3.10.1.1.2").pointer
              when nil
                OID.new("1.3.6.1.6.3.10.1.1.1").pointer
          end
          @sess.securityPrivProto = case options[:priv_protocol]
              when :aes
                OID.new("1.3.6.1.6.3.10.1.2.4").pointer
              when :des
                OID.new("1.3.6.1.6.3.10.1.2.2").pointer
              when nil
                OID.new("1.3.6.1.6.3.10.1.2.1").pointer
          end

          @sess.securityAuthProtoLen = 10
          @sess.securityAuthKeyLen = Constants::USM_AUTH_KU_LEN

          @sess.securityPrivProtoLen = 10
          @sess.securityPrivKeyLen = Constants::USM_PRIV_KU_LEN

          if options[:context]
            @sess.contextName = FFI::MemoryPointer.from_string(options[:context])
            @sess.contextNameLen = options[:context].length
          end

          # Do not generate_Ku, unless we're Auth or AuthPriv
          unless @sess.securityLevel == Constants::SNMP_SEC_LEVEL_NOAUTH
            options[:auth_password] ||= options[:password]  # backward compatability
            if options[:username].nil? or options[:auth_password].nil?
              raise Net::SNMP::Error.new "SecurityLevel requires username and password"
            end
            if options[:username]
              @sess.securityName = FFI::MemoryPointer.from_string(options[:username])
              @sess.securityNameLen = options[:username].length
            end

            auth_len_ptr = FFI::MemoryPointer.new(:size_t)
            auth_len_ptr.write_int(Constants::USM_AUTH_KU_LEN)
            auth_key_result = Wrapper.generate_Ku(@sess.securityAuthProto,
                                             @sess.securityAuthProtoLen,
                                             options[:auth_password],
                                             options[:auth_password].length,
                                             @sess.securityAuthKey,
                                             auth_len_ptr)
            @sess.securityAuthKeyLen = auth_len_ptr.read_int

            if @sess.securityLevel == Constants::SNMP_SEC_LEVEL_AUTHPRIV
              priv_len_ptr = FFI::MemoryPointer.new(:size_t)
              priv_len_ptr.write_int(Constants::USM_PRIV_KU_LEN)

              # NOTE I know this is handing off the AuthProto, but generates a proper
              # key for encryption, and using PrivProto does not.
              priv_key_result = Wrapper.generate_Ku(@sess.securityAuthProto,
                                               @sess.securityAuthProtoLen,
                                               options[:priv_password],
                                               options[:priv_password].length,
                                               @sess.securityPrivKey,
                                               priv_len_ptr)
              @sess.securityPrivKeyLen = priv_len_ptr.read_int
            end

            unless auth_key_result == Constants::SNMPERR_SUCCESS and priv_key_result == Constants::SNMPERR_SUCCESS
              Wrapper.snmp_perror("netsnmp")
            end
          end
        end
        # General callback just takes the pdu, calls the session callback if any, then the request specific callback.

        @struct = Wrapper.snmp_sess_open(@sess.pointer)
      end

      # Close the snmp session and free associated resources.
      def close
        if Net::SNMP.thread_safe
          self.class.lock.synchronize {
            Wrapper.snmp_sess_close(@struct)
            self.class.sessions.delete(self.sessid)
          }
        else
          Wrapper.snmp_sess_close(@struct)
          self.class.sessions.delete(self.sessid)
        end
      end

      # Issue an SNMP GET Request.
      # See #send_pdu
      def get(oidlist, options = {}, &block)
        pdu = PDU.new(Constants::SNMP_MSG_GET)
        oidlist = [oidlist] unless oidlist.kind_of?(Array)
        oidlist.each do |oid|
          pdu.add_varbind(:oid => oid)
        end
        send_pdu(pdu, options, &block)
      end

      # Issue an SNMP GETNEXT Request
      # See #send_pdu
      def get_next(oidlist, options = {}, &block)
        pdu = PDU.new(Constants::SNMP_MSG_GETNEXT)
        oidlist = [oidlist] unless oidlist.kind_of?(Array)
        oidlist.each do |oid|
          pdu.add_varbind(:oid => oid)
        end
        send_pdu(pdu, options, &block)
      end

      # Issue an SNMP GETBULK Request
      # See #send_pdu
      def get_bulk(oidlist, options = {}, &block)
        pdu = PDU.new(Constants::SNMP_MSG_GETBULK)
        oidlist = [oidlist] unless oidlist.kind_of?(Array)
        oidlist.each do |oid|
          pdu.add_varbind(:oid => oid)
        end
        pdu.non_repeaters = options[:non_repeaters] || 0
        pdu.max_repetitions = options[:max_repetitions] || 10
        send_pdu(pdu, options, &block)
      end

      # Issue an SNMP Set Request
      # See #send_pdu
      def set(oidlist, options = {}, &block)
        pdu = PDU.new(Constants::SNMP_MSG_SET)
        oidlist.each do |oid|
          pdu.add_varbind(:oid => oid[0], :type => oid[1], :value => oid[2])
        end
        send_pdu(pdu, options, &block)
      end

      # Proxy getters to the C struct representing the session
      def method_missing(m, *args)
        if @struct.respond_to?(m)
          @struct.send(m, *args)
        else
          super
        end
      end

      def default_max_repeaters
        # We could do something based on transport here.  25 seems safe
        25
      end

      # Raise a NET::SNMP::Error with the session attached
      def error(msg, options = {})
        #Wrapper.snmp_sess_perror(msg, @sess.pointer)
        err =  Error.new({:session => self}.merge(options))
        err.print
        raise err, msg
      end

      # Check the session for SNMP responses from asynchronous SNMP requests
      # This method will check for new responses and call the associated
      # response callbacks.
      # +timeout+  A timeout of nil indicates a poll and will return immediately.
      # A value of false will block until data is available.  Otherwise, pass
      # the number of seconds to block.
      # Returns the number of file descriptors handled.
      def select(timeout = nil)
          fdset = FFI::MemoryPointer.new(:pointer, Net::SNMP::Inline.fd_setsize / 8)
          num_fds = FFI::MemoryPointer.new(:int)
          tv_sec = timeout ? timeout.round : 0
          tv_usec = timeout ? (timeout - timeout.round) * 1000000 : 0
          tval = Wrapper::TimeVal.new(:tv_sec => tv_sec, :tv_usec => tv_usec)
          block = FFI::MemoryPointer.new(:int)
          if timeout.nil?
            block.write_int(0)
          else
            block.write_int(1)
          end

          Wrapper.snmp_sess_select_info(@struct, num_fds, fdset, tval.pointer, block )
          tv = (timeout == false ? nil : tval)
          #debug "Calling select #{Time.now}"
          num_ready = FFI::LibC.select(num_fds.read_int, fdset, nil, nil, tv)
          #debug "Done select #{Time.now}"
          if num_ready > 0
            Wrapper.snmp_sess_read(@struct, fdset)
          elsif num_ready == 0
            Wrapper.snmp_sess_timeout(@struct)
          elsif num_ready == -1
            # error.  check snmp_error?
            error("select")
          else
            error("wtf is wrong with select?")
          end
          num_ready
      end

      alias :poll :select

      # Issue repeated getnext requests on each oid passed in until
      # the result is no longer a child.  Returns a hash with the numeric
      # oid strings as keys.
      # XXX work in progress.   only works synchronously (except with EM + fibers).
      # Need to do better error checking and use getbulk when avaiable.
      def walk(oidlist, options = {})
        oidlist = [oidlist] unless oidlist.kind_of?(Array)
        oidlist = oidlist.map {|o| o.kind_of?(OID) ? o : OID.new(o)}
        all_results = {}
        base_list = oidlist
        while(!oidlist.empty? && pdu = get_next(oidlist, options))
          debug "==============================================================="
          debug "base_list = #{base_list}"
          prev_base = base_list.dup
          oidlist = []
          #print_errors
          #pdu.print_errors
          pdu.varbinds.each_with_index do |vb, i|
            if prev_base[i].parent_of?(vb.oid) && vb.object_type != Constants::SNMP_ENDOFMIBVIEW
              # Still in subtree.  Store results and add next oid to list
              debug "adding #{vb.oid} to oidlist"
              all_results[vb.oid.to_s] = vb.value
              oidlist << vb.oid
            else
              # End of subtree.  Don't add to list or results
              debug "End of subtree"
              base_list.delete_at(i)
              debug "not adding #{vb.oid}"
            end
            # If get a pdu error, we can only tell the first failing varbind,
            # So we remove it and resend all the rest
            if pdu.error? && pdu.errindex == i + 1
              oidlist.pop  # remove the bad oid
              debug "caught error"
              if pdu.varbinds.size > i+1
                # recram rest of oids on list
                ((i+1)..pdu.varbinds.size).each do |j|
                  debug "j = #{j}"
                  debug "adding #{j} = #{prev_list[j]}"
                  oidlist << prev_list[j]
                end
                # delete failing oid from base_list
                base_list.delete_at(i)
              end
              break
            end
          end
        end
        if block_given?
          yield all_results
        end
        all_results
      end

      # Given a list of columns (e.g ['ifIndex', 'ifDescr'], will return a hash with
      # the indexes as keys and hashes as values.
      #   puts sess.get_columns(['ifIndex', 'ifDescr']).inspect
      #   {'1' => {'ifIndex' => '1', 'ifDescr' => 'lo0'}, '2' => {'ifIndex' => '2', 'ifDescr' => 'en0'}}
      def columns(columns, options = {})
        columns = columns.map {|c| c.kind_of?(OID) ? c : OID.new(c)}
        walk_hash = walk(columns, options)
        results = {}
        walk_hash.each do |k, v|
          oid = OID.new(k)
          results[oid.index] ||= {}
          results[oid.index][oid.node.label] = v
        end
        if block_given?
          yield results
        end
        results
      end

      # table('ifEntry').  You must pass the direct parent entry.  Calls columns with all
      # columns in +table_name+
      def table(table_name, &blk)
        column_names = MIB::Node.get_node(table_name).children.collect {|c| c.oid }
        results = columns(column_names)
        if block_given?
          yield results
        end
        results
      end

      # Send a PDU
      # +pdu+  The Net::SNMP::PDU object to send.  Usually created by Session.get, Session.getnext, etc.
      # +callback+ An optional callback.  It should take two parameters, status and response_pdu.
      # If no +callback+ is given, the call will block until the response is available and will return
      # the response pdu.  If an error occurs, a Net::SNMP::Error will be thrown.
      # If +callback+ is passed, the PDU will be sent and +send_pdu+ will return immediately.  You must
      # then call Session.select to invoke the callback.  This is usually done in some sort of event loop.
      # See Net::SNMP::Dispatcher.
      #
      # If you're running inside eventmachine and have fibers (ruby 1.9, jruby, etc), sychronous calls will
      # actually run asynchronously behind the scenes.  Just run Net::SNMP::Dispatcher.fiber_loop in your
      # reactor.
      #
      #   pdu = Net::SNMP::PDU.new(Constants::SNMP_MSG_GET)
      #   pdu.add_varbind(:oid => 'sysDescr.0')
      #   session.send_pdu(pdu) do |status, pdu|
      #     if status == :success
      #       pdu.print
      #     elsif status == :timeout
      #       puts "Timed Out"
      #     else
      #       puts "A problem occurred"
      #     end
      #   end
      #   session.select(false)  #block until data is ready.  Callback will be called.
      #   begin
      #     result = session.send_pdu(pdu)
      #     puts result.inspect
      #   rescue Net::SNMP::Error => e
      #     puts e.message
      #   end
      def send_pdu(pdu, options = {},  &callback)
        if options[:blocking]
          return send_pdu_blocking(pdu)
        end
        if block_given?
          @requests[pdu.reqid] = callback
          debug "calling async_send"
          if Wrapper.snmp_sess_async_send(@struct, pdu.pointer, sess_callback, nil) == 0
            error("snmp_get async failed")
          end
          #pdu.free
          nil
        else
          if defined?(EM) && EM.reactor_running? && defined?(Fiber)
            f = Fiber.current
            send_pdu pdu do | op, response_pdu |
              #pdu.free
              f.resume([op, response_pdu])
            end
            op, result = Fiber.yield
            case op
              when :timeout
                raise TimeoutError.new, "timeout"
              when :send_failed
                error "send failed"
              when :success
                result
              when :connect, :disconnect
                nil   #does this ever happen?
              else
                error "unknown operation #{op}"
            end
          else
            send_pdu_blocking(pdu)
          end
        end
      end

      def send_pdu_blocking(pdu)
        response_ptr = FFI::MemoryPointer.new(:pointer)
        if [Constants::SNMP_MSG_TRAP, Constants::SNMP_MSG_TRAP2].include?(pdu.command)
          status = Wrapper.snmp_sess_send(@struct, pdu.pointer)
          if status == 0
            error("snmp_sess_send")
          end
        else
          status = Wrapper.snmp_sess_synch_response(@struct, pdu.pointer, response_ptr)
          unless status == Constants::STAT_SUCCESS
            error("snmp_sess_synch_response", :status => status)
          end
        end
        if [Constants::SNMP_MSG_TRAP, Constants::SNMP_MSG_TRAP2].include?(pdu.command)
          1
        else
          PDU.new(response_ptr.read_pointer)
        end
      end

      def errno
        get_error
        @errno
      end

      # The SNMP Session error code
      def snmp_err
        get_error
        @snmp_err
      end

      # The SNMP Session error message
      def error_message
        get_error
        @snmp_msg
      end

      def print_errors
        puts "errno: #{errno}, snmp_err: #{@snmp_err}, message: #{@snmp_msg}"
      end
      private
      def sess_callback
        @sess_callback ||= FFI::Function.new(:int, [:int, :pointer, :int, :pointer, :pointer]) do |operation, session, reqid, pdu_ptr, magic|
          debug "in callback #{operation.inspect} #{session.inspect}"
          op = case operation
            when Constants::NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE
              :success
            when Constants::NETSNMP_CALLBACK_OP_TIMED_OUT
              :timeout
            when Constants::NETSNMP_CALLBACK_OP_SEND_FAILED
              :send_failed
            when Constants::NETSNMP_CALLBACK_OP_CONNECT
              :connect
            when Constants::NETSNMP_CALLBACK_OP_DISCONNECT
              :disconnect
            else
              error "Invalid PDU Operation"
          end

          if @requests[reqid]
            pdu = PDU.new(pdu_ptr)
            callback_return = @requests[reqid].call(op, pdu)
            @requests.delete(reqid)
            callback_return == false ? 0 : 1 #if callback returns false (failure), pass it on.  otherwise return 1 (success)
          else
            0# Do what here?  Can this happen?  Maybe request timed out and was deleted?
          end
        end
      end
      def get_error
          errno_ptr = FFI::MemoryPointer.new(:int)
          snmp_err_ptr = FFI::MemoryPointer.new(:int)
          msg_ptr = FFI::MemoryPointer.new(:pointer)
          Wrapper.snmp_sess_error(@struct.pointer, errno_ptr, snmp_err_ptr, msg_ptr)
          @errno = errno_ptr.read_int
          @snmp_err = snmp_err_ptr.read_int
          @snmp_msg = msg_ptr.read_pointer.read_string
      end

    end
  end
end

module Net
module SNMP
module Wrapper
  extend NiceFFI::Library
  ffi_lib "libnetsnmp"
  #ffi_lib "netsnmp"
  typedef :u_long, :oid

  class Counter64 < NiceFFI::Struct
    layout(
           :high, :u_long,
           :low, :u_long
    )
  end

  class TimeVal < NiceFFI::Struct
    layout(:tv_sec, :long, :tv_usec, :long)
  end
  def self.print_timeval(tv)
    puts "tv_sec = #{tv.tv_sec}, tv_usec = #{tv.tv_usec} "
  end

  class NetsnmpVardata < FFI::Union
    layout(
           :integer, :pointer,
           :string, :pointer,
           :objid, :pointer,
           :bitstring, :pointer,
           :counter64, :pointer,
           :float, :pointer,
           :double, :pointer
    )
  end

  def self.print_varbind(v)
    puts "---------------------VARBIND------------------------"
    puts %{
      name_length #{v.name_length}
      name #{v.name.read_array_of_long(v.name_length).join(".")}
      type = #{v.type}

    }
  end
 # puts "Vardata size = #{NetsnmpVardata.size}"
  class VariableList < NiceFFI::Struct
    layout(
           :next_variable, :pointer,
           :name, :pointer,
           :name_length, :size_t,
           :type, :u_char,
           :val, NetsnmpVardata,
           :val_len, :size_t,
           :name_loc, [:oid, Net::SNMP::MAX_OID_LEN],
           :buf, [:u_char, 40],
           :data, :pointer,
           :dataFreeHook, callback([ :pointer ], :void),
           :index, :int
    )
  end

  #puts "VariableList size = #{VariableList.size}"

  def self.print_pdu(p)
    puts "--------------PDU---------------"
    puts %{
      version = #{p.version}
      command = #{p.command}
      errstat = #{p.errstat}
      errindex = #{p.errindex}
    }
    v = p.variables.pointer
    puts "-----VARIABLES------"
    while !v.null? do
      var = VariableList.new v
      print_varbind(var)
      v = var.next_variable
    end

  end
  class SnmpPdu < NiceFFI::Struct
    layout(
           :version, :long,
           :command, :int,
           :reqid, :long,
           :msgid, :long,
           :transid, :long,
           :sessid, :long,
           :errstat, :long,
           :errindex, :long,
           :time, :u_long,
           :flags, :u_long,
           :securityModel, :int,
           :securityLevel, :int,
           :msgParseModel, :int,
           :transport_data, :pointer,
           :transport_data_length, :int,
           :tDomain, :pointer,
           :tDomainLen, :size_t,
           :variables, VariableList.typed_pointer,
           :community, :pointer,
           :community_len, :size_t,
           :enterprise, :pointer,
           :enterprise_length, :size_t,
           :trap_type, :long,
           :specific_type, :long,
           :agent_addr, [:uchar, 4],
           :contextEngineID, :pointer,
           :contextEngineIDLen, :size_t,
           :contextName, :pointer,
           :contextNameLen, :size_t,
           :securityEngineID, :pointer,
           :securityEngineIDLen, :size_t,
           :securityName, :pointer,
           :securityNameLen, :size_t,
           :priority, :int,
           :range_subid, :int,
           :securityStateRef, :pointer
    )

  end
 # puts "snmppdu size = #{SnmpPdu.size}"
  callback(:snmp_callback, [ :int, :pointer, :int, :pointer, :pointer ], :int)
  callback(:netsnmp_callback, [ :int, :pointer, :int, :pointer, :pointer ], :int)
  def self.print_session(s)
    puts "-------------------SESSION---------------------"
    puts %{
      peername = #{s.peername.read_string}
      community = #{s.community.read_string(s.community_len)}
      s_errno = #{s.s_errno}
      s_snmp_errno = #{s.s_snmp_errno}
      securityAuthKey = #{s.securityAuthKey.to_ptr.read_string}

    }
  end
  class SnmpSession < NiceFFI::Struct
    layout(
           :version, :long,
           :retries, :int,
           :timeout, :long,
           :flags, :u_long,
           :subsession, :pointer,
           :next, :pointer,
           :peername, :pointer,
           :remote_port, :u_short,
           :localname, :pointer,
           :local_port, :u_short,
           :authenticator, callback([ :pointer, :pointer, :pointer, :uint ], :pointer),
           :callback, :netsnmp_callback,
           :callback_magic, :pointer,
           :s_errno, :int,
           :s_snmp_errno, :int,
           :sessid, :long,
           :community, :pointer,
           :community_len, :size_t,
           :rcvMsgMaxSize, :size_t,
           :sndMsgMaxSize, :size_t,
           :isAuthoritative, :u_char,
           :contextEngineID, :pointer,
           :contextEngineIDLen, :size_t,
           :engineBoots, :u_int,
           :engineTime, :u_int,
           :contextName, :pointer,
           :contextNameLen, :size_t,
           :securityEngineID, :pointer,
           :securityEngineIDLen, :size_t,
           :securityName, :pointer,
           :securityNameLen, :size_t,
           :securityAuthProto, :pointer,
           :securityAuthProtoLen, :size_t,
           :securityAuthKey, [:u_char, 32],
           :securityAuthKeyLen, :size_t,
           :securityAuthLocalKey, :pointer,
           :securityAuthLocalKeyLen, :size_t,
           :securityPrivProto, :pointer,
           :securityPrivProtoLen, :size_t,
           :securityPrivKey, [:u_char, 32],
           :securityPrivKeyLen, :size_t,
           :securityPrivLocalKey, :pointer,
           :securityPrivLocalKeyLen, :size_t,
           :securityModel, :int,
           :securityLevel, :int,
           :paramName, :pointer,
           :securityInfo, :pointer,
           :myvoid, :pointer
    )
  end
  class Tree < NiceFFI::Struct
    layout(
      :child_list, :pointer,
      :next_peer, :pointer,
      :next, :pointer,
      :parent, :pointer,
      :label, :string,
      :subid, :u_long,
      :modid, :int,
      :number_modules, :int,
      :module_list, :pointer,
      :tc_index, :int,
      :type, :int,
      :access, :int,
      :status, :int,
      :enums, :pointer,
      :ranges, :pointer,
      :indexes, :pointer,
      :augments, :pointer,
      :varbinds, :pointer,
      :hint, :pointer,
      :units, :pointer,
      :printomat, callback([:pointer, :pointer, :pointer, :int, :pointer, :pointer, :pointer, :pointer], :int),
      :printer, callback([:pointer, :pointer, :pointer, :pointer, :pointer], :void),
      :description, :pointer,
      :reference, :pointer,
      :reported, :int,
      :defaultValue, :pointer
    )
  end
  class IndexList < NiceFFI::Struct
    layout(
        :next, :pointer,
        :ilabel, :pointer,
        :isimplied, :char
    )
  end

#  puts "snmp_session size = #{SnmpSession.size}"
  attach_function :snmp_open, [ :pointer ], SnmpSession.typed_pointer
  attach_function :snmp_errstring, [:int], :string
  attach_function :snmp_close, [ :pointer ], :int
  attach_function :snmp_close_sessions, [  ], :int
  attach_function :snmp_send, [ :pointer, :pointer ], :int
  attach_function :snmp_async_send, [ :pointer, :pointer, :netsnmp_callback, :pointer ], :int
  attach_function :snmp_read, [ :pointer ], :void
  attach_function :snmp_free_pdu, [ :pointer ], :void
  attach_function :snmp_free_var, [ :pointer ], :void
  attach_function :snmp_free_varbind, [ :pointer ], :void
  attach_function :snmp_select_info, [ :pointer, :pointer, :pointer, :pointer ], :int
  attach_function :snmp_timeout, [  ], :void

  attach_function :snmp_get_next_msgid, [  ], :long
  attach_function :snmp_get_next_reqid, [  ], :long
  attach_function :snmp_get_next_sessid, [  ], :long
  attach_function :snmp_get_next_transid, [  ], :long
  attach_function :snmp_oid_compare, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :snmp_oid_ncompare, [ :pointer, :uint, :pointer, :uint, :uint ], :int
  attach_function :snmp_oidtree_compare, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :snmp_oidsubtree_compare, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :netsnmp_oid_compare_ll, [ :pointer, :uint, :pointer, :uint, :pointer ], :int
  attach_function :netsnmp_oid_equals, [ :pointer, :uint, :pointer, :uint ], :int
#  attach_function :netsnmp_oid_tree_equals, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :netsnmp_oid_is_subtree, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :netsnmp_oid_find_prefix, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :netsnmp_transport_open_client, [:string, :pointer], :pointer
  attach_function :init_snmp, [ :string ], :void
  attach_function :snmp_pdu_build, [ :pointer, :pointer, :pointer ], :pointer
  attach_function :snmpv3_parse, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :int
  attach_function :snmpv3_packet_build, [ :pointer, :pointer, :pointer, :pointer, :pointer, :uint ], :int
#  attach_function :snmpv3_packet_rbuild, [ :pointer, :pointer, :pointer, :pointer, :pointer, :uint ], :int
  attach_function :snmpv3_make_report, [ :pointer, :int ], :int
  attach_function :snmpv3_get_report_type, [ :pointer ], :int
  attach_function :snmp_pdu_parse, [ :pointer, :pointer, :pointer ], :int
  attach_function :snmpv3_scopedPDU_parse, [ :pointer, :pointer, :pointer ], :pointer
  attach_function :snmp_store, [ :string ], :void
  attach_function :snmp_shutdown, [ :string ], :void
  attach_function :snmp_pdu_add_variable, [ :pointer, :pointer, :uint, :u_char, :pointer, :size_t ], :pointer
  attach_function :snmp_varlist_add_variable, [ :pointer, :pointer, :uint, :u_char, :pointer, :uint ], :pointer
  attach_function :snmp_add_var, [ :pointer, :pointer, :uint, :char, :string ], :int
  attach_function :snmp_duplicate_objid, [ :pointer, :uint ], :pointer
  attach_function :snmp_increment_statistic, [ :int ], :u_int
  attach_function :snmp_increment_statistic_by, [ :int, :int ], :u_int
  attach_function :snmp_get_statistic, [ :int ], :u_int
  attach_function :snmp_init_statistics, [  ], :void
  attach_function :create_user_from_session, [ :pointer ], :int
 # attach_function :snmp_get_fd_for_session, [ :pointer ], :int
  attach_function :snmp_open_ex, [ :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :uint ], :int), callback([ :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :uint ], :int) ], :pointer
  attach_function :snmp_set_do_debugging, [ :int ], :void
  attach_function :snmp_get_do_debugging, [  ], :int
  attach_function :snmp_error, [ :pointer, :pointer, :pointer, :pointer ], :void
  attach_function :snmp_sess_init, [ :pointer ], :void
  attach_function :snmp_sess_open, [ :pointer ], SnmpSession.typed_pointer
  attach_function :snmp_sess_pointer, [ :pointer ], :pointer
  attach_function :snmp_sess_session, [ :pointer ], SnmpSession.typed_pointer
  attach_function :snmp_sess_transport, [ :pointer ], :pointer
  attach_function :snmp_sess_transport_set, [ :pointer, :pointer ], :void
  attach_function :snmp_sess_add_ex, [ :pointer, :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :uint ], :int), callback([ :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :uint ], :int), callback([ :pointer, :pointer, :uint ], :pointer) ], :pointer
  attach_function :snmp_sess_add, [ :pointer, :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :int ], :int) ], :pointer
  attach_function :snmp_add, [ :pointer, :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :int ], :int) ], :pointer
  attach_function :snmp_add_full, [ :pointer, :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :uint ], :int), callback([ :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :uint ], :int), callback([ :pointer, :pointer, :uint ], :pointer) ], :pointer
  attach_function :snmp_sess_send, [ :pointer, :pointer ], :int
  attach_function :snmp_sess_async_send, [ :pointer, :pointer, :snmp_callback, :pointer ], :int
  attach_function :snmp_sess_select_info, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :int
  attach_function :snmp_sess_read, [ :pointer, :pointer ], :int
  attach_function :snmp_sess_timeout, [ :pointer ], :void
  attach_function :snmp_sess_close, [ :pointer ], :int
  attach_function :snmp_sess_error, [ :pointer, :pointer, :pointer, :pointer ], :void
  attach_function :netsnmp_sess_log_error, [ :int, :string, :pointer ], :void
  attach_function :snmp_sess_perror, [ :string, :pointer ], :void
  attach_function :snmp_pdu_type, [ :int ], :string

  attach_function :asn_check_packet, [ :pointer, :uint ], :int
  attach_function :asn_parse_int, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_int, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_unsigned_int, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_unsigned_int, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_string, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_build_string, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_header, [ :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_parse_sequence, [ :pointer, :pointer, :pointer, :u_char, :string ], :pointer
  attach_function :asn_build_header, [ :pointer, :pointer, :u_char, :uint ], :pointer
  attach_function :asn_build_sequence, [ :pointer, :pointer, :u_char, :uint ], :pointer
  attach_function :asn_parse_length, [ :pointer, :pointer ], :pointer
  attach_function :asn_build_length, [ :pointer, :pointer, :uint ], :pointer
  attach_function :asn_parse_objid, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_build_objid, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_null, [ :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_build_null, [ :pointer, :pointer, :u_char ], :pointer
  attach_function :asn_parse_bitstring, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_build_bitstring, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_unsigned_int64, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_unsigned_int64, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_signed_int64, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_signed_int64, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_build_float, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_float, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_double, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_double, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer

  attach_function :snmp_pdu_create, [:int], SnmpPdu.typed_pointer
  attach_function :get_node,[:string, :pointer, :pointer], :int
  attach_function :read_objid, [:string, :pointer, :pointer], :int
  attach_function :snmp_add_null_var, [:pointer, :pointer, :size_t], :pointer
  attach_function :snmp_sess_synch_response, [:pointer, :pointer, :pointer], :int
  attach_function :snmp_synch_response, [:pointer, :pointer, :pointer], :int
  attach_function :snmp_parse_oid, [:string, :pointer, :pointer], :pointer
  attach_function :snmp_api_errstring, [ :int ], :string
  attach_function :snmp_perror, [ :string ], :void
  attach_function :snmp_set_detail, [ :string ], :void

  attach_function :snmp_select_info, [:pointer, :pointer, :pointer, :pointer], :int
  attach_function :snmp_read, [:pointer], :void
  attach_function :generate_Ku, [:pointer, :int, :string, :int, :pointer, :pointer], :int

  # MIB functions
  attach_function :netsnmp_init_mib, [], :void
  attach_function :read_all_mibs, [], :void
  attach_function :add_mibdir, [:string], :int
  attach_function :read_mib, [:string], Tree.typed_pointer
  attach_function :netsnmp_read_module, [:string], Tree.typed_pointer
  attach_function :snmp_set_save_descriptions, [:int], :void

  attach_function :get_tree_head, [], Tree.typed_pointer
  attach_function :get_tree, [:pointer, :int, :pointer], Tree.typed_pointer
  #attach_function :send_easy_trap, [:int, :int], :void
  #attach_function :send_trap_vars, [:int, :int, :pointer], :void
  #attach_function :send_v2trap, [:pointer], :void

  def self.get_fd_set
    FFI::MemoryPointer.new(:pointer, 128)
  end

end
end
end

#module RubyWrapper
#  extend FFI::Library
#  ffi_lib FFI::CURRENT_PROCESS
#  attach_function :rb_thread_select, [:int, :pointer, :pointer, :pointer, :pointer], :int
#end

module FFI
  module LibC
    extend FFI::Library
    ffi_lib [FFI::CURRENT_PROCESS, 'c']

    typedef :pointer, :FILE
    typedef :uint32, :in_addr_t
    typedef :uint16, :in_port_t

    class Timeval < FFI::Struct
      layout :tv_sec, :time_t,
             :tv_usec, :suseconds_t
    end

    # Standard IO functions
    #@blocking = true  # some undocumented voodoo that tells the next attach_function to release the GIL
    attach_function :select, [:int, :pointer, :pointer, :pointer, :pointer], :int
    attach_variable :errno, :int
    attach_function :malloc, [:size_t], :pointer
    attach_function :free, [:pointer], :void
  end
end

require 'thread'
require 'forwardable'
require 'pp'
module Net
  module SNMP
    class Session
      # == SNMP Session
      #
      # Provides API for interacting with a host with snmp
      extend Forwardable
      include Net::SNMP::Debug
      attr_accessor :struct, :callback, :requests, :peername, :community
      attr_reader :version
      def_delegator :@struct, :pointer
      #@sessions = []
      @lock = Mutex.new
      @sessions = {}

      class << self
        attr_accessor :sessions, :lock

        # Open a new session.  Accepts a block which yields the session.
        #
        #   Net::SNMP::Session.open(:peername => 'test.net-snmp.org', :community => 'public') do |sess|
        #     pdu = sess.get(["sysDescr.0"])
        #     pdu.print
        #   end
        # Options:
        # * +peername+ - hostname
        # * +community+ - snmp community string.  Default is public
        # * +version+ - snmp version.  Possible values include 1, '2c', and 3. Default is 1.
        # * +timeout+ - snmp timeout in seconds
        # * +retries+ - snmp retries.  default = 5
        # * +security_level+ - SNMPv3 only. default = Net::SNMP::Constants::SNMP_SEC_LEVEL_NOAUTH
        # * +auth_protocol+ - SNMPv3 only. default is nil (usmNoAuthProtocol). Possible values include :md5, :sha1, and nil
        # * +priv_protocol+ - SNMPv3 only. default is nil (usmNoPrivProtocol). Possible values include :des, :aes, and nil
        # * +context+ - SNMPv3 only.
        # * +username+ - SNMPv3 only.
        # * +auth_password+ - SNMPv3 only.
        # * +priv_password+ - SNMPv3 only.
        # Returns:
        # Net::SNMP::Session
        def open(options = {})
          session = new(options)
          if Net::SNMP::thread_safe
            Net::SNMP::Session.lock.synchronize {
              Net::SNMP::Session.sessions[session.sessid] = session
            }
          else
            Net::SNMP::Session.sessions[session.sessid] = session
          end
          if block_given?
            yield session
          end
          session
        end
      end

      def initialize(options = {})
        @timeout = options[:timeout] || 1
        @retries = options[:retries] || 5
        @requests = {}
        @peername = options[:peername] || 'localhost'
        @peername = "#{@peername}:#{options[:port]}" if options[:port]
        @community = options[:community] || "public"
        options[:community_len] = @community.length
        @version = options[:version] || 1
        options[:version] ||= Constants::SNMP_VERSION_1
        @version = options[:version] || 1
        #self.class.sessions << self
        @sess = Wrapper::SnmpSession.new(nil)
        Wrapper.snmp_sess_init(@sess.pointer)
        #options.each_pair {|k,v| ptr.send("#{k}=", v)}
        @sess.community = FFI::MemoryPointer.from_string(@community)
        @sess.community_len = @community.length
        @sess.peername = FFI::MemoryPointer.from_string(@peername)
        #@sess.remote_port = options[:port] || 162
        @sess.version = case options[:version].to_s
        when '1'
          Constants::SNMP_VERSION_1
        when '2', '2c'
          Constants::SNMP_VERSION_2c
        when '3'
          Constants::SNMP_VERSION_3
        else
          Constants::SNMP_VERSION_1
        end
        debug "setting timeout = #{@timeout} retries = #{@retries}"
        @sess.timeout = @timeout * 1000000
        @sess.retries = @retries

        if @sess.version == Constants::SNMP_VERSION_3
          @sess.securityLevel = options[:security_level] || Constants::SNMP_SEC_LEVEL_NOAUTH
          @sess.securityAuthProto = case options[:auth_protocol]
              when :sha1
                OID.new("1.3.6.1.6.3.10.1.1.3").pointer
              when :md5
                OID.new("1.3.6.1.6.3.10.1.1.2").pointer
              when nil
                OID.new("1.3.6.1.6.3.10.1.1.1").pointer
          end
          @sess.securityPrivProto = case options[:priv_protocol]
              when :aes
                OID.new("1.3.6.1.6.3.10.1.2.4").pointer
              when :des
                OID.new("1.3.6.1.6.3.10.1.2.2").pointer
              when nil
                OID.new("1.3.6.1.6.3.10.1.2.1").pointer
          end

          @sess.securityAuthProtoLen = 10
          @sess.securityAuthKeyLen = Constants::USM_AUTH_KU_LEN

          @sess.securityPrivProtoLen = 10
          @sess.securityPrivKeyLen = Constants::USM_PRIV_KU_LEN

          if options[:context]
            @sess.contextName = FFI::MemoryPointer.from_string(options[:context])
            @sess.contextNameLen = options[:context].length
          end

          # Do not generate_Ku, unless we're Auth or AuthPriv
          unless @sess.securityLevel == Constants::SNMP_SEC_LEVEL_NOAUTH
            options[:auth_password] ||= options[:password]  # backward compatability
            if options[:username].nil? or options[:auth_password].nil?
              raise Net::SNMP::Error.new "SecurityLevel requires username and password"
            end
            if options[:username]
              @sess.securityName = FFI::MemoryPointer.from_string(options[:username])
              @sess.securityNameLen = options[:username].length
            end

            auth_len_ptr = FFI::MemoryPointer.new(:size_t)
            auth_len_ptr.write_int(Constants::USM_AUTH_KU_LEN)
            auth_key_result = Wrapper.generate_Ku(@sess.securityAuthProto,
                                             @sess.securityAuthProtoLen,
                                             options[:auth_password],
                                             options[:auth_password].bytesize,
                                             @sess.securityAuthKey,
                                             auth_len_ptr)
            @sess.securityAuthKeyLen = auth_len_ptr.read_int

            if @sess.securityLevel == Constants::SNMP_SEC_LEVEL_AUTHPRIV
              priv_len_ptr = FFI::MemoryPointer.new(:size_t)
              priv_len_ptr.write_int(Constants::USM_PRIV_KU_LEN)

              # NOTE I know this is handing off the AuthProto, but generates a proper
              # key for encryption, and using PrivProto does not.
              priv_key_result = Wrapper.generate_Ku(@sess.securityAuthProto,
                                               @sess.securityAuthProtoLen,
                                               options[:priv_password],
                                               options[:priv_password].bytesize,
                                               @sess.securityPrivKey,
                                               priv_len_ptr)
              @sess.securityPrivKeyLen = priv_len_ptr.read_int
            end

            unless auth_key_result == Constants::SNMPERR_SUCCESS and priv_key_result == Constants::SNMPERR_SUCCESS
              Wrapper.snmp_perror("netsnmp")
            end
          end
        end
        # General callback just takes the pdu, calls the session callback if any, then the request specific callback.

        @struct = Wrapper.snmp_sess_open(@sess.pointer)
      end

      # Close the snmp session and free associated resources.
      def close
        if Net::SNMP.thread_safe
          self.class.lock.synchronize {
            Wrapper..shutdown_usm()
	    Wrapper.snmp_sess_close(@struct)
            self.class.sessions.delete(self.sessid)
          }
        else
          Wrapper.shutdown_usm()
          Wrapper.snmp_sess_close(@struct)
          self.class.sessions.delete(self.sessid)
        end
      end

      # Issue an SNMP GET Request.
      # See #send_pdu
      def get(oidlist, options = {}, &block)
        pdu = PDU.new(Constants::SNMP_MSG_GET)
        oidlist = [oidlist] unless oidlist.kind_of?(Array)
        oidlist.each do |oid|
          pdu.add_varbind(:oid => oid)
        end
        send_pdu(pdu, options, &block)
      end

      # Issue an SNMP GETNEXT Request
      # See #send_pdu
      def get_next(oidlist, options = {}, &block)
        pdu = PDU.new(Constants::SNMP_MSG_GETNEXT)
        oidlist = [oidlist] unless oidlist.kind_of?(Array)
        oidlist.each do |oid|
          pdu.add_varbind(:oid => oid)
        end
        send_pdu(pdu, options, &block)
      end

      # Issue an SNMP GETBULK Request
      # See #send_pdu
      def get_bulk(oidlist, options = {}, &block)
        pdu = PDU.new(Constants::SNMP_MSG_GETBULK)
        oidlist = [oidlist] unless oidlist.kind_of?(Array)
        oidlist.each do |oid|
          pdu.add_varbind(:oid => oid)
        end
        pdu.non_repeaters = options[:non_repeaters] || 0
        pdu.max_repetitions = options[:max_repetitions] || 10
        send_pdu(pdu, options, &block)
      end

      # Issue an SNMP Set Request
      # See #send_pdu
      def set(oidlist, options = {}, &block)
        pdu = PDU.new(Constants::SNMP_MSG_SET)
        oidlist.each do |oid|
          pdu.add_varbind(:oid => oid[0], :type => oid[1], :value => oid[2])
        end
        send_pdu(pdu, options, &block)
      end

      # Proxy getters to the C struct representing the session
      def method_missing(m, *args)
        if @struct.respond_to?(m)
          @struct.send(m, *args)
        else
          super
        end
      end

      def default_max_repeaters
        # We could do something based on transport here.  25 seems safe
        25
      end

      # Raise a NET::SNMP::Error with the session attached
      def error(msg, options = {})
        #Wrapper.snmp_sess_perror(msg, @sess.pointer)
        err =  Error.new({:session => self}.merge(options))
        err.print
        raise err, msg
      end

      # Check the session for SNMP responses from asynchronous SNMP requests
      # This method will check for new responses and call the associated
      # response callbacks.
      # +timeout+  A timeout of nil indicates a poll and will return immediately.
      # A value of false will block until data is available.  Otherwise, pass
      # the number of seconds to block.
      # Returns the number of file descriptors handled.
      def select(timeout = nil)
          fdset = FFI::MemoryPointer.new(:pointer, Net::SNMP::Inline.fd_setsize / 8)
          num_fds = FFI::MemoryPointer.new(:int)
          tv_sec = timeout ? timeout.round : 0
          tv_usec = timeout ? (timeout - timeout.round) * 1000000 : 0
          tval = Wrapper::TimeVal.new(:tv_sec => tv_sec, :tv_usec => tv_usec)
          block = FFI::MemoryPointer.new(:int)
          if timeout.nil?
            block.write_int(0)
          else
            block.write_int(1)
          end

          Wrapper.snmp_sess_select_info(@struct, num_fds, fdset, tval.pointer, block )
          tv = (timeout == false ? nil : tval)
          #debug "Calling select #{Time.now}"
          num_ready = FFI::LibC.select(num_fds.read_int, fdset, nil, nil, tv)
          #debug "Done select #{Time.now}"
          if num_ready > 0
            Wrapper.snmp_sess_read(@struct, fdset)
          elsif num_ready == 0
            Wrapper.snmp_sess_timeout(@struct)
          elsif num_ready == -1
            # error.  check snmp_error?
            error("select")
          else
            error("wtf is wrong with select?")
          end
          num_ready
      end

      alias :poll :select

      # Issue repeated getnext requests on each oid passed in until
      # the result is no longer a child.  Returns a hash with the numeric
      # oid strings as keys.
      # XXX work in progress.   only works synchronously (except with EM + fibers).
      # Need to do better error checking and use getbulk when avaiable.
      def walk(oidlist, options = {})
        oidlist = [oidlist] unless oidlist.kind_of?(Array)
        oidlist = oidlist.map {|o| o.kind_of?(OID) ? o : OID.new(o)}
        all_results = {}
        base_list = oidlist
        while(!oidlist.empty? && pdu = get_next(oidlist, options))
          debug "==============================================================="
          debug "base_list = #{base_list}"
          prev_base = base_list.dup
          oidlist = []
          #print_errors
          #pdu.print_errors
          pdu.varbinds.each_with_index do |vb, i|
            if prev_base[i].parent_of?(vb.oid) && vb.object_type != Constants::SNMP_ENDOFMIBVIEW
              # Still in subtree.  Store results and add next oid to list
              debug "adding #{vb.oid} to oidlist"
              all_results[vb.oid.to_s] = vb.value
              oidlist << vb.oid
            else
              # End of subtree.  Don't add to list or results
              debug "End of subtree"
              base_list.delete_at(i)
              debug "not adding #{vb.oid}"
            end
            # If get a pdu error, we can only tell the first failing varbind,
            # So we remove it and resend all the rest
            if pdu.error? && pdu.errindex == i + 1
              oidlist.pop  # remove the bad oid
              debug "caught error"
              if pdu.varbinds.size > i+1
                # recram rest of oids on list
                ((i+1)..pdu.varbinds.size).each do |j|
                  debug "j = #{j}"
                  debug "adding #{j} = #{prev_list[j]}"
                  oidlist << prev_list[j]
                end
                # delete failing oid from base_list
                base_list.delete_at(i)
              end
              break
            end
          end
        end
        if block_given?
          yield all_results
        end
        all_results
      end

      # Given a list of columns (e.g ['ifIndex', 'ifDescr'], will return a hash with
      # the indexes as keys and hashes as values.
      #   puts sess.get_columns(['ifIndex', 'ifDescr']).inspect
      #   {'1' => {'ifIndex' => '1', 'ifDescr' => 'lo0'}, '2' => {'ifIndex' => '2', 'ifDescr' => 'en0'}}
      def columns(columns, options = {})
        columns = columns.map {|c| c.kind_of?(OID) ? c : OID.new(c)}
        walk_hash = walk(columns, options)
        results = {}
        walk_hash.each do |k, v|
          oid = OID.new(k)
          results[oid.index] ||= {}
          results[oid.index][oid.node.label] = v
        end
        if block_given?
          yield results
        end
        results
      end

      # table('ifEntry').  You must pass the direct parent entry.  Calls columns with all
      # columns in +table_name+
      def table(table_name, &blk)
        column_names = MIB::Node.get_node(table_name).children.collect {|c| c.oid }
        results = columns(column_names)
        if block_given?
          yield results
        end
        results
      end

      # Send a PDU
      # +pdu+  The Net::SNMP::PDU object to send.  Usually created by Session.get, Session.getnext, etc.
      # +callback+ An optional callback.  It should take two parameters, status and response_pdu.
      # If no +callback+ is given, the call will block until the response is available and will return
      # the response pdu.  If an error occurs, a Net::SNMP::Error will be thrown.
      # If +callback+ is passed, the PDU will be sent and +send_pdu+ will return immediately.  You must
      # then call Session.select to invoke the callback.  This is usually done in some sort of event loop.
      # See Net::SNMP::Dispatcher.
      #
      # If you're running inside eventmachine and have fibers (ruby 1.9, jruby, etc), sychronous calls will
      # actually run asynchronously behind the scenes.  Just run Net::SNMP::Dispatcher.fiber_loop in your
      # reactor.
      #
      #   pdu = Net::SNMP::PDU.new(Constants::SNMP_MSG_GET)
      #   pdu.add_varbind(:oid => 'sysDescr.0')
      #   session.send_pdu(pdu) do |status, pdu|
      #     if status == :success
      #       pdu.print
      #     elsif status == :timeout
      #       puts "Timed Out"
      #     else
      #       puts "A problem occurred"
      #     end
      #   end
      #   session.select(false)  #block until data is ready.  Callback will be called.
      #   begin
      #     result = session.send_pdu(pdu)
      #     puts result.inspect
      #   rescue Net::SNMP::Error => e
      #     puts e.message
      #   end
      def send_pdu(pdu, options = {},  &callback)
        if options[:blocking]
          return send_pdu_blocking(pdu)
        end
        if block_given?
          @requests[pdu.reqid] = callback
          debug "calling async_send"
          if Wrapper.snmp_sess_async_send(@struct, pdu.pointer, sess_callback, nil) == 0
            error("snmp_get async failed")
          end
          #pdu.free
          nil
        else
          if defined?(EM) && EM.reactor_running? && defined?(Fiber)
            f = Fiber.current
            send_pdu pdu do | op, response_pdu |
              #pdu.free
              f.resume([op, response_pdu])
            end
            op, result = Fiber.yield
            case op
              when :timeout
                raise TimeoutError.new, "timeout"
              when :send_failed
                error "send failed"
              when :success
                result
              when :connect, :disconnect
                nil   #does this ever happen?
              else
                error "unknown operation #{op}"
            end
          else
            send_pdu_blocking(pdu)
          end
        end
      end

      def send_pdu_blocking(pdu)
        response_ptr = FFI::MemoryPointer.new(:pointer)
        if [Constants::SNMP_MSG_TRAP, Constants::SNMP_MSG_TRAP2].include?(pdu.command)
          status = Wrapper.snmp_sess_send(@struct, pdu.pointer)
          if status == 0
            error("snmp_sess_send")
          end
        else
          status = Wrapper.snmp_sess_synch_response(@struct, pdu.pointer, response_ptr)
          unless status == Constants::STAT_SUCCESS
            error("snmp_sess_synch_response", :status => status)
          end
        end
        if [Constants::SNMP_MSG_TRAP, Constants::SNMP_MSG_TRAP2].include?(pdu.command)
          1
        else
          PDU.new(response_ptr.read_pointer)
        end
      end

      def errno
        get_error
        @errno
      end

      # The SNMP Session error code
      def snmp_err
        get_error
        @snmp_err
      end

      # The SNMP Session error message
      def error_message
        get_error
        @snmp_msg
      end

      def print_errors
        puts "errno: #{errno}, snmp_err: #{@snmp_err}, message: #{@snmp_msg}"
      end
      private
      def sess_callback
        @sess_callback ||= FFI::Function.new(:int, [:int, :pointer, :int, :pointer, :pointer]) do |operation, session, reqid, pdu_ptr, magic|
          debug "in callback #{operation.inspect} #{session.inspect}"
          op = case operation
            when Constants::NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE
              :success
            when Constants::NETSNMP_CALLBACK_OP_TIMED_OUT
              :timeout
            when Constants::NETSNMP_CALLBACK_OP_SEND_FAILED
              :send_failed
            when Constants::NETSNMP_CALLBACK_OP_CONNECT
              :connect
            when Constants::NETSNMP_CALLBACK_OP_DISCONNECT
              :disconnect
            else
              error "Invalid PDU Operation"
          end

          if @requests[reqid]
            pdu = PDU.new(pdu_ptr)
            callback_return = @requests[reqid].call(op, pdu)
            @requests.delete(reqid)
            callback_return == false ? 0 : 1 #if callback returns false (failure), pass it on.  otherwise return 1 (success)
          else
            0# Do what here?  Can this happen?  Maybe request timed out and was deleted?
          end
        end
      end
      def get_error
          errno_ptr = FFI::MemoryPointer.new(:int)
          snmp_err_ptr = FFI::MemoryPointer.new(:int)
          msg_ptr = FFI::MemoryPointer.new(:pointer)
          Wrapper.snmp_sess_error(@struct.pointer, errno_ptr, snmp_err_ptr, msg_ptr)
          @errno = errno_ptr.read_int
          @snmp_err = snmp_err_ptr.read_int
          @snmp_msg = msg_ptr.read_pointer.read_string
      end

    end
  end
end

module Net
module SNMP
module Wrapper
  extend NiceFFI::Library
  ffi_lib "libnetsnmp"
  #ffi_lib "netsnmp"
  typedef :u_long, :oid

  class Counter64 < NiceFFI::Struct
    layout(
           :high, :u_long,
           :low, :u_long
    )
  end

  class TimeVal < NiceFFI::Struct
    layout(:tv_sec, :long, :tv_usec, :long)
  end
  def self.print_timeval(tv)
    puts "tv_sec = #{tv.tv_sec}, tv_usec = #{tv.tv_usec} "
  end

  class NetsnmpVardata < FFI::Union
    layout(
           :integer, :pointer,
           :string, :pointer,
           :objid, :pointer,
           :bitstring, :pointer,
           :counter64, :pointer,
           :float, :pointer,
           :double, :pointer
    )
  end

  def self.print_varbind(v)
    puts "---------------------VARBIND------------------------"
    puts %{
      name_length #{v.name_length}
      name #{v.name.read_array_of_long(v.name_length).join(".")}
      type = #{v.type}

    }
  end
 # puts "Vardata size = #{NetsnmpVardata.size}"
  class VariableList < NiceFFI::Struct
    layout(
           :next_variable, :pointer,
           :name, :pointer,
           :name_length, :size_t,
           :type, :u_char,
           :val, NetsnmpVardata,
           :val_len, :size_t,
           :name_loc, [:oid, Net::SNMP::MAX_OID_LEN],
           :buf, [:u_char, 40],
           :data, :pointer,
           :dataFreeHook, callback([ :pointer ], :void),
           :index, :int
    )
  end

  #puts "VariableList size = #{VariableList.size}"

  def self.print_pdu(p)
    puts "--------------PDU---------------"
    puts %{
      version = #{p.version}
      command = #{p.command}
      errstat = #{p.errstat}
      errindex = #{p.errindex}
    }
    v = p.variables.pointer
    puts "-----VARIABLES------"
    while !v.null? do
      var = VariableList.new v
      print_varbind(var)
      v = var.next_variable
    end

  end
  class SnmpPdu < NiceFFI::Struct
    layout(
           :version, :long,
           :command, :int,
           :reqid, :long,
           :msgid, :long,
           :transid, :long,
           :sessid, :long,
           :errstat, :long,
           :errindex, :long,
           :time, :u_long,
           :flags, :u_long,
           :securityModel, :int,
           :securityLevel, :int,
           :msgParseModel, :int,
           :transport_data, :pointer,
           :transport_data_length, :int,
           :tDomain, :pointer,
           :tDomainLen, :size_t,
           :variables, VariableList.typed_pointer,
           :community, :pointer,
           :community_len, :size_t,
           :enterprise, :pointer,
           :enterprise_length, :size_t,
           :trap_type, :long,
           :specific_type, :long,
           :agent_addr, [:uchar, 4],
           :contextEngineID, :pointer,
           :contextEngineIDLen, :size_t,
           :contextName, :pointer,
           :contextNameLen, :size_t,
           :securityEngineID, :pointer,
           :securityEngineIDLen, :size_t,
           :securityName, :pointer,
           :securityNameLen, :size_t,
           :priority, :int,
           :range_subid, :int,
           :securityStateRef, :pointer
    )

  end
 # puts "snmppdu size = #{SnmpPdu.size}"
  callback(:snmp_callback, [ :int, :pointer, :int, :pointer, :pointer ], :int)
  callback(:netsnmp_callback, [ :int, :pointer, :int, :pointer, :pointer ], :int)
  def self.print_session(s)
    puts "-------------------SESSION---------------------"
    puts %{
      peername = #{s.peername.read_string}
      community = #{s.community.read_string(s.community_len)}
      s_errno = #{s.s_errno}
      s_snmp_errno = #{s.s_snmp_errno}
      securityAuthKey = #{s.securityAuthKey.to_ptr.read_string}

    }
  end
  class SnmpSession < NiceFFI::Struct
    layout(
           :version, :long,
           :retries, :int,
           :timeout, :long,
           :flags, :u_long,
           :subsession, :pointer,
           :next, :pointer,
           :peername, :pointer,
           :remote_port, :u_short,
           :localname, :pointer,
           :local_port, :u_short,
           :authenticator, callback([ :pointer, :pointer, :pointer, :uint ], :pointer),
           :callback, :netsnmp_callback,
           :callback_magic, :pointer,
           :s_errno, :int,
           :s_snmp_errno, :int,
           :sessid, :long,
           :community, :pointer,
           :community_len, :size_t,
           :rcvMsgMaxSize, :size_t,
           :sndMsgMaxSize, :size_t,
           :isAuthoritative, :u_char,
           :contextEngineID, :pointer,
           :contextEngineIDLen, :size_t,
           :engineBoots, :u_int,
           :engineTime, :u_int,
           :contextName, :pointer,
           :contextNameLen, :size_t,
           :securityEngineID, :pointer,
           :securityEngineIDLen, :size_t,
           :securityName, :pointer,
           :securityNameLen, :size_t,
           :securityAuthProto, :pointer,
           :securityAuthProtoLen, :size_t,
           :securityAuthKey, [:u_char, 32],
           :securityAuthKeyLen, :size_t,
           :securityAuthLocalKey, :pointer,
           :securityAuthLocalKeyLen, :size_t,
           :securityPrivProto, :pointer,
           :securityPrivProtoLen, :size_t,
           :securityPrivKey, [:u_char, 32],
           :securityPrivKeyLen, :size_t,
           :securityPrivLocalKey, :pointer,
           :securityPrivLocalKeyLen, :size_t,
           :securityModel, :int,
           :securityLevel, :int,
           :paramName, :pointer,
           :securityInfo, :pointer,
           :myvoid, :pointer
    )
  end
  class Tree < NiceFFI::Struct
    layout(
      :child_list, :pointer,
      :next_peer, :pointer,
      :next, :pointer,
      :parent, :pointer,
      :label, :string,
      :subid, :u_long,
      :modid, :int,
      :number_modules, :int,
      :module_list, :pointer,
      :tc_index, :int,
      :type, :int,
      :access, :int,
      :status, :int,
      :enums, :pointer,
      :ranges, :pointer,
      :indexes, :pointer,
      :augments, :pointer,
      :varbinds, :pointer,
      :hint, :pointer,
      :units, :pointer,
      :printomat, callback([:pointer, :pointer, :pointer, :int, :pointer, :pointer, :pointer, :pointer], :int),
      :printer, callback([:pointer, :pointer, :pointer, :pointer, :pointer], :void),
      :description, :pointer,
      :reference, :pointer,
      :reported, :int,
      :defaultValue, :pointer
    )
  end
  class IndexList < NiceFFI::Struct
    layout(
        :next, :pointer,
        :ilabel, :pointer,
        :isimplied, :char
    )
  end

#  puts "snmp_session size = #{SnmpSession.size}"
  attach_function :snmp_open, [ :pointer ], SnmpSession.typed_pointer
  attach_function :snmp_errstring, [:int], :string
  attach_function :snmp_close, [ :pointer ], :int
  attach_function :snmp_close_sessions, [  ], :int
  attach_function :snmp_send, [ :pointer, :pointer ], :int
  attach_function :snmp_async_send, [ :pointer, :pointer, :netsnmp_callback, :pointer ], :int
  attach_function :snmp_read, [ :pointer ], :void
  attach_function :snmp_free_pdu, [ :pointer ], :void
  attach_function :snmp_free_var, [ :pointer ], :void
  attach_function :snmp_free_varbind, [ :pointer ], :void
  attach_function :snmp_select_info, [ :pointer, :pointer, :pointer, :pointer ], :int
  attach_function :snmp_timeout, [  ], :void

  attach_function :snmp_get_next_msgid, [  ], :long
  attach_function :snmp_get_next_reqid, [  ], :long
  attach_function :snmp_get_next_sessid, [  ], :long
  attach_function :snmp_get_next_transid, [  ], :long
  attach_function :snmp_oid_compare, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :snmp_oid_ncompare, [ :pointer, :uint, :pointer, :uint, :uint ], :int
  attach_function :snmp_oidtree_compare, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :snmp_oidsubtree_compare, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :netsnmp_oid_compare_ll, [ :pointer, :uint, :pointer, :uint, :pointer ], :int
  attach_function :netsnmp_oid_equals, [ :pointer, :uint, :pointer, :uint ], :int
#  attach_function :netsnmp_oid_tree_equals, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :netsnmp_oid_is_subtree, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :netsnmp_oid_find_prefix, [ :pointer, :uint, :pointer, :uint ], :int
  attach_function :netsnmp_transport_open_client, [:string, :pointer], :pointer
  attach_function :init_snmp, [ :string ], :void
  attach_function :snmp_pdu_build, [ :pointer, :pointer, :pointer ], :pointer
  attach_function :snmpv3_parse, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :int
  attach_function :snmpv3_packet_build, [ :pointer, :pointer, :pointer, :pointer, :pointer, :uint ], :int
#  attach_function :snmpv3_packet_rbuild, [ :pointer, :pointer, :pointer, :pointer, :pointer, :uint ], :int
  attach_function :snmpv3_make_report, [ :pointer, :int ], :int
  attach_function :snmpv3_get_report_type, [ :pointer ], :int
  attach_function :snmp_pdu_parse, [ :pointer, :pointer, :pointer ], :int
  attach_function :snmpv3_scopedPDU_parse, [ :pointer, :pointer, :pointer ], :pointer
  attach_function :snmp_store, [ :string ], :void
  attach_function :snmp_shutdown, [ :string ], :void
  attach_function :snmp_pdu_add_variable, [ :pointer, :pointer, :uint, :u_char, :pointer, :size_t ], :pointer
  attach_function :snmp_varlist_add_variable, [ :pointer, :pointer, :uint, :u_char, :pointer, :uint ], :pointer
  attach_function :snmp_add_var, [ :pointer, :pointer, :uint, :char, :string ], :int
  attach_function :snmp_duplicate_objid, [ :pointer, :uint ], :pointer
  attach_function :snmp_increment_statistic, [ :int ], :u_int
  attach_function :snmp_increment_statistic_by, [ :int, :int ], :u_int
  attach_function :snmp_get_statistic, [ :int ], :u_int
  attach_function :snmp_init_statistics, [  ], :void
  attach_function :create_user_from_session, [ :pointer ], :int
 # attach_function :snmp_get_fd_for_session, [ :pointer ], :int
  attach_function :snmp_open_ex, [ :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :uint ], :int), callback([ :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :uint ], :int) ], :pointer
  attach_function :snmp_set_do_debugging, [ :int ], :void
  attach_function :snmp_get_do_debugging, [  ], :int
  attach_function :snmp_error, [ :pointer, :pointer, :pointer, :pointer ], :void
  attach_function :snmp_sess_init, [ :pointer ], :void
  attach_function :snmp_sess_open, [ :pointer ], SnmpSession.typed_pointer
  attach_function :snmp_sess_pointer, [ :pointer ], :pointer
  attach_function :snmp_sess_session, [ :pointer ], SnmpSession.typed_pointer
  attach_function :snmp_sess_transport, [ :pointer ], :pointer
  attach_function :snmp_sess_transport_set, [ :pointer, :pointer ], :void
  attach_function :snmp_sess_add_ex, [ :pointer, :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :uint ], :int), callback([ :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :uint ], :int), callback([ :pointer, :pointer, :uint ], :pointer) ], :pointer
  attach_function :snmp_sess_add, [ :pointer, :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :int ], :int) ], :pointer
  attach_function :snmp_add, [ :pointer, :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :int ], :int) ], :pointer
  attach_function :snmp_add_full, [ :pointer, :pointer, callback([ :pointer, :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :uint ], :int), callback([ :pointer, :pointer, :int ], :int), callback([ :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :pointer, :pointer, :pointer, :pointer ], :int), callback([ :pointer, :uint ], :int), callback([ :pointer, :pointer, :uint ], :pointer) ], :pointer
  attach_function :snmp_sess_send, [ :pointer, :pointer ], :int
  attach_function :snmp_sess_async_send, [ :pointer, :pointer, :snmp_callback, :pointer ], :int
  attach_function :snmp_sess_select_info, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :int
  attach_function :snmp_sess_read, [ :pointer, :pointer ], :int
  attach_function :snmp_sess_timeout, [ :pointer ], :void
  attach_function :snmp_sess_close, [ :pointer ], :int
  attach_function :snmp_sess_error, [ :pointer, :pointer, :pointer, :pointer ], :void
  attach_function :netsnmp_sess_log_error, [ :int, :string, :pointer ], :void
  attach_function :snmp_sess_perror, [ :string, :pointer ], :void
  attach_function :snmp_pdu_type, [ :int ], :string

  attach_function :asn_check_packet, [ :pointer, :uint ], :int
  attach_function :asn_parse_int, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_int, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_unsigned_int, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_unsigned_int, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_string, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_build_string, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_header, [ :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_parse_sequence, [ :pointer, :pointer, :pointer, :u_char, :string ], :pointer
  attach_function :asn_build_header, [ :pointer, :pointer, :u_char, :uint ], :pointer
  attach_function :asn_build_sequence, [ :pointer, :pointer, :u_char, :uint ], :pointer
  attach_function :asn_parse_length, [ :pointer, :pointer ], :pointer
  attach_function :asn_build_length, [ :pointer, :pointer, :uint ], :pointer
  attach_function :asn_parse_objid, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_build_objid, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_null, [ :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_build_null, [ :pointer, :pointer, :u_char ], :pointer
  attach_function :asn_parse_bitstring, [ :pointer, :pointer, :pointer, :pointer, :pointer ], :pointer
  attach_function :asn_build_bitstring, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_unsigned_int64, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_unsigned_int64, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_signed_int64, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_signed_int64, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_build_float, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_float, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer
  attach_function :asn_build_double, [ :pointer, :pointer, :u_char, :pointer, :uint ], :pointer
  attach_function :asn_parse_double, [ :pointer, :pointer, :pointer, :pointer, :uint ], :pointer

  attach_function :snmp_pdu_create, [:int], SnmpPdu.typed_pointer
  attach_function :get_node,[:string, :pointer, :pointer], :int
  attach_function :read_objid, [:string, :pointer, :pointer], :int
  attach_function :snmp_add_null_var, [:pointer, :pointer, :size_t], :pointer
  attach_function :snmp_sess_synch_response, [:pointer, :pointer, :pointer], :int
  attach_function :snmp_synch_response, [:pointer, :pointer, :pointer], :int
  attach_function :snmp_parse_oid, [:string, :pointer, :pointer], :pointer
  attach_function :snmp_api_errstring, [ :int ], :string
  attach_function :snmp_perror, [ :string ], :void
  attach_function :snmp_set_detail, [ :string ], :void

  attach_function :snmp_select_info, [:pointer, :pointer, :pointer, :pointer], :int
  attach_function :snmp_read, [:pointer], :void
  attach_function :generate_Ku, [:pointer, :int, :string, :int, :pointer, :pointer], :int

  # MIB functions
  attach_function :netsnmp_init_mib, [], :void
  attach_function :read_all_mibs, [], :void
  attach_function :add_mibdir, [:string], :int
  attach_function :read_mib, [:string], Tree.typed_pointer
  attach_function :netsnmp_read_module, [:string], Tree.typed_pointer
  attach_function :snmp_set_save_descriptions, [:int], :void

  attach_function :get_tree_head, [], Tree.typed_pointer
  attach_function :get_tree, [:pointer, :int, :pointer], Tree.typed_pointer
  #attach_function :send_easy_trap, [:int, :int], :void
  #attach_function :send_trap_vars, [:int, :int, :pointer], :void
  #attach_function :send_v2trap, [:pointer], :void

  attach_function :shutdown_usm, [], :void

  def self.get_fd_set
    FFI::MemoryPointer.new(:pointer, 128)
  end

end
end
end

#module RubyWrapper
#  extend FFI::Library
#  ffi_lib FFI::CURRENT_PROCESS
#  attach_function :rb_thread_select, [:int, :pointer, :pointer, :pointer, :pointer], :int
#end

module FFI
  module LibC
    extend FFI::Library
    ffi_lib [FFI::CURRENT_PROCESS, 'c']

    typedef :pointer, :FILE
    typedef :uint32, :in_addr_t
    typedef :uint16, :in_port_t

    class Timeval < FFI::Struct
      layout :tv_sec, :time_t,
             :tv_usec, :suseconds_t
    end

    # Standard IO functions
    #@blocking = true  # some undocumented voodoo that tells the next attach_function to release the GIL
    attach_function :select, [:int, :pointer, :pointer, :pointer, :pointer], :int
    attach_variable :errno, :int
    attach_function :malloc, [:size_t], :pointer
    attach_function :free, [:pointer], :void
  end
end


add adb shell command to pull wildcard files, watch the '()' containing filenames

#!/bin/sh -e

test -z "$1" && echo "usage: $0 /sdcard/DCIM/Camera/*20121111*" && exit 1

ls $1 | tr -d '\r' | xargs -i sh -c 'echo {} ; adb pull {} .'

#!/bin/sh -e

test -z "$1" && echo "usage: $0 /sdcard/DCIM/Camera/*20121111*" && exit 1

adb shell ls "$1" | tr -d '\r' | xargs -i sh -c 'echo "{}" ; adb pull "{}" .'


Update plugins

# Alias Generator for Posts.
#
# Generates redirect pages for posts with aliases set in the YAML Front Matter.
#
# Place the full path of the alias (place to redirect from) inside the
# destination post's YAML Front Matter. One or more aliases may be given.
#
# Example Post Configuration:
#
#   ---
#     layout: post
#     title: "How I Keep Limited Pressing Running"
#     alias: /post/6301645915/how-i-keep-limited-pressing-running/index.html
#   ---
#
# Example Post Configuration:
#
#   ---
#     layout: post
#     title: "How I Keep Limited Pressing Running"
#     alias: [/first-alias/index.html, /second-alias/index.html]
#   ---
#
# Author: Thomas Mango
# Site: http://thomasmango.com
# Plugin Source: http://github.com/tsmango/jekyll_alias_generator
# Site Source: http://github.com/tsmango/thomasmango.com
# PLugin License: MIT

module Jekyll

  class AliasGenerator < Generator

    def generate(site)
      @site = site

      process_posts
      process_pages
    end

    def process_posts
      @site.posts.each do |post|
        generate_aliases(post.url, post.data['alias'])
      end
    end

    def process_pages
      @site.pages.each do |page|
        generate_aliases(page.destination('').gsub(/index\.(html|htm)$/, ''), page.data['alias'])
      end
    end

    def generate_aliases(destination_path, aliases)
      alias_paths ||= Array.new
      alias_paths << aliases
      alias_paths.compact!

      alias_paths.flatten.each do |alias_path|
        alias_path = alias_path.to_s

        alias_dir  = File.extname(alias_path).empty? ? alias_path : File.dirname(alias_path)
        alias_file = File.extname(alias_path).empty? ? "index.html" : File.basename(alias_path)

        fs_path_to_dir   = File.join(@site.dest, alias_dir)
        alias_index_path = File.join(alias_dir, alias_file)

        FileUtils.mkdir_p(fs_path_to_dir)

        File.open(File.join(fs_path_to_dir, alias_file), 'w') do |file|
          file.write(alias_template(destination_path))
        end

        (alias_index_path.split('/').size + 1).times do |sections|
          @site.static_files << Jekyll::AliasFile.new(@site, @site.dest, alias_index_path.split('/')[0, sections].join('/'), '')
        end
      end
    end

    def alias_template(destination_path)
      <<-EOF
      <!DOCTYPE html>
      <html>
      <head>
      <link rel="canonical" href="#{destination_path}"/>
      <meta http-equiv="content-type" content="text/html; charset=utf-8" />
      <meta http-equiv="refresh" content="0;url=#{destination_path}" />
      </head>
      </html>
      EOF
    end
  end

  class AliasFile < StaticFile
    require 'set'

    def destination(dest)
      File.join(dest, @dir)
    end

    def modified?
      return false
    end

    def write(dest)
      return true
    end
  end
end

require "jekyll-assets"
require "jekyll-assets/compass"

# Alias Generator for Posts.
#
# Generates redirect pages for posts with aliases set in the YAML Front Matter.
#
# Place the full path of the alias (place to redirect from) inside the
# destination post's YAML Front Matter. One or more aliases may be given.
#
# Example Post Configuration:
#
#   ---
#     layout: post
#     title: "How I Keep Limited Pressing Running"
#     alias: /post/6301645915/how-i-keep-limited-pressing-running/index.html
#   ---
#
# Example Post Configuration:
#
#   ---
#     layout: post
#     title: "How I Keep Limited Pressing Running"
#     alias: [/first-alias/index.html, /second-alias/index.html]
#   ---
#
# Author: Thomas Mango
# Site: http://thomasmango.com
# Plugin Source: http://github.com/tsmango/jekyll_alias_generator
# Site Source: http://github.com/tsmango/tsmango.github.com
# Plugin License: MIT

module Jekyll

  class AliasGenerator < Generator

    def generate(site)
      @site = site

      process_posts
      process_pages
    end

    def process_posts
      @site.posts.docs.each do |post|
        generate_aliases(post.url, post.data['alias'])
      end
    end

    def process_pages
      @site.pages.each do |page|
        generate_aliases(page.destination('').gsub(/index\.(html|htm)$/, ''), page.data['alias'])
      end
    end

    def generate_aliases(destination_path, aliases)
      alias_paths ||= Array.new
      alias_paths << aliases
      alias_paths.compact!

      alias_paths.flatten.each do |alias_path|
        alias_path = File.join('/', alias_path.to_s)

        alias_dir  = File.extname(alias_path).empty? ? alias_path : File.dirname(alias_path)
        alias_file = File.extname(alias_path).empty? ? "index.html" : File.basename(alias_path)

        fs_path_to_dir = File.join(@site.dest, alias_dir)
        alias_sections = alias_dir.split('/')[1..-1]

        FileUtils.mkdir_p(fs_path_to_dir)

        File.open(File.join(fs_path_to_dir, alias_file), 'w') do |file|
          file.write(alias_template(destination_path))
        end

        alias_sections.size.times do |sections|
          @site.static_files << Jekyll::AliasFile.new(@site, @site.dest, alias_sections[0, sections + 1].join('/'), '')
        end
        @site.static_files << Jekyll::AliasFile.new(@site, @site.dest, alias_dir, alias_file)
      end
    end

    def alias_template(destination_path)
      <<-EOF
      <!DOCTYPE html>
      <html>
      <head>
      <link rel="canonical" href="#{destination_path}"/>
      <meta http-equiv="content-type" content="text/html; charset=utf-8" />
      <meta http-equiv="refresh" content="0;url=#{destination_path}" />
      </head>
      </html>
      EOF
    end
  end

  class AliasFile < StaticFile
    require 'set'

    def modified?
      return false
    end

    def write(dest)
      return true
    end
  end
end

require "jekyll-assets"
require "compass"


Update bright.c

/*
 * Copyright (c) 2010, David Kelso <david at kelso dot id dot au>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>

// How far the brightness should move with each step.
#define JUMP 0xa

int main(int argc, char *argv[]) {
  unsigned int value, location[3];
  int success;
  char command[100];
  FILE *fp;

  if (geteuid()) {
    printf("Must be root\n");
    return 1;
  }

  if (argc != 2) {
    printf("One argument required: ++ or --\n");
    return 1;
  }

  // Get the PCI location of the VGA adapter
  fp = popen("lspci | grep VGA", "r");
  success = fscanf(fp, "%20d:%2d.%1d VGA", &location[0], &location[1], &location[2]);
  pclose(fp);

  if (!success) {
    printf("lspci couldn't find your vga adapter\n");
    return 1;
  }

  // Get the existing value of the brightness
  snprintf(command, sizeof command, "setpci -s %02d:%02d.%01d F4.B",
      location[0], location[1], location[2]);
  fp = popen(command, "r");
  success = fscanf(fp, "%x", &value);
  pclose(fp);

  if (!success) {
    printf("setpci didn't work\n");
    return 1;
  }

  // Increase of decrease the existing brightness value
  if (strcmp(argv[1], "++") == 0) {
    value += JUMP;
    if (value > 0xff) value = 0xff;
  } else if (strcmp(argv[1], "--") == 0) {
    if (value < JUMP) value = 0;
    else value -= JUMP;
  } else {
    printf("++ to increase, -- to decrease\n");
    return 1;
  }

  // Set the new brightness value
  snprintf(command, sizeof command, "setpci -s %02d:%02d.%01d F4.B=%x",
      location[0], location[1], location[2], value);
  system(command);

  return 0;
}

/*
 * Copyright (c) 2010, David Kelso <david at kelso dot id dot au>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>

// How far the brightness should move with each step.
#define JUMP 0xa

int main(int argc, char *argv[]) {
  unsigned int value, location[3];
  int success;
  char command[100];
  FILE *fp;

  if (geteuid()) {
    printf("Must be root\n");
    return 1;
  }

  if (argc != 2) {
    printf("One argument required: ++ or --\n");
    return 1;
  }

  // Get the PCI location of the VGA adapter
  fp = popen("lspci | grep VGA", "r");
  success = fscanf(fp, "%20d:%2d.%1d VGA", &location[0], &location[1], &location[2]);
  pclose(fp);

  if (!success) {
    printf("lspci couldn't find your vga adapter\n");
    return 1;
  }

  // Get the existing value of the brightness
  snprintf(command, sizeof command, "setpci -s %02d:%02d.%01d F4.B",
      location[0], location[1], location[2]);
  fp = popen(command, "r");
  success = fscanf(fp, "%x", &value);
  pclose(fp);

  if (!success) {
    printf("setpci didn't work\n");
    return 1;
  }

  // Increase or decrease the existing brightness value
  if (strcmp(argv[1], "++") == 0) {
    value += JUMP;
    if (value > 0xff) value = 0xff;
  } else if (strcmp(argv[1], "--") == 0) {
    if (value < JUMP) value = 0;
    else value -= JUMP;
  } else {
    printf("++ to increase, -- to decrease\n");
    return 1;
  }

  // Set the new brightness value
  snprintf(command, sizeof command, "setpci -s %02d:%02d.%01d F4.B=%x",
      location[0], location[1], location[2], value);
  system(command);

  return 0;
}


Don't autofix for the Objects own ID

require 'friendly_url'

module HasPermalink
  require 'railtie' if defined?(Rails) && Rails.version >= "3"

  def has_permalink(generate_from = :title, auto_fix_duplication = false)
    unless included_modules.include? InstanceMethods
      class_attribute :generate_from
      class_attribute :auto_fix_duplication
      extend ClassMethods
      include InstanceMethods
    end

    self.generate_from        = generate_from
    self.auto_fix_duplication = auto_fix_duplication
    before_validation :generate_permalink
  end

  module ClassMethods
    # Makes it possible to generate permalinks for
    # all instances of self:
    #
    #   Product.generate_permalinks
    #
    def generate_permalinks
      self.all.each do |item|
        item.generate_permalink
        item.save
      end
    end
  end

  module InstanceMethods
    include FriendlyUrl

    # Generate permalink for the instance if the permalink is empty or nil.
    def generate_permalink
      self.permalink = fix_duplication(normalize(self.send(generate_from))) if permalink.blank?
    end

    # Generate permalink for the instance and overwrite any existing value.
    def generate_permalink!
      self.permalink = fix_duplication(normalize(self.send(generate_from)))
    end

    # Override to send permalink as params[:id]
    def to_param
      permalink
    end

    private

    # Autofix duplication of permalinks
    def fix_duplication(permalink)
      if auto_fix_duplication
        n = self.class.where(["permalink = ?", permalink]).count

        if n > 0
          links = self.class.where(["permalink LIKE ?", "#{permalink}%"]).order("id")

          number = 0

          links.each_with_index do |link, index|
            if link.permalink =~ /#{permalink}-\d*\.?\d+?$/
              new_number = link.permalink.match(/-(\d*\.?\d+?)$/)[1].to_i
              number = new_number if new_number > number
            end
          end
          resolve_duplication(permalink, number + 1)
        else
          permalink
        end
      else
        permalink
      end
    end

    def resolve_duplication(permalink, number)
      "#{permalink}-#{number}"
    end
  end
end

ActiveRecord::Base.send :extend, HasPermalink

require 'friendly_url'

module HasPermalink
  require 'railtie' if defined?(Rails) && Rails.version >= "3"

  def has_permalink(generate_from = :title, auto_fix_duplication = false)
    unless included_modules.include? InstanceMethods
      class_attribute :generate_from
      class_attribute :auto_fix_duplication
      extend ClassMethods
      include InstanceMethods
    end

    self.generate_from        = generate_from
    self.auto_fix_duplication = auto_fix_duplication
    before_validation :generate_permalink
  end

  module ClassMethods
    # Makes it possible to generate permalinks for
    # all instances of self:
    #
    #   Product.generate_permalinks
    #
    def generate_permalinks
      self.all.each do |item|
        item.generate_permalink
        item.save
      end
    end
  end

  module InstanceMethods
    include FriendlyUrl

    # Generate permalink for the instance if the permalink is empty or nil.
    def generate_permalink
      self.permalink = fix_duplication(normalize(self.send(generate_from))) if permalink.blank?
    end

    # Generate permalink for the instance and overwrite any existing value.
    def generate_permalink!
      self.permalink = fix_duplication(normalize(self.send(generate_from)))
    end

    # Override to send permalink as params[:id]
    def to_param
      permalink
    end

    private

    # Autofix duplication of permalinks
    def fix_duplication(permalink)
      if auto_fix_duplication
        n = if id.present?
          self.class.where(["permalink = ? AND id != ?", permalink, id]).count
        else
          self.class.where(["permalink = ?", permalink]).count
        end

        if n > 0
          links = self.class.where(["permalink LIKE ?", "#{permalink}%"]).order("id")

          number = 0

          links.each_with_index do |link, index|
            if link.permalink =~ /#{permalink}-\d*\.?\d+?$/
              new_number = link.permalink.match(/-(\d*\.?\d+?)$/)[1].to_i
              number = new_number if new_number > number
            end
          end
          resolve_duplication(permalink, number + 1)
        else
          permalink
        end
      else
        permalink
      end
    end

    def resolve_duplication(permalink, number)
      "#{permalink}-#{number}"
    end
  end
end

ActiveRecord::Base.send :extend, HasPermalink