/**
  * This method determines whether the inform request is to be retried. This is used if the peer
  * did not respond to a previous request. If the request exceeds the maxTries limit, a timeout is
  * signaled.
  */
 void action() {
   if (inProgress() == false) return;
   while (true) {
     try {
       if (numTries == 0) {
         invokeOnReady();
       } else if (numTries < getMaxTries()) {
         invokeOnRetry();
       } else {
         invokeOnTimeout();
       }
       return;
     } catch (OutOfMemoryError omerr) {
       // Consider it as a try !
       //
       numTries++;
       if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
         SNMP_ADAPTOR_LOGGER.logp(
             Level.FINEST,
             SnmpInformRequest.class.getName(),
             "action",
             "Inform request hit out of memory situation...");
       }
       Thread.currentThread().yield();
     }
   }
 }
  /** For SNMP Runtime internal use only. */
  final void processResponse() {

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(
          Level.FINER,
          SnmpInformRequest.class.getName(),
          "processResponse",
          "errstatus = " + errorStatus);
    }

    if (inProgress() == false) { // check if this request is still alive.
      responsePdu = null;
      return; // the request may have  cancelled.
    }

    if (errorStatus >= snmpReqInternalError) {
      handleInternalError("Internal Error...");
      return;
    }

    try {
      parsePduPacket(responsePdu);
      // responsePdu = null;

      // At this point the errorIndex is rationalized to start with 0.
      switch (errorStatus) {
        case snmpRspNoError:
          handleSuccess();
          return;
        case snmpReqTimeout:
          handleTimeout();
          return;
        case snmpReqInternalError:
          handleInternalError("Unknown internal error.  deal with it later!");
          return;
        case snmpReqHandleTooBig:
          setErrorStatusAndIndex(snmpRspTooBig, 0);
          handleError("Cannot handle too-big situation...");
          return;
        case snmpReqRefireAfterVbFix:
          // Refire request after fixing varbindlist.
          initializeAndFire();
          return;
        default:
          handleError("Error status set in packet...!!");
          return;
      }
    } catch (Exception e) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpInformRequest.class.getName(),
            "processResponse",
            "Got unexpected exception",
            e);
      }
      reason = e.getMessage();
    }
    handleInternalError(reason);
  }
  /** For SNMP Runtime internal use only. */
  final void setRequestSentTime(long sendtime) {
    numTries++;
    setPrevPollTime(sendtime);
    waitTimeForResponse = prevPollTime + timeout * numTries;
    setRequestStatus(stWaitingForReply);

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(
          Level.FINER,
          SnmpInformRequest.class.getName(),
          "setRequestSentTime",
          "Inform request Successfully sent");
    }

    informSession.getSnmpQManager().addWaiting(this);
  }
  /**
   * Checks if a <CODE>set</CODE> operation can be performed. If the operation can not be performed,
   * the method should emit a <CODE>SnmpStatusException</CODE>.
   *
   * @param inRequest The SnmpMibRequest object holding the list of variables to be set. This list
   *     is composed of <CODE>SnmpVarBind</CODE> objects.
   * @exception SnmpStatusException The <CODE>set</CODE> operation cannot be performed.
   */
  @Override
  public void check(SnmpMibRequest inRequest) throws SnmpStatusException {

    SNMP_ADAPTOR_LOGGER.logp(
        Level.FINEST, SnmpErrorHandlerAgent.class.getName(), "check", "Check in Exception");

    throw new SnmpStatusException(SnmpDefinitions.snmpRspNotWritable);
  }
  /**
   * Processes a <CODE>set</CODE> operation. Should never be called (check previously called having
   * failed).
   *
   * @param inRequest The SnmpMibRequest object holding the list of variable to be set.
   * @exception SnmpStatusException An error occurred during the operation.
   */
  @Override
  public void set(SnmpMibRequest inRequest) throws SnmpStatusException {

    SNMP_ADAPTOR_LOGGER.logp(
        Level.FINEST,
        SnmpErrorHandlerAgent.class.getName(),
        "set",
        "Set in Exception, CANNOT be called");

    throw new SnmpStatusException(SnmpDefinitions.snmpRspNotWritable);
  }
  /**
   * Parses the inform response packet. If the agent responds with error set, it does not parse any
   * further.
   */
  synchronized void parsePduPacket(SnmpPduRequestType rpdu) {

    if (rpdu == null) return;

    errorStatus = rpdu.getErrorStatus();
    errorIndex = rpdu.getErrorIndex();

    if (errorStatus == snmpRspNoError) {
      updateInternalVarBindWithResult(((SnmpPdu) rpdu).varBindList);
      return;
    }

    if (errorStatus != snmpRspNoError) --errorIndex; // rationalize for index to start with 0.

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(
          Level.FINER,
          SnmpInformRequest.class.getName(),
          "parsePduPacket",
          "received inform response. ErrorStatus/ErrorIndex = " + errorStatus + "/" + errorIndex);
    }
  }
  /**
   * Sends the prepared PDU packet to the manager and updates the data structure to expect a
   * response. It acquires a lock on the socket to prevent a case where a response arrives before
   * this thread could insert the request into the wait queue.
   *
   * @exception IOException Signals that an I/O exception of some sort has occurred.
   */
  final void sendPduPacket(byte[] buffer, int length) throws java.io.IOException {

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(
          Level.FINER,
          SnmpInformRequest.class.getName(),
          "sendPduPacket",
          "Send to peer. Peer/Port : "
              + address.getHostName()
              + "/"
              + port
              + ". Length = "
              + length
              + "\nDump : \n"
              + SnmpMessage.dumpHexBuffer(buffer, 0, length));
    }
    SnmpSocket theSocket = informSession.getSocket();
    synchronized (theSocket) {
      theSocket.sendPacket(buffer, length, address, port);
      setRequestSentTime(System.currentTimeMillis());
    }
  }
  /** SNMP V1/V2 . To be called with updateRequest. */
  protected SnmpSubRequestHandler(SnmpMibAgent agent, SnmpPdu req) {
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(
          Level.FINER,
          SnmpSubRequestHandler.class.getName(),
          "constructor",
          "creating instance for request " + String.valueOf(req.requestId));
    }

    version = req.version;
    type = req.type;
    this.agent = agent;

    // We get a ref on the pdu in order to pass it to SnmpMibRequest.
    reqPdu = req;

    // Pre-allocate room for storing varbindlist and translation table.
    //
    int length = req.varBindList.length;
    translation = new int[length];
    varBind = new NonSyncVector<SnmpVarBind>(length);
  }
  /**
   * The method updates a given var bind list with the result of a previsouly invoked operation.
   * Prior to calling the method, one must make sure that the operation was successful. As such the
   * method getErrorIndex or getErrorStatus should be called.
   */
  protected void updateResult(SnmpVarBind[] result) {

    if (result == null) return;
    final int max = varBind.size();
    final int len = result.length;
    for (int i = 0; i < max; i++) {
      // bugId 4641694: must check position in order to avoid
      //       ArrayIndexOutOfBoundException
      final int pos = translation[i];
      if (pos < len) {
        result[pos] = (SnmpVarBind) ((NonSyncVector) varBind).elementAtNonSync(i);
      } else {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(
              Level.FINEST,
              SnmpSubRequestHandler.class.getName(),
              "updateResult",
              "Position `" + pos + "' is out of bound...");
        }
      }
    }
  }
  /**
   * Processes a <CODE>get</CODE> operation. It will throw an exception for V1 requests or it will
   * set exceptions within the list for V2 requests.
   *
   * @param inRequest The SnmpMibRequest object holding the list of variable to be retrieved.
   * @exception SnmpStatusException An error occurred during the operation.
   */
  @Override
  public void get(SnmpMibRequest inRequest) throws SnmpStatusException {

    SNMP_ADAPTOR_LOGGER.logp(
        Level.FINEST, SnmpErrorHandlerAgent.class.getName(), "get", "Get in Exception");

    if (inRequest.getVersion() == SnmpDefinitions.snmpVersionOne)
      throw new SnmpStatusException(SnmpStatusException.noSuchName);

    Enumeration<SnmpVarBind> l = inRequest.getElements();
    while (l.hasMoreElements()) {
      SnmpVarBind varbind = l.nextElement();
      varbind.setNoSuchObject();
    }
  }
  /** Constructs an inform request PDU. */
  synchronized SnmpPdu constructPduPacket() {
    SnmpPduPacket reqpdu = null;
    Exception excep = null;
    try {
      reqpdu = new SnmpPduRequest();
      reqpdu.port = port;
      reqpdu.type = pduInformRequestPdu;
      reqpdu.version = snmpVersionTwo;
      reqpdu.community = communityString.getBytes("8859_1");
      reqpdu.requestId = getRequestId();
      reqpdu.varBindList = internalVarBind;

      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINER, SnmpInformRequest.class.getName(), "constructPduPacket", "Packet built");
      }

    } catch (Exception e) {
      excep = e;
      errorStatus = snmpReqUnknownError;
      reason = e.getMessage();
    }
    if (excep != null) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpInformRequest.class.getName(),
            "constructPduPacket",
            "Got unexpected exception",
            excep);
      }
      reqpdu = null;
      queueResponse();
    }
    return reqpdu;
  }
  /**
   * Processes a <CODE>getBulk</CODE> operation. It will throw an exception if the request is a V1
   * one or it will set exceptions within the list for V2 ones.
   *
   * @param inRequest The SnmpMibRequest object holding the list of variable to be retrieved.
   * @exception SnmpStatusException An error occurred during the operation.
   */
  @Override
  public void getBulk(SnmpMibRequest inRequest, int nonRepeat, int maxRepeat)
      throws SnmpStatusException {

    SNMP_ADAPTOR_LOGGER.logp(
        Level.FINEST, SnmpErrorHandlerAgent.class.getName(), "getBulk", "GetBulk in Exception");

    if (inRequest.getVersion() == SnmpDefinitions.snmpVersionOne)
      throw new SnmpStatusException(SnmpDefinitions.snmpRspGenErr, 0);

    Enumeration<SnmpVarBind> l = inRequest.getElements();
    while (l.hasMoreElements()) {
      SnmpVarBind varbind = l.nextElement();
      varbind.setEndOfMibView();
    }
  }
  /** Calls the user implementation of the <CODE>SnmpInformHandler</CODE> interface. */
  private void handleInternalError(String msg) {

    setRequestStatus(stInternalError);
    if (reason == null) reason = msg;

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
      SNMP_ADAPTOR_LOGGER.logp(
          Level.FINEST,
          SnmpInformRequest.class.getName(),
          "handleInternalError",
          "Snmp error/index = "
              + snmpErrorToString(errorStatus)
              + "/"
              + errorIndex
              + ". Invoking internal error user defined callback...\n"
              + getVarBindList());
    }

    deleteRequest();
    notifyClient();

    requestPdu = null;
    responsePdu = null;
    internalVarBind = null;

    try {
      if (callback != null) callback.processSnmpInternalError(this, reason);
    } catch (Exception e) { // catch any exception a user might not handle.
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpInformRequest.class.getName(),
            "handleInternalError",
            "Exception generated by user callback",
            e);
      }
    } catch (OutOfMemoryError ome) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpInformRequest.class.getName(),
            "handleInternalError",
            "OutOfMemory Error generated by user callback",
            ome);
      }
      Thread.currentThread().yield();
    }
  }
  /** Calls the user implementation of the <CODE>SnmpInformHandler</CODE> interface. */
  private void handleSuccess() {

    setRequestStatus(stResultsAvailable);

    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(
          Level.FINER,
          SnmpInformRequest.class.getName(),
          "handleSuccess",
          "Invoking user defined callback...");
    }

    deleteRequest(); // delete only non-poll request.
    notifyClient();

    requestPdu = null;
    // responsePdu = null;
    internalVarBind = null;

    try { // catch all user exception which may happen in callback.
      if (callback != null)
        callback.processSnmpPollData(this, errorStatus, errorIndex, getVarBindList());
    } catch (Exception e) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpInformRequest.class.getName(),
            "handleSuccess",
            "Exception generated by user callback",
            e);
      }
    } catch (OutOfMemoryError ome) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpInformRequest.class.getName(),
            "handleSuccess",
            "OutOfMemory Error generated by user callback",
            ome);
      }
      Thread.currentThread().yield();
    }
    return;
  }
  boolean sendPdu() {
    try {
      responsePdu = null;

      SnmpPduFactory pduFactory = adaptor.getPduFactory();
      SnmpMessage msg =
          (SnmpMessage)
              pduFactory.encodeSnmpPdu(
                  (SnmpPduPacket) requestPdu, adaptor.getBufferSize().intValue());

      if (msg == null) {
        if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
          SNMP_ADAPTOR_LOGGER.logp(
              Level.FINEST,
              SnmpInformRequest.class.getName(),
              "sendPdu",
              "pdu factory returned a null value");
        }
        throw new SnmpStatusException(snmpReqUnknownError);
        // This exception will caught hereafter and reported as an snmpReqUnknownError
        // FIXME: may be it's not the best behaviour ?
      }

      int maxPktSize = adaptor.getBufferSize().intValue();
      byte[] encoding = new byte[maxPktSize];
      int encodingLength = msg.encodeMessage(encoding);

      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINER,
            SnmpInformRequest.class.getName(),
            "sendPdu",
            "Dump : \n" + msg.printMessage());
      }

      sendPduPacket(encoding, encodingLength);
      return true;
    } catch (SnmpTooBigException ar) {

      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpInformRequest.class.getName(),
            "sendPdu",
            "Got unexpected exception",
            ar);
      }

      setErrorStatusAndIndex(snmpReqPacketOverflow, ar.getVarBindCount());
      requestPdu = null;
      reason = ar.getMessage();
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpInformRequest.class.getName(),
            "sendPdu",
            "Packet Overflow while building inform request");
      }
    } catch (java.io.IOException ioe) {
      setErrorStatusAndIndex(snmpReqSocketIOError, 0);
      reason = ioe.getMessage();
    } catch (Exception e) {
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpInformRequest.class.getName(),
            "sendPdu",
            "Got unexpected exception",
            e);
      }
      setErrorStatusAndIndex(snmpReqUnknownError, 0);
      reason = e.getMessage();
    }
    return false;
  }
  public void run() {

    try {
      final ThreadContext oldContext = ThreadContext.push("SnmpUserData", data);
      try {
        switch (type) {
          case pduGetRequestPdu:
            // Invoke a get operation
            //
            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
              SNMP_ADAPTOR_LOGGER.logp(
                  Level.FINER,
                  SnmpSubRequestHandler.class.getName(),
                  "run",
                  "[" + Thread.currentThread() + "]:get operation on " + agent.getMibName());
            }

            agent.get(createMibRequest(varBind, version, data));
            break;

          case pduGetNextRequestPdu:
            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
              SNMP_ADAPTOR_LOGGER.logp(
                  Level.FINER,
                  SnmpSubRequestHandler.class.getName(),
                  "run",
                  "[" + Thread.currentThread() + "]:getNext operation on " + agent.getMibName());
            }
            // #ifdef DEBUG
            agent.getNext(createMibRequest(varBind, version, data));
            break;

          case pduSetRequestPdu:
            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
              SNMP_ADAPTOR_LOGGER.logp(
                  Level.FINER,
                  SnmpSubRequestHandler.class.getName(),
                  "run",
                  "[" + Thread.currentThread() + "]:set operation on " + agent.getMibName());
            }
            agent.set(createMibRequest(varBind, version, data));
            break;

          case pduWalkRequest:
            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
              SNMP_ADAPTOR_LOGGER.logp(
                  Level.FINER,
                  SnmpSubRequestHandler.class.getName(),
                  "run",
                  "[" + Thread.currentThread() + "]:check operation on " + agent.getMibName());
            }
            agent.check(createMibRequest(varBind, version, data));
            break;

          default:
            if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
              SNMP_ADAPTOR_LOGGER.logp(
                  Level.FINEST,
                  SnmpSubRequestHandler.class.getName(),
                  "run",
                  "["
                      + Thread.currentThread()
                      + "]:unknown operation ("
                      + type
                      + ") on "
                      + agent.getMibName());
            }
            errorStatus = snmpRspGenErr;
            errorIndex = 1;
            break;
        } // end of switch

      } finally {
        ThreadContext.restore(oldContext);
      }
    } catch (SnmpStatusException x) {
      errorStatus = x.getStatus();
      errorIndex = x.getErrorIndex();
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpSubRequestHandler.class.getName(),
            "run",
            "[" + Thread.currentThread() + "]:an Snmp error occured during the operation",
            x);
      }
    } catch (Exception x) {
      errorStatus = SnmpDefinitions.snmpRspGenErr;
      if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINEST)) {
        SNMP_ADAPTOR_LOGGER.logp(
            Level.FINEST,
            SnmpSubRequestHandler.class.getName(),
            "run",
            "[" + Thread.currentThread() + "]:a generic error occured during the operation",
            x);
      }
    }
    if (SNMP_ADAPTOR_LOGGER.isLoggable(Level.FINER)) {
      SNMP_ADAPTOR_LOGGER.logp(
          Level.FINER,
          SnmpSubRequestHandler.class.getName(),
          "run",
          "[" + Thread.currentThread() + "]:operation completed");
    }
  }