/**
   * Read data from underlying stream and process with cipher until end of stream or some data is
   * available after cipher processing.
   *
   * @return -1 to indicate end of stream, or the number of bytes (> 0) available.
   */
  private int nextChunk() throws IOException {
    if (finalized) {
      return -1;
    }

    bufOff = 0;
    maxBuf = 0;

    // Keep reading until EOF or cipher processing produces data
    while (maxBuf == 0) {
      int read = in.read(inBuf);
      if (read == -1) {
        finaliseCipher();
        if (maxBuf == 0) {
          return -1;
        }
        return maxBuf;
      }

      try {
        if (bufferedBlockCipher != null) {
          maxBuf = bufferedBlockCipher.processBytes(inBuf, 0, read, buf, 0);
        } else if (aeadBlockCipher != null) {
          maxBuf = aeadBlockCipher.processBytes(inBuf, 0, read, buf, 0);
        } else {
          streamCipher.processBytes(inBuf, 0, read, buf, 0);
          maxBuf = read;
        }
      } catch (Exception e) {
        throw new IOException("Error processing stream " + e);
      }
    }
    return maxBuf;
  }
  /** Constructs a CipherInputStream from an InputStream and an AEADBlockCipher. */
  public CipherInputStream(InputStream is, AEADBlockCipher cipher) {
    super(is);

    this.aeadBlockCipher = cipher;

    buf = new byte[cipher.getOutputSize(INPUT_BUF_SIZE)];
    inBuf = new byte[INPUT_BUF_SIZE];
  }
 private void finaliseCipher() throws IOException {
   try {
     finalized = true;
     if (bufferedBlockCipher != null) {
       maxBuf = bufferedBlockCipher.doFinal(buf, 0);
     } else if (aeadBlockCipher != null) {
       maxBuf = aeadBlockCipher.doFinal(buf, 0);
     } else {
       maxBuf = 0; // a stream cipher
     }
   } catch (final InvalidCipherTextException e) {
     throw new InvalidCipherTextIOException("Error finalising cipher", e);
   } catch (Exception e) {
     throw new IOException("Error finalising cipher " + e);
   }
 }