/**
   * Writes the tail portion of the file to the {@link OutputStream}.
   *
   * @param start The byte offset in the input file where the write operation starts.
   * @return if the file is still being written, this method writes the file until the last newline
   *     character and returns the offset to start the next write operation.
   */
  public long writeLogTo(long start, OutputStream out) throws IOException {
    CountingOutputStream os = new CountingOutputStream(out);

    Session f = source.open();
    f.skip(start);

    if (completed) {
      // write everything till EOF
      byte[] buf = new byte[1024];
      int sz;
      while ((sz = f.read(buf)) >= 0) os.write(buf, 0, sz);
    } else {
      ByteBuf buf = new ByteBuf(null, f);
      HeadMark head = new HeadMark(buf);
      TailMark tail = new TailMark(buf);
      buf = null;

      int readLines = 0;
      while (tail.moveToNextLine(f) && readLines++ < MAX_LINES_READ) {
        head.moveTo(tail, os);
      }
      head.finish(os);
    }

    f.close();
    os.flush();

    return os.getCount() + start;
  }
  private int receive(
      Transaction[] txs, Session session, StatsManager statsManager, int lastTxIdx) {

    boolean isSessionReadable = true;
    int firstFailedIndex = Integer.MAX_VALUE;

    for (int j = 0; j <= lastTxIdx && isSessionReadable; j++) {
      switch (txs[j].getState()) {
        case PROCESSED:
          continue;
        case FATAL_ERROR:
          firstFailedIndex = Math.min(j, firstFailedIndex);
          continue;
        default:
      }

      Response response = txs[j].getResponse();

      try {
        session.read(response);
        txs[j].setState(TransactionState.PROCESSED);
        statsManager.recordResponseTime(
            txs[j].getCommand().getCommandType(), txs[j].getResponseTime());
      } catch (ParsingException pe) {
        txs[j].setState(TransactionState.FATAL_ERROR);
        if (pe.getCause() instanceof SAXException) {
          SAXException saxe = (SAXException) pe.getCause();
          userLogger.warning(saxe.getMessage());
          txs[j].setCause(pe.getCause());
        } else {
          userLogger.warning(pe.getMessage());
          txs[j].setCause(pe);
        }

        // We can't trust that the message boundaries will be correct
        // following a parsing error, so the session must be closed in
        // order to prevent incorrect interpretation of further service
        // elements received via this session.
        session.close();
        firstFailedIndex = Math.min(j, firstFailedIndex);
        isSessionReadable = false;
      } catch (IOException ioe) {
        userLogger.warning(ioe.getMessage());
        txs[j].setState(TransactionState.RETRY);
        txs[j].setCause(ioe);
        firstFailedIndex = Math.min(j, firstFailedIndex);
        isSessionReadable = false;
      }
    }

    return Math.min(firstFailedIndex, txs.length);
  }
    public ByteBuf(ByteBuf previous, Session f, int size) throws IOException {
      this.buf = new byte[size];
      if (previous != null) {
        assert previous.next == null;
        previous.next = this;
      }

      while (!this.isFull()) {
        int chunk = f.read(buf, size, buf.length - size);
        if (chunk == -1) return;
        size += chunk;
      }
    }
  /**
   * Writes the section of the file {@link OutputStream}.
   *
   * @param start The byte offset in the input file where the write operation starts.
   * @return if the file is still being written, this method writes the file until the last newline
   *     character and returns the offset to start the next write operation.
   */
  public long writeLogTo(long start, int size, OutputStream out) throws IOException {
    if (size <= 0) {
      return 0;
    }

    CountingOutputStream os = new CountingOutputStream(out);

    Session f = source.open();
    f.skip(start);

    long end = start + size;

    byte[] buf = new byte[size];
    int sz;
    if ((sz = f.read(buf)) >= 0) {
      os.write(buf, 0, sz);
    }
    /*
            if(completed) {
            } else {
                ByteBuf buf = new ByteBuf(null,f, size);
                HeadMark head = new HeadMark(buf);
                TailMark tail = new TailMark(buf);

                int readLines = 0;
                while(tail.moveToNextLine(f) && readLines++ < MAX_LINES_READ) {
                    head.moveTo(tail, os);
                    if (buf.isFull() || os.getCount() >= end) {
                        break;
                    }
                }
                head.finish(os);
            }
    */

    f.close();
    os.flush();

    return os.getCount() + start;
  }
  /**
   * Try to process a single transaction. Up to {@code MAX_ACCEPTABLE_FAIL_COUNT} attempts will be
   * made to process the transaction in cases where I/O errors or non-protocol server errors occur
   * during processing. Use of the underlying session is protected against concurrent use by other
   * threads by using the getSession/releaseSession features of this SessionManager's {@link
   * com.ausregistry.jtoolkit2.session.SessionPool}. This method guarantees that the session used
   * will be returned to the pool before the method returns.
   *
   * @throws FatalSessionException No session could be acquired to process the transaction. Check
   *     the exception message and log records for details.
   * @throws IOException Every attempt to execute the transaction command failed due to an
   *     IOException. This is the last such IOException.
   * @throws ParsingException Parsing of the response failed. Check the exception message for the
   *     cause.
   * @throws CommandFailedException The acceptable limit on the number of failed commands due to
   *     server error was exceeded in trying to process the command. This probably indicates a
   *     server limitation related to the command being processed.
   * @throws IllegalStateException The SessionManager had been shutdown or not started up prior to
   *     invoking this method.
   */
  @Override
  public void execute(Transaction tx)
      throws FatalSessionException, IOException, ParsingException, CommandFailedException,
          IllegalStateException {

    debugLogger.finest("enter");

    if (state == SMState.STOPPED) {
      throw new IllegalStateException();
    }

    Command cmd = tx.getCommand();
    Response response = tx.getResponse();

    int failCount = 0;
    boolean isExecuted = false;
    Session session = null;

    // if only processing one transaction, get a new session for each
    // attempt in case the session fails mid-transaction.
    while (!isExecuted && state != SMState.STOPPED) {
      try {
        session = sessionPool.getSession(cmd.getCommandType());
        StatsManager statsManager = session.getStatsManager();
        statsManager.incCommandCounter(cmd.getCommandType());

        tx.start();
        session.write(cmd);
        isExecuted = true;
        session.read(response);
        tx.setState(TransactionState.PROCESSED);
        statsManager.recordResponseTime(cmd.getCommandType(), tx.getResponseTime());

        Result[] results = response.getResults();
        assert results != null;
        if (results != null) {
          for (Result result : results) {
            assert result != null;
            statsManager.incResultCounter(result.getResultCode());
            int code = result.getResultCode();

            switch (code) {
              case ResultCode.CMD_FAILED:
                throw new CommandFailedException();
              case ResultCode.CMD_FAILED_CLOSING:
              default:
                throw new CommandFailedException();
            }
          }
        }
      } catch (CommandFailedException cfe) {
        userLogger.warning(cfe.getMessage());

        if (state != SMState.STOPPED && failCount < MAX_ACCEPTABLE_FAIL_COUNT) {

          failCount++;
        } else {
          throw cfe;
        }
      } catch (IOException ioe) {
        userLogger.warning(ioe.getMessage());
        userLogger.warning("net.socket.closed");
        session.close();
        if (state != SMState.STOPPED && failCount < MAX_ACCEPTABLE_FAIL_COUNT) {
          failCount++;
        } else {
          throw ioe;
        }
      } catch (InterruptedException ie) {
        // if interrupted by shutdown, then started will be false
        // Note: this still enters the finally block
        continue;
      } catch (SessionConfigurationException sce) {
        throw new FatalSessionException(sce);
      } catch (SessionOpenException soe) {
        throw new FatalSessionException(soe);
      } finally {
        sessionPool.releaseSession(session);
      }
    }

    if (!isExecuted && state == SMState.STOPPED) {
      throw new IllegalStateException();
    }

    debugLogger.finest("exit");
  }
  @Override
  public boolean start(Session session) throws Exception {
    super.start(session);

    if (userinfo != null && !(userinfo instanceof UIKeyboardInteractive)) {
      return false;
    }

    String dest = username + "@" + session.host;
    if (session.port != 22) {
      dest += (":" + session.port);
    }
    byte[] password = session.password;

    boolean cancel = false;

    byte[] _username = null;
    _username = Util.str2byte(username);

    while (true) {
      // send
      // byte SSH_MSG_USERAUTH_REQUEST(50)
      // string user name (ISO-10646 UTF-8, as defined in [RFC-2279])
      // string service name (US-ASCII) "ssh-userauth" ? "ssh-connection"
      // string "keyboard-interactive" (US-ASCII)
      // string language tag (as defined in [RFC-3066])
      // string submethods (ISO-10646 UTF-8)
      packet.reset();
      buf.putByte((byte) SSH_MSG_USERAUTH_REQUEST);
      buf.putString(_username);
      buf.putString("ssh-connection".getBytes());
      // buf.putString("ssh-userauth".getBytes());
      buf.putString("keyboard-interactive".getBytes());
      buf.putString("".getBytes());
      buf.putString("".getBytes());
      session.write(packet);

      boolean firsttime = true;
      loop:
      while (true) {
        buf = session.read(buf);
        int command = buf.getCommand() & 0xff;

        if (command == SSH_MSG_USERAUTH_SUCCESS) {
          return true;
        }
        if (command == SSH_MSG_USERAUTH_BANNER) {
          buf.getInt();
          buf.getByte();
          buf.getByte();
          byte[] _message = buf.getString();
          byte[] lang = buf.getString();
          String message = null;
          try {
            message = new String(_message, "UTF-8");
          } catch (java.io.UnsupportedEncodingException e) {
            message = new String(_message);
          }
          if (userinfo != null) {
            userinfo.showMessage(message);
          }
          continue loop;
        }
        if (command == SSH_MSG_USERAUTH_FAILURE) {
          buf.getInt();
          buf.getByte();
          buf.getByte();
          byte[] foo = buf.getString();
          int partial_success = buf.getByte();
          // System.err.println(new String(foo)+
          // " partial_success:"+(partial_success!=0));

          if (partial_success != 0) {
            throw new JSchPartialAuthException(new String(foo));
          }

          if (firsttime) {
            return false;
            // throw new JSchException("USERAUTH KI is not supported");
            // cancel=true; // ??
          }
          break;
        }
        if (command == SSH_MSG_USERAUTH_INFO_REQUEST) {
          firsttime = false;
          buf.getInt();
          buf.getByte();
          buf.getByte();
          String name = new String(buf.getString());
          String instruction = new String(buf.getString());
          String languate_tag = new String(buf.getString());
          int num = buf.getInt();
          String[] prompt = new String[num];
          boolean[] echo = new boolean[num];
          for (int i = 0; i < num; i++) {
            prompt[i] = new String(buf.getString());
            echo[i] = (buf.getByte() != 0);
          }

          byte[][] response = null;
          if (num > 0 || (name.length() > 0 || instruction.length() > 0)) {
            if (userinfo != null) {
              UIKeyboardInteractive kbi = (UIKeyboardInteractive) userinfo;
              String[] _response =
                  kbi.promptKeyboardInteractive(dest, name, instruction, prompt, echo);
              if (_response != null) {
                response = new byte[_response.length][];
                for (int i = 0; i < _response.length; i++) {
                  response[i] = Util.str2byte(_response[i]);
                }
              }
            } else if (password != null
                && prompt.length == 1
                && !echo[0]
                && prompt[0].toLowerCase().startsWith("password:"******"response.length="+response.length);
          // else
          // System.err.println("response is null");
          packet.reset();
          buf.putByte((byte) SSH_MSG_USERAUTH_INFO_RESPONSE);
          if (num > 0
              && (response == null
                  || // cancel
                  num != response.length)) {

            if (response == null) {
              // working around the bug in OpenSSH ;-<
              buf.putInt(num);
              for (int i = 0; i < num; i++) {
                buf.putString("".getBytes());
              }
            } else {
              buf.putInt(0);
            }

            if (response == null) cancel = true;
          } else {
            buf.putInt(num);
            for (int i = 0; i < num; i++) {
              // System.err.println("response: |"+new String(response[i])+"| <- replace here with
              // **** if you need");
              buf.putString(response[i]);
            }
          }
          session.write(packet);
          /*
           * if(cancel) break;
           */
          continue loop;
        }
        // throw new JSchException("USERAUTH fail ("+command+")");
        return false;
      }
      if (cancel) {
        throw new JSchAuthCancelException("keyboard-interactive");
        // break;
      }
    }
    // return false;
  }