public static void testSkipWithStream(
      StreamingAead ags, int firstSegmentOffset, int plaintextSize, int chunkSize)
      throws Exception {
    byte[] associatedData = Hex.decode("aabbccddeeff");
    byte[] plaintext = generatePlaintext(plaintextSize);
    byte[] ciphertext = encryptWithStream(ags, plaintext, associatedData, firstSegmentOffset);

    // Runs this part twice skips the chunk number i if skipChunk == i % 2.
    for (int skipChunk = 0; skipChunk < 2; skipChunk++) {
      // Construct an InputStream from the ciphertext where the first
      // firstSegmentOffset bytes have already been read.
      InputStream ctStream = new ByteArrayInputStream(ciphertext);
      ctStream.read(new byte[firstSegmentOffset]);

      // Construct an InputStream that returns the plaintext.
      InputStream ptStream = ags.newDecryptingStream(ctStream, associatedData);
      int decryptedSize = 0;
      int chunkNumber = 0;
      while (true) {
        if (chunkNumber % 2 == skipChunk) {
          int bytesSkipped = (int) ptStream.skip(chunkSize);
          if (bytesSkipped < 0) {
            fail("skip must not return a negative integer (not even at eof).");
          }
          if (bytesSkipped == 0) {
            // The implementation here is blocking. Hence getting 0 here implies that
            // the end of the stream has been reached. However, this has not been
            // verified yet.
            assertEquals("Expecting end of stream after a 0-byte skip.", -1, ptStream.read());
            break;
          }
          decryptedSize += bytesSkipped;
          if (decryptedSize < plaintextSize) {
            // The stream is blocking. Hence we expect the number of requested
            // bytes unless the end of the stream has been reached.
            assertEquals("Size of skipped chunk is invalid", chunkSize, bytesSkipped);
          }
        } else {
          byte[] chunk = new byte[chunkSize];
          int read = ptStream.read(chunk);
          if (read == -1) {
            break;
          }
          byte[] expected = Arrays.copyOfRange(plaintext, decryptedSize, decryptedSize + read);
          TestUtil.assertByteArrayEquals(expected, Arrays.copyOf(chunk, read));
          decryptedSize += read;
          if (read < chunkSize && decryptedSize < plaintextSize) {
            // read should block until either all requested bytes are read, the end of the stream
            // has been reached or an error occurred.
            fail("read did not return enough bytes");
          }
        }
        chunkNumber += 1;
      }
      assertEquals("Size of decryption does not match plaintext", plaintextSize, decryptedSize);
    }

    // Checks whether skipping at the end of a broken ciphertext is detected.
    InputStream brokenCtStream = new ByteArrayInputStream(ciphertext, 0, ciphertext.length - 1);
    brokenCtStream.read(new byte[firstSegmentOffset]);
    InputStream brokenPtStream = ags.newDecryptingStream(brokenCtStream, associatedData);
    try {
      brokenPtStream.skip(2 * plaintextSize);
      brokenPtStream.read();
      fail("Failed to detect invalid ciphertext");
    } catch (IOException ex) {
      // expected
    }
  }
Encrypts and decrypts some plaintext in a stream using skips and checks that the expected
plaintext is returned for the parts not skipped.

@param ags the StreamingAead test object.
@param firstSegmentOffset number of bytes prepended to the ciphertext stream.
@param plaintextSize the size of the plaintext
@param chunkSize decryption skips and reads chunks of this size.
java_src/src/main/java/com/google/crypto/tink/testing/StreamingTestUtil.java