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

    // truncate the ciphertext
    for (int i = 0; i < ciphertext.length; i += 8) {
      byte[] truncatedCiphertext = Arrays.copyOf(ciphertext, i);
      tryDecryptModifiedCiphertext(
          ags, firstSegmentOffset, truncatedCiphertext, associatedData, 128, 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]);
      tryDecryptModifiedCiphertext(
          ags, firstSegmentOffset, modifiedCiphertext, associatedData, 128, plaintext);
    }

    // flip bits
    for (int pos = firstSegmentOffset; pos < ciphertext.length; pos++) {
      byte[] modifiedCiphertext = Arrays.copyOf(ciphertext, ciphertext.length);
      modifiedCiphertext[pos] ^= (byte) 1;
      tryDecryptModifiedCiphertext(
          ags, firstSegmentOffset, modifiedCiphertext, associatedData, 128, 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));
      tryDecryptModifiedCiphertext(
          ags, firstSegmentOffset, modifiedCiphertext, associatedData, 128, 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));
      tryDecryptModifiedCiphertext(
          ags, firstSegmentOffset, modifiedCiphertext, associatedData, 128, 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[] modifiedAd = Arrays.copyOf(associatedData, associatedData.length);
      modifiedAd[pos] ^= (byte) 1;
      tryDecryptModifiedCiphertext(
          ags, firstSegmentOffset, ciphertext, modifiedAd, 128, new byte[0]);
    }
  }
Tries to decrypt a modified ciphertext. Each call to read must either return the original
plaintext (e.g. when the modification in the ciphertext has not yet been read) or it must throw
an IOException.
java_src/src/main/java/com/google/crypto/tink/testing/StreamingTestUtil.java