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

    // truncate the ciphertext
    for (int i = 0; i < ciphertext.length; i += 64) {
      byte[] truncatedCiphertext = Arrays.copyOf(ciphertext, i);
      tryDecryptModifiedCiphertextWithSeekableByteChannel(
          ags, truncatedCiphertext, associatedData, plaintext);
    }

    // Append stuff to ciphertext
    int[] sizes = new int[] {1, (segmentSize - ciphertext.length % segmentSize), segmentSize};
    for (int appendedBytes : sizes) {
      byte[] modifiedCiphertext = concatBytes(ciphertext, new byte[appendedBytes]);
      tryDecryptModifiedCiphertextWithSeekableByteChannel(
          ags, modifiedCiphertext, associatedData, plaintext);
    }

    // flip bits
    for (int pos = firstSegmentOffset; pos < ciphertext.length; pos++) {
      byte[] modifiedCiphertext = Arrays.copyOf(ciphertext, ciphertext.length);
      modifiedCiphertext[pos] ^= (byte) 1;
      tryDecryptModifiedCiphertextWithSeekableByteChannel(
          ags, modifiedCiphertext, associatedData, plaintext);
    }

    // delete segments
    for (int segment = 0; segment < (ciphertext.length / segmentSize); segment++) {
      byte[] modifiedCiphertext =
          concatBytes(
              Arrays.copyOf(ciphertext, segment * segmentSize),
              Arrays.copyOfRange(ciphertext, (segment + 1) * segmentSize, ciphertext.length));
      tryDecryptModifiedCiphertextWithSeekableByteChannel(
          ags, modifiedCiphertext, associatedData, plaintext);
    }

    // duplicate segments
    for (int segment = 0; segment < (ciphertext.length / segmentSize); segment++) {
      byte[] modifiedCiphertext =
          concatBytes(
              Arrays.copyOf(ciphertext, (segment + 1) * segmentSize),
              Arrays.copyOfRange(ciphertext, segment * segmentSize, ciphertext.length));
      tryDecryptModifiedCiphertextWithSeekableByteChannel(
          ags, modifiedCiphertext, associatedData, plaintext);
    }

    // Modify associatedData
    // When the associated data is modified then any attempt to read plaintext must fail.
    for (int pos = 0; pos < associatedData.length; pos++) {
      byte[] modifiedAad = Arrays.copyOf(associatedData, associatedData.length);
      modifiedAad[pos] ^= (byte) 1;
      tryDecryptModifiedCiphertextWithSeekableByteChannel(
          ags, ciphertext, modifiedAad, new byte[0]);
    }
  }
Tries to decrypt a modified ciphertext using an SeekableByteChannel. Each call to read must
either return the original plaintext (e.g. when the modification in the ciphertext does not
affect the plaintext) or it must throw an IOException.
java_src/src/main/java/com/google/crypto/tink/testing/StreamingTestUtil.java