private void checkCipher(CipherTestParam p, String provider) throws Exception {
        if (!p.compatibleWith(provider)) {
            return;
        }
        Cipher c = Cipher.getInstance(p.transformation, provider);

        c.init(Cipher.ENCRYPT_MODE, p.encryptKey, p.spec);

        // This doesn't quite work on OAEPPadding unless it's the default case,
        // because its size depends on the message digest algorithms used.
        if (!p.transformation.endsWith("OAEPPADDING")) {
            assertEquals(p.transformation + " getBlockSize() ENCRYPT_MODE",
                    getExpectedBlockSize(p.transformation, Cipher.ENCRYPT_MODE, provider),
                    c.getBlockSize());
        }
        assertTrue(p.transformation + " getOutputSize(0) ENCRYPT_MODE",
                getExpectedOutputSize(p.transformation, Cipher.ENCRYPT_MODE, provider) <= c
                        .getOutputSize(0));

        if (p.aad != null) {
            c.updateAAD(p.aad);
        }
        final byte[] actualCiphertext = c.doFinal(p.plaintext);
        if (!isRandomizedEncryption(p.transformation)) {
            assertEquals(p.transformation + " " + provider, Arrays.toString(p.ciphertext),
                    Arrays.toString(actualCiphertext));
        }

        c = Cipher.getInstance(p.transformation, provider);
        c.init(Cipher.ENCRYPT_MODE, p.encryptKey, p.spec);
        if (!(p instanceof OAEPCipherTestParam) || p.spec != null) {
            assertCorrectAlgorithmParameters(provider, p.transformation, p.spec, c.getParameters());
        }

        byte[] emptyCipherText = c.doFinal();
        assertNotNull(emptyCipherText);

        c.init(Cipher.DECRYPT_MODE, p.decryptKey, p.spec);

        assertEquals(p.transformation + " getBlockSize() DECRYPT_MODE",
                getExpectedBlockSize(p.transformation, Cipher.DECRYPT_MODE, provider),
                c.getBlockSize());

        // This doesn't quite work on OAEPPadding unless it's the default case,
        // because its size depends on the message digest algorithms used.
        if (!p.transformation.endsWith("OAEPPADDING")) {
            assertTrue(p.transformation + " getOutputSize(0) DECRYPT_MODE",
                    getExpectedOutputSize(p.transformation, Cipher.DECRYPT_MODE, provider) <= c
                            .getOutputSize(0));
        }

        if (!isAEAD(p.transformation)) {
            try {
                c.updateAAD(new byte[8]);
                fail("Cipher should not support AAD");
            } catch (UnsupportedOperationException | IllegalStateException expected) {
            }
        }

        try {
            byte[] emptyPlainText = c.doFinal(emptyCipherText);
            assertEquals(Arrays.toString(new byte[0]), Arrays.toString(emptyPlainText));
        } catch (AEADBadTagException maybe) {
            if (!"AndroidOpenSSL".equals(provider) || !isAEAD(p.transformation)) {
                throw maybe;
            }
        } catch (BadPaddingException maybe) {
            // BC's OAEP has a bug where it doesn't support decrypt of a zero-length plaintext
            if (!("BC".equals(provider) && p.transformation.contains("OAEP"))) {
                throw maybe;
            }
        }

        // decrypt an empty ciphertext; not valid for RSA
        if (!p.transformation.contains("OAEP")) {
            if ((!isAEAD(p.transformation)
                    && (StandardNames.IS_RI || provider.equals("AndroidOpenSSL") ||
                            (provider.equals("BC") && p.transformation.contains("/CTR/"))))
                    || p.transformation.equals("ARC4")) {
                assertEquals(Arrays.toString(new byte[0]),
                             Arrays.toString(c.doFinal()));

                c.update(new byte[0]);
                assertEquals(Arrays.toString(new byte[0]),
                             Arrays.toString(c.doFinal()));
            } else if (provider.equals("BC") || isAEAD(p.transformation)) {
                try {
                    c.doFinal();
                    fail(p.transformation + " " + provider);
                } catch (IllegalBlockSizeException maybe) {
                    if (isAEAD(p.transformation)) {
                        throw maybe;
                    }
                } catch (AEADBadTagException maybe) {
                    if (!isAEAD(p.transformation)) {
                        throw maybe;
                    }
                } catch (ProviderException maybe) {
                    boolean isShortBufferException
                            = maybe.getCause() instanceof ShortBufferException;
                    if (!isAEAD(p.transformation)
                            || !isBuggyProvider(provider)
                            || !isShortBufferException) {
                        throw maybe;
                    }
                }
                try {
                    c.update(new byte[0]);
                    c.doFinal();
                    fail(p.transformation + " " + provider);
                } catch (IllegalBlockSizeException maybe) {
                    if (isAEAD(p.transformation)) {
                        throw maybe;
                    }
                } catch (AEADBadTagException maybe) {
                    if (!isAEAD(p.transformation)) {
                        throw maybe;
                    }
                } catch (ProviderException maybe) {
                    boolean isShortBufferException
                            = maybe.getCause() instanceof ShortBufferException;
                    if (!isAEAD(p.transformation)
                            || !isBuggyProvider(provider)
                            || !isShortBufferException) {
                        throw maybe;
                    }
                }
            } else {
                throw new AssertionError("Define your behavior here for " + provider);
            }
        }

        // Cipher might be in unspecified state from failures above.
        c.init(Cipher.DECRYPT_MODE, p.decryptKey, p.spec);

        // .doFinal(input)
        {
            if (p.aad != null) {
                c.updateAAD(p.aad);
            }
            final byte[] actualPlaintext = c.doFinal(p.ciphertext);
            assertEquals(Arrays.toString(p.plaintext), Arrays.toString(actualPlaintext));
        }

        // .doFinal(input, offset, len, output)
        {
            final byte[] largerThanCiphertext = new byte[p.ciphertext.length + 5];
            System.arraycopy(p.ciphertext, 0, largerThanCiphertext, 5, p.ciphertext.length);

            if (p.aad != null) {
                final byte[] largerThanAad = new byte[p.aad.length + 100];
                System.arraycopy(p.aad, 0, largerThanAad, 50, p.aad.length);
                assertTrue(p.aad.length > 1);
                c.updateAAD(largerThanAad, 50, 1);
                c.updateAAD(largerThanAad, 51, p.aad.length - 1);
            }

            final byte[] actualPlaintext = new byte[c.getOutputSize(p.ciphertext.length)];
            assertEquals(p.plaintext.length,
                    c.doFinal(largerThanCiphertext, 5, p.ciphertext.length, actualPlaintext));
            assertEquals(Arrays.toString(p.plaintext),
                    Arrays.toString(Arrays.copyOfRange(actualPlaintext, 0, p.plaintext.length)));
        }

        // .doFinal(input, offset, len, output, offset)
        {
            final byte[] largerThanCiphertext = new byte[p.ciphertext.length + 10];
            System.arraycopy(p.ciphertext, 0, largerThanCiphertext, 5, p.ciphertext.length);

            if (p.aad != null) {
                final byte[] largerThanAad = new byte[p.aad.length + 2];
                System.arraycopy(p.aad, 0, largerThanAad, 2, p.aad.length);
                c.updateAAD(largerThanAad, 2, p.aad.length);
            }

            final byte[] actualPlaintext = new byte[c.getOutputSize(p.ciphertext.length) + 2];
            assertEquals(p.plaintext.length,
                    c.doFinal(largerThanCiphertext, 5, p.ciphertext.length, actualPlaintext, 1));
            assertEquals(Arrays.toString(p.plaintext),
                    Arrays.toString(Arrays.copyOfRange(actualPlaintext, 1, p.plaintext.length + 1)));
        }

        if (!p.isStreamCipher && !p.transformation.endsWith("NOPADDING")
                && !isRandomizedEncryption(p.transformation)) {
            Cipher cNoPad = Cipher.getInstance(
                    getCipherTransformationWithNoPadding(p.transformation), provider);
            cNoPad.init(Cipher.DECRYPT_MODE, p.decryptKey, p.spec);

            if (p.aad != null) {
                c.updateAAD(p.aad);
            }
            final byte[] actualPlaintextPadded = cNoPad.doFinal(p.ciphertext);
            assertEquals(provider + ":" + cNoPad.getAlgorithm(),
                    Arrays.toString(p.plaintextPadded), Arrays.toString(actualPlaintextPadded));
        }

        // Test wrapping a key. Every cipher should be able to wrap.
        {
            // Generate a small SecretKey for AES.
            KeyGenerator kg = KeyGenerator.getInstance("AES");
            kg.init(128);
            SecretKey sk = kg.generateKey();

            // Wrap it
            c = Cipher.getInstance(p.transformation, provider);
            c.init(Cipher.WRAP_MODE, p.encryptKey, p.spec);
            byte[] cipherText = c.wrap(sk);

            // Unwrap it
            c.init(Cipher.UNWRAP_MODE, p.decryptKey, p.spec);
            Key decryptedKey = c.unwrap(cipherText, sk.getAlgorithm(), Cipher.SECRET_KEY);

            assertEquals(
                    "sk.getAlgorithm()=" + sk.getAlgorithm() + " decryptedKey.getAlgorithm()="
                            + decryptedKey.getAlgorithm() + " encryptKey.getEncoded()="
                            + Arrays.toString(sk.getEncoded()) + " decryptedKey.getEncoded()="
                            + Arrays.toString(decryptedKey.getEncoded()), sk, decryptedKey);
        }
    }
For each test vector in the list, tests that the transformation is supported by at least one
provider and that all implementations of the transformation pass the Known Answer Test (KAT)
as well as other functional tests.
common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java