@Override
  @GuardedBy("this")
  public synchronized int read(byte[] b, int offset, int len) throws IOException {
    if (len == 0) {
      return 0;
    }
    if (matchingStream != null) {
      return matchingStream.read(b, offset, len);
    } else {
      if (attemptedMatching) {
        throw new IOException("No matching key found for the ciphertext in the stream.");
      }
      attemptedMatching = true;
      for (StreamingAead streamingAead : primitives) {
        try {
          InputStream attemptedStream =
              streamingAead.newDecryptingStream(ciphertextStream, associatedData);
          int retValue = attemptedStream.read(b, offset, len);
          if (retValue == 0) {
            // Read should never return 0 when len > 0.
            throw new IOException("Could not read bytes from the ciphertext stream");
          }
          // Found a matching stream.
          // If retValue > 0 then the first ciphertext segment has been decrypted and
          // authenticated. If retValue == -1 then plaintext is empty and again this has been
          // authenticated.
          matchingStream = attemptedStream;
          disableRewinding();
          return retValue;
        } catch (IOException e) {
          // Try another key.
          // IOException is thrown e.g. when MAC is incorrect, but also in case
          // of I/O failures.
          // TODO(b/66098906): Use a subclass of IOException.
          rewind();
          continue;
        } catch (GeneralSecurityException e) {
          // Try another key.
          rewind();
          continue;
        }
      }
      throw new IOException("No matching key found for the ciphertext in the stream.");
    }
  }
This class does not support mark() and reset().
Applications that need random access to the plaintext of a long ciphertext
should use a SeekableByteChannelDecrypter (or SeekableDecryptingChannel).
java_src/src/main/java/com/google/crypto/tink/streamingaead/InputStreamDecrypter.java