blob: 11b757e0a8d066083f67b3b572e5164d969a4c4d [file] [log] [blame]
/* GENERATED SOURCE. DO NOT MODIFY. */
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.org.conscrypt;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import com.android.org.conscrypt.java.security.StandardNames;
import com.android.org.conscrypt.java.security.TestKeyStore;
import com.android.org.conscrypt.testing.Streams;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assume;
/**
* Utility methods to support testing.
* @hide This class is not part of the Android public SDK API
*/
public final class TestUtils {
public static final Charset UTF_8 = StandardCharsets.UTF_8;
private static final String PROTOCOL_TLS_V1_3 = "TLSv1.3";
private static final String PROTOCOL_TLS_V1_2 = "TLSv1.2";
private static final String PROTOCOL_TLS_V1_1 = "TLSv1.1";
// For interop testing we need a JDK Provider that can do TLS 1.2 as 1.x may be disabled
// in Conscrypt and 1.3 does not (yet) handle interoperability with the JDK Provider.
private static final String[] DESIRED_JDK_PROTOCOLS = new String[] {PROTOCOL_TLS_V1_2};
private static final Provider JDK_PROVIDER = getNonConscryptTlsProvider();
private static final byte[] CHARS =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".getBytes(UTF_8);
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0);
static final String TEST_CIPHER = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
private TestUtils() {}
private static Provider getNonConscryptTlsProvider() {
for (String protocol : DESIRED_JDK_PROTOCOLS) {
for (Provider p : Security.getProviders()) {
if (!p.getClass().getPackage().getName().contains("conscrypt")
&& hasSslContext(p, protocol)) {
return p;
}
}
}
return new BouncyCastleProvider();
}
private static boolean hasSslContext(Provider p, String protocol) {
return p.get("SSLContext." + protocol) != null;
}
static Provider getJdkProvider() {
return JDK_PROVIDER;
}
public static boolean isClassAvailable(String classname) {
try {
Class.forName(classname);
return true;
} catch (ClassNotFoundException ignore) {
// Ignored
}
return false;
}
private static void assumeClassAvailable(String classname) {
Assume.assumeTrue("Skipping test: " + classname + " unavailable",
isClassAvailable(classname));
}
public static void assumeSNIHostnameAvailable() {
assumeClassAvailable("javax.net.ssl.SNIHostName");
}
public static void assumeExtendedTrustManagerAvailable() {
assumeClassAvailable("javax.net.ssl.X509ExtendedTrustManager");
}
public static void assumeSetEndpointIdentificationAlgorithmAvailable() {
boolean supported = false;
try {
SSLParameters.class.getMethod("setEndpointIdentificationAlgorithm", String.class);
supported = true;
} catch (NoSuchMethodException ignore) {
// Ignored
}
Assume.assumeTrue("Skipping test: "
+ "SSLParameters.setEndpointIdentificationAlgorithm unavailable", supported);
}
public static void assumeAEADAvailable() {
assumeClassAvailable("javax.crypto.AEADBadTagException");
}
private static boolean isAndroid() {
try {
Class.forName("android.app.Application", false, ClassLoader.getSystemClassLoader());
return true;
} catch (Throwable ignored) {
// Failed to load the class uniquely available in Android.
return false;
}
}
// Is a pre-Android 12 mainline module installed. Detect based on a class that
// was renamed in the Android 12 codebase.
private static boolean isBeforeAndroid12Mainline() {
return isClassAvailable("com.android.org.conscrypt.CertBlacklistImpl");
}
public static void assumeAndroid() {
Assume.assumeTrue(isAndroid());
}
public static void assumeBeforeAndroid12Mainline() {
Assume.assumeTrue(isAndroid() && isBeforeAndroid12Mainline());
}
// Assume a pre-Android 12 mainline module is installed.
public static void assumeAllowsUnsignedCrypto() {
// The Oracle JRE disallows loading crypto providers from unsigned jars
Assume.assumeTrue(isAndroid()
|| !System.getProperty("java.vm.name").contains("HotSpot"));
}
public static void assumeSHA2WithDSAAvailable() {
boolean available;
try {
Signature.getInstance("SHA256withDSA");
available = true;
} catch (NoSuchAlgorithmException e) {
available = false;
}
Assume.assumeTrue("SHA2 with DSA signatures not available", available);
}
private static Method findMethod(Class<?> cls, String methodName, Class<?>... methodParams) {
try {
return cls.getDeclaredMethod(methodName, methodParams);
} catch (NoSuchMethodException e) {
return null;
}
}
public static Method findWrapVerifierMethod() {
return findMethod(Conscrypt.class, "wrapHostnameVerifier", HostnameVerifier.class);
}
public static InetAddress getLoopbackAddress() {
try {
Method method = InetAddress.class.getMethod("getLoopbackAddress");
return (InetAddress) method.invoke(null);
} catch (Exception ignore) {
// Ignored.
}
try {
return InetAddress.getLocalHost();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
public static Provider getConscryptProvider() {
try {
String defaultName = (String) conscryptClass("Platform")
.getDeclaredMethod("getDefaultProviderName")
.invoke(null);
Constructor<?> c =
conscryptClass("OpenSSLProvider")
.getDeclaredConstructor(String.class, Boolean.TYPE, String.class);
if (!isClassAvailable("javax.net.ssl.X509ExtendedTrustManager")) {
return (Provider) c.newInstance(defaultName, false, "TLSv1.3");
} else {
return (Provider) c.newInstance(defaultName, true, "TLSv1.3");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static synchronized void installConscryptAsDefaultProvider() {
Provider conscryptProvider = getConscryptProvider();
Provider[] providers = Security.getProviders();
if (providers.length == 0 || !providers[0].equals(conscryptProvider)) {
Security.insertProviderAt(conscryptProvider, 1);
}
}
public static InputStream openTestFile(String name) throws FileNotFoundException {
InputStream is = TestUtils.class.getResourceAsStream("/" + name);
if (is == null) {
throw new FileNotFoundException(name);
}
return is;
}
public static byte[] readTestFile(String name) throws IOException {
return Streams.readFully(openTestFile(name));
}
public static PublicKey readPublicKeyPemFile(String name)
throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
String keyData = new String(readTestFile(name), "US-ASCII");
keyData = keyData.replace("-----BEGIN PUBLIC KEY-----", "");
keyData = keyData.replace("-----END PUBLIC KEY-----", "");
keyData = keyData.replace("\r", "");
keyData = keyData.replace("\n", "");
return KeyFactory.getInstance("EC").generatePublic(
new X509EncodedKeySpec(decodeBase64(keyData)));
}
/**
* Looks up the conscrypt class for the given simple name (i.e. no package prefix).
*/
public static Class<?> conscryptClass(String simpleName) throws ClassNotFoundException {
ClassNotFoundException ex = null;
for (String packageName : new String[] {"com.android.org.conscrypt", "com.android.com.android.org.conscrypt"}) {
String name = packageName + "." + simpleName;
try {
return Class.forName(name);
} catch (ClassNotFoundException e) {
ex = e;
}
}
throw ex;
}
static SSLSocketFactory setUseEngineSocket(
SSLSocketFactory conscryptFactory, boolean useEngineSocket) {
try {
Class<?> clazz = conscryptClass("Conscrypt");
Method method =
clazz.getMethod("setUseEngineSocket", SSLSocketFactory.class, boolean.class);
method.invoke(null, conscryptFactory, useEngineSocket);
return conscryptFactory;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static SSLServerSocketFactory setUseEngineSocket(
SSLServerSocketFactory conscryptFactory, boolean useEngineSocket) {
try {
Class<?> clazz = conscryptClass("Conscrypt");
Method method = clazz.getMethod(
"setUseEngineSocket", SSLServerSocketFactory.class, boolean.class);
method.invoke(null, conscryptFactory, useEngineSocket);
return conscryptFactory;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void setUseSessionTickets(SSLSocket socket, boolean useTickets) {
try {
Class<?> clazz = conscryptClass("Conscrypt");
Method method = clazz.getMethod("setUseSessionTickets", SSLSocket.class, boolean.class);
method.invoke(null, socket, useTickets);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static SSLSocketFactory getConscryptSocketFactory(boolean useEngineSocket) {
return setUseEngineSocket(getSocketFactory(getConscryptProvider()), useEngineSocket);
}
public static SSLServerSocketFactory getConscryptServerSocketFactory(boolean useEngineSocket) {
return setUseEngineSocket(getServerSocketFactory(getConscryptProvider()), useEngineSocket);
}
private static SSLSocketFactory getSocketFactory(Provider provider) {
SSLContext clientContext = initClientSslContext(newContext(provider));
return clientContext.getSocketFactory();
}
private static SSLServerSocketFactory getServerSocketFactory(Provider provider) {
SSLContext serverContext = initServerSslContext(newContext(provider));
return serverContext.getServerSocketFactory();
}
static SSLContext newContext(Provider provider) {
try {
return SSLContext.getInstance("TLS", provider);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static String highestCommonProtocol() {
String[] common = getCommonProtocolSuites();
Arrays.sort(common);
return common[common.length - 1];
}
public static String[] getCommonProtocolSuites() {
SSLContext jdkContext = newClientSslContext(getJdkProvider());
SSLContext conscryptContext = newClientSslContext(getConscryptProvider());
// No point building a Set here due to small list sizes.
final List<String> conscryptProtocols = getSupportedProtocols(conscryptContext);
// TODO(prb): Certificate auth fails when connecting Conscrypt and JDK's TLS 1.3.
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String string) {
return conscryptProtocols.contains(string) && !string.equals(PROTOCOL_TLS_V1_3);
}
};
return getSupportedProtocols(jdkContext, predicate);
}
public static String[] getCommonCipherSuites() {
SSLContext jdkContext = newClientSslContext(getJdkProvider());
SSLContext conscryptContext = newClientSslContext(getConscryptProvider());
final Set<String> conscryptCiphers = new HashSet<>(getSupportedCiphers(conscryptContext));
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String string) {
return isTlsCipherSuite(string) && conscryptCiphers.contains(string);
}
};
return getSupportedCiphers(jdkContext, predicate);
}
public static List<String> getSupportedCiphers(SSLContext ctx) {
return Arrays.asList(ctx.getDefaultSSLParameters().getCipherSuites());
}
public static String[] getSupportedCiphers(SSLContext ctx, Predicate<String> predicate) {
IntFunction<String[]> transform = new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
};
return Arrays.stream(ctx.getDefaultSSLParameters().getCipherSuites())
.filter(predicate)
.toArray(transform);
}
public static List<String> getSupportedProtocols(SSLContext ctx) {
return Arrays.asList(ctx.getDefaultSSLParameters().getProtocols());
}
public static String[] getSupportedProtocols(SSLContext ctx, Predicate<String> predicate) {
IntFunction<String[]> transform = new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[value];
}
};
return Arrays.stream(ctx.getDefaultSSLParameters().getProtocols())
.filter(predicate)
.toArray(transform);
}
private static boolean isTlsCipherSuite(String cipher) {
return !cipher.startsWith("SSL_") && !cipher.startsWith("TLS_EMPTY")
&& !cipher.contains("_RC4_");
}
public static void assumeTlsV11Enabled(SSLContext context) {
Assume.assumeTrue(getSupportedProtocols(context).contains(PROTOCOL_TLS_V1_1));
}
/**
* Picks a port that is not used right at this moment.
* Warning: Not thread safe. May see "BindException: Address already in use: bind" if using the
* returned port to create a new server socket when other threads/processes are concurrently
* creating new sockets without a specific port.
*/
public static int pickUnusedPort() {
try {
ServerSocket serverSocket = new ServerSocket(0);
int port = serverSocket.getLocalPort();
serverSocket.close();
return port;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Creates a text message of the given length.
*/
public static byte[] newTextMessage(int length) {
byte[] msg = new byte[length];
for (int msgIndex = 0; msgIndex < length;) {
int remaining = length - msgIndex;
int numChars = Math.min(remaining, CHARS.length);
System.arraycopy(CHARS, 0, msg, msgIndex, numChars);
msgIndex += numChars;
}
return msg;
}
static SSLContext newClientSslContext(Provider provider) {
SSLContext context = newContext(provider);
return initClientSslContext(context);
}
static SSLContext newServerSslContext(Provider provider) {
SSLContext context = newContext(provider);
return initServerSslContext(context);
}
/**
* Initializes the given client-side {@code context} with a default cert.
*/
public static SSLContext initClientSslContext(SSLContext context) {
return initSslContext(context, TestKeyStore.getClient());
}
/**
* Initializes the given server-side {@code context} with the given cert chain and private key.
*/
public static SSLContext initServerSslContext(SSLContext context) {
return initSslContext(context, TestKeyStore.getServer());
}
/**
* Initializes the given {@code context} from the {@code keyStore}.
*/
static SSLContext initSslContext(SSLContext context, TestKeyStore keyStore) {
try {
context.init(keyStore.keyManagers, keyStore.trustManagers, null);
return context;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Performs the intial TLS handshake between the two {@link SSLEngine} instances.
*/
public static void doEngineHandshake(SSLEngine clientEngine, SSLEngine serverEngine,
ByteBuffer clientAppBuffer, ByteBuffer clientPacketBuffer, ByteBuffer serverAppBuffer,
ByteBuffer serverPacketBuffer, boolean beginHandshake) throws SSLException {
if (beginHandshake) {
clientEngine.beginHandshake();
serverEngine.beginHandshake();
}
SSLEngineResult clientResult;
SSLEngineResult serverResult;
boolean clientHandshakeFinished = false;
boolean serverHandshakeFinished = false;
do {
int cTOsPos = clientPacketBuffer.position();
int sTOcPos = serverPacketBuffer.position();
clientResult = clientEngine.wrap(EMPTY_BUFFER, clientPacketBuffer);
runDelegatedTasks(clientResult, clientEngine);
serverResult = serverEngine.wrap(EMPTY_BUFFER, serverPacketBuffer);
runDelegatedTasks(serverResult, serverEngine);
// Verify that the consumed and produced number match what is in the buffers now.
assertEquals(0, clientResult.bytesConsumed());
assertEquals(0, serverResult.bytesConsumed());
assertEquals(clientPacketBuffer.position() - cTOsPos, clientResult.bytesProduced());
assertEquals(serverPacketBuffer.position() - sTOcPos, serverResult.bytesProduced());
clientPacketBuffer.flip();
serverPacketBuffer.flip();
// Verify that we only had one SSLEngineResult.HandshakeStatus.FINISHED
if (isHandshakeFinished(clientResult)) {
assertFalse(clientHandshakeFinished);
clientHandshakeFinished = true;
}
if (isHandshakeFinished(serverResult)) {
assertFalse(serverHandshakeFinished);
serverHandshakeFinished = true;
}
cTOsPos = clientPacketBuffer.position();
sTOcPos = serverPacketBuffer.position();
int clientAppReadBufferPos = clientAppBuffer.position();
int serverAppReadBufferPos = serverAppBuffer.position();
clientResult = clientEngine.unwrap(serverPacketBuffer, clientAppBuffer);
runDelegatedTasks(clientResult, clientEngine);
serverResult = serverEngine.unwrap(clientPacketBuffer, serverAppBuffer);
runDelegatedTasks(serverResult, serverEngine);
// Verify that the consumed and produced number match what is in the buffers now.
assertEquals(serverPacketBuffer.position() - sTOcPos, clientResult.bytesConsumed());
assertEquals(clientPacketBuffer.position() - cTOsPos, serverResult.bytesConsumed());
assertEquals(clientAppBuffer.position() - clientAppReadBufferPos,
clientResult.bytesProduced());
assertEquals(serverAppBuffer.position() - serverAppReadBufferPos,
serverResult.bytesProduced());
clientPacketBuffer.compact();
serverPacketBuffer.compact();
// Verify that we only had one SSLEngineResult.HandshakeStatus.FINISHED
if (isHandshakeFinished(clientResult)) {
assertFalse(clientHandshakeFinished);
clientHandshakeFinished = true;
}
if (isHandshakeFinished(serverResult)) {
assertFalse(serverHandshakeFinished);
serverHandshakeFinished = true;
}
} while (!clientHandshakeFinished || !serverHandshakeFinished);
}
private static boolean isHandshakeFinished(SSLEngineResult result) {
return result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED;
}
private static void runDelegatedTasks(SSLEngineResult result, SSLEngine engine) {
if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
for (;;) {
Runnable task = engine.getDelegatedTask();
if (task == null) {
break;
}
task.run();
}
}
}
public static String pickArbitraryNonTls13Suite(String[] cipherSuites) {
return pickArbitraryNonTls13Suite(Arrays.asList(cipherSuites));
}
public static String pickArbitraryNonTls13Suite(Iterable<String> cipherSuites) {
for (String cipherSuite : cipherSuites) {
if (!StandardNames.CIPHER_SUITES_TLS13.contains(cipherSuite)) {
return cipherSuite;
}
}
fail("No non-TLSv1.3 cipher suite available.");
return null;
}
/**
* Decodes the provided hexadecimal string into a byte array. Odd-length inputs
* are not allowed.
*
* Throws an {@code IllegalArgumentException} if the input is malformed.
*/
public static byte[] decodeHex(String encoded) throws IllegalArgumentException {
return decodeHex(encoded.toCharArray());
}
/**
* Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar}
* is {@code true} odd-length inputs are allowed and the first character is interpreted
* as the lower bits of the first result byte.
*
* Throws an {@code IllegalArgumentException} if the input is malformed.
*/
public static byte[] decodeHex(String encoded, boolean allowSingleChar) throws IllegalArgumentException {
return decodeHex(encoded.toCharArray(), allowSingleChar);
}
/**
* Decodes the provided hexadecimal string into a byte array. Odd-length inputs
* are not allowed.
*
* Throws an {@code IllegalArgumentException} if the input is malformed.
*/
public static byte[] decodeHex(char[] encoded) throws IllegalArgumentException {
return decodeHex(encoded, false);
}
/**
* Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar}
* is {@code true} odd-length inputs are allowed and the first character is interpreted
* as the lower bits of the first result byte.
*
* Throws an {@code IllegalArgumentException} if the input is malformed.
*/
public static byte[] decodeHex(char[] encoded, boolean allowSingleChar) throws IllegalArgumentException {
int resultLengthBytes = (encoded.length + 1) / 2;
byte[] result = new byte[resultLengthBytes];
int resultOffset = 0;
int i = 0;
if (allowSingleChar) {
if ((encoded.length % 2) != 0) {
// Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
result[resultOffset++] = (byte) toDigit(encoded, i);
i++;
}
} else {
if ((encoded.length % 2) != 0) {
throw new IllegalArgumentException("Invalid input length: " + encoded.length);
}
}
for (int len = encoded.length; i < len; i += 2) {
result[resultOffset++] = (byte) ((toDigit(encoded, i) << 4) | toDigit(encoded, i + 1));
}
return result;
}
private static int toDigit(char[] str, int offset) throws IllegalArgumentException {
// NOTE: that this isn't really a code point in the traditional sense, since we're
// just rejecting surrogate pairs outright.
int pseudoCodePoint = str[offset];
if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') {
return pseudoCodePoint - '0';
} else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') {
return 10 + (pseudoCodePoint - 'a');
} else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') {
return 10 + (pseudoCodePoint - 'A');
}
throw new IllegalArgumentException("Illegal char: " + str[offset] +
" at offset " + offset);
}
private static final String BASE64_ALPHABET =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
public static String encodeBase64(byte[] data) {
// Base64 was introduced in Java 8, so if it's not available we can use a hacky
// solution that works in previous versions
if (isClassAvailable("java.util.Base64")) {
return Base64.getEncoder().encodeToString(data);
} else {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < data.length; i += 3) {
int padding = (i + 2 < data.length) ? 0 : (i + 3 - data.length);
byte b1 = data[i];
byte b2 = padding >= 2 ? 0 : data[i+1];
byte b3 = padding >= 1 ? 0 : data[i+2];
char c1 = BASE64_ALPHABET.charAt((b1 & 0xFF) >>> 2);
char c2 = BASE64_ALPHABET.charAt(((b1 & 0x03) << 4) | ((b2 & 0xFF) >>> 4));
char c3 = BASE64_ALPHABET.charAt(((b2 & 0x0F) << 2) | ((b3 & 0xFF) >>> 6));
char c4 = BASE64_ALPHABET.charAt(b3 & 0x3F);
if (padding >= 1) {
c4 = '=';
}
if (padding >= 2) {
c3 = '=';
}
builder.append(c1).append(c2).append(c3).append(c4);
}
return builder.toString();
}
}
public static byte[] decodeBase64(String data) {
// Base64 was introduced in Java 8, so if it's not available we can use a hacky
// solution that works in previous versions
if (isClassAvailable("java.util.Base64")) {
return Base64.getDecoder().decode(data);
} else {
while (data.endsWith("=")) {
data = data.substring(0, data.length() - 1);
}
int padding = (data.length() % 4 == 0) ? 0 : 4 - (data.length() % 4);
byte[] output = new byte[((data.length() - 1) / 4) * 3 + 3 - padding];
int outputindex = 0;
for (int i = 0; i < data.length(); i += 4) {
char c1 = data.charAt(i);
char c2 = data.charAt(i+1);
char c3 = (i+2 < data.length()) ? data.charAt(i+2) : 'A';
char c4 = (i+3 < data.length()) ? data.charAt(i+3) : 'A';
byte b1 = (byte)
(BASE64_ALPHABET.indexOf(c1) << 2 | BASE64_ALPHABET.indexOf(c2) >>> 4);
byte b2 = (byte)
((BASE64_ALPHABET.indexOf(c2) & 0x0F) << 4 | BASE64_ALPHABET.indexOf(c3) >>> 2);
byte b3 = (byte)
((BASE64_ALPHABET.indexOf(c3) & 0x03) << 6 | BASE64_ALPHABET.indexOf(c4));
output[outputindex++] = b1;
if (outputindex < output.length) {
output[outputindex++] = b2;
}
if (outputindex < output.length) {
output[outputindex++] = b3;
}
}
return output;
}
}
public static boolean isJavaVersion(int version) {
return javaVersion() >= version;
}
private static int javaVersion() {
String[] v = System.getProperty("java.specification.version", "1.6").split("\\.", -1);
if ("1".equals(v[0])) {
return Integer.parseInt(v[1]);
}
return Integer.parseInt(v[0]);
}
public static void assumeJava8() {
Assume.assumeTrue("Require Java 8: " + javaVersion(), isJavaVersion(8));
}
}