diff options
Diffstat (limited to 'luni/src/test')
93 files changed, 6063 insertions, 1293 deletions
diff --git a/luni/src/test/java/com/android/org/bouncycastle/crypto/digests/DigestTest.java b/luni/src/test/java/com/android/org/bouncycastle/crypto/digests/DigestTest.java index 8708214..fce8507 100644 --- a/luni/src/test/java/com/android/org/bouncycastle/crypto/digests/DigestTest.java +++ b/luni/src/test/java/com/android/org/bouncycastle/crypto/digests/DigestTest.java @@ -19,6 +19,7 @@ package com.android.org.bouncycastle.crypto.digests; import junit.framework.TestCase; import com.android.org.bouncycastle.crypto.Digest; import com.android.org.bouncycastle.crypto.ExtendedDigest; +import tests.util.SummaryStatistics; /** * Implements unit tests for our JNI wrapper around OpenSSL. We use the @@ -36,6 +37,7 @@ public class DigestTest extends TestCase { * @param newDigest The new digest implementation, provided by OpenSSL */ public void doTestMessageDigest(Digest oldDigest, Digest newDigest) { + final int WARMUP = 10; final int ITERATIONS = 100; byte[] data = new byte[1024]; @@ -54,27 +56,31 @@ public class DigestTest extends TestCase { data[i] = (byte)i; } - long oldTime = 0; - long newTime = 0; + SummaryStatistics oldTime = new SummaryStatistics(); + SummaryStatistics newTime = new SummaryStatistics(); - for (int j = 0; j < ITERATIONS; j++) { - long t0 = System.currentTimeMillis(); + for (int j = 0; j < ITERATIONS + WARMUP; j++) { + long t0 = System.nanoTime(); for (int i = 0; i < 4; i++) { oldDigest.update(data, 0, data.length); } int oldLength = oldDigest.doFinal(oldHash, 0); - long t1 = System.currentTimeMillis(); + long t1 = System.nanoTime(); - oldTime = oldTime + (t1 - t0); + if (j >= WARMUP) { + oldTime.add(t1 - t0); + } - long t2 = System.currentTimeMillis(); + long t2 = System.nanoTime(); for (int i = 0; i < 4; i++) { newDigest.update(data, 0, data.length); } int newLength = newDigest.doFinal(newHash, 0); - long t3 = System.currentTimeMillis(); + long t3 = System.nanoTime(); - newTime = newTime + (t3 - t2); + if (j >= WARMUP) { + newTime.add(t3 - t2); + } assertEquals("Hash sizes must be equal", oldLength, newLength); @@ -83,10 +89,13 @@ public class DigestTest extends TestCase { } } - System.out.println("Time for " + ITERATIONS + " x old hash processing: " + oldTime + " ms"); - System.out.println("Time for " + ITERATIONS + " x new hash processing: " + newTime + " ms"); + System.out.println("Time for " + ITERATIONS + " x old hash processing: " + + oldTime.toString()); + System.out.println("Time for " + ITERATIONS + " x new hash processing: " + + newTime.toString()); - assertTrue("New hash should be faster", newTime < oldTime); + assertTrue("New hash should be faster:\nold=" + oldTime.toString() + "\nnew=" + + newTime.toString(), newTime.mean() < oldTime.mean()); } /** diff --git a/luni/src/test/java/com/android/org/bouncycastle/jce/provider/CertBlacklistTest.java b/luni/src/test/java/com/android/org/bouncycastle/jce/provider/CertBlacklistTest.java index 8627225..48a175c 100644 --- a/luni/src/test/java/com/android/org/bouncycastle/jce/provider/CertBlacklistTest.java +++ b/luni/src/test/java/com/android/org/bouncycastle/jce/provider/CertBlacklistTest.java @@ -401,11 +401,6 @@ public class CertBlacklistTest extends TestCase { assertEquals(bl, getCurrentSerialBlacklist()); } - public void testTurkTrustIntermediate1SerialBlacklist() throws Exception { - CertBlacklist bl = new CertBlacklist(); - assertEquals(bl.isSerialNumberBlackListed(createSerialNumber(TURKTRUST_1)), true); - } - public void testTurkTrustIntermediate1PubkeyBlacklist() throws Exception { // build the public key PublicKey pk = createPublicKey(TURKTRUST_1); @@ -417,11 +412,6 @@ public class CertBlacklistTest extends TestCase { assertEquals(bl.isPublicKeyBlackListed(pk), true); } - public void testTurkTrustIntermediate2SerialBlacklist() throws Exception { - CertBlacklist bl = new CertBlacklist(); - assertEquals(bl.isSerialNumberBlackListed(createSerialNumber(TURKTRUST_2)), true); - } - public void testTurkTrustIntermediate2PubkeyBlacklist() throws Exception { // build the public key PublicKey pk = createPublicKey(TURKTRUST_2); @@ -431,11 +421,6 @@ public class CertBlacklistTest extends TestCase { assertEquals(bl.isPublicKeyBlackListed(pk), true); } - public void testANSSISerialBlacklist() throws Exception { - CertBlacklist bl = new CertBlacklist(); - assertEquals(bl.isSerialNumberBlackListed(createSerialNumber(ANSSI)), true); - } - public void testANSSIIntermediatePubkeyBlacklist() throws Exception { // build the public key PublicKey pk = createPublicKey(ANSSI); diff --git a/luni/src/test/java/libcore/dalvik/system/PathClassLoaderTest.java b/luni/src/test/java/libcore/dalvik/system/PathClassLoaderTest.java index 9e6d8d7..31e8fc7 100644 --- a/luni/src/test/java/libcore/dalvik/system/PathClassLoaderTest.java +++ b/luni/src/test/java/libcore/dalvik/system/PathClassLoaderTest.java @@ -17,9 +17,12 @@ package libcore.dalvik.system; import dalvik.system.PathClassLoader; +import java.lang.reflect.Method; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import libcore.io.Streams; import junit.framework.TestCase; public final class PathClassLoaderTest extends TestCase { @@ -52,6 +55,26 @@ public final class PathClassLoaderTest extends TestCase { return result; } + public void testAppUseOfPathClassLoader() throws Exception { + // Extract loading-test.jar from the resource. + ClassLoader pcl = PathClassLoaderTest.class.getClassLoader(); + File jar = File.createTempFile("loading-test", ".jar"); + try (InputStream in = pcl.getResourceAsStream("dalvik/system/loading-test.jar"); + FileOutputStream out = new FileOutputStream(jar)) { + Streams.copy(in, out); + } + + // Execute code from the jar file using a PathClassLoader. + PathClassLoader cl = new PathClassLoader(jar.getPath(), pcl); + Class c = cl.loadClass("test.Test1"); + Method m = c.getMethod("test", (Class[]) null); + String result = (String) m.invoke(null, (Object[]) null); + assertSame("blort", result); + + // Clean up the extracted jar file. + assertTrue(jar.delete()); + } + @Override protected void setUp() throws Exception { super.setUp(); } diff --git a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java index 1c1296b..4c22371 100644 --- a/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java +++ b/luni/src/test/java/libcore/icu/DateIntervalFormatTest.java @@ -16,10 +16,12 @@ package libcore.icu; -import java.util.Calendar; -import java.util.Locale; -import java.util.TimeZone; -import static libcore.icu.DateIntervalFormat.*; +import android.icu.util.Calendar; +import android.icu.util.TimeZone; +import android.icu.util.ULocale; + +import static libcore.icu.DateIntervalFormat.formatDateRange; +import static libcore.icu.DateUtilsBridge.*; public class DateIntervalFormatTest extends junit.framework.TestCase { private static final long MINUTE = 60 * 1000; @@ -32,7 +34,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { public void test_formatDateInterval() throws Exception { TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); - Calendar c = Calendar.getInstance(tz, Locale.US); + Calendar c = Calendar.getInstance(tz, ULocale.US); c.set(Calendar.MONTH, Calendar.JANUARY); c.set(Calendar.DAY_OF_MONTH, 19); c.set(Calendar.HOUR_OF_DAY, 3); @@ -51,10 +53,10 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { long noonDuration = (8 * 60 + 30) * 60 * 1000 - 15 * 1000; long midnightDuration = (3 * 60 + 30) * 60 * 1000 + 15 * 1000; - Locale de_DE = new Locale("de", "DE"); - Locale en_US = new Locale("en", "US"); - Locale es_ES = new Locale("es", "ES"); - Locale es_US = new Locale("es", "US"); + ULocale de_DE = new ULocale("de", "DE"); + ULocale en_US = new ULocale("en", "US"); + ULocale es_ES = new ULocale("es", "ES"); + ULocale es_US = new ULocale("es", "US"); assertEquals("Monday", formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_WEEKDAY)); assertEquals("January 19", formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR, FORMAT_SHOW_DATE)); @@ -81,11 +83,11 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { assertEquals("1/19/2009 – 2/9/2012", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19.1.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); - assertEquals("19.01.2009 - 22.01.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); - assertEquals("19.01.2009 - 22.04.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); - assertEquals("19.01.2009 - 09.02.2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); + assertEquals("19.01.2009 – 22.01.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); + assertEquals("19.01.2009 – 22.04.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); + assertEquals("19.01.2009 – 09.02.2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); - assertEquals("1/19/2009", formatDateRange(es_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); + assertEquals("19/1/2009", formatDateRange(es_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19/1/2009–22/1/2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19/1/2009–22/4/2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19/1/2009–9/2/2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); @@ -114,60 +116,61 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // The same tests but for de_DE. - assertEquals("19.-22. Januar 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, 0)); - assertEquals("19.-22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("Mo., 19. - Do., 22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); - assertEquals("Montag, 19. - Donnerstag, 22. Januar 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); + assertEquals("19.–22. Januar 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, 0)); + assertEquals("19.–22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("Mo., 19. – Do., 22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); - assertEquals("19. Januar - 22. April 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, 0)); - assertEquals("19. Jan. - 22. Apr. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("Mo., 19. Jan. - Mi., 22. Apr. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); - assertEquals("Januar-April 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); + assertEquals("19. Januar – 22. April 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, 0)); + assertEquals("19. Jan. – 22. Apr. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("Mo., 19. Jan. – Mi., 22. Apr. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("Januar–April 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); - assertEquals("19. Jan. 2009 - 9. Feb. 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("Jan. 2009 - Feb. 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); - assertEquals("19. Januar 2009 - 9. Februar 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, 0)); - assertEquals("Montag, 19. Januar 2009 - Donnerstag, 9. Februar 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); + assertEquals("19. Jan. 2009 – 9. Feb. 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("Jan. 2009 – Feb. 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); + assertEquals("19. Januar 2009 – 9. Februar 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, 0)); + assertEquals("Montag, 19. Januar 2009 – Donnerstag, 9. Februar 2012", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); // The same tests but for es_US. - assertEquals("19–22 enero 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, 0)); - assertEquals("19–22 ene. 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene.–jue., 22 ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); - assertEquals("lunes, 19 enero–jueves, 22 enero de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); + assertEquals("19–22 de enero de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, 0)); + assertEquals("19 – 22 de ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("lun., 19 de ene. – jue., 22 de ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); - assertEquals("19 enero–22 abril de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0)); - assertEquals("19 ene.–22 abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene.–mié., 22 abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("19 de enero–22 de abril de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0)); + assertEquals("19 de ene. – 22 de abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("lun., 19 de ene. – mié., 22 de abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("enero–abril de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); - assertEquals("19 ene. de 2009–9 feb. de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("ene. 2009–feb. 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); - assertEquals("19 enero de 2009–9 febrero de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0)); - assertEquals("lunes, 19 enero de 2009–jueves, 9 febrero de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); + assertEquals("19 de ene. de 2009 – 9 de feb. de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("ene. de 2009 – feb. de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); + + assertEquals("19 de enero de 2009–9 de febrero de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0)); + assertEquals("lunes, 19 de enero de 2009–jueves, 9 de febrero de 2012", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); // The same tests but for es_ES. - assertEquals("19–22 enero 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0)); + assertEquals("19–22 de enero de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0)); assertEquals("19–22 ene. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene.–jue., 22 ene. de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); - assertEquals("lunes, 19 enero–jueves, 22 enero de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); + assertEquals("lun., 19 ene.–jue., 22 ene. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); - assertEquals("19 enero–22 abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0)); - assertEquals("19 ene.–22 abr. de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene.–mié., 22 abr. de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); + assertEquals("19 de enero–22 de abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0)); + assertEquals("19 ene.–22 abr. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("lun., 19 ene.–mié., 22 abr. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("enero–abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); - assertEquals("19 ene. de 2009–9 feb. de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); + assertEquals("19 ene. 2009–9 feb. 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); assertEquals("ene. 2009–feb. 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); - assertEquals("19 enero de 2009–9 febrero de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0)); - assertEquals("lunes, 19 enero de 2009–jueves, 9 febrero de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); + assertEquals("19 de enero de 2009–9 de febrero de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0)); + assertEquals("lunes, 19 de enero de 2009–jueves, 9 de febrero de 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY)); } // http://b/8862241 - we should be able to format dates past 2038. // See also http://code.google.com/p/android/issues/detail?id=13050. public void test8862241() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); Calendar c = Calendar.getInstance(tz, l); c.clear(); @@ -181,7 +184,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10089890 - we should take the given time zone into account. public void test10089890() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); TimeZone pacific = TimeZone.getTimeZone("America/Los_Angeles"); int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL | FORMAT_SHOW_TIME | FORMAT_24HOUR; @@ -203,7 +206,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { int abbr12 = time12 | FORMAT_ABBREV_ALL; int abbr24 = time24 | FORMAT_ABBREV_ALL; - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); // Full length on-the-hour times. @@ -230,7 +233,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10560853 - when the time is not displayed, an end time 0 ms into the next day is // considered to belong to the previous day. public void test10560853_when_time_not_displayed() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); long midnight = 0; @@ -254,7 +257,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10560853 - when the start and end times are otherwise on the same day, // an end time 0 ms into the next day is considered to belong to the previous day. public void test10560853_for_single_day_events() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE; @@ -266,7 +269,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // The fix for http://b/10560853 didn't work except for the day around the epoch, which was // all the unit test checked! public void test_single_day_events_later_than_epoch() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE; @@ -284,7 +287,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // The fix for http://b/10560853 didn't work except for UTC, which was // all the unit test checked! public void test_single_day_events_not_in_UTC() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone pacific = TimeZone.getTimeZone("America/Los_Angeles"); int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE; @@ -305,7 +308,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10209343 - even if the caller didn't explicitly ask us to include the year, // we should do so for years other than the current year. public void test10209343_when_not_this_year() { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_SHOW_TIME | FORMAT_24HOUR; @@ -328,7 +331,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/10209343 - for the current year, we should honor the FORMAT_SHOW_YEAR flags. public void test10209343_when_this_year() { // Construct a date in the current year (whenever the test happens to be run). - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); Calendar c = Calendar.getInstance(utc, l); c.set(Calendar.MONTH, Calendar.FEBRUARY); @@ -363,7 +366,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/8467515 - yet another y2k38 bug report. public void test8467515() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH | FORMAT_ABBREV_WEEKDAY; long t; @@ -383,7 +386,7 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { // http://b/12004664 public void test12004664() throws Exception { TimeZone utc = TimeZone.getTimeZone("UTC"); - Calendar c = Calendar.getInstance(utc, Locale.US); + Calendar c = Calendar.getInstance(utc, ULocale.US); c.clear(); c.set(Calendar.YEAR, 1980); c.set(Calendar.MONTH, Calendar.FEBRUARY); @@ -392,26 +395,26 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { long thisYear = c.getTimeInMillis(); int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_SHOW_YEAR; - assertEquals("Sunday, February 10, 1980", formatDateRange(new Locale("en", "US"), utc, thisYear, thisYear, flags)); + assertEquals("Sunday, February 10, 1980", formatDateRange(new ULocale("en", "US"), utc, thisYear, thisYear, flags)); - // If we supported non-Gregorian calendars, this is what that we'd expect for these locales. + // If we supported non-Gregorian calendars, this is what that we'd expect for these ULocales. // This is really the correct behavior, but since java.util.Calendar currently only supports // the Gregorian calendar, we want to deliberately force icu4c to agree, otherwise we'd have // a mix of calendars throughout an app's UI depending on whether Java or native code formatted // the date. - // assertEquals("یکشنبه ۲۱ بهمن ۱۳۵۸ ه.ش.", formatDateRange(new Locale("fa"), utc, thisYear, thisYear, flags)); - // assertEquals("AP ۱۳۵۸ سلواغه ۲۱, یکشنبه", formatDateRange(new Locale("ps"), utc, thisYear, thisYear, flags)); - // assertEquals("วันอาทิตย์ 10 กุมภาพันธ์ 2523", formatDateRange(new Locale("th"), utc, thisYear, thisYear, flags)); + // assertEquals("یکشنبه ۲۱ بهمن ۱۳۵۸ ه.ش.", formatDateRange(new ULocale("fa"), utc, thisYear, thisYear, flags)); + // assertEquals("AP ۱۳۵۸ سلواغه ۲۱, یکشنبه", formatDateRange(new ULocale("ps"), utc, thisYear, thisYear, flags)); + // assertEquals("วันอาทิตย์ 10 กุมภาพันธ์ 2523", formatDateRange(new ULocale("th"), utc, thisYear, thisYear, flags)); // For now, here are the localized Gregorian strings instead... - assertEquals("یکشنبه ۱۰ فوریهٔ ۱۹۸۰", formatDateRange(new Locale("fa"), utc, thisYear, thisYear, flags)); - assertEquals("یکشنبه د ۱۹۸۰ د فبروري ۱۰", formatDateRange(new Locale("ps"), utc, thisYear, thisYear, flags)); - assertEquals("วันอาทิตย์ 10 กุมภาพันธ์ 1980", formatDateRange(new Locale("th"), utc, thisYear, thisYear, flags)); + assertEquals("یکشنبه ۱۰ فوریهٔ ۱۹۸۰", formatDateRange(new ULocale("fa"), utc, thisYear, thisYear, flags)); + assertEquals("یکشنبه د ۱۹۸۰ د فبروري ۱۰", formatDateRange(new ULocale("ps"), utc, thisYear, thisYear, flags)); + assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ ค.ศ. 1980", formatDateRange(new ULocale("th"), utc, thisYear, thisYear, flags)); } // http://b/13234532 public void test13234532() throws Exception { - Locale l = Locale.US; + ULocale l = ULocale.US; TimeZone utc = TimeZone.getTimeZone("UTC"); int flags = FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL | FORMAT_12HOUR; @@ -420,4 +423,13 @@ public class DateIntervalFormatTest extends junit.framework.TestCase { assertEquals("11 AM – 1 PM", formatDateRange(l, utc, 11*HOUR, 13*HOUR, flags)); assertEquals("2 – 3 PM", formatDateRange(l, utc, 14*HOUR, 15*HOUR, flags)); } + + // http://b/20708022 + public void testEndOfDayOnLastDayOfMonth() throws Exception { + final ULocale locale = new ULocale("en"); + final TimeZone timeZone = TimeZone.getTimeZone("UTC"); + + assertEquals("11:00 PM – 12:00 AM", formatDateRange(locale, timeZone, + 1430434800000L, 1430438400000L, FORMAT_SHOW_TIME)); + } } diff --git a/luni/src/test/java/libcore/icu/ICUTest.java b/luni/src/test/java/libcore/icu/ICUTest.java index a7cc7a0..5eab070 100644 --- a/luni/src/test/java/libcore/icu/ICUTest.java +++ b/luni/src/test/java/libcore/icu/ICUTest.java @@ -20,6 +20,7 @@ import java.text.BreakIterator; import java.text.Collator; import java.util.Arrays; import java.util.Locale; +import libcore.util.ZoneInfoDB; public class ICUTest extends junit.framework.TestCase { public void test_getISOLanguages() throws Exception { @@ -152,24 +153,24 @@ public class ICUTest extends junit.framework.TestCase { assertEquals("sr_ME_#Latn", sr_Latn_ME.toString()); assertEquals("Latn", sr_Latn_ME.getScript()); - assertEquals("Српски", sr_Cyrl_BA.getDisplayLanguage(sr_Cyrl_BA)); + assertEquals("српски", sr_Cyrl_BA.getDisplayLanguage(sr_Cyrl_BA)); assertEquals("Босна и Херцеговина", sr_Cyrl_BA.getDisplayCountry(sr_Cyrl_BA)); - assertEquals("Ћирилица", sr_Cyrl_BA.getDisplayScript(sr_Cyrl_BA)); + assertEquals("ћирилица", sr_Cyrl_BA.getDisplayScript(sr_Cyrl_BA)); assertEquals("", sr_Cyrl_BA.getDisplayVariant(sr_Cyrl_BA)); - assertEquals("Српски", sr_Cyrl_ME.getDisplayLanguage(sr_Cyrl_ME)); + assertEquals("српски", sr_Cyrl_ME.getDisplayLanguage(sr_Cyrl_ME)); assertEquals("Црна Гора", sr_Cyrl_ME.getDisplayCountry(sr_Cyrl_ME)); - assertEquals("Ћирилица", sr_Cyrl_ME.getDisplayScript(sr_Cyrl_ME)); + assertEquals("ћирилица", sr_Cyrl_ME.getDisplayScript(sr_Cyrl_ME)); assertEquals("", sr_Cyrl_ME.getDisplayVariant(sr_Cyrl_ME)); - assertEquals("Srpski", sr_Latn_BA.getDisplayLanguage(sr_Latn_BA)); + assertEquals("srpski", sr_Latn_BA.getDisplayLanguage(sr_Latn_BA)); assertEquals("Bosna i Hercegovina", sr_Latn_BA.getDisplayCountry(sr_Latn_BA)); - assertEquals("Latinica", sr_Latn_BA.getDisplayScript(sr_Latn_BA)); + assertEquals("latinica", sr_Latn_BA.getDisplayScript(sr_Latn_BA)); assertEquals("", sr_Latn_BA.getDisplayVariant(sr_Latn_BA)); - assertEquals("Srpski", sr_Latn_ME.getDisplayLanguage(sr_Latn_ME)); + assertEquals("srpski", sr_Latn_ME.getDisplayLanguage(sr_Latn_ME)); assertEquals("Crna Gora", sr_Latn_ME.getDisplayCountry(sr_Latn_ME)); - assertEquals("Latinica", sr_Latn_ME.getDisplayScript(sr_Latn_ME)); + assertEquals("latinica", sr_Latn_ME.getDisplayScript(sr_Latn_ME)); assertEquals("", sr_Latn_ME.getDisplayVariant(sr_Latn_ME)); assertEquals("BIH", sr_Cyrl_BA.getISO3Country()); @@ -253,4 +254,14 @@ public class ICUTest extends junit.framework.TestCase { ICU.setDefaultLocale(initialDefaultLocale); } } + + /** Confirms that ICU agrees with the rest of libcore about the version of the TZ data in use. */ + public void testTimeZoneDataVersion() { + String icu4cTzVersion = ICU.getTZDataVersion(); + String zoneInfoTzVersion = ZoneInfoDB.getInstance().getVersion(); + assertEquals(icu4cTzVersion, zoneInfoTzVersion); + + String icu4jTzVersion = android.icu.util.TimeZone.getTZDataVersion(); + assertEquals(icu4jTzVersion, zoneInfoTzVersion); + } } diff --git a/luni/src/test/java/libcore/icu/LocaleDataTest.java b/luni/src/test/java/libcore/icu/LocaleDataTest.java index 0a83c53..09c3f0f 100644 --- a/luni/src/test/java/libcore/icu/LocaleDataTest.java +++ b/luni/src/test/java/libcore/icu/LocaleDataTest.java @@ -24,7 +24,7 @@ public class LocaleDataTest extends junit.framework.TestCase { for (Locale l : Locale.getAvailableLocales()) { LocaleData d = LocaleData.get(l); // System.err.format("%20s %s %s %s\n", l, d.yesterday, d.today, d.tomorrow); - // System.err.format("%20s %10s %10s\n", l, d.timeFormat12, d.timeFormat24); + // System.err.format("%20s %10s %10s\n", l, d.timeFormat_hm, d.timeFormat_Hm); } } @@ -73,7 +73,7 @@ public class LocaleDataTest extends junit.framework.TestCase { assertEquals("leden", l.longStandAloneMonthNames[0]); assertEquals("led", l.shortStandAloneMonthNames[0]); - assertEquals("l", l.tinyStandAloneMonthNames[0]); + assertEquals("1", l.tinyStandAloneMonthNames[0]); } public void test_ko_KR() throws Exception { @@ -124,11 +124,11 @@ public class LocaleDataTest extends junit.framework.TestCase { // http://b/7924970 public void testTimeFormat12And24() throws Exception { LocaleData en_US = LocaleData.get(Locale.US); - assertEquals("h:mm a", en_US.timeFormat12); - assertEquals("HH:mm", en_US.timeFormat24); + assertEquals("h:mm a", en_US.timeFormat_hm); + assertEquals("HH:mm", en_US.timeFormat_Hm); LocaleData ja_JP = LocaleData.get(Locale.JAPAN); - assertEquals("aK:mm", ja_JP.timeFormat12); - assertEquals("H:mm", ja_JP.timeFormat24); + assertEquals("aK:mm", ja_JP.timeFormat_hm); + assertEquals("H:mm", ja_JP.timeFormat_Hm); } } diff --git a/luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java b/luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java new file mode 100644 index 0000000..101896f --- /dev/null +++ b/luni/src/test/java/libcore/icu/RelativeDateTimeFormatterTest.java @@ -0,0 +1,667 @@ +/* + * 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 libcore.icu; + +import android.icu.util.ULocale; + +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +import static libcore.icu.DateUtilsBridge.FORMAT_ABBREV_ALL; +import static libcore.icu.DateUtilsBridge.FORMAT_ABBREV_RELATIVE; +import static libcore.icu.DateUtilsBridge.FORMAT_NO_YEAR; +import static libcore.icu.DateUtilsBridge.FORMAT_NUMERIC_DATE; +import static libcore.icu.DateUtilsBridge.FORMAT_SHOW_YEAR; +import static libcore.icu.RelativeDateTimeFormatter.DAY_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.HOUR_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.MINUTE_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.SECOND_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.WEEK_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.YEAR_IN_MILLIS; +import static libcore.icu.RelativeDateTimeFormatter.getRelativeDateTimeString; +import static libcore.icu.RelativeDateTimeFormatter.getRelativeTimeSpanString; + +public class RelativeDateTimeFormatterTest extends junit.framework.TestCase { + + // Tests adopted from CTS tests for DateUtils.getRelativeTimeSpanString. + public void test_getRelativeTimeSpanStringCTS() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("GMT"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2015 at 10:50 GMT + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long baseTime = cal.getTimeInMillis(); + + assertEquals("0 minutes ago", + getRelativeTimeSpanString(en_US, tz, baseTime - SECOND_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + assertEquals("In 0 minutes", + getRelativeTimeSpanString(en_US, tz, baseTime + SECOND_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + + assertEquals("1 minute ago", + getRelativeTimeSpanString(en_US, tz, 0, MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 0)); + assertEquals("In 1 minute", + getRelativeTimeSpanString(en_US, tz, MINUTE_IN_MILLIS, 0, MINUTE_IN_MILLIS, 0)); + + assertEquals("42 minutes ago", + getRelativeTimeSpanString(en_US, tz, baseTime - 42 * MINUTE_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + assertEquals("In 42 minutes", + getRelativeTimeSpanString(en_US, tz, baseTime + 42 * MINUTE_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + + final long TWO_HOURS_IN_MS = 2 * HOUR_IN_MILLIS; + assertEquals("2 hours ago", + getRelativeTimeSpanString(en_US, tz, baseTime - TWO_HOURS_IN_MS, baseTime, + MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE)); + assertEquals("In 2 hours", + getRelativeTimeSpanString(en_US, tz, baseTime + TWO_HOURS_IN_MS, baseTime, + MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE)); + + assertEquals("In 42 min.", + getRelativeTimeSpanString(en_US, tz, baseTime + (42 * MINUTE_IN_MILLIS), baseTime, + MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + + assertEquals("Tomorrow", + getRelativeTimeSpanString(en_US, tz, DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0)); + assertEquals("In 2 days", + getRelativeTimeSpanString(en_US, tz, 2 * DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0)); + assertEquals("Yesterday", + getRelativeTimeSpanString(en_US, tz, 0, DAY_IN_MILLIS, DAY_IN_MILLIS, 0)); + assertEquals("2 days ago", + getRelativeTimeSpanString(en_US, tz, 0, 2 * DAY_IN_MILLIS, DAY_IN_MILLIS, 0)); + + final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000; + assertEquals("5 days ago", + getRelativeTimeSpanString(en_US, tz, baseTime - DAY_DURATION, baseTime, + DAY_IN_MILLIS, 0)); + } + + private void test_getRelativeTimeSpanString_helper(long delta, long minResolution, int flags, + String expectedInPast, + String expectedInFuture) throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2015 at 10:50 PST + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long base = cal.getTimeInMillis(); + + assertEquals(expectedInPast, + getRelativeTimeSpanString(en_US, tz, base - delta, base, minResolution, flags)); + assertEquals(expectedInFuture, + getRelativeTimeSpanString(en_US, tz, base + delta, base, minResolution, flags)); + } + + private void test_getRelativeTimeSpanString_helper(long delta, long minResolution, + String expectedInPast, + String expectedInFuture) throws Exception { + test_getRelativeTimeSpanString_helper(delta, minResolution, 0, expectedInPast, expectedInFuture); + } + + public void test_getRelativeTimeSpanString() throws Exception { + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, "0 seconds ago", "0 seconds ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago", "In 1 minute"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago", "In 1 minute"); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, "5 days ago", "In 5 days"); + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "0 seconds ago", + "0 seconds ago"); + test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "1 second ago", + "In 1 second"); + test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "2 seconds ago", + "In 2 seconds"); + test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "25 seconds ago", + "In 25 seconds"); + test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, "1 hour ago", + "In 1 hour"); + + test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "0 minutes ago", + "0 minutes ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "2 minutes ago", + "In 2 minutes"); + test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "25 minutes ago", + "In 25 minutes"); + test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "12 hours ago", + "In 12 hours"); + + test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago", + "0 hours ago"); + test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "2 hours ago", + "In 2 hours"); + test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "5 hours ago", + "In 5 hours"); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "20 hours ago", + "In 20 hours"); + + test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today"); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", + "In 2 days"); + test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, "January 11", + "March 2"); + + test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago", + "0 weeks ago"); + test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago", + "In 1 week"); + test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "2 weeks ago", + "In 2 weeks"); + test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "25 weeks ago", + "In 25 weeks"); + + // duration >= minResolution + test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, "30 seconds ago", + "In 30 seconds"); + test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, + "30 minutes ago", "In 30 minutes"); + test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, "5 days ago", + "In 5 days"); + test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, "July 10, 2014", + "September 3"); + test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, + "February 6, 2010", "February 4, 2020"); + + test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS, + "1 minute ago", "In 1 minute"); + test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today"); + test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Today"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", + "In 2 days"); + test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", + "In 2 days"); + test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago", + "In 1 week"); + test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, "1 week ago", + "In 1 week"); + + // duration < minResolution + test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, "0 minutes ago", + "In 0 minutes"); + test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago", + "In 0 hours"); + test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, "0 hours ago", + "In 0 hours"); + test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago", + "In 0 weeks"); + test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, "0 weeks ago", + "In 0 weeks"); + } + + public void test_getRelativeTimeSpanStringAbbrev() throws Exception { + int flags = FORMAT_ABBREV_RELATIVE; + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, flags, "0 sec. ago", + "0 sec. ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, flags, "1 min. ago", + "In 1 min."); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, flags, "5 days ago", "In 5 days"); + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "0 sec. ago", "0 sec. ago"); + test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "1 sec. ago", "In 1 sec."); + test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "2 sec. ago", "In 2 sec."); + test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "25 sec. ago", "In 25 sec."); + test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + + test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "0 min. ago", "0 min. ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "2 min. ago", "In 2 min."); + test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "25 min. ago", "In 25 min."); + test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "12 hr. ago", "In 12 hr."); + + test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "0 hr. ago", "0 hr. ago"); + test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "2 hr. ago", "In 2 hr."); + test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "5 hr. ago", "In 5 hr."); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "20 hr. ago", "In 20 hr."); + + test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, "Today", + "Today"); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, + "2 days ago", "In 2 days"); + test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, + "January 11", "March 2"); + + test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "0 wk. ago", "0 wk. ago"); + test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "1 wk. ago", "In 1 wk."); + test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "2 wk. ago", "In 2 wk."); + test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "25 wk. ago", "In 25 wk."); + + // duration >= minResolution + test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, flags, "30 sec. ago", + "In 30 sec."); + test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "30 min. ago", "In 30 min."); + test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "5 days ago", "In 5 days"); + test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "July 10, 2014", "September 3"); + test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "February 6, 2010", "February 4, 2020"); + + test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, "Today", + "Today"); + test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Today"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "2 days ago", "In 2 days"); + test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "2 days ago", "In 2 days"); + test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, flags, + "1 wk. ago", "In 1 wk."); + test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, flags, + "1 wk. ago", "In 1 wk."); + + // duration < minResolution + test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "0 min. ago", "In 0 min."); + test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags, + "0 hr. ago", "In 0 hr."); + test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, flags, + "0 hr. ago", "In 0 hr."); + test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, flags, + "0 wk. ago", "In 0 wk."); + test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, flags, + "0 wk. ago", "In 0 wk."); + + } + + public void test_getRelativeTimeSpanStringGerman() throws Exception { + // Bug: 19744876 + // We need to specify the timezone and the time explicitly. Otherwise it + // may not always give a correct answer of "tomorrow" by using + // (now + DAY_IN_MILLIS). + Locale de_DE = new Locale("de", "DE"); + TimeZone tz = TimeZone.getTimeZone("Europe/Berlin"); + Calendar cal = Calendar.getInstance(tz, de_DE); + // Feb 5, 2015 at 10:50 CET + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long now = cal.getTimeInMillis(); + + // 42 minutes ago + assertEquals("Vor 42 Minuten", getRelativeTimeSpanString(de_DE, tz, + now - 42 * MINUTE_IN_MILLIS, now, MINUTE_IN_MILLIS, 0)); + // In 42 minutes + assertEquals("In 42 Minuten", getRelativeTimeSpanString(de_DE, tz, + now + 42 * MINUTE_IN_MILLIS, now, MINUTE_IN_MILLIS, 0)); + // Yesterday + assertEquals("Gestern", getRelativeTimeSpanString(de_DE, tz, + now - DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day before yesterday + assertEquals("Vorgestern", getRelativeTimeSpanString(de_DE, tz, + now - 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // Tomorrow + assertEquals("Morgen", getRelativeTimeSpanString(de_DE, tz, + now + DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day after tomorrow + assertEquals("Übermorgen", getRelativeTimeSpanString(de_DE, tz, + now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + } + + public void test_getRelativeTimeSpanStringFrench() throws Exception { + Locale fr_FR = new Locale("fr", "FR"); + TimeZone tz = TimeZone.getTimeZone("Europe/Paris"); + Calendar cal = Calendar.getInstance(tz, fr_FR); + // Feb 5, 2015 at 10:50 CET + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long now = cal.getTimeInMillis(); + + // 42 minutes ago + assertEquals("Il y a 42 minutes", getRelativeTimeSpanString(fr_FR, tz, + now - (42 * MINUTE_IN_MILLIS), now, MINUTE_IN_MILLIS, 0)); + // In 42 minutes + assertEquals("Dans 42 minutes", getRelativeTimeSpanString(fr_FR, tz, + now + (42 * MINUTE_IN_MILLIS), now, MINUTE_IN_MILLIS, 0)); + // Yesterday + assertEquals("Hier", getRelativeTimeSpanString(fr_FR, tz, + now - DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day before yesterday + assertEquals("Avant-hier", getRelativeTimeSpanString(fr_FR, tz, + now - 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // Tomorrow + assertEquals("Demain", getRelativeTimeSpanString(fr_FR, tz, + now + DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day after tomorrow + assertEquals("Après-demain", getRelativeTimeSpanString(fr_FR, tz, + now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + } + + // Tests adopted from CTS tests for DateUtils.getRelativeDateTimeString. + public void test_getRelativeDateTimeStringCTS() throws Exception { + Locale en_US = Locale.getDefault(); + TimeZone tz = TimeZone.getDefault(); + final long baseTime = System.currentTimeMillis(); + + final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000; + assertNotNull(getRelativeDateTimeString(en_US, tz, baseTime - DAY_DURATION, baseTime, + MINUTE_IN_MILLIS, DAY_IN_MILLIS, + FORMAT_NUMERIC_DATE)); + } + + public void test_getRelativeDateTimeString() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2015 at 10:50 PST + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long base = cal.getTimeInMillis(); + + assertEquals("5 seconds ago, 10:49 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0, + MINUTE_IN_MILLIS, 0)); + assertEquals("5 min. ago, 10:45 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0, + HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("0 hr. ago, 10:45 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, + HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("5 hours ago, 5:50 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, + HOUR_IN_MILLIS, DAY_IN_MILLIS, 0)); + assertEquals("Yesterday, 7:50 PM", + getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("5 days ago, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("Jan 29, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2014, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2014, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + YEAR_IN_MILLIS, 0)); + + // User-supplied flags should be ignored when formatting the date clause. + final int FORMAT_SHOW_WEEKDAY = 0x00002; + assertEquals("11/27/2014, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, + FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY)); + } + + public void test_getRelativeDateTimeStringDST() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + + // DST starts on Mar 9, 2014 at 2:00 AM. + // So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'. + cal.set(2014, Calendar.MARCH, 9, 3, 15, 0); + long base = cal.getTimeInMillis(); + assertEquals("Yesterday, 9:15 PM", + getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + + // 1 hour after 2:00 AM should be formatted as 'In 1 hour, 4:00 AM'. + cal.set(2014, Calendar.MARCH, 9, 2, 0, 0); + base = cal.getTimeInMillis(); + assertEquals("In 1 hour, 4:00 AM", + getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + + // DST ends on Nov 2, 2014 at 2:00 AM. Clocks are turned backward 1 hour to + // 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'. + cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0); + base = cal.getTimeInMillis(); + assertEquals("Yesterday, 10:20 PM", + getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + + cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0); + base = cal.getTimeInMillis(); + // 45 minutes after 0:45 AM should be 'In 45 minutes, 1:30 AM'. + assertEquals("In 45 minutes, 1:30 AM", + getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + // 45 minutes later, it should be 'In 45 minutes, 1:15 AM'. + assertEquals("In 45 minutes, 1:15 AM", + getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS, + base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0)); + // Another 45 minutes later, it should be 'In 45 minutes, 2:00 AM'. + assertEquals("In 45 minutes, 2:00 AM", + getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS, + base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0)); + } + + + public void test_getRelativeDateTimeStringItalian() throws Exception { + Locale it_IT = new Locale("it", "IT"); + TimeZone tz = TimeZone.getTimeZone("Europe/Rome"); + Calendar cal = Calendar.getInstance(tz, it_IT); + // 05 febbraio 2015 20:15 + cal.set(2015, Calendar.FEBRUARY, 5, 20, 15, 0); + final long base = cal.getTimeInMillis(); + + assertEquals("5 secondi fa, 20:14", + getRelativeDateTimeString(it_IT, tz, base - 5 * SECOND_IN_MILLIS, base, 0, + MINUTE_IN_MILLIS, 0)); + assertEquals("5 min. fa, 20:10", + getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base, 0, + HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("0 h. fa, 20:10", + getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base, + HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("Ieri, 22:15", + getRelativeDateTimeString(it_IT, tz, base - 22 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("5 giorni fa, 20:15", + getRelativeDateTimeString(it_IT, tz, base - 5 * DAY_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("27/11/2014, 20:15", + getRelativeDateTimeString(it_IT, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + } + + // http://b/5252772: detect the actual date difference + public void test5252772() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + + // Now is Sep 2, 2011, 10:23 AM PDT. + Calendar nowCalendar = Calendar.getInstance(tz, en_US); + nowCalendar.set(2011, Calendar.SEPTEMBER, 2, 10, 23, 0); + final long now = nowCalendar.getTimeInMillis(); + + // Sep 1, 2011, 10:24 AM + Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US); + yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0); + long yesterday1 = yesterdayCalendar1.getTimeInMillis(); + assertEquals("Yesterday, 10:24 AM", + getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 1, 2011, 10:22 AM + Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US); + yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0); + long yesterday2 = yesterdayCalendar2.getTimeInMillis(); + assertEquals("Yesterday, 10:22 AM", + getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Aug 31, 2011, 10:24 AM + Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US); + twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0); + long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis(); + assertEquals("2 days ago, 10:24 AM", + getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Aug 31, 2011, 10:22 AM + Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US); + twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0); + long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis(); + assertEquals("2 days ago, 10:22 AM", + getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 3, 2011, 10:22 AM + Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US); + tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0); + long tomorrow1 = tomorrowCalendar1.getTimeInMillis(); + assertEquals("Tomorrow, 10:22 AM", + getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 3, 2011, 10:24 AM + Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US); + tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0); + long tomorrow2 = tomorrowCalendar2.getTimeInMillis(); + assertEquals("Tomorrow, 10:24 AM", + getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 4, 2011, 10:22 AM + Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US); + twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0); + long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis(); + assertEquals("In 2 days, 10:22 AM", + getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 4, 2011, 10:24 AM + Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US); + twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0); + long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis(); + assertEquals("In 2 days, 10:24 AM", + getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + } + + // b/19822016: show / hide the year based on the dates in the arguments. + public void test_bug19822016() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2012 at 10:50 PST + cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0); + long base = cal.getTimeInMillis(); + + assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); + assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + assertEquals("January 6, 2012", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + + // Feb 5, 2018 at 10:50 PST + cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0); + base = cal.getTimeInMillis(); + assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); + assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + assertEquals("January 6, 2018", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + } +} diff --git a/luni/src/test/java/libcore/io/OsTest.java b/luni/src/test/java/libcore/io/OsTest.java index a0d1e5a..9b38ee9 100644 --- a/luni/src/test/java/libcore/io/OsTest.java +++ b/luni/src/test/java/libcore/io/OsTest.java @@ -16,18 +16,27 @@ package libcore.io; +import android.system.ErrnoException; +import android.system.NetlinkSocketAddress; +import android.system.OsConstants; +import android.system.PacketSocketAddress; +import android.system.StructTimeval; import android.system.StructUcred; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.InetUnixAddress; +import java.net.NetworkInterface; import java.net.ServerSocket; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Locale; import junit.framework.TestCase; import static android.system.OsConstants.*; @@ -44,6 +53,20 @@ public class OsTest extends TestCase { s.close(); } + public void testFcntlInt() throws Exception { + File f = File.createTempFile("OsTest", "tst"); + FileInputStream fis = null; + try { + fis = new FileInputStream(f); + Libcore.os.fcntlInt(fis.getFD(), F_SETFD, FD_CLOEXEC); + int flags = Libcore.os.fcntlVoid(fis.getFD(), F_GETFD); + assertTrue((flags & FD_CLOEXEC) != 0); + } finally { + IoUtils.closeQuietly(fis); + f.delete(); + } + } + public void testUnixDomainSockets_in_file_system() throws Exception { String path = System.getProperty("java.io.tmpdir") + "/test_unix_socket"; new File(path).delete(); @@ -196,9 +219,10 @@ public class OsTest extends TestCase { fis.close(); } - public void test_byteBufferPositions_sendto_recvfrom() throws Exception { - final FileDescriptor serverFd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); - Libcore.os.bind(serverFd, InetAddress.getLoopbackAddress(), 0); + static void checkByteBufferPositions_sendto_recvfrom( + int family, InetAddress loopback) throws Exception { + final FileDescriptor serverFd = Libcore.os.socket(family, SOCK_STREAM, 0); + Libcore.os.bind(serverFd, loopback, 0); Libcore.os.listen(serverFd, 5); InetSocketAddress address = (InetSocketAddress) Libcore.os.getsockname(serverFd); @@ -232,7 +256,7 @@ public class OsTest extends TestCase { server.start(); - FileDescriptor clientFd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); + FileDescriptor clientFd = Libcore.os.socket(family, SOCK_STREAM, 0); Libcore.os.connect(clientFd, address.getAddress(), address.getPort()); final byte[] bytes = "good bye, cruel black hole with fancy distortion".getBytes(StandardCharsets.US_ASCII); @@ -254,4 +278,201 @@ public class OsTest extends TestCase { Libcore.os.close(clientFd); } + + public void test_NetlinkSocket() throws Exception { + FileDescriptor nlSocket = Libcore.os.socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + Libcore.os.bind(nlSocket, new NetlinkSocketAddress()); + NetlinkSocketAddress address = (NetlinkSocketAddress) Libcore.os.getsockname(nlSocket); + assertTrue(address.getPortId() > 0); + assertEquals(0, address.getGroupsMask()); + + NetlinkSocketAddress nlKernel = new NetlinkSocketAddress(); + Libcore.os.connect(nlSocket, nlKernel); + NetlinkSocketAddress nlPeer = (NetlinkSocketAddress) Libcore.os.getpeername(nlSocket); + assertEquals(0, nlPeer.getPortId()); + assertEquals(0, nlPeer.getGroupsMask()); + Libcore.os.close(nlSocket); + } + + public void test_PacketSocketAddress() throws Exception { + NetworkInterface lo = NetworkInterface.getByName("lo"); + FileDescriptor fd = Libcore.os.socket(AF_PACKET, SOCK_DGRAM, ETH_P_IPV6); + PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IPV6, lo.getIndex()); + Libcore.os.bind(fd, addr); + + PacketSocketAddress bound = (PacketSocketAddress) Libcore.os.getsockname(fd); + assertEquals((short) ETH_P_IPV6, bound.sll_protocol); // ETH_P_IPV6 is an int. + assertEquals(lo.getIndex(), bound.sll_ifindex); + assertEquals(ARPHRD_LOOPBACK, bound.sll_hatype); + assertEquals(0, bound.sll_pkttype); + + // The loopback address is ETH_ALEN bytes long and is all zeros. + // http://lxr.free-electrons.com/source/drivers/net/loopback.c?v=3.10#L167 + assertEquals(6, bound.sll_addr.length); + for (int i = 0; i < 6; i++) { + assertEquals(0, bound.sll_addr[i]); + } + } + + public void test_byteBufferPositions_sendto_recvfrom_af_inet() throws Exception { + checkByteBufferPositions_sendto_recvfrom(AF_INET, InetAddress.getByName("127.0.0.1")); + } + + public void test_byteBufferPositions_sendto_recvfrom_af_inet6() throws Exception { + checkByteBufferPositions_sendto_recvfrom(AF_INET6, InetAddress.getByName("::1")); + } + + private void checkSendToSocketAddress(int family, InetAddress loopback) throws Exception { + FileDescriptor recvFd = Libcore.os.socket(family, SOCK_DGRAM, 0); + Libcore.os.bind(recvFd, loopback, 0); + StructTimeval tv = StructTimeval.fromMillis(20); + Libcore.os.setsockoptTimeval(recvFd, SOL_SOCKET, SO_RCVTIMEO, tv); + + InetSocketAddress to = ((InetSocketAddress) Libcore.os.getsockname(recvFd)); + FileDescriptor sendFd = Libcore.os.socket(family, SOCK_DGRAM, 0); + byte[] msg = ("Hello, I'm going to a socket address: " + to.toString()).getBytes("UTF-8"); + int len = msg.length; + + assertEquals(len, Libcore.os.sendto(sendFd, msg, 0, len, 0, to)); + byte[] received = new byte[msg.length + 42]; + InetSocketAddress from = new InetSocketAddress(); + assertEquals(len, Libcore.os.recvfrom(recvFd, received, 0, received.length, 0, from)); + assertEquals(loopback, from.getAddress()); + } + + public void test_sendtoSocketAddress_af_inet() throws Exception { + checkSendToSocketAddress(AF_INET, InetAddress.getByName("127.0.0.1")); + } + + public void test_sendtoSocketAddress_af_inet6() throws Exception { + checkSendToSocketAddress(AF_INET6, InetAddress.getByName("::1")); + } + + public void test_socketFamilies() throws Exception { + FileDescriptor fd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); + Libcore.os.bind(fd, InetAddress.getByName("::"), 0); + InetSocketAddress localSocketAddress = (InetSocketAddress) Libcore.os.getsockname(fd); + assertEquals(Inet6Address.ANY, localSocketAddress.getAddress()); + + fd = Libcore.os.socket(AF_INET6, SOCK_STREAM, 0); + Libcore.os.bind(fd, InetAddress.getByName("0.0.0.0"), 0); + localSocketAddress = (InetSocketAddress) Libcore.os.getsockname(fd); + assertEquals(Inet6Address.ANY, localSocketAddress.getAddress()); + + fd = Libcore.os.socket(AF_INET, SOCK_STREAM, 0); + Libcore.os.bind(fd, InetAddress.getByName("0.0.0.0"), 0); + localSocketAddress = (InetSocketAddress) Libcore.os.getsockname(fd); + assertEquals(Inet4Address.ANY, localSocketAddress.getAddress()); + try { + Libcore.os.bind(fd, InetAddress.getByName("::"), 0); + fail("Expected ErrnoException binding IPv4 socket to ::"); + } catch (ErrnoException expected) { + assertEquals("Expected EAFNOSUPPORT binding IPv4 socket to ::", EAFNOSUPPORT, expected.errno); + } + } + + private static void assertArrayEquals(byte[] expected, byte[] actual) { + assertTrue("Expected=" + Arrays.toString(expected) + ", actual=" + Arrays.toString(actual), + Arrays.equals(expected, actual)); + } + + private static void checkSocketPing(FileDescriptor fd, InetAddress to, byte[] packet, + byte type, byte responseType, boolean useSendto) throws Exception { + int len = packet.length; + packet[0] = type; + if (useSendto) { + assertEquals(len, Libcore.os.sendto(fd, packet, 0, len, 0, to, 0)); + } else { + Libcore.os.connect(fd, to, 0); + assertEquals(len, Libcore.os.sendto(fd, packet, 0, len, 0, null, 0)); + } + + int icmpId = ((InetSocketAddress) Libcore.os.getsockname(fd)).getPort(); + byte[] received = new byte[4096]; + InetSocketAddress srcAddress = new InetSocketAddress(); + assertEquals(len, Libcore.os.recvfrom(fd, received, 0, received.length, 0, srcAddress)); + assertEquals(to, srcAddress.getAddress()); + assertEquals(responseType, received[0]); + assertEquals(received[4], (byte) (icmpId >> 8)); + assertEquals(received[5], (byte) (icmpId & 0xff)); + + received = Arrays.copyOf(received, len); + received[0] = (byte) type; + received[2] = received[3] = 0; // Checksum. + received[4] = received[5] = 0; // ICMP ID. + assertArrayEquals(packet, received); + } + + public void test_socketPing() throws Exception { + final byte ICMP_ECHO = 8, ICMP_ECHOREPLY = 0; + final byte ICMPV6_ECHO_REQUEST = (byte) 128, ICMPV6_ECHO_REPLY = (byte) 129; + final byte[] packet = ("\000\000\000\000" + // ICMP type, code. + "\000\000\000\003" + // ICMP ID (== port), sequence number. + "Hello myself").getBytes(StandardCharsets.US_ASCII); + + FileDescriptor fd = Libcore.os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); + InetAddress ipv6Loopback = InetAddress.getByName("::1"); + checkSocketPing(fd, ipv6Loopback, packet, ICMPV6_ECHO_REQUEST, ICMPV6_ECHO_REPLY, true); + checkSocketPing(fd, ipv6Loopback, packet, ICMPV6_ECHO_REQUEST, ICMPV6_ECHO_REPLY, false); + + fd = Libcore.os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + InetAddress ipv4Loopback = InetAddress.getByName("127.0.0.1"); + checkSocketPing(fd, ipv4Loopback, packet, ICMP_ECHO, ICMP_ECHOREPLY, true); + checkSocketPing(fd, ipv4Loopback, packet, ICMP_ECHO, ICMP_ECHOREPLY, false); + } + + private static void assertPartial(byte[] expected, byte[] actual) { + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + fail("Expected " + Arrays.toString(expected) + " but found " + + Arrays.toString(actual)); + } + } + } + + public void test_xattr() throws Exception { + final String NAME_TEST = "user.meow"; + + final byte[] VALUE_CAKE = "cake cake cake".getBytes(StandardCharsets.UTF_8); + final byte[] VALUE_PIE = "pie".getBytes(StandardCharsets.UTF_8); + + File file = File.createTempFile("xattr", "test"); + String path = file.getAbsolutePath(); + + byte[] tmp = new byte[1024]; + try { + try { + Libcore.os.getxattr(path, NAME_TEST, tmp); + fail("Expected ENODATA"); + } catch (ErrnoException e) { + assertEquals(OsConstants.ENODATA, e.errno); + } + + Libcore.os.setxattr(path, NAME_TEST, VALUE_CAKE, OsConstants.XATTR_CREATE); + assertEquals(VALUE_CAKE.length, Libcore.os.getxattr(path, NAME_TEST, tmp)); + assertPartial(VALUE_CAKE, tmp); + + try { + Libcore.os.setxattr(path, NAME_TEST, VALUE_PIE, OsConstants.XATTR_CREATE); + fail("Expected EEXIST"); + } catch (ErrnoException e) { + assertEquals(OsConstants.EEXIST, e.errno); + } + + Libcore.os.setxattr(path, NAME_TEST, VALUE_PIE, OsConstants.XATTR_REPLACE); + assertEquals(VALUE_PIE.length, Libcore.os.getxattr(path, NAME_TEST, tmp)); + assertPartial(VALUE_PIE, tmp); + + Libcore.os.removexattr(path, NAME_TEST); + try { + Libcore.os.getxattr(path, NAME_TEST, tmp); + fail("Expected ENODATA"); + } catch (ErrnoException e) { + assertEquals(OsConstants.ENODATA, e.errno); + } + + } finally { + file.delete(); + } + } } diff --git a/luni/src/test/java/libcore/java/io/FileDescriptorTest.java b/luni/src/test/java/libcore/java/io/FileDescriptorTest.java index 39472df..e2780c9 100644 --- a/luni/src/test/java/libcore/java/io/FileDescriptorTest.java +++ b/luni/src/test/java/libcore/java/io/FileDescriptorTest.java @@ -30,14 +30,14 @@ public class FileDescriptorTest extends TestCase { new RandomAccessFile(f, "r").getFD().sync(); } - public void test_isSocket() throws Exception { + public void test_isSocket$() throws Exception { File f = new File("/dev/null"); FileInputStream fis = new FileInputStream(f); - assertFalse(fis.getFD().isSocket()); + assertFalse(fis.getFD().isSocket$()); fis.close(); ServerSocket s = new ServerSocket(); - assertTrue(s.getImpl$().getFD$().isSocket()); + assertTrue(s.getImpl$().getFD$().isSocket$()); s.close(); } } diff --git a/luni/src/test/java/libcore/java/io/FileInputStreamTest.java b/luni/src/test/java/libcore/java/io/FileInputStreamTest.java index 14950ee..26de11a 100644 --- a/luni/src/test/java/libcore/java/io/FileInputStreamTest.java +++ b/luni/src/test/java/libcore/java/io/FileInputStreamTest.java @@ -67,7 +67,7 @@ public final class FileInputStreamTest extends TestCase { } public void testSkipInPipes() throws Exception { - FileDescriptor[] pipe = Libcore.os.pipe(); + FileDescriptor[] pipe = Libcore.os.pipe2(0); DataFeeder feeder = new DataFeeder(pipe[1]); try { feeder.start(); diff --git a/luni/src/test/java/libcore/java/io/RandomAccessFileTest.java b/luni/src/test/java/libcore/java/io/RandomAccessFileTest.java index afe49b7..8d99457 100644 --- a/luni/src/test/java/libcore/java/io/RandomAccessFileTest.java +++ b/luni/src/test/java/libcore/java/io/RandomAccessFileTest.java @@ -20,6 +20,8 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; + import junit.framework.TestCase; import libcore.java.lang.ref.FinalizationTester; @@ -73,6 +75,61 @@ public final class RandomAccessFileTest extends TestCase { FinalizationTester.induceFinalization(); } } + + // http://b/19892782 + public void testCloseRaf_sameChannelReturned() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + + FileChannel fileChannelBeforeClosing = raf.getChannel(); + raf.close(); + FileChannel fileChannelAfterClosing = raf.getChannel(); + assertSame(fileChannelBeforeClosing, fileChannelAfterClosing); + } + + // http://b/19892782 + public void testCloseRaf_channelIsClosed() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + + FileChannel fileChannelBeforeClosing = raf.getChannel(); + raf.close(); + FileChannel fileChannelAfterClosing = raf.getChannel(); + assertFalse(fileChannelBeforeClosing.isOpen()); + } + + // http://b/19892782 + public void testCloseFileChannel_sameChannelReturned() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + + FileChannel fileChannelBeforeClosing = raf.getChannel(); + fileChannelBeforeClosing.close(); + + FileChannel fileChannelAfterClosing = raf.getChannel(); + assertSame(fileChannelBeforeClosing, fileChannelAfterClosing); + } + + // http://b/19892782 + public void testCloseFileChannel_returnedFileChannelIsClosed() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + + FileChannel fileChannelBeforeClosing = raf.getChannel(); + // This should close the Raf, and previous implementations wrongly returned a new + // open (but useless) channel in this case. + fileChannelBeforeClosing.close(); + FileChannel fileChannelAfterClosing = raf.getChannel(); + assertFalse(fileChannelBeforeClosing.isOpen()); + } + + // http://b/19892782 + public void testCloseRafBeforeGetChannel_returnChannelWithCloseFdAfterClose() throws Exception { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + raf.close(); + try { + raf.getChannel().size(); + fail(); + } catch (IOException expected) { + } + } + private void createRandomAccessFile(File file) throws Exception { // TODO: fix our register maps and remove this otherwise unnecessary // indirection! (http://b/5412580) diff --git a/luni/src/test/java/libcore/java/lang/FloatTest.java b/luni/src/test/java/libcore/java/lang/FloatTest.java index 92e7ae4..c25bd5c 100644 --- a/luni/src/test/java/libcore/java/lang/FloatTest.java +++ b/luni/src/test/java/libcore/java/lang/FloatTest.java @@ -121,4 +121,20 @@ public class FloatTest extends junit.framework.TestCase { } assertEquals(f1, 0f); } + + // Float equivalent of testParseLargestSubnormalDoublePrecision. http://b/18087920. + public void testParseLargestSubnormalFloatPrecision() { + // These are different ways of saying MIN_NORMAL. + assertEquals(1.1754943508222875e-38f, Float.parseFloat("1.1754943508222875e-38")); + assertEquals(1.1754943508222875e-38f, Float.parseFloat("0.00011754943508222875e-34f")); + assertEquals(1.1754943508222875e-38f, Float.parseFloat("00000001.1754943508222875e-38f")); + assertEquals(1.1754943508222875e-38f, Float.parseFloat("1.17549435082228750000e-38f")); + assertEquals(1.1754943508222875e-38f, Float.parseFloat("1.1754943508222875e-0038f")); + assertEquals(-1.1754943508222875e-38f, Float.parseFloat("-1.1754943508222875e-38f")); + + // Extra interesting values suggested as part of http://b/18087920. + assertEquals(1.1754944e-38f, Float.parseFloat("11754942807573643E-54")); + assertEquals(1.1754944e-38f, Float.parseFloat("11754942807573644E-54")); + assertEquals(1.1754944e-38f, Float.parseFloat("11754942807573645E-54")); + } } diff --git a/luni/src/test/java/libcore/java/lang/OldAndroidMonitorTest.java b/luni/src/test/java/libcore/java/lang/OldAndroidMonitorTest.java index d1704fb..4760c6d 100644..100755 --- a/luni/src/test/java/libcore/java/lang/OldAndroidMonitorTest.java +++ b/luni/src/test/java/libcore/java/lang/OldAndroidMonitorTest.java @@ -103,7 +103,7 @@ public class OldAndroidMonitorTest extends TestCase { } private class Interrupter extends Thread { - Waiter waiter; + private final Waiter waiter; Interrupter(String name, Waiter waiter) { super(name); @@ -119,8 +119,7 @@ public class OldAndroidMonitorTest extends TestCase { } } - void run_inner() { - waiter.spin = true; + private void run_inner() { // System.out.println("InterruptTest: starting waiter"); waiter.start(); @@ -168,7 +167,7 @@ public class OldAndroidMonitorTest extends TestCase { private class Waiter extends Thread { Object interrupterLock = new Object(); - Boolean spin = false; + volatile boolean spin = true; Waiter(String name) { super(name); @@ -188,6 +187,7 @@ public class OldAndroidMonitorTest extends TestCase { while (spin) { // We're going to get interrupted while we spin. } + if (interrupted()) { // System.out.println("Waiter done spinning; interrupted."); } else { @@ -196,7 +196,7 @@ public class OldAndroidMonitorTest extends TestCase { } synchronized (this) { - Boolean sawEx = false; + boolean sawEx = false; try { synchronized (interrupterLock) { @@ -216,7 +216,7 @@ public class OldAndroidMonitorTest extends TestCase { } } synchronized (this) { - Boolean sawEx = false; + boolean sawEx = false; try { synchronized (interrupterLock) { @@ -236,7 +236,7 @@ public class OldAndroidMonitorTest extends TestCase { } } synchronized (this) { - Boolean sawEx = false; + boolean sawEx = false; try { synchronized (interrupterLock) { diff --git a/luni/src/test/java/libcore/java/lang/OldClassTest.java b/luni/src/test/java/libcore/java/lang/OldClassTest.java index 23a42bd..f5bc787 100644 --- a/luni/src/test/java/libcore/java/lang/OldClassTest.java +++ b/luni/src/test/java/libcore/java/lang/OldClassTest.java @@ -40,11 +40,6 @@ import tests.support.resource.Support_Resources; @SuppressWarnings("deprecation") public class OldClassTest extends junit.framework.TestCase { - - public static final String FILENAME = - OldClassTest.class.getPackage().getName().replace('.', '/') + - "/test#.properties"; - final String packageName = getClass().getPackage().getName(); final String classNameInitError1 = packageName + ".TestClass1"; final String classNameInitError2 = packageName + ".TestClass1B"; diff --git a/luni/src/test/java/libcore/java/lang/OldSystemTest.java b/luni/src/test/java/libcore/java/lang/OldSystemTest.java index dee5bdd..93b06c8 100644 --- a/luni/src/test/java/libcore/java/lang/OldSystemTest.java +++ b/luni/src/test/java/libcore/java/lang/OldSystemTest.java @@ -260,12 +260,14 @@ public class OldSystemTest extends junit.framework.TestCase { while(rt.freeMemory() < beforeTest * 2/3) { vec.add(new StringBuffer(1000)); } - long beforeGC = rt.freeMemory(); + long beforeGC = rt.totalMemory() - rt.freeMemory(); + vec = null; System.gc(); - long afterGC = rt.freeMemory(); + System.runFinalization(); + long afterGC = rt.totalMemory() - rt.freeMemory(); assertTrue("memory was not released after calling System.gc()." + "before gc: " + beforeGC + "; after gc: " + afterGC, - beforeGC < afterGC); + beforeGC > afterGC); } public void test_getenv() { diff --git a/luni/src/test/java/libcore/java/lang/PackageTest.java b/luni/src/test/java/libcore/java/lang/PackageTest.java index 6e274a0..c004e23 100644 --- a/luni/src/test/java/libcore/java/lang/PackageTest.java +++ b/luni/src/test/java/libcore/java/lang/PackageTest.java @@ -25,9 +25,10 @@ public final class PackageTest extends TestCase { private static final List<Package> packages = Arrays.asList(Package.getPackages()); public void test_getAnnotations() throws Exception { - // Package annotations aren't supported, but pre-ICS we crashed. - assertEquals(0, getClass().getPackage().getAnnotations().length); - assertEquals(0, getClass().getPackage().getDeclaredAnnotations().length); + // Pre-ICS we crashed. To pass, the package-info and TestPackageAnnotation classes must be + // on the classpath. + assertEquals(1, getClass().getPackage().getAnnotations().length); + assertEquals(1, getClass().getPackage().getDeclaredAnnotations().length); } public void testGetPackage() { diff --git a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java index 9766cef..51aed38 100644 --- a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java +++ b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java @@ -28,7 +28,7 @@ import static tests.support.Support_Exec.execAndCheckOutput; public class ProcessBuilderTest extends AbstractResourceLeakageDetectorTestCase { private static String shell() { - String deviceSh = "/system/bin/sh"; + String deviceSh = System.getenv("ANDROID_ROOT") + "/bin/sh"; String desktopSh = "/bin/sh"; return new File(deviceSh).exists() ? deviceSh : desktopSh; } diff --git a/luni/src/test/java/libcore/java/lang/StringTest.java b/luni/src/test/java/libcore/java/lang/StringTest.java index bf162e5..bd52e06 100644 --- a/luni/src/test/java/libcore/java/lang/StringTest.java +++ b/luni/src/test/java/libcore/java/lang/StringTest.java @@ -173,47 +173,6 @@ public class StringTest extends TestCase { } /** - * Tests a widely assumed performance characteristic of String.substring(): - * that it reuses the original's backing array. Although behavior should be - * correct even if this test fails, many applications may suffer - * significant performance degradation. - */ - public void testSubstringSharesBackingArray() throws IllegalAccessException { - String abcdefghij = "ABCDEFGHIJ"; - String cdefg = abcdefghij.substring(2, 7); - assertSame(getBackingArray(abcdefghij), getBackingArray(cdefg)); - } - - /** - * Tests a widely assumed performance characteristic of string's copy - * constructor: that it ensures the backing array is the same length as the - * string. Although behavior should be correct even if this test fails, - * many applications may suffer significant performance degradation. - */ - public void testStringCopiesAvoidHeapRetention() throws IllegalAccessException { - String abcdefghij = "ABCDEFGHIJ"; - assertSame(getBackingArray(abcdefghij), getBackingArray(new String(abcdefghij))); - - String cdefg = abcdefghij.substring(2, 7); - assertSame(getBackingArray(abcdefghij), getBackingArray(cdefg)); - assertEquals(5, getBackingArray(new String(cdefg)).length); - } - - /** - * Uses reflection to return the char[] backing the given string. This - * returns the actual backing array; which must not be modified. - */ - private char[] getBackingArray(String string) throws IllegalAccessException { - for (Field f : String.class.getDeclaredFields()) { - if (!Modifier.isStatic(f.getModifiers()) && f.getType() == char[].class) { - f.setAccessible(true); - return (char[]) f.get(string); - } - } - throw new UnsupportedOperationException("No chars[] field on String!"); - } - - /** * Test that strings interned manually and then later loaded as literals * maintain reference equality. http://b/3098960 */ diff --git a/luni/src/test/java/libcore/java/lang/TestPackageAnnotation.java b/luni/src/test/java/libcore/java/lang/TestPackageAnnotation.java new file mode 100644 index 0000000..7626206 --- /dev/null +++ b/luni/src/test/java/libcore/java/lang/TestPackageAnnotation.java @@ -0,0 +1,26 @@ +/* + * 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 libcore.java.lang; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +// Used by PackageTest +@Target(ElementType.PACKAGE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestPackageAnnotation {} diff --git a/luni/src/test/java/libcore/java/lang/package-info.java b/luni/src/test/java/libcore/java/lang/package-info.java new file mode 100644 index 0000000..d916e9a --- /dev/null +++ b/luni/src/test/java/libcore/java/lang/package-info.java @@ -0,0 +1,18 @@ +/* + * 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. + */ +// Used by PackageTest +@TestPackageAnnotation +package libcore.java.lang;
\ No newline at end of file diff --git a/luni/src/test/java/libcore/java/lang/reflect/FieldTest.java b/luni/src/test/java/libcore/java/lang/reflect/FieldTest.java index b60d984..75665db 100644 --- a/luni/src/test/java/libcore/java/lang/reflect/FieldTest.java +++ b/luni/src/test/java/libcore/java/lang/reflect/FieldTest.java @@ -17,6 +17,8 @@ package libcore.java.lang.reflect; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + import junit.framework.TestCase; public final class FieldTest extends TestCase { @@ -46,6 +48,56 @@ public final class FieldTest extends TestCase { assertFalse(f1.equals(f2)); } + // Tests that the "synthetic" modifier is handled correctly. + // It's supposed to be present but not shown in toString. + public void testSyntheticModifier() throws NoSuchFieldException { + Field valuesField = Thread.State.class.getDeclaredField("$VALUES"); + // Check that this test makes sense. + assertTrue(valuesField.isSynthetic()); + assertEquals(Modifier.SYNTHETIC, valuesField.getModifiers() & Modifier.SYNTHETIC); + assertEquals("private static final java.lang.Thread$State[] java.lang.Thread$State.$VALUES", + valuesField.toString()); + } + + // Ensure that the "enum constant" bit is not returned in toString. + public void testEnumValueField() throws NoSuchFieldException { + Field blockedField = Thread.State.class.getDeclaredField("BLOCKED"); + assertTrue(Thread.State.class.getDeclaredField("BLOCKED").isEnumConstant()); + assertEquals("public static final", Modifier.toString(blockedField.getModifiers())); + assertEquals( + "public static final java.lang.Thread$State java.lang.Thread$State.BLOCKED", + blockedField.toString()); + } + + class ClassWithATransientField { + private transient Class<String> transientField = String.class; + } + + // Tests that the "transient" modifier is handled correctly. + // The underlying constant value for it is the same as for the "varargs" method modifier. + // http://b/18488857 + public void testTransientModifier() throws NoSuchFieldException { + Field transientField = ClassWithATransientField.class.getDeclaredField("transientField"); + // Check that this test makes sense. + assertEquals(Modifier.TRANSIENT, transientField.getModifiers() & Modifier.TRANSIENT); + assertEquals( + "private transient java.lang.Class " + + "libcore.java.lang.reflect.FieldTest$ClassWithATransientField" + + ".transientField", + transientField.toString()); + } + + public void testToGenericString() throws NoSuchFieldException { + Field transientField = ClassWithATransientField.class.getDeclaredField("transientField"); + // Check that this test makes sense. + assertEquals(Modifier.TRANSIENT, transientField.getModifiers() & Modifier.TRANSIENT); + assertEquals( + "private transient java.lang.Class<java.lang.String> " + + "libcore.java.lang.reflect.FieldTest$ClassWithATransientField" + + ".transientField", + transientField.toGenericString()); + } + static class FieldTestHelper { public String a; public Object b; diff --git a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java index c3a436c..a3f9065 100644 --- a/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java +++ b/luni/src/test/java/libcore/java/lang/reflect/MethodTest.java @@ -17,6 +17,7 @@ package libcore.java.lang.reflect; import java.lang.reflect.Method; + import junit.framework.TestCase; public final class MethodTest extends TestCase { @@ -197,6 +198,23 @@ public final class MethodTest extends TestCase { assertEquals( "public java.lang.Process java.lang.Runtime.exec(java.lang.String[])" + " throws java.io.IOException", Runtime.class.getMethod("exec", new Class[] { String[].class }).toString()); + // http://b/18488857 + assertEquals( + "public int java.lang.String.compareTo(java.lang.Object)", + String.class.getMethod("compareTo", Object.class).toString()); + } + + // Tests that the "varargs" modifier is handled correctly. + // The underlying constant value for it is the same as for the "transient" field modifier. + // http://b/18488857 + public void testVarargsModifier() throws NoSuchMethodException { + Method stringFormatMethod = String.class.getMethod( + "format", new Class[] { String.class, Object[].class }); + assertTrue(stringFormatMethod.isVarArgs()); + assertEquals( + "public static java.lang.String java.lang.String.format(" + + "java.lang.String,java.lang.Object[])", + stringFormatMethod.toString()); } public static class MethodTestHelper { diff --git a/luni/src/test/java/libcore/java/lang/reflect/ModifierTest.java b/luni/src/test/java/libcore/java/lang/reflect/ModifierTest.java index 1bde157..0505f2f 100644 --- a/luni/src/test/java/libcore/java/lang/reflect/ModifierTest.java +++ b/luni/src/test/java/libcore/java/lang/reflect/ModifierTest.java @@ -100,6 +100,9 @@ public class ModifierTest extends junit.framework.TestCase { } public void test_toStringI() { - assertEquals("public abstract", Modifier.toString(Modifier.PUBLIC | Modifier.ABSTRACT)); + // Note that it checks that "STRICT" is rendered as "strictfp" (for other modifiers, + // the displayed name is the same as the lowercase constant name). + assertEquals("public abstract strictfp", + Modifier.toString(Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.STRICT)); } } diff --git a/luni/src/test/java/libcore/java/net/InetAddressTest.java b/luni/src/test/java/libcore/java/net/InetAddressTest.java index 4b656cc..8bdcf64 100644 --- a/luni/src/test/java/libcore/java/net/InetAddressTest.java +++ b/luni/src/test/java/libcore/java/net/InetAddressTest.java @@ -21,10 +21,14 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.UnknownHostException; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import libcore.util.SerializationTester; public class InetAddressTest extends junit.framework.TestCase { + private static final byte[] LOOPBACK4_BYTES = new byte[] { 127, 0, 0, 1 }; private static final byte[] LOOPBACK6_BYTES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; private static final String[] INVALID_IPv4_NUMERIC_ADDRESSES = new String[] { @@ -72,7 +76,7 @@ public class InetAddressTest extends junit.framework.TestCase { } private static Inet6Address localhost6() throws Exception { - return (Inet6Address) InetAddress.getByAddress("localhost", LOOPBACK6_BYTES); + return (Inet6Address) InetAddress.getByAddress("ip6-localhost", LOOPBACK6_BYTES); } public void test_parseNumericAddress() throws Exception { @@ -311,4 +315,84 @@ public class InetAddressTest extends junit.framework.TestCase { assertEquals(resultStrings[i], result); } } + + public void test_getHostNameCaches() throws Exception { + InetAddress inetAddress = InetAddress.getByAddress(LOOPBACK6_BYTES); + assertEquals("::1", inetAddress.getHostString()); + assertEquals("ip6-localhost", inetAddress.getHostName()); + // getHostString() should now be different. + assertEquals("ip6-localhost", inetAddress.getHostString()); + } + + public void test_getByAddress_loopbackIpv4() throws Exception { + InetAddress inetAddress = InetAddress.getByAddress(LOOPBACK4_BYTES); + assertEquals(LOOPBACK4_BYTES, "localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getByAddress_loopbackIpv6() throws Exception { + InetAddress inetAddress = InetAddress.getByAddress(LOOPBACK6_BYTES); + assertEquals(LOOPBACK6_BYTES, "ip6-localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getByName_loopbackIpv4() throws Exception { + InetAddress inetAddress = InetAddress.getByName("127.0.0.1"); + assertEquals(LOOPBACK4_BYTES, "localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getByName_loopbackIpv6() throws Exception { + InetAddress inetAddress = InetAddress.getByName("::1"); + assertEquals(LOOPBACK6_BYTES, "ip6-localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getAllByName_localhost() throws Exception { + InetAddress[] inetAddresses = InetAddress.getAllByName("localhost"); + assertEquals(1, inetAddresses.length); + InetAddress inetAddress = inetAddresses[0]; + assertEquals(LOOPBACK4_BYTES, "localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getAllByName_ip6_localhost() throws Exception { + InetAddress[] inetAddresses = InetAddress.getAllByName("ip6-localhost"); + assertEquals(1, inetAddresses.length); + InetAddress inetAddress = inetAddresses[0]; + assertEquals(LOOPBACK6_BYTES, "ip6-localhost", inetAddress); + assertTrue(inetAddress.isLoopbackAddress()); + } + + public void test_getByName_null() throws Exception { + InetAddress inetAddress = InetAddress.getByName("::1"); + + Set<InetAddress> expectedLoopbackAddresses = + createSet(Inet4Address.LOOPBACK, Inet6Address.LOOPBACK); + assertTrue(expectedLoopbackAddresses.contains(inetAddress)); + } + + public void test_getAllByName_null() throws Exception { + InetAddress[] inetAddresses = InetAddress.getAllByName(null); + assertEquals(2, inetAddresses.length); + Set<InetAddress> expectedLoopbackAddresses = + createSet(Inet4Address.LOOPBACK, Inet6Address.LOOPBACK); + assertEquals(expectedLoopbackAddresses, createSet(inetAddresses)); + } + + private static void assertEquals( + byte[] expectedAddressBytes, String expectedHostname, InetAddress actual) { + assertArrayEquals(expectedAddressBytes, actual.getAddress()); + assertEquals(expectedHostname, actual.getHostName()); + + } + + private static void assertArrayEquals(byte[] expected, byte[] actual) { + assertTrue("Expected=" + Arrays.toString(expected) + ", actual=" + Arrays.toString(actual), + Arrays.equals(expected, actual)); + } + + private static Set<InetAddress> createSet(InetAddress... members) { + return new HashSet<InetAddress>(Arrays.asList(members)); + } } diff --git a/luni/src/test/java/libcore/java/net/InetSocketAddressTest.java b/luni/src/test/java/libcore/java/net/InetSocketAddressTest.java index 3bca8dc..d97c48a 100644 --- a/luni/src/test/java/libcore/java/net/InetSocketAddressTest.java +++ b/luni/src/test/java/libcore/java/net/InetSocketAddressTest.java @@ -63,7 +63,7 @@ public class InetSocketAddressTest extends TestCase { } InetSocketAddress isa = new InetSocketAddress((InetAddress)null, 80); - assertEquals("0.0.0.0", isa.getHostName()); + assertEquals("::", isa.getHostName()); try { new InetSocketAddress(InetAddress.getByName("localhost"), 65536); @@ -80,7 +80,7 @@ public class InetSocketAddressTest extends TestCase { public void test_ConstructorI() { InetSocketAddress isa = new InetSocketAddress(65535); - assertEquals("0.0.0.0", isa.getHostName()); + assertEquals("::", isa.getHostName()); assertEquals(65535, isa.getPort()); try { @@ -150,6 +150,20 @@ public class InetSocketAddressTest extends TestCase { assertTrue(hasHostname.isUnresolved()); assertEquals("some host", hasHostname.getHostString()); assertEquals("some host", hasHostname.getHostName()); + + InetSocketAddress hasHostnameAndAddress = new InetSocketAddress( + InetAddress.getByAddress("some host", new byte[] { 127, 0, 0, 1 }), + 1234); + assertFalse(hasHostnameAndAddress.isUnresolved()); + assertEquals("some host", hasHostnameAndAddress.getHostString()); + assertEquals("some host", hasHostnameAndAddress.getHostName()); + + // Using a host name that is actually an IP. + InetSocketAddress hostnameIsIp = InetSocketAddress.createUnresolved("127.0.0.1", 1234); + assertTrue(hostnameIsIp.isUnresolved()); + assertEquals("127.0.0.1", hostnameIsIp.getHostString()); + assertEquals("127.0.0.1", hostnameIsIp.getHostName()); + // When we don't have a hostname, whether or not we do the reverse lookup is the difference // between getHostString and getHostName... InetAddress address = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }); @@ -157,4 +171,18 @@ public class InetSocketAddressTest extends TestCase { assertEquals("127.0.0.1", noHostname.getHostString()); assertEquals("localhost", noHostname.getHostName()); } + + public void test_getHostString_cachingBehavior() throws Exception { + InetAddress inetAddress = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }); + InetSocketAddress socketAddress = new InetSocketAddress(inetAddress, 1234); + assertEquals("127.0.0.1", socketAddress.getHostString()); + assertEquals("localhost", socketAddress.getHostName()); + assertEquals("localhost", socketAddress.getHostString()); + + inetAddress = InetAddress.getByName("127.0.0.1"); + socketAddress = new InetSocketAddress(inetAddress, 1234); + assertEquals("127.0.0.1", socketAddress.getHostString()); + assertEquals("localhost", socketAddress.getHostName()); + assertEquals("localhost", socketAddress.getHostString()); + } } diff --git a/luni/src/test/java/libcore/java/net/OldSocketTest.java b/luni/src/test/java/libcore/java/net/OldSocketTest.java index 7973965..ded5802 100644 --- a/luni/src/test/java/libcore/java/net/OldSocketTest.java +++ b/luni/src/test/java/libcore/java/net/OldSocketTest.java @@ -37,6 +37,7 @@ import java.net.UnknownHostException; import java.nio.channels.IllegalBlockingModeException; import java.nio.channels.SocketChannel; import java.security.Permission; +import tests.net.StuckServer; import tests.support.Support_Configuration; public class OldSocketTest extends OldSocketTestCase { @@ -932,25 +933,15 @@ public class OldSocketTest extends OldSocketTestCase { } // start by validating the error checks - int portNumber = 0; - Socket theSocket = null; - ServerSocket serverSocket = null; - SocketAddress theAddress = null; - SocketAddress nonConnectableAddress = null; - SocketAddress nonReachableAddress = null; - SocketAddress invalidType = null; - // byte[] theBytes = {-1,-1,-1,-1}; - byte[] theBytes = { 0, 0, 0, 0 }; - theAddress = new InetSocketAddress(InetAddress.getLocalHost(), - portNumber); - nonConnectableAddress = new InetSocketAddress(InetAddress - .getByAddress(theBytes), portNumber); - nonReachableAddress = new InetSocketAddress(InetAddress - .getByName(Support_Configuration.ResolvedNotExistingHost), - portNumber); - invalidType = new mySocketAddress(); + byte[] theBytes = { 0, 0, 0, 0 }; + SocketAddress theAddress = new InetSocketAddress(InetAddress.getLocalHost(), 0); + SocketAddress nonConnectableAddress = new InetSocketAddress(InetAddress.getByAddress(theBytes), 0); + SocketAddress nonReachableAddress = new InetSocketAddress(StuckServer.UNREACHABLE_ADDRESS, 0); + SocketAddress invalidType = new mySocketAddress(); + Socket theSocket = null; + ServerSocket serverSocket = null; try { theSocket = new Socket(); theSocket.connect(null); @@ -1165,7 +1156,7 @@ public class OldSocketTest extends OldSocketTestCase { byte[] theBytes = { 0, 0, 0, 0 }; SocketAddress theAddress = new InetSocketAddress(InetAddress.getLocalHost(), 0); SocketAddress nonConnectableAddress = new InetSocketAddress(InetAddress.getByAddress(theBytes), 0); - SocketAddress nonReachableAddress = new InetSocketAddress(InetAddress.getByName(Support_Configuration.ResolvedNotExistingHost), 0); + SocketAddress nonReachableAddress = new InetSocketAddress(StuckServer.UNREACHABLE_ADDRESS, 0); SocketAddress invalidType = new mySocketAddress(); Socket theSocket = null; diff --git a/luni/src/test/java/libcore/java/net/SocketTest.java b/luni/src/test/java/libcore/java/net/SocketTest.java index fb09be0..9765a45 100644 --- a/luni/src/test/java/libcore/java/net/SocketTest.java +++ b/luni/src/test/java/libcore/java/net/SocketTest.java @@ -31,9 +31,11 @@ import java.net.SocketImpl; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; public class SocketTest extends junit.framework.TestCase { // See http://b/2980559. @@ -353,6 +355,37 @@ public class SocketTest extends junit.framework.TestCase { assertEquals(boundAddress.getPort(), localAddressAfterClose.getPort()); } + public void testCloseDuringConnect() throws Exception { + final CountDownLatch signal = new CountDownLatch(1); + + final Socket s = new Socket(); + new Thread() { + @Override + public void run() { + try { + // This address is reserved for documentation: should never be reachable. + InetSocketAddress unreachableIp = new InetSocketAddress("192.0.2.0", 80); + // This should never return. + s.connect(unreachableIp, 0 /* infinite */); + fail("Connect returned unexpectedly for: " + unreachableIp); + } catch (SocketException expected) { + assertTrue(expected.getMessage().contains("Socket closed")); + signal.countDown(); + } catch (IOException e) { + fail("Unexpected exception: " + e); + } + } + }.start(); + + // Wait for the connect() thread to run and start connect() + Thread.sleep(2000); + + s.close(); + + boolean connectUnblocked = signal.await(2000, TimeUnit.MILLISECONDS); + assertTrue(connectUnblocked); + } + static class MockServer { private ExecutorService executor; private ServerSocket serverSocket; diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java index c09939f..3f831e0 100644 --- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java +++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java @@ -16,7 +16,8 @@ package libcore.java.net; -import com.android.okhttp.HttpResponseCache; +import com.android.okhttp.AndroidShimResponseCache; + import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; import com.google.mockwebserver.RecordedRequest; @@ -37,11 +38,14 @@ import java.net.ProtocolException; import java.net.Proxy; import java.net.ResponseCache; import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.net.UnknownHostException; +import java.nio.channels.SocketChannel; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -58,18 +62,18 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -import javax.net.SocketFactory; +import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; -import libcore.java.security.StandardNames; import libcore.java.security.TestKeyStore; import libcore.java.util.AbstractResourceLeakageDetectorTestCase; import libcore.javax.net.ssl.TestSSLContext; @@ -84,7 +88,7 @@ import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; public final class URLConnectionTest extends AbstractResourceLeakageDetectorTestCase { private MockWebServer server; - private HttpResponseCache cache; + private AndroidShimResponseCache cache; private String hostName; @Override protected void setUp() throws Exception { @@ -674,11 +678,144 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest private void initResponseCache() throws IOException { String tmp = System.getProperty("java.io.tmpdir"); File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); - cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE); + cache = AndroidShimResponseCache.create(cacheDir, Integer.MAX_VALUE); ResponseCache.setDefault(cache); } /** + * Test Etag headers are returned correctly when a client-side cache is not installed. + * https://code.google.com/p/android/issues/detail?id=108949 + */ + public void testEtagHeaders_uncached() throws Exception { + final String etagValue1 = "686897696a7c876b7e"; + final String body1 = "Response with etag 1"; + final String etagValue2 = "686897696a7c876b7f"; + final String body2 = "Response with etag 2"; + + server.enqueue( + new MockResponse() + .setBody(body1) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue1)); + server.enqueue( + new MockResponse() + .setBody(body2) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue2)); + server.play(); + + URL url = server.getUrl("/"); + HttpURLConnection connection1 = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue1, connection1.getHeaderField("Etag")); + assertContent(body1, connection1); + connection1.disconnect(); + + // Discard the server-side record of the request made. + server.takeRequest(); + + HttpURLConnection connection2 = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue2, connection2.getHeaderField("Etag")); + assertContent(body2, connection2); + connection2.disconnect(); + + // Check the client did not cache. + RecordedRequest request = server.takeRequest(); + assertNull(request.getHeader("If-None-Match")); + } + + /** + * Test Etag headers are returned correctly when a client-side cache is installed and the server + * data is unchanged. + * https://code.google.com/p/android/issues/detail?id=108949 + */ + public void testEtagHeaders_cachedWithServerHit() throws Exception { + final String etagValue = "686897696a7c876b7e"; + final String body = "Response with etag"; + + server.enqueue( + new MockResponse() + .setBody(body) + .setResponseCode(HttpURLConnection.HTTP_OK) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue)); + + server.enqueue( + new MockResponse() + .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)); + server.play(); + + initResponseCache(); + + URL url = server.getUrl("/"); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue, connection.getHeaderField("Etag")); + assertContent(body, connection); + connection.disconnect(); + + // Discard the server-side record of the request made. + server.takeRequest(); + + // Confirm the cached body is returned along with the original etag header. + HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue, cachedConnection.getHeaderField("Etag")); + assertContent(body, cachedConnection); + cachedConnection.disconnect(); + + // Check the client formatted the request correctly. + RecordedRequest request = server.takeRequest(); + assertEquals(etagValue, request.getHeader("If-None-Match")); + } + + /** + * Test Etag headers are returned correctly when a client-side cache is installed and the server + * data has changed. + * https://code.google.com/p/android/issues/detail?id=108949 + */ + public void testEtagHeaders_cachedWithServerMiss() throws Exception { + final String etagValue1 = "686897696a7c876b7e"; + final String body1 = "Response with etag 1"; + final String etagValue2 = "686897696a7c876b7f"; + final String body2 = "Response with etag 2"; + + server.enqueue( + new MockResponse() + .setBody(body1) + .setResponseCode(HttpURLConnection.HTTP_OK) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue1)); + + server.enqueue( + new MockResponse() + .setBody(body2) + .setResponseCode(HttpURLConnection.HTTP_OK) + .setHeader("Content-Type", "text/plain") + .setHeader("Etag", etagValue2)); + + server.play(); + + initResponseCache(); + + URL url = server.getUrl("/"); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue1, connection.getHeaderField("Etag")); + assertContent(body1, connection); + connection.disconnect(); + + // Discard the server-side record of the request made. + server.takeRequest(); + + // Confirm the new body is returned along with the new etag header. + HttpURLConnection cachedConnection = (HttpURLConnection) url.openConnection(); + assertEquals(etagValue2, cachedConnection.getHeaderField("Etag")); + assertContent(body2, cachedConnection); + cachedConnection.disconnect(); + + // Check the client formatted the request correctly. + RecordedRequest request = server.takeRequest(); + assertEquals(etagValue1, request.getHeader("If-None-Match")); + } + + /** * Test which headers are sent unencrypted to the HTTP proxy. */ public void testProxyConnectIncludesProxyHeadersOnly() @@ -2074,6 +2211,19 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest connection.disconnect(); } + public void testLastModified() throws Exception { + server.enqueue(new MockResponse() + .addHeader("Last-Modified", "Wed, 27 Nov 2013 11:26:00 GMT") + .setBody("Hello")); + server.play(); + + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + connection.connect(); + + assertEquals(1385551560000L, connection.getLastModified()); + assertEquals(1385551560000L, connection.getHeaderFieldDate("Last-Modified", -1)); + } + public void testClientSendsContentLength() throws Exception { server.enqueue(new MockResponse().setBody("A")); server.play(); @@ -2185,52 +2335,107 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest urlConnection.getInputStream(); } - public void testSslFallback() throws Exception { + public void testSslFallback_allSupportedProtocols() throws Exception { TestSSLContext testSSLContext = TestSSLContext.create(); - // This server socket factory only supports SSLv3. This is to avoid issues due to SCSV - // checks. See https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 + String[] allSupportedProtocols = { "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3" }; SSLSocketFactory serverSocketFactory = new LimitedProtocolsSocketFactory( testSSLContext.serverContext.getSocketFactory(), - "SSLv3"); - + allSupportedProtocols); server.useHttps(serverSocketFactory, false); server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); - server.enqueue(new MockResponse().setBody("This required a 2nd handshake")); + server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); + server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); + server.enqueue(new MockResponse().setBody("This required fallbacks")); server.play(); HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); - // Keep track of the client sockets created so that we can interrogate them. - RecordingSocketFactory clientSocketFactory = - new RecordingSocketFactory(testSSLContext.clientContext.getSocketFactory()); + // Keeps track of the client sockets created so that we can interrogate them. + final boolean disableFallbackScsv = true; + FallbackTestClientSocketFactory clientSocketFactory = new FallbackTestClientSocketFactory( + new LimitedProtocolsSocketFactory( + testSSLContext.clientContext.getSocketFactory(), allSupportedProtocols), + disableFallbackScsv); connection.setSSLSocketFactory(clientSocketFactory); - assertEquals("This required a 2nd handshake", + assertEquals("This required fallbacks", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + // Confirm the server accepted a single connection. RecordedRequest retry = server.takeRequest(); assertEquals(0, retry.getSequenceNumber()); assertEquals("SSLv3", retry.getSslProtocol()); // Confirm the client fallback looks ok. List<SSLSocket> createdSockets = clientSocketFactory.getCreatedSockets(); - assertEquals(2, createdSockets.size()); - SSLSocket clientSocket1 = createdSockets.get(0); - List<String> clientSocket1EnabledProtocols = Arrays.asList( - clientSocket1.getEnabledProtocols()); - assertContains(clientSocket1EnabledProtocols, "TLSv1.2"); - List<String> clientSocket1EnabledCiphers = - Arrays.asList(clientSocket1.getEnabledCipherSuites()); - assertContainsNoneMatching( - clientSocket1EnabledCiphers, StandardNames.CIPHER_SUITE_FALLBACK); - - SSLSocket clientSocket2 = createdSockets.get(1); - List<String> clientSocket2EnabledProtocols = - Arrays.asList(clientSocket2.getEnabledProtocols()); - assertContainsNoneMatching(clientSocket2EnabledProtocols, "TLSv1.2"); - List<String> clientSocket2EnabledCiphers = - Arrays.asList(clientSocket2.getEnabledCipherSuites()); - assertContains(clientSocket2EnabledCiphers, StandardNames.CIPHER_SUITE_FALLBACK); + assertEquals(4, createdSockets.size()); + TlsFallbackDisabledScsvSSLSocket clientSocket1 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(0); + assertSslSocket(clientSocket1, + false /* expectedWasFallbackScsvSet */, "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3"); + + TlsFallbackDisabledScsvSSLSocket clientSocket2 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(1); + assertSslSocket(clientSocket2, + true /* expectedWasFallbackScsvSet */, "TLSv1.1", "TLSv1", "SSLv3"); + + TlsFallbackDisabledScsvSSLSocket clientSocket3 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(2); + assertSslSocket(clientSocket3, true /* expectedWasFallbackScsvSet */, "TLSv1", "SSLv3"); + + TlsFallbackDisabledScsvSSLSocket clientSocket4 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(3); + assertSslSocket(clientSocket4, true /* expectedWasFallbackScsvSet */, "SSLv3"); + } + + public void testSslFallback_defaultProtocols() throws Exception { + TestSSLContext testSSLContext = TestSSLContext.create(); + + server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); + server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); + server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE)); + server.enqueue(new MockResponse().setBody("This required fallbacks")); + server.play(); + + HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); + // Keeps track of the client sockets created so that we can interrogate them. + final boolean disableFallbackScsv = true; + FallbackTestClientSocketFactory clientSocketFactory = new FallbackTestClientSocketFactory( + testSSLContext.clientContext.getSocketFactory(), + disableFallbackScsv); + connection.setSSLSocketFactory(clientSocketFactory); + assertEquals("This required fallbacks", + readAscii(connection.getInputStream(), Integer.MAX_VALUE)); + + // Confirm the server accepted a single connection. + RecordedRequest retry = server.takeRequest(); + assertEquals(0, retry.getSequenceNumber()); + assertEquals("TLSv1", retry.getSslProtocol()); + + // Confirm the client fallback looks ok. + List<SSLSocket> createdSockets = clientSocketFactory.getCreatedSockets(); + assertEquals(3, createdSockets.size()); + TlsFallbackDisabledScsvSSLSocket clientSocket1 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(0); + assertSslSocket(clientSocket1, + false /* expectedWasFallbackScsvSet */, "TLSv1.2", "TLSv1.1", "TLSv1"); + + TlsFallbackDisabledScsvSSLSocket clientSocket2 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(1); + assertSslSocket(clientSocket2, true /* expectedWasFallbackScsvSet */, "TLSv1.1", "TLSv1"); + + TlsFallbackDisabledScsvSSLSocket clientSocket3 = + (TlsFallbackDisabledScsvSSLSocket) createdSockets.get(2); + assertSslSocket(clientSocket3, true /* expectedWasFallbackScsvSet */, "TLSv1"); + } + + private static void assertSslSocket(TlsFallbackDisabledScsvSSLSocket socket, + boolean expectedWasFallbackScsvSet, String... expectedEnabledProtocols) { + Set<String> enabledProtocols = + new HashSet<String>(Arrays.asList(socket.getEnabledProtocols())); + Set<String> expectedProtocolsSet = new HashSet<String>(Arrays.asList(expectedEnabledProtocols)); + assertEquals(expectedProtocolsSet, enabledProtocols); + assertEquals(expectedWasFallbackScsvSet, socket.wasTlsFallbackScsvSet()); } public void testInspectSslBeforeConnect() throws Exception { @@ -2491,36 +2696,37 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) + public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - return delegate.createSocket(s, host, port, autoClose); + return (SSLSocket) delegate.createSocket(s, host, port, autoClose); } @Override - public Socket createSocket() throws IOException { - return delegate.createSocket(); + public SSLSocket createSocket() throws IOException { + return (SSLSocket) delegate.createSocket(); } @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { - return delegate.createSocket(host, port); + public SSLSocket createSocket(String host, int port) + throws IOException, UnknownHostException { + return (SSLSocket) delegate.createSocket(host, port); } @Override - public Socket createSocket(String host, int port, InetAddress localHost, + public SSLSocket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { - return delegate.createSocket(host, port, localHost, localPort); + return (SSLSocket) delegate.createSocket(host, port, localHost, localPort); } @Override - public Socket createSocket(InetAddress host, int port) throws IOException { - return delegate.createSocket(host, port); + public SSLSocket createSocket(InetAddress host, int port) throws IOException { + return (SSLSocket) delegate.createSocket(host, port); } @Override - public Socket createSocket(InetAddress address, int port, + public SSLSocket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - return delegate.createSocket(address, port, localAddress, localPort); + return (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); } } @@ -2539,7 +2745,7 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) + public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose); socket.setEnabledProtocols(protocols); @@ -2547,21 +2753,22 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } @Override - public Socket createSocket() throws IOException { + public SSLSocket createSocket() throws IOException { SSLSocket socket = (SSLSocket) delegate.createSocket(); socket.setEnabledProtocols(protocols); return socket; } @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + public SSLSocket createSocket(String host, int port) + throws IOException, UnknownHostException { SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); socket.setEnabledProtocols(protocols); return socket; } @Override - public Socket createSocket(String host, int port, InetAddress localHost, + public SSLSocket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort); socket.setEnabledProtocols(protocols); @@ -2569,14 +2776,14 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } @Override - public Socket createSocket(InetAddress host, int port) throws IOException { + public SSLSocket createSocket(InetAddress host, int port) throws IOException { SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); socket.setEnabledProtocols(protocols); return socket; } @Override - public Socket createSocket(InetAddress address, int port, + public SSLSocket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { SSLSocket socket = (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); @@ -2586,58 +2793,337 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } /** - * An SSLSocketFactory that delegates calls and keeps a record of any sockets created. + * An {@link javax.net.ssl.SSLSocket} that delegates all calls. */ - private static class RecordingSocketFactory extends DelegatingSSLSocketFactory { + private static abstract class DelegatingSSLSocket extends SSLSocket { + protected final SSLSocket delegate; + + public DelegatingSSLSocket(SSLSocket delegate) { + this.delegate = delegate; + } + + @Override public void shutdownInput() throws IOException { + delegate.shutdownInput(); + } + + @Override public void shutdownOutput() throws IOException { + delegate.shutdownOutput(); + } + + @Override public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override public String[] getEnabledCipherSuites() { + return delegate.getEnabledCipherSuites(); + } + + @Override public void setEnabledCipherSuites(String[] suites) { + delegate.setEnabledCipherSuites(suites); + } + + @Override public String[] getSupportedProtocols() { + return delegate.getSupportedProtocols(); + } + + @Override public String[] getEnabledProtocols() { + return delegate.getEnabledProtocols(); + } + + @Override public void setEnabledProtocols(String[] protocols) { + delegate.setEnabledProtocols(protocols); + } + + @Override public SSLSession getSession() { + return delegate.getSession(); + } + + @Override public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { + delegate.addHandshakeCompletedListener(listener); + } + + @Override public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { + delegate.removeHandshakeCompletedListener(listener); + } + + @Override public void startHandshake() throws IOException { + delegate.startHandshake(); + } + + @Override public void setUseClientMode(boolean mode) { + delegate.setUseClientMode(mode); + } + + @Override public boolean getUseClientMode() { + return delegate.getUseClientMode(); + } + + @Override public void setNeedClientAuth(boolean need) { + delegate.setNeedClientAuth(need); + } + + @Override public void setWantClientAuth(boolean want) { + delegate.setWantClientAuth(want); + } + + @Override public boolean getNeedClientAuth() { + return delegate.getNeedClientAuth(); + } + + @Override public boolean getWantClientAuth() { + return delegate.getWantClientAuth(); + } + @Override public void setEnableSessionCreation(boolean flag) { + delegate.setEnableSessionCreation(flag); + } + + @Override public boolean getEnableSessionCreation() { + return delegate.getEnableSessionCreation(); + } + + @Override public SSLParameters getSSLParameters() { + return delegate.getSSLParameters(); + } + + @Override public void setSSLParameters(SSLParameters p) { + delegate.setSSLParameters(p); + } + + @Override public void close() throws IOException { + delegate.close(); + } + + @Override public InetAddress getInetAddress() { + return delegate.getInetAddress(); + } + + @Override public InputStream getInputStream() throws IOException { + return delegate.getInputStream(); + } + + @Override public boolean getKeepAlive() throws SocketException { + return delegate.getKeepAlive(); + } + + @Override public InetAddress getLocalAddress() { + return delegate.getLocalAddress(); + } + + @Override public int getLocalPort() { + return delegate.getLocalPort(); + } + + @Override public OutputStream getOutputStream() throws IOException { + return delegate.getOutputStream(); + } + + @Override public int getPort() { + return delegate.getPort(); + } + + @Override public int getSoLinger() throws SocketException { + return delegate.getSoLinger(); + } + + @Override public int getReceiveBufferSize() throws SocketException { + return delegate.getReceiveBufferSize(); + } + + @Override public int getSendBufferSize() throws SocketException { + return delegate.getSendBufferSize(); + } + + @Override public int getSoTimeout() throws SocketException { + return delegate.getSoTimeout(); + } + + @Override public boolean getTcpNoDelay() throws SocketException { + return delegate.getTcpNoDelay(); + } + + @Override public void setKeepAlive(boolean keepAlive) throws SocketException { + delegate.setKeepAlive(keepAlive); + } + + @Override public void setSendBufferSize(int size) throws SocketException { + delegate.setSendBufferSize(size); + } + + @Override public void setReceiveBufferSize(int size) throws SocketException { + delegate.setReceiveBufferSize(size); + } + + @Override public void setSoLinger(boolean on, int timeout) throws SocketException { + delegate.setSoLinger(on, timeout); + } + + @Override public void setSoTimeout(int timeout) throws SocketException { + delegate.setSoTimeout(timeout); + } + + @Override public void setTcpNoDelay(boolean on) throws SocketException { + delegate.setTcpNoDelay(on); + } + + @Override public String toString() { + return delegate.toString(); + } + + @Override public SocketAddress getLocalSocketAddress() { + return delegate.getLocalSocketAddress(); + } + + @Override public SocketAddress getRemoteSocketAddress() { + return delegate.getRemoteSocketAddress(); + } + + @Override public boolean isBound() { + return delegate.isBound(); + } + + @Override public boolean isConnected() { + return delegate.isConnected(); + } + + @Override public boolean isClosed() { + return delegate.isClosed(); + } + + @Override public void bind(SocketAddress localAddr) throws IOException { + delegate.bind(localAddr); + } + + @Override public void connect(SocketAddress remoteAddr) throws IOException { + delegate.connect(remoteAddr); + } + + @Override public void connect(SocketAddress remoteAddr, int timeout) throws IOException { + delegate.connect(remoteAddr, timeout); + } + + @Override public boolean isInputShutdown() { + return delegate.isInputShutdown(); + } + + @Override public boolean isOutputShutdown() { + return delegate.isOutputShutdown(); + } + + @Override public void setReuseAddress(boolean reuse) throws SocketException { + delegate.setReuseAddress(reuse); + } + + @Override public boolean getReuseAddress() throws SocketException { + return delegate.getReuseAddress(); + } + + @Override public void setOOBInline(boolean oobinline) throws SocketException { + delegate.setOOBInline(oobinline); + } + + @Override public boolean getOOBInline() throws SocketException { + return delegate.getOOBInline(); + } + + @Override public void setTrafficClass(int value) throws SocketException { + delegate.setTrafficClass(value); + } + + @Override public int getTrafficClass() throws SocketException { + return delegate.getTrafficClass(); + } + + @Override public void sendUrgentData(int value) throws IOException { + delegate.sendUrgentData(value); + } + + @Override public SocketChannel getChannel() { + return delegate.getChannel(); + } + + @Override public void setPerformancePreferences(int connectionTime, int latency, + int bandwidth) { + delegate.setPerformancePreferences(connectionTime, latency, bandwidth); + } + } + + /** + * An SSLSocketFactory that delegates calls. It keeps a record of any sockets created. + * If {@link #disableTlsFallbackScsv} is set to {@code true} then sockets created by the + * delegate are wrapped with ones that will not accept the {@link #TLS_FALLBACK_SCSV} cipher, + * thus bypassing server-side fallback checks on platforms that support it. Unfortunately this + * wrapping will disable any reflection-based calls to SSLSocket from Platform. + */ + private static class FallbackTestClientSocketFactory extends DelegatingSSLSocketFactory { + /** + * The cipher suite used during TLS connection fallback to indicate a fallback. + * See https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 + */ + public static final String TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV"; + + private final boolean disableTlsFallbackScsv; private final List<SSLSocket> createdSockets = new ArrayList<SSLSocket>(); - private RecordingSocketFactory(SSLSocketFactory delegate) { + public FallbackTestClientSocketFactory(SSLSocketFactory delegate, + boolean disableTlsFallbackScsv) { super(delegate); + this.disableTlsFallbackScsv = disableTlsFallbackScsv; } - @Override - public Socket createSocket(Socket s, String host, int port, boolean autoClose) + @Override public SSLSocket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose); + SSLSocket socket = super.createSocket(s, host, port, autoClose); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket() throws IOException { - SSLSocket socket = (SSLSocket) delegate.createSocket(); + @Override public SSLSocket createSocket() throws IOException { + SSLSocket socket = super.createSocket(); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket(String host, int port) throws IOException, UnknownHostException { - SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); + @Override public SSLSocket createSocket(String host,int port) throws IOException { + SSLSocket socket = super.createSocket(host, port); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket(String host, int port, InetAddress localHost, - int localPort) throws IOException, UnknownHostException { - SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort); + @Override public SSLSocket createSocket(String host,int port, InetAddress localHost, + int localPort) throws IOException { + SSLSocket socket = super.createSocket(host, port, localHost, localPort); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket(InetAddress host, int port) throws IOException { - SSLSocket socket = (SSLSocket) delegate.createSocket(host, port); + @Override public SSLSocket createSocket(InetAddress host,int port) throws IOException { + SSLSocket socket = super.createSocket(host, port); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } - @Override - public Socket createSocket(InetAddress address, int port, + @Override public SSLSocket createSocket(InetAddress address,int port, InetAddress localAddress, int localPort) throws IOException { - SSLSocket socket = - (SSLSocket) delegate.createSocket(address, port, localAddress, localPort); + SSLSocket socket = super.createSocket(address, port, localAddress, localPort); + if (disableTlsFallbackScsv) { + socket = new TlsFallbackDisabledScsvSSLSocket(socket); + } createdSockets.add(socket); return socket; } @@ -2647,4 +3133,31 @@ public final class URLConnectionTest extends AbstractResourceLeakageDetectorTest } } + private static class TlsFallbackDisabledScsvSSLSocket extends DelegatingSSLSocket { + + private boolean tlsFallbackScsvSet; + + public TlsFallbackDisabledScsvSSLSocket(SSLSocket socket) { + super(socket); + } + + @Override public void setEnabledCipherSuites(String[] suites) { + List<String> enabledCipherSuites = new ArrayList<String>(suites.length); + for (String suite : suites) { + if (suite.equals(FallbackTestClientSocketFactory.TLS_FALLBACK_SCSV)) { + // Record that an attempt was made to set TLS_FALLBACK_SCSV, but don't actually + // set it. + tlsFallbackScsvSet = true; + } else { + enabledCipherSuites.add(suite); + } + } + delegate.setEnabledCipherSuites( + enabledCipherSuites.toArray(new String[enabledCipherSuites.size()])); + } + + public boolean wasTlsFallbackScsvSet() { + return tlsFallbackScsvSet; + } + } } diff --git a/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java b/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java index 9789197..41b434d 100644 --- a/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/SelectorTest.java @@ -15,7 +15,6 @@ */ package libcore.java.nio.channels; -import android.system.OsConstants; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; @@ -28,7 +27,6 @@ import java.nio.channels.SocketChannel; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; -import libcore.io.Libcore; import tests.net.StuckServer; public class SelectorTest extends TestCase { @@ -71,25 +69,6 @@ public class SelectorTest extends TestCase { } } - // http://b/6453247 - // This test won't work on the host until/unless we start using libcorkscrew there. - // The runtime itself blocks SIGQUIT, so that doesn't cause poll(2) to EINTR directly. - // The EINTR is caused by the way libcorkscrew works. - public void testEINTR() throws Exception { - Selector selector = Selector.open(); - new Thread(new Runnable() { - @Override public void run() { - try { - Thread.sleep(2000); - Libcore.os.kill(Libcore.os.getpid(), OsConstants.SIGQUIT); - } catch (Exception ex) { - fail(); - } - } - }).start(); - assertEquals(0, selector.select()); - } - // http://code.google.com/p/android/issues/detail?id=15388 public void testInterrupted() throws IOException { Selector selector = Selector.open(); diff --git a/luni/src/test/java/libcore/java/nio/charset/CharsetEncoderTest.java b/luni/src/test/java/libcore/java/nio/charset/CharsetEncoderTest.java index ff510e0..e9ab8ae 100644 --- a/luni/src/test/java/libcore/java/nio/charset/CharsetEncoderTest.java +++ b/luni/src/test/java/libcore/java/nio/charset/CharsetEncoderTest.java @@ -20,8 +20,10 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; +import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; import java.util.Arrays; public class CharsetEncoderTest extends junit.framework.TestCase { @@ -161,4 +163,71 @@ public class CharsetEncoderTest extends junit.framework.TestCase { assertEquals(CoderResult.UNDERFLOW, cr); assertEquals(8, bb.position()); } + + // Discards all input. Outputs a single byte 'X' on flush. + private static final class MockCharset extends Charset { + static final Charset INSTANCE = new MockCharset(); + + private MockCharset() { + super("MockCharset", new String[0]); + } + + public boolean contains(Charset charset) { + return false; + } + + public CharsetEncoder newEncoder() { + return new CharsetEncoder(INSTANCE, 1.f, 1.f) { + protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) { + in.position(in.limit()); + return CoderResult.UNDERFLOW; + } + + protected CoderResult implFlush(ByteBuffer out) { + out.put((byte) 'X'); + return CoderResult.UNDERFLOW; + } + }; + } + + public CharsetDecoder newDecoder() { + return new CharsetDecoder(INSTANCE, 1.f, 1.f) { + protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { + in.position(in.limit()); + return CoderResult.UNDERFLOW; + } + }; + } + } + + // Repeated calls to flush() should not result in repeated calls to implFlush(). + public void testFlushNotCallingImplFlushRepeatedly() { + CharsetEncoder e = MockCharset.INSTANCE.newEncoder(); + ByteBuffer bb = ByteBuffer.allocate(4); + CoderResult cr = e.encode(CharBuffer.allocate(0), bb, true); + assertEquals(CoderResult.UNDERFLOW, cr); + cr = e.flush(bb); + assertEquals(CoderResult.UNDERFLOW, cr); + cr = e.flush(bb); + assertEquals(CoderResult.UNDERFLOW, cr); + assertEquals(1, bb.position()); + assertEquals((byte) 'X', bb.get(0)); + assertEquals(0x00, bb.get(1)); + assertEquals(0x00, bb.get(2)); + assertEquals(0x00, bb.get(3)); + } + + // http://b/19185235 + public void testFlushWithIncompleteInput() { + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); + ByteBuffer output = ByteBuffer.allocate(10); + CoderResult result = encoder.encode(CharBuffer.wrap("\ud800"), output, + true /* endOfInput */); + assertTrue(result.isUnderflow()); + + result = encoder.flush(output); + assertTrue(result.isMalformed()); + assertEquals(1, result.length()); + assertEquals(0, output.position()); + } } diff --git a/luni/src/test/java/libcore/java/nio/charset/SettableCharsetProvider.java b/luni/src/test/java/libcore/java/nio/charset/SettableCharsetProvider.java new file mode 100644 index 0000000..b4886d2 --- /dev/null +++ b/luni/src/test/java/libcore/java/nio/charset/SettableCharsetProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 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 libcore.java.nio.charset; + +import java.nio.charset.Charset; +import java.nio.charset.spi.CharsetProvider; +import java.util.Collections; +import java.util.Iterator; + +/** + * This class is registered as a charset provider by the META-INF in the libcore + * tests jar. Since there isn't any convenient API to dynamically register and de-register + * charset-providers, this class allows tests to plug in a delegate that lives for the + * duration of the test. + */ +public final class SettableCharsetProvider extends CharsetProvider { + private static CharsetProvider delegate; + + public static void setDelegate(CharsetProvider cp) { + delegate = cp; + } + + public static void clearDelegate() { + delegate = null; + } + + @Override + public Iterator<Charset> charsets() { + if (delegate != null) { + return delegate.charsets(); + } + + return Collections.emptyIterator(); + } + + @Override + public Charset charsetForName(String charsetName) { + if (delegate != null) { + return delegate.charsetForName(charsetName); + } + + return null; + } +} diff --git a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java index e7fdb1f..10bb621 100644 --- a/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java +++ b/luni/src/test/java/libcore/java/security/KeyPairGeneratorTest.java @@ -45,6 +45,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import javax.crypto.interfaces.DHPrivateKey; @@ -55,13 +56,32 @@ import junit.framework.TestCase; public class KeyPairGeneratorTest extends TestCase { - public void test_providerCount() { + private List<Provider> providers = new ArrayList<Provider>(); + + @Override + public void setUp() { Provider[] providers = Security.getProviders(); + for (Provider p : providers) { + // Do not test AndroidKeyStore Provider. It does not accept vanilla public keys for + // signature verification. It's OKish not to test here because it's tested by + // cts/tests/tests/keystore. + if (!p.getName().startsWith("AndroidKeyStore")) { + this.providers.add(p); + } + } + } + + @Override + public void tearDown() { + providers.clear(); + } + + public void test_providerCount() { // We expect there to be at least one provider. - assertTrue(providers.length > 0); + assertTrue(providers.size() > 0); // If this fails remember to add _provider methods below. This test is sharded because it // takes a long time to execute. - assertTrue(providers.length < 10); + assertTrue(providers.size() < 10); } public void test_getInstance_provider0() throws Exception { @@ -105,14 +125,14 @@ public class KeyPairGeneratorTest extends TestCase { } private void test_getInstance(int providerIndex) throws Exception { - Provider[] providers = Security.getProviders(); - if (providerIndex >= providers.length) { + if (providerIndex >= providers.size()) { // Providers can be added by vendors and other tests. We do not // specify a fixed number and silenty pass if the provider at the // specified index does not exist. return; } - Provider provider = providers[providerIndex]; + + Provider provider = providers.get(providerIndex); Set<Provider.Service> services = provider.getServices(); for (Provider.Service service : services) { String type = service.getType(); @@ -120,12 +140,6 @@ public class KeyPairGeneratorTest extends TestCase { continue; } String algorithm = service.getAlgorithm(); - - // AndroidKeyStore is tested in CTS. - if ("AndroidKeyStore".equals(provider.getName())) { - continue; - } - AlgorithmParameterSpec params = null; if ("DH".equals(algorithm)) { @@ -195,7 +209,6 @@ public class KeyPairGeneratorTest extends TestCase { putKeySize("DiffieHellman", 512); putKeySize("DiffieHellman", 512+64); putKeySize("DiffieHellman", 1024); - putKeySize("EC", 192); putKeySize("EC", 224); putKeySize("EC", 256); putKeySize("EC", 384); @@ -204,10 +217,10 @@ public class KeyPairGeneratorTest extends TestCase { /** Elliptic Curve Crypto named curves that should be supported. */ private static final String[] EC_NAMED_CURVES = { - // NIST P-192 aka SECG secp192r1 aka ANSI X9.62 prime192v1 - "secp192r1", "prime192v1", // NIST P-256 aka SECG secp256r1 aka ANSI X9.62 prime256v1 "secp256r1", "prime256v1", + // NIST P-521 aka SECG secp521r1 + "secp521r1", }; private void test_KeyPairGenerator(KeyPairGenerator kpg) throws Exception { @@ -264,7 +277,7 @@ public class KeyPairGeneratorTest extends TestCase { } private void test_Key(KeyPairGenerator kpg, Key k) throws Exception { - String expectedAlgorithm = kpg.getAlgorithm().toUpperCase(); + String expectedAlgorithm = kpg.getAlgorithm().toUpperCase(Locale.ROOT); if (StandardNames.IS_RI && expectedAlgorithm.equals("DIFFIEHELLMAN")) { expectedAlgorithm = "DH"; } @@ -303,8 +316,6 @@ public class KeyPairGeneratorTest extends TestCase { byte[] encoded = k.getEncoded(); String keyAlgo = k.getAlgorithm(); - - Provider[] providers = Security.getProviders(); for (Provider p : providers) { Set<Provider.Service> services = p.getServices(); for (Provider.Service service : services) { diff --git a/luni/src/test/java/libcore/java/security/ProviderTest.java b/luni/src/test/java/libcore/java/security/ProviderTest.java index 994214b..d3ccae1 100644 --- a/luni/src/test/java/libcore/java/security/ProviderTest.java +++ b/luni/src/test/java/libcore/java/security/ProviderTest.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.crypto.Cipher; @@ -160,7 +161,7 @@ public class ProviderTest extends TestCase { public void test_Provider_Properties() throws Exception { /* * A useful reference on Provider properties - * <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/crypto/HowToImplAProvider.html> + * <a href="http://java.sun.com/javase/6/docs/technotes/guides/security/crypto/HowToImplAProvider.html"> * How to Implement a Provider in the Java ™ Cryptography Architecture * </a> */ @@ -178,8 +179,8 @@ public class ProviderTest extends TestCase { provider.get("Provider.id className")); // build map of all known aliases and implementations - Map<String,String> aliases = new HashMap<String,String>(); - Map<String,String> implementations = new HashMap<String,String>(); + Map<String,String> aliases = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + Map<String,String> implementations = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (Entry<Object,Object> entry : provider.entrySet()) { Object k = entry.getKey(); Object v = entry.getValue(); @@ -218,7 +219,8 @@ public class ProviderTest extends TestCase { } catch (ClassNotFoundException e) { // Sun forgot their own class if (!className.equals("sun.security.pkcs11.P11MAC")) { - fail("Could not find class " + className + " for " + typeAndAlgorithm); + fail("Could not find class " + className + " for " + typeAndAlgorithm + + " [provider=" + provider.getName() + "]"); } } } @@ -227,8 +229,9 @@ public class ProviderTest extends TestCase { for (Entry<String,String> entry : aliases.entrySet()) { String alias = entry.getKey(); String actual = entry.getValue(); - assertTrue("Could not find implementation " + actual + " for alias " + alias, - implementations.containsKey(actual)); + assertTrue("Could not find implementation " + actual + " for alias " + alias + + " [provider=" + provider.getName() + "]", + implementations.containsKey(actual)); } } } @@ -547,6 +550,15 @@ public class ProviderTest extends TestCase { } } + public void testProvider_removeProvider_Success() throws Exception { + MockProvider provider = new MockProvider("MockProvider"); + assertNull(Security.getProvider(provider.getName())); + Security.addProvider(provider); + assertNotNull(Security.getProvider(provider.getName())); + Security.removeProvider(provider.getName()); + assertNull(Security.getProvider(provider.getName())); + } + public static class MyCertStoreSpi extends CertStoreSpi { public MyCertStoreSpi(CertStoreParameters params) throws InvalidAlgorithmParameterException { super(params); diff --git a/luni/src/test/java/libcore/java/security/SecureRandomTest.java b/luni/src/test/java/libcore/java/security/SecureRandomTest.java index f9edbaa..e296775 100644 --- a/luni/src/test/java/libcore/java/security/SecureRandomTest.java +++ b/luni/src/test/java/libcore/java/security/SecureRandomTest.java @@ -102,6 +102,7 @@ public class SecureRandomTest extends TestCase { public void testNewConstructors_Success() throws Exception { SecureRandom sr1 = new SecureRandom(); + assertNotNull(sr1.getProvider()); assertEquals(EXPECTED_PROVIDER, sr1.getProvider().getClass().getName()); test_SecureRandom(sr1); diff --git a/luni/src/test/java/libcore/java/security/SignatureTest.java b/luni/src/test/java/libcore/java/security/SignatureTest.java index 5e02f10..9aa75bd 100644 --- a/luni/src/test/java/libcore/java/security/SignatureTest.java +++ b/luni/src/test/java/libcore/java/security/SignatureTest.java @@ -31,6 +31,11 @@ import java.security.Signature; import java.security.SignatureException; import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; +import java.security.spec.ECFieldFp; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPrivateKeySpec; @@ -71,6 +76,49 @@ public class SignatureTest extends TestCase { } } + public void testSignature_getInstance_DoesNotSupportKeyClass_Success() throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Signature.FOO", MockSignatureSpi.AllKeyTypes.class.getName()); + put("Signature.FOO SupportedKeyClasses", "None"); + } + }; + + Security.addProvider(mockProvider); + try { + Signature s = Signature.getInstance("FOO", mockProvider); + s.initSign(new MockPrivateKey()); + assertEquals(mockProvider, s.getProvider()); + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + + /** + * Several exceptions can be thrown by init. Check that in this case we throw the right one, + * as the error could fall under the umbrella of other exceptions. + * http://b/18987633 + */ + public void testSignature_init_DoesNotSupportKeyClass_throwsInvalidKeyException() + throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Signature.FOO", MockSignatureSpi.AllKeyTypes.class.getName()); + put("Signature.FOO SupportedKeyClasses", "None"); + } + }; + + Security.addProvider(mockProvider); + try { + Signature s = Signature.getInstance("FOO"); + s.initSign(new MockPrivateKey()); + fail("Expected InvalidKeyException"); + } catch (InvalidKeyException expected) { + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + public void testSignature_getInstance_OnlyUsesSpecifiedProvider_SameNameAndClass_Success() throws Exception { Provider mockProvider = new MockProvider("MockProvider") { @@ -238,6 +286,12 @@ public class SignatureTest extends TestCase { public void test_getInstance() throws Exception { Provider[] providers = Security.getProviders(); for (Provider provider : providers) { + // Do not test AndroidKeyStore's Signature. It needs an AndroidKeyStore-specific key. + // It's OKish not to test AndroidKeyStore's Signature here because it's tested + // by cts/tests/test/keystore. + if (provider.getName().startsWith("AndroidKeyStore")) { + continue; + } Set<Provider.Service> services = provider.getServices(); for (Provider.Service service : services) { String type = service.getType(); @@ -370,9 +424,14 @@ public class SignatureTest extends TestCase { return data; } - // http://code.google.com/p/android/issues/detail?id=18566 - // http://b/5038554 - public void test18566() throws Exception { + /** + * This should actually fail because the ASN.1 encoding is incorrect. It is + * missing the NULL in the AlgorithmIdentifier field. + * <p> + * http://code.google.com/p/android/issues/detail?id=18566 <br/> + * http://b/5038554 + */ + public void test18566_AlgorithmOid_MissingNull_Failure() throws Exception { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(PK_BYTES); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey pk = keyFactory.generatePublic(keySpec); @@ -380,7 +439,7 @@ public class SignatureTest extends TestCase { Signature sig = Signature.getInstance("SHA256withRSA"); sig.initVerify(pk); sig.update(CONTENT); - assertTrue(sig.verify(SIGNATURE)); + assertFalse(sig.verify(SIGNATURE)); } /* @@ -1646,4 +1705,43 @@ public class SignatureTest extends TestCase { es.shutdown(); assertTrue("Test should not timeout", es.awaitTermination(1, TimeUnit.MINUTES)); } + + public void testArbitraryCurve() throws Exception { + // These are the parameters for the BitCoin curve (secp256k1). See + // https://en.bitcoin.it/wiki/Secp256k1. + final BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16); + final BigInteger a = BigInteger.valueOf(0); + final BigInteger b = BigInteger.valueOf(7); + final BigInteger x = new BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16); + final BigInteger y = new BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16); + final BigInteger order = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16); + final int cofactor = 1; + + final ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new ECFieldFp(p), a, b), new ECPoint(x, y), order, cofactor); + final KeyFactory factory = KeyFactory.getInstance("EC"); + + // $ openssl ecparam -name secp256k1 -genkey > key.pem + // $ openssl ec -text -noout < key.pem + final BigInteger Px = new BigInteger("2d45572747a625db5fd23b30f97044a682f2d42d31959295043c1fa0034c8ed3", 16); + final BigInteger Py = new BigInteger("4d330f52e4bba00145a331041c8bbcf300c4fbfdf3d63d8de7608155b2793808", 16); + + final ECPublicKeySpec keySpec = new ECPublicKeySpec(new ECPoint(Px, Py), spec); + final PublicKey pub = factory.generatePublic(keySpec); + + // $ echo -n "Satoshi Nakamoto" > signed + // $ openssl dgst -ecdsa-with-SHA1 -sign key.pem -out sig signed + final byte[] SIGNATURE = hexToBytes("304402205b41ece6dcc1c5bfcfdae74658d99c08c5e783f3926c11ecc1a8bea5d95cdf27022061a7d5fc687287e2e02dd7c6723e2e27fe0555f789590a37e96b1bb0355b4df0"); + + Signature ecdsaVerify = Signature.getInstance("SHA1withECDSA"); + ecdsaVerify.initVerify(pub); + ecdsaVerify.update("Satoshi Nakamoto".getBytes("UTF-8")); + boolean result = ecdsaVerify.verify(SIGNATURE); + assertEquals(true, result); + + ecdsaVerify = Signature.getInstance("SHA1withECDSA"); + ecdsaVerify.initVerify(pub); + ecdsaVerify.update("Not Satoshi Nakamoto".getBytes("UTF-8")); + result = ecdsaVerify.verify(SIGNATURE); + assertEquals(false, result); + } } diff --git a/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java b/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java index e2f21e8..a3a721a 100644 --- a/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java +++ b/luni/src/test/java/libcore/java/security/cert/CertificateFactoryTest.java @@ -16,11 +16,13 @@ package libcore.java.security.cert; +import com.android.org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import com.android.org.bouncycastle.asn1.x509.BasicConstraints; -import com.android.org.bouncycastle.asn1.x509.X509Extensions; +import com.android.org.bouncycastle.asn1.x509.Extension; +import com.android.org.bouncycastle.asn1.x509.SubjectKeyIdentifier; +import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import com.android.org.bouncycastle.x509.X509V3CertificateGenerator; import com.android.org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure; -import com.android.org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -28,13 +30,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.OptionalDataException; -import java.io.StreamCorruptedException; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; +import java.security.PublicKey; import java.security.Security; import java.security.cert.CertPath; import java.security.cert.Certificate; @@ -554,25 +557,26 @@ public class CertificateFactoryTest extends TestCase { X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); + PublicKey pubKey = keyPair.getPublic(); certGen.setSerialNumber(serial); certGen.setIssuerDN(issuerPrincipal); certGen.setNotBefore(startDate); certGen.setNotAfter(expiryDate); certGen.setSubjectDN(subjectPrincipal); - certGen.setPublicKey(keyPair.getPublic()); + certGen.setPublicKey(pubKey); certGen.setSignatureAlgorithm("SHA1withRSA"); if (issuer != null) { - certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, + certGen.addExtension(Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(issuer.certificate)); } else { - certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false, - new AuthorityKeyIdentifierStructure(keyPair.getPublic())); + certGen.addExtension(Extension.authorityKeyIdentifier, false, + new AuthorityKeyIdentifier(generatePublicKeyDigest(pubKey))); } - certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false, - new SubjectKeyIdentifierStructure(keyPair.getPublic())); - certGen.addExtension(X509Extensions.BasicConstraints, true, basicConstraints); + certGen.addExtension(Extension.subjectKeyIdentifier, false, + new SubjectKeyIdentifier(generatePublicKeyDigest(pubKey))); + certGen.addExtension(Extension.basicConstraints, true, basicConstraints); X509Certificate cert = certGen.generate(caKey); @@ -582,4 +586,18 @@ public class CertificateFactoryTest extends TestCase { return holder; } + + /** + * Generates a type 1 key identifier according to RFC 3280 4.2.1.2. + */ + private static byte[] generatePublicKeyDigest(PublicKey pubKey) { + SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()); + MessageDigest sha1digest; + try { + sha1digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-1 not available"); + } + return sha1digest.digest(spki.getPublicKeyData().getBytes()); + } } diff --git a/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java b/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java index 42de50a..1611120 100644 --- a/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java +++ b/luni/src/test/java/libcore/java/security/cert/X509CRLTest.java @@ -256,15 +256,7 @@ public class X509CRLTest extends TestCase { private void getSigAlgName(CertificateFactory f) throws Exception { X509CRL crlRsa = getCRL(f, CRL_RSA); - - String actual = crlRsa.getSigAlgName().toUpperCase(Locale.US); - - // Bouncycastle is broken - if ("BC".equals(f.getProvider().getName())) { - assertEquals("1.2.840.113549.1.1.5", actual); - } else { - assertEquals("SHA1WITHRSA", actual); - } + assertEquals("SHA1WITHRSA", getCRL(f, CRL_RSA).getSigAlgName().toUpperCase(Locale.ROOT)); } private void getSigAlgOID(CertificateFactory f) throws Exception { diff --git a/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java b/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java index c35f8e6..14c22ef 100644 --- a/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java +++ b/luni/src/test/java/libcore/java/security/cert/X509CertificateTest.java @@ -339,6 +339,13 @@ public class X509CertificateTest extends TestCase { Provider[] providers = Security.getProviders("Signature." + c.getSigAlgName()); for (Provider p : providers) { + // Do not test AndroidKeyStore Provider. It does not accept vanilla public keys for + // signature verification. It's OKish not to test here because it's tested by + // cts/tests/tests/keystore. + if (p.getName().startsWith("AndroidKeyStore")) { + continue; + } + c.verify(signer, p.getName()); try { diff --git a/luni/src/test/java/libcore/java/sql/TimestampTest.java b/luni/src/test/java/libcore/java/sql/TimestampTest.java index 2985848..71ac8c8 100644 --- a/luni/src/test/java/libcore/java/sql/TimestampTest.java +++ b/luni/src/test/java/libcore/java/sql/TimestampTest.java @@ -144,4 +144,12 @@ public final class TimestampTest extends TestCase { } catch (IllegalArgumentException expected) { } } + // http://b/19756610 + public void testAsymmetricEquals() { + Timestamp timestamp = new Timestamp(0); + java.util.Date date = new java.util.Date(0); + + assertTrue(date.equals(timestamp)); + assertFalse(timestamp.equals(date)); + } } diff --git a/luni/src/test/java/libcore/java/text/BreakIteratorTest.java b/luni/src/test/java/libcore/java/text/BreakIteratorTest.java index 47701c8..de2ae52 100644 --- a/luni/src/test/java/libcore/java/text/BreakIteratorTest.java +++ b/luni/src/test/java/libcore/java/text/BreakIteratorTest.java @@ -172,32 +172,4 @@ public class BreakIteratorTest extends junit.framework.TestCase { // Expected exception } } - - // http://code.google.com/p/android/issues/detail?id=41143 - // This code is inherently unsafe and crazy; - // we're just trying to provoke native crashes! - public void testConcurrentBreakIteratorAccess() throws Exception { - final BreakIterator it = BreakIterator.getCharacterInstance(); - - ArrayList<Thread> threads = new ArrayList<Thread>(); - for (int i = 0; i < 10; ++i) { - Thread t = new Thread(new Runnable() { - public void run() { - for (int i = 0; i < 4096; ++i) { - it.setText("some example text"); - for (int index = it.first(); index != BreakIterator.DONE; index = it.next()) { - } - } - } - }); - threads.add(t); - } - - for (Thread t : threads) { - t.start(); - } - for (Thread t : threads) { - t.join(); - } - } } diff --git a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java index e6933e6..0c97f34 100644 --- a/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java +++ b/luni/src/test/java/libcore/java/text/DateFormatSymbolsTest.java @@ -147,17 +147,17 @@ public class DateFormatSymbolsTest extends junit.framework.TestCase { } // http://b/7955614 - public void test_getZoneStrings_GMT_short_names() throws Exception { + public void test_getZoneStrings_Apia() throws Exception { String[][] array = DateFormatSymbols.getInstance(Locale.US).getZoneStrings(); for (int i = 0; i < array.length; ++i) { String[] row = array[i]; - // America/Santiago is somewhat arbitrary; we just want a zone we have to generate + // Pacific/Apia is somewhat arbitrary; we just want a zone we have to generate // "GMT" strings for the short names. - if (row[0].equals("America/Santiago")) { - assertEquals("Chile Standard Time", row[1]); - assertEquals("GMT-03:00", row[2]); - assertEquals("Chile Summer Time", row[3]); - assertEquals("GMT-03:00", row[4]); + if (row[0].equals("Pacific/Apia")) { + assertEquals("Apia Standard Time", row[1]); + assertEquals("GMT+13:00", row[2]); + assertEquals("Apia Daylight Time", row[3]); + assertEquals("GMT+14:00", row[4]); } } } diff --git a/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java b/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java index 3e0aeba..b1d37f3 100644 --- a/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java +++ b/luni/src/test/java/libcore/java/text/DecimalFormatSymbolsTest.java @@ -86,4 +86,34 @@ public class DecimalFormatSymbolsTest extends junit.framework.TestCase { assertEquals("$", dfs.getCurrencySymbol()); assertEquals(null, dfs.getInternationalCurrencySymbol()); } + + // https://code.google.com/p/android/issues/detail?id=170718 + public void testSerializationOfMultiCharNegativeAndPercentage() throws Exception { + DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.forLanguageTag("ar-AR")); + assertTrue(dfs.getMinusSignString().length() > 1); + assertTrue(dfs.getPercentString().length() > 1); + + // Serialize... + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new ObjectOutputStream(out).writeObject(dfs); + byte[] bytes = out.toByteArray(); + + // Deserialize... + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); + DecimalFormatSymbols deserializedDfs = (DecimalFormatSymbols) in.readObject(); + assertEquals(-1, in.read()); + + assertEquals(dfs.getMinusSignString(), deserializedDfs.getMinusSignString()); + assertEquals(dfs.getPercentString(), deserializedDfs.getPercentString()); + } + + // http://b/18785260 + public void testMultiCharMinusSignAndPercentage() { + DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.forLanguageTag("ar-AR")); + assertTrue(dfs.getMinusSignString().length() > 1); + assertTrue(dfs.getPercentString().length() > 1); + + assertEquals('%', dfs.getPercent()); + assertEquals('-', dfs.getMinusSign()); + } } diff --git a/luni/src/test/java/libcore/java/text/NumberFormatTest.java b/luni/src/test/java/libcore/java/text/NumberFormatTest.java index 0678e96..87fe96d 100644 --- a/luni/src/test/java/libcore/java/text/NumberFormatTest.java +++ b/luni/src/test/java/libcore/java/text/NumberFormatTest.java @@ -17,6 +17,7 @@ package libcore.java.text; import java.math.BigInteger; +import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.FieldPosition; @@ -93,6 +94,21 @@ public class NumberFormatTest extends junit.framework.TestCase { } } + public void testPercentageRounding() throws Exception { + NumberFormat nf = NumberFormat.getPercentInstance(Locale.US); + assertEquals("15%", nf.format(0.149)); + assertEquals("14%", nf.format(0.142)); + + nf.setRoundingMode(RoundingMode.UP); + assertEquals("15%", nf.format(0.142)); + + nf.setRoundingMode(RoundingMode.DOWN); + assertEquals("14%", nf.format(0.149)); + + nf.setMaximumFractionDigits(1); + assertEquals("14.9%", nf.format(0.149)); + } + // https://code.google.com/p/android/issues/detail?id=62269 public void test_62269() throws Exception { NumberFormat nf = NumberFormat.getNumberInstance(Locale.US); diff --git a/luni/src/test/java/libcore/java/util/CalendarTest.java b/luni/src/test/java/libcore/java/util/CalendarTest.java index e0e1a35..2e13ad8 100644 --- a/luni/src/test/java/libcore/java/util/CalendarTest.java +++ b/luni/src/test/java/libcore/java/util/CalendarTest.java @@ -263,4 +263,76 @@ public class CalendarTest extends junit.framework.TestCase { b.setTime(d); assertEquals(a, b); } + + public void testCloneMakesDeepCopyOfCalendarFields() { + FakeCalendar c = new FakeCalendar(); + FakeCalendar c2 = (FakeCalendar) c.clone(); + + assertFalse(c.getTimeZone() == c2.getTimeZone()); + assertEquals(c.getTimeZone(), c2.getTimeZone()); + + // The default clone() implementation makes a deep copy of calendar + // fields... + assertFalse(c.getCalenderFields() == c2.getCalenderFields()); + // ,,, and a shallow copy of subclass fields. + assertSame(c.getSubclassFields(), c2.getSubclassFields()); + } + + public static class FakeCalendar extends Calendar { + + private int[] subclassFields; + + public FakeCalendar() { + super(TimeZone.getDefault(), Locale.getDefault()); + subclassFields = new int[12]; + } + + public int[] getCalenderFields() { + return fields; + } + + public int[] getSubclassFields() { + return subclassFields; + } + + @Override + public void add(int field, int value) { + + } + + @Override + protected void computeFields() { + + } + + @Override + protected void computeTime() { + + } + + @Override + public int getGreatestMinimum(int field) { + return 0; + } + + @Override + public int getLeastMaximum(int field) { + return 0; + } + + @Override + public int getMaximum(int field) { + return 0; + } + + @Override + public int getMinimum(int field) { + return 0; + } + + @Override + public void roll(int field, boolean increment) { + + } + } } diff --git a/luni/src/test/java/libcore/java/util/CollectionsTest.java b/luni/src/test/java/libcore/java/util/CollectionsTest.java index 80c769e..bc73817 100644 --- a/luni/src/test/java/libcore/java/util/CollectionsTest.java +++ b/luni/src/test/java/libcore/java/util/CollectionsTest.java @@ -17,7 +17,10 @@ package libcore.java.util; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; import java.util.Enumeration; import java.util.Iterator; import java.util.ListIterator; @@ -94,4 +97,92 @@ public final class CollectionsTest extends TestCase { } catch (IllegalStateException expected) { } } + + public void testSortFastPath_incrementsModcount() { + ArrayList<String> list = new ArrayList<String>(16); + list.add("coven"); + list.add("asylum"); + list.add("murder house"); + list.add("freak show"); + + Iterator<String> it = list.iterator(); + it.next(); + Collections.sort(list); + try { + it.next(); + fail(); + } catch (ConcurrentModificationException expected) { + } + } + + /** + * A value type whose {@code compareTo} method returns one of {@code 0}, + * {@code Integer.MIN_VALUE} and {@code Integer.MAX_VALUE}. + */ + static final class IntegerWithExtremeComparator + implements Comparable<IntegerWithExtremeComparator> { + private final int value; + + public IntegerWithExtremeComparator(int value) { + this.value = value; + } + + @Override + public int compareTo(IntegerWithExtremeComparator another) { + if (another.value == this.value) { + return 0; + } else if (another.value > this.value) { + return Integer.MIN_VALUE; + } else { + return Integer.MAX_VALUE; + } + } + } + + // http://b/19749094 + public void testBinarySearch_comparatorThatReturnsMinAndMaxValue() { + ArrayList<Integer> list = new ArrayList<Integer>(16); + list.add(4); + list.add(9); + list.add(11); + list.add(14); + list.add(16); + + int index = Collections.binarySearch(list, 9, new Comparator<Integer>() { + @Override + public int compare(Integer lhs, Integer rhs) { + final int compare = lhs.compareTo(rhs); + if (compare == 0) { + return 0; + } else if (compare < 0) { + return Integer.MIN_VALUE; + } else { + return Integer.MAX_VALUE; + } + } + }); + assertEquals(1, index); + + ArrayList<IntegerWithExtremeComparator> list2 = + new ArrayList<IntegerWithExtremeComparator>(); + list2.add(new IntegerWithExtremeComparator(4)); + list2.add(new IntegerWithExtremeComparator(9)); + list2.add(new IntegerWithExtremeComparator(11)); + list2.add(new IntegerWithExtremeComparator(14)); + list2.add(new IntegerWithExtremeComparator(16)); + + assertEquals(1, Collections.binarySearch(list2, new IntegerWithExtremeComparator(9))); + } + + public void testBinarySearch_emptyCollection() { + assertEquals(-1, Collections.binarySearch(new ArrayList<Integer>(), 9)); + + assertEquals(-1, Collections.binarySearch(new ArrayList<Integer>(), 9, + new Comparator<Integer>() { + @Override + public int compare(Integer lhs, Integer rhs) { + return lhs.compareTo(rhs); + } + })); + } } diff --git a/luni/src/test/java/libcore/java/util/CurrencyTest.java b/luni/src/test/java/libcore/java/util/CurrencyTest.java index cf2a1b6..e4c56e5 100644 --- a/luni/src/test/java/libcore/java/util/CurrencyTest.java +++ b/luni/src/test/java/libcore/java/util/CurrencyTest.java @@ -58,7 +58,7 @@ public class CurrencyTest extends junit.framework.TestCase { assertEquals("Swiss Franc", Currency.getInstance("CHF").getDisplayName(Locale.US)); assertEquals("Schweizer Franken", Currency.getInstance("CHF").getDisplayName(new Locale("de", "CH"))); assertEquals("franc suisse", Currency.getInstance("CHF").getDisplayName(new Locale("fr", "CH"))); - assertEquals("Franco Svizzero", Currency.getInstance("CHF").getDisplayName(new Locale("it", "CH"))); + assertEquals("franco svizzero", Currency.getInstance("CHF").getDisplayName(new Locale("it", "CH"))); } public void test_getDefaultFractionDigits() throws Exception { diff --git a/luni/src/test/java/libcore/java/util/DateTest.java b/luni/src/test/java/libcore/java/util/DateTest.java index 3ed0952..076c6e2 100644 --- a/luni/src/test/java/libcore/java/util/DateTest.java +++ b/luni/src/test/java/libcore/java/util/DateTest.java @@ -31,12 +31,13 @@ public class DateTest extends TestCase { assertEquals("Wed Dec 31 18:00:00 CST 1969", new Date(0).toString()); } + // https://code.google.com/p/android/issues/detail?id=81924 public void test_toString_nonUs() { // The string for the timezone depends on what the default locale is. Not every locale - // has a short-name for America/Chicago -> PST. - Locale.setDefault(Locale.UK); + // has a short-name for America/Chicago -> CST. + Locale.setDefault(Locale.CHINA); TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago")); - assertEquals("Wed Dec 31 18:00:00 GMT-06:00 1969", new Date(0).toString()); + assertEquals("Wed Dec 31 18:00:00 CST 1969", new Date(0).toString()); } public void test_toGMTString_us() throws Exception { @@ -61,10 +62,10 @@ public class DateTest extends TestCase { Calendar c = Calendar.getInstance(); c.clear(); c.set(Calendar.YEAR, 21); - assertEquals("Wed Jan 01 00:00:00 GMT-08:00 21", c.getTime().toString()); + assertEquals("Wed Jan 01 00:00:00 PST 21", c.getTime().toString()); assertEquals("1 Jan 21 08:00:00 GMT", c.getTime().toGMTString()); c.set(Calendar.YEAR, 321); - assertEquals("Sun Jan 01 00:00:00 GMT-08:00 321", c.getTime().toString()); + assertEquals("Sun Jan 01 00:00:00 PST 321", c.getTime().toString()); assertEquals("1 Jan 321 08:00:00 GMT", c.getTime().toGMTString()); } } diff --git a/luni/src/test/java/libcore/java/util/LocaleTest.java b/luni/src/test/java/libcore/java/util/LocaleTest.java index c72ecd7..9005f25 100644 --- a/luni/src/test/java/libcore/java/util/LocaleTest.java +++ b/luni/src/test/java/libcore/java/util/LocaleTest.java @@ -20,6 +20,7 @@ import java.text.BreakIterator; import java.text.Collator; import java.text.DateFormat; import java.text.DateFormatSymbols; +import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.Calendar; import java.util.IllformedLocaleException; @@ -1146,4 +1147,94 @@ public class LocaleTest extends junit.framework.TestCase { assertEquals("variant", locale.getVariant()); assertEquals(locale, Locale.forLanguageTag(locale.toLanguageTag())); } + + public void testArabicDigits() throws Exception { + // ar-DZ uses latn digits by default, but we can override that. + Locale ar_DZ = Locale.forLanguageTag("ar-DZ"); + Locale ar_DZ_arab = Locale.forLanguageTag("ar-DZ-u-nu-arab"); + Locale ar_DZ_latn = Locale.forLanguageTag("ar-DZ-u-nu-latn"); + assertEquals('0', new DecimalFormatSymbols(ar_DZ).getZeroDigit()); + assertEquals('\u0660', new DecimalFormatSymbols(ar_DZ_arab).getZeroDigit()); + assertEquals('0', new DecimalFormatSymbols(ar_DZ_latn).getZeroDigit()); + + // ar-EG uses arab digits by default, but we can override that. + Locale ar_EG = Locale.forLanguageTag("ar-EG"); + Locale ar_EG_arab = Locale.forLanguageTag("ar-EG-u-nu-arab"); + Locale ar_EG_latn = Locale.forLanguageTag("ar-EG-u-nu-latn"); + assertEquals('\u0660', new DecimalFormatSymbols(ar_EG).getZeroDigit()); + assertEquals('\u0660', new DecimalFormatSymbols(ar_EG_arab).getZeroDigit()); + assertEquals('0', new DecimalFormatSymbols(ar_EG_latn).getZeroDigit()); + } + + public void testDefaultLocale() throws Exception { + final String userLanguage = System.getProperty("user.language", ""); + final String userRegion = System.getProperty("user.region", ""); + final String userLocale = System.getProperty("user.locale", ""); + try { + // Assert that user.locale gets priority. + System.setUnchangeableSystemProperty("user.locale", "de-DE"); + System.setUnchangeableSystemProperty("user.language", "en"); + System.setUnchangeableSystemProperty("user.region", "US"); + + Locale l = Locale.getDefaultLocaleFromSystemProperties(); + assertEquals("de", l.getLanguage()); + assertEquals("DE", l.getCountry()); + + // Assert that it's parsed as a full language tag. + System.setUnchangeableSystemProperty("user.locale", "de-Latn-DE"); + System.setUnchangeableSystemProperty("user.language", "en"); + System.setUnchangeableSystemProperty("user.region", "US"); + + l = Locale.getDefaultLocaleFromSystemProperties(); + assertEquals("de", l.getLanguage()); + assertEquals("DE", l.getCountry()); + assertEquals("Latn", l.getScript()); + + // Assert that we use "und" if we're faced with a bad language tag, and + // that we don't end up with a null default locale or an exception. + System.setUnchangeableSystemProperty("user.locale", "dexx-Latn-DE"); + + l = Locale.getDefaultLocaleFromSystemProperties(); + assertEquals("und", l.getLanguage()); + assertEquals("DE", l.getCountry()); + } finally { + System.setUnchangeableSystemProperty("user.language", userLanguage); + System.setUnchangeableSystemProperty("user.region", userRegion); + System.setUnchangeableSystemProperty("user.locale", userLocale); + } + } + + // http://b/20252611 + public void testLegacyLocalesWithExtensions() { + Locale ja_JP_JP = new Locale("ja", "JP", "JP"); + assertEquals("ca-japanese", ja_JP_JP.getExtension(Locale.UNICODE_LOCALE_EXTENSION)); + assertEquals("japanese", ja_JP_JP.getUnicodeLocaleType("ca")); + + Locale th_TH_TH = new Locale("th", "TH", "TH"); + assertEquals("nu-thai", th_TH_TH.getExtension(Locale.UNICODE_LOCALE_EXTENSION)); + assertEquals("thai", th_TH_TH.getUnicodeLocaleType("nu")); + } + + // http://b/20252611 + public void testLowerCaseExtensionKeys() { + // We must lowercase extension keys in forLanguageTag.. + Locale ar_EG = Locale.forLanguageTag("ar-EG-U-nu-arab"); + assertEquals("nu-arab", ar_EG.getExtension(Locale.UNICODE_LOCALE_EXTENSION)); + assertEquals("ar-EG-u-nu-arab", ar_EG.toLanguageTag()); + + // ... and in builders. + Locale.Builder b = new Locale.Builder(); + b.setLanguage("ar"); + b.setRegion("EG"); + b.setExtension('U', "nu-arab"); + assertEquals("ar-EG-u-nu-arab", b.build().toLanguageTag()); + + // Corollary : extension keys are case insensitive. + b = new Locale.Builder(); + b.setLanguage("ar"); + b.setRegion("EG"); + b.setExtension('U', "nu-arab"); + b.setExtension('u', "nu-thai"); + assertEquals("ar-EG-u-nu-thai", b.build().toLanguageTag()); + } } diff --git a/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java b/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java index ecf2e5f..7a5fc4a 100644 --- a/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java +++ b/luni/src/test/java/libcore/java/util/OldTimeZoneTest.java @@ -108,7 +108,7 @@ public class OldTimeZoneTest extends TestCase { TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); assertEquals("Pacific Daylight Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US)); assertEquals("Pacific Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.UK)); - assertEquals("heure avanc\u00e9e du Pacifique", + assertEquals("heure d’été du Pacifique", tz.getDisplayName(true, TimeZone.LONG, Locale.FRANCE)); assertEquals("heure normale du Pacifique nord-américain", tz.getDisplayName(false, TimeZone.LONG, Locale.FRANCE)); @@ -123,18 +123,6 @@ public class OldTimeZoneTest extends TestCase { assertEquals("GMT-07:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.FRANCE)); assertEquals("GMT-08:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.UK)); assertEquals("GMT-07:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.UK)); - - // The RI behavior mentioned above does not appear to be because "PST" is a legacy - // three-character timezone supported by the RI: it happens for "Asia/Tehran"/"IRST" too - // (IRST is not a legacy code). The RI may just use a different dataset that has "PST" / - // "IRST" as valid translations (even for scripts like Chinese). - TimeZone iranTz = TimeZone.getTimeZone("Asia/Tehran"); - assertEquals("Iran Summer Time", iranTz.getDisplayName(true, TimeZone.LONG, Locale.UK)); - assertEquals("Iran Daylight Time", iranTz.getDisplayName(true, TimeZone.LONG, Locale.US)); - assertEquals("Iran Standard Time", iranTz.getDisplayName(false, TimeZone.LONG, Locale.UK)); - assertEquals("Iran Standard Time", iranTz.getDisplayName(false, TimeZone.LONG, Locale.US)); - assertEquals("GMT+03:30", iranTz.getDisplayName(false, TimeZone.SHORT, Locale.UK)); - assertEquals("GMT+04:30", iranTz.getDisplayName(true, TimeZone.SHORT, Locale.UK)); } public void test_getID() { diff --git a/luni/src/test/java/libcore/java/util/TimSortTest.java b/luni/src/test/java/libcore/java/util/TimSortTest.java new file mode 100644 index 0000000..0e928ba --- /dev/null +++ b/luni/src/test/java/libcore/java/util/TimSortTest.java @@ -0,0 +1,82 @@ +/* + * 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 libcore.java.util; + +import junit.framework.TestCase; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * This test is based on test data generated by + * https://github.com/abstools/java-timsort-bug/blob/master/TestTimSort.java + */ +public class TimSortTest extends TestCase { + + private static final Comparator<Integer> NATURAL_ORDER_COMPARATOR = new Comparator<Integer>() { + public int compare(Integer first, Integer second) { + return first.compareTo(second); + } + }; + + private static final int BAD_DATA_SIZE = 65536; + + private static int[] BAD_RUN_OFFSETS = { + 20204, 20221, 20237, 20255, 20289, 20363, 20521, 20837, 21469, 22733, 25260, 30315, + 40408, 40425, 40441, 40459, 40493, 40567, 40725, 41041, 41673, 42936, 45463, 50500, + 50517, 50533, 50551, 50585, 50659, 50817, 51133, 51764, 53027, 55536, 55553, 55569, + 55587, 55621, 55695, 55853, 56168, 56799, 58044, 58061, 58077, 58095, 58129, 58203, + 58360, 58675, 59288, 59305, 59321, 59339, 59373, 59446, 59603, 59900, 59917, 59933, + 59951, 59985, 60059, 60196, 60217, 60236, 60274, 60332, 60351, 60369, 60389, 60405, + }; + + public void testBug19493779WithComparable() throws Exception { + Integer[] array = createBugTriggerData(); + Arrays.sort(array); + // The bug caused an ArrayIndexOutOfBoundsException, but we check this anyway. + assertSorted(array); + } + + public void testBug19493779WithComparator() throws Exception { + Integer[] array = createBugTriggerData(); + Arrays.sort(array, NATURAL_ORDER_COMPARATOR); + // The bug caused an ArrayIndexOutOfBoundsException, but we check this anyway. + assertSorted(array); + } + + private static void assertSorted(Integer[] arrayToSort) { + for (int i = 1; i < arrayToSort.length; i++) { + if (arrayToSort[i - 1] > arrayToSort[i]) { + fail("Array not sorted at element " + i + ": " + Arrays.toString(arrayToSort)); + } + } + } + + private static Integer[] createBugTriggerData() { + final Integer zero = 0; + final Integer one = 1; + + Integer[] bugTriggerData = new Integer[BAD_DATA_SIZE]; + for (int i = 0; i < bugTriggerData.length; i++) { + bugTriggerData[i] = zero; + } + + for (int i = 0; i < BAD_RUN_OFFSETS.length; i++) { + bugTriggerData[BAD_RUN_OFFSETS[i]] = one; + } + return bugTriggerData; + } +}
\ No newline at end of file diff --git a/luni/src/test/java/libcore/java/util/TimeZoneTest.java b/luni/src/test/java/libcore/java/util/TimeZoneTest.java index 1ca950c..68e9109 100644 --- a/luni/src/test/java/libcore/java/util/TimeZoneTest.java +++ b/luni/src/test/java/libcore/java/util/TimeZoneTest.java @@ -73,6 +73,12 @@ public class TimeZoneTest extends TestCase { assertFalse(tz.inDaylightTime(date)); } + public void testGetDisplayNameShort_nonHourOffsets() { + TimeZone iranTz = TimeZone.getTimeZone("Asia/Tehran"); + assertEquals("GMT+03:30", iranTz.getDisplayName(false, TimeZone.SHORT, Locale.UK)); + assertEquals("GMT+04:30", iranTz.getDisplayName(true, TimeZone.SHORT, Locale.UK)); + } + public void testPreHistoricOffsets() throws Exception { // "Africa/Bissau" has just a few transitions and hasn't changed in a long time. // 1912-01-01 00:02:19-0100 ... 1912-01-01 00:02:20-0100 @@ -256,12 +262,12 @@ public class TimeZoneTest extends TestCase { } // http://b/7955614 - public void test_getDisplayName_GMT_short_names() throws Exception { - TimeZone tz = TimeZone.getTimeZone("America/Santiago"); - assertEquals("Chile Summer Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US)); - assertEquals("Chile Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.US)); - assertEquals("GMT-03:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.US)); - assertEquals("GMT-03:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.US)); + public void testApia() throws Exception { + TimeZone tz = TimeZone.getTimeZone("Pacific/Apia"); + assertEquals("Apia Daylight Time", tz.getDisplayName(true, TimeZone.LONG, Locale.US)); + assertEquals("Apia Standard Time", tz.getDisplayName(false, TimeZone.LONG, Locale.US)); + assertEquals("GMT+14:00", tz.getDisplayName(true, TimeZone.SHORT, Locale.US)); + assertEquals("GMT+13:00", tz.getDisplayName(false, TimeZone.SHORT, Locale.US)); } private static boolean isGmtString(String s) { @@ -290,4 +296,32 @@ public class TimeZoneTest extends TestCase { } } } + + // http://b/18839557 + public void testOverflowing32BitUnixDates() { + final TimeZone tz = TimeZone.getTimeZone("America/New_York"); + + // This timezone didn't have any daylight savings prior to 1917 and this + // date is sometime in 1901. + assertFalse(tz.inDaylightTime(new Date(-2206292400000L))); + assertEquals(-18000000, tz.getOffset(-2206292400000L)); + + // Nov 30th 2039, no daylight savings as per current rules. + assertFalse(tz.inDaylightTime(new Date(2206292400000L))); + assertEquals(-18000000, tz.getOffset(2206292400000L)); + } + + public void testTimeZoneIDLocalization() { + Locale defaultLocale = Locale.getDefault(); + try { + Locale.setDefault(new Locale("en")); + TimeZone en_timezone = TimeZone.getTimeZone("GMT+09:00"); + Locale.setDefault(new Locale("ar")); + TimeZone ar_timezone = TimeZone.getTimeZone("GMT+09:00"); + + assertEquals(en_timezone.getID(), ar_timezone.getID()); + } finally { + Locale.setDefault(defaultLocale); + } + } } diff --git a/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java b/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java index e5a6cd8..9496ad0 100644 --- a/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java +++ b/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java @@ -169,6 +169,10 @@ public class StrictJarFileTest extends TestCase { assertThrowsOnInit("Modified_SF_EntryAttributes.jar"); } + public void testJarSigning_removedEntry() throws Exception { + assertThrowsOnInit("removed.jar"); + } + private void assertThrowsOnInit(String name) throws Exception { Support_Resources.copyFile(resources, null, name); try { diff --git a/luni/src/test/java/libcore/java/util/zip/AbstractZipFileTest.java b/luni/src/test/java/libcore/java/util/zip/AbstractZipFileTest.java new file mode 100644 index 0000000..9e049c0 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/AbstractZipFileTest.java @@ -0,0 +1,548 @@ +/* + * Copyright (C) 2010 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 libcore.java.util.zip; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; +import junit.framework.TestCase; + +import tests.support.resource.Support_Resources; + +public abstract class AbstractZipFileTest extends TestCase { + /** + * Exercise Inflater's ability to refill the zlib's input buffer. As of this + * writing, this buffer's max size is 64KiB compressed bytes. We'll write a + * full megabyte of uncompressed data, which should be sufficient to exhaust + * the buffer. http://b/issue?id=2734751 + */ + public void testInflatingFilesRequiringZipRefill() throws IOException { + int originalSize = 1024 * 1024; + byte[] readBuffer = new byte[8192]; + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */); + ZipFile zipFile = new ZipFile(f); + for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { + ZipEntry zipEntry = e.nextElement(); + assertTrue("This test needs >64 KiB of compressed data to exercise Inflater", + zipEntry.getCompressedSize() > (64 * 1024)); + InputStream is = zipFile.getInputStream(zipEntry); + while (is.read(readBuffer, 0, readBuffer.length) != -1) {} + is.close(); + } + zipFile.close(); + } + + private static void replaceBytes(byte[] buffer, byte[] original, byte[] replacement) { + // Gotcha here: original and replacement must be the same length + assertEquals(original.length, replacement.length); + boolean found; + for(int i=0; i < buffer.length - original.length; i++) { + found = false; + if (buffer[i] == original[0]) { + found = true; + for (int j=0; j < original.length; j++) { + if (buffer[i+j] != original[j]) { + found = false; + break; + } + } + } + if (found) { + for (int j=0; j < original.length; j++) { + buffer[i+j] = replacement[j]; + } + } + } + } + + private static void writeBytes(File f, byte[] bytes) throws IOException { + FileOutputStream out = new FileOutputStream(f); + out.write(bytes); + out.close(); + } + + /** + * Make sure we don't fail silently for duplicate entries. + * b/8219321 + */ + public void testDuplicateEntries() throws Exception { + String name1 = "test_file_name1"; + String name2 = "test_file_name2"; + + // Create the good zip file. + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream out = createZipOutputStream(baos); + out.putNextEntry(new ZipEntry(name2)); + out.closeEntry(); + out.putNextEntry(new ZipEntry(name1)); + out.closeEntry(); + out.close(); + + // Rewrite one of the filenames. + byte[] buffer = baos.toByteArray(); + replaceBytes(buffer, name2.getBytes(), name1.getBytes()); + + // Write the result to a file. + File badZip = createTemporaryZipFile(); + writeBytes(badZip, buffer); + + // Check that we refuse to load the modified file. + try { + ZipFile bad = new ZipFile(badZip); + fail(); + } catch (ZipException expected) { + } + } + + /** + * Make sure the size used for stored zip entires is the uncompressed size. + * b/10227498 + */ + public void testStoredEntrySize() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ZipOutputStream out = createZipOutputStream(baos); + + // Set up a single stored entry. + String name = "test_file"; + int expectedLength = 5; + ZipEntry outEntry = new ZipEntry(name); + byte[] buffer = new byte[expectedLength]; + outEntry.setMethod(ZipEntry.STORED); + CRC32 crc = new CRC32(); + crc.update(buffer); + outEntry.setCrc(crc.getValue()); + outEntry.setSize(buffer.length); + + out.putNextEntry(outEntry); + out.write(buffer); + out.closeEntry(); + out.close(); + + // Write the result to a file. + byte[] outBuffer = baos.toByteArray(); + File zipFile = createTemporaryZipFile(); + writeBytes(zipFile, outBuffer); + + ZipFile zip = new ZipFile(zipFile); + // Set up the zip entry to have different compressed/uncompressed sizes. + ZipEntry ze = zip.getEntry(name); + ze.setCompressedSize(expectedLength - 1); + // Read the contents of the stream and verify uncompressed size was used. + InputStream stream = zip.getInputStream(ze); + int count = 0; + int read; + while ((read = stream.read(buffer)) != -1) { + count += read; + } + + assertEquals(expectedLength, count); + zip.close(); + } + + public void testInflatingStreamsRequiringZipRefill() throws IOException { + int originalSize = 1024 * 1024; + byte[] readBuffer = new byte[8192]; + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */); + + ZipInputStream in = new ZipInputStream(new FileInputStream(f)); + while (in.getNextEntry() != null) { + while (in.read(readBuffer, 0, readBuffer.length) != -1) {} + } + in.close(); + } + + public void testZipFileWithLotsOfEntries() throws IOException { + int expectedEntryCount = 64*1024 - 1; + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), expectedEntryCount, 0, false /* setEntrySize */); + ZipFile zipFile = new ZipFile(f); + int entryCount = 0; + for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { + ZipEntry zipEntry = e.nextElement(); + ++entryCount; + } + assertEquals(expectedEntryCount, entryCount); + zipFile.close(); + } + + // http://code.google.com/p/android/issues/detail?id=36187 + public void testZipFileLargerThan2GiB() throws IOException { + if (false) { // TODO: this test requires too much time and too much disk space! + final File f = createTemporaryZipFile(); + writeEntries(createZipOutputStream(f), 1024, 3*1024*1024, false /* setEntrySize */); + ZipFile zipFile = new ZipFile(f); + int entryCount = 0; + for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { + e.nextElement(); + ++entryCount; + } + assertEquals(1024, entryCount); + zipFile.close(); + } + } + + /** + * Compresses the given number of files, each of the given size, into a .zip archive. + */ + protected void writeEntries(ZipOutputStream out, int entryCount, long entrySize, + boolean setEntrySize) + throws IOException { + byte[] writeBuffer = new byte[8192]; + Random random = new Random(); + try { + for (int entry = 0; entry < entryCount; ++entry) { + ZipEntry ze = new ZipEntry(Integer.toHexString(entry)); + if (setEntrySize) { + ze.setSize(entrySize); + } + out.putNextEntry(ze); + + for (long i = 0; i < entrySize; i += writeBuffer.length) { + random.nextBytes(writeBuffer); + int byteCount = (int) Math.min(writeBuffer.length, entrySize - i); + out.write(writeBuffer, 0, byteCount); + } + + out.closeEntry(); + } + } finally { + out.close(); + } + } + + static File createTemporaryZipFile() throws IOException { + File result = File.createTempFile("ZipFileTest", ".zip"); + result.deleteOnExit(); + return result; + } + + private ZipOutputStream createZipOutputStream(File f) throws IOException { + return createZipOutputStream(new BufferedOutputStream(new FileOutputStream(f))); + } + + protected abstract ZipOutputStream createZipOutputStream(OutputStream wrapped); + + public void testSTORED() throws IOException { + ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); + CRC32 crc = new CRC32(); + + // Missing CRC, size, and compressed size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Missing CRC and compressed size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + ze.setSize(0); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Missing CRC and size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + ze.setSize(0); + ze.setCompressedSize(0); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Missing size and compressed size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Missing size is copied from compressed size. + { + ZipEntry ze = new ZipEntry("okay1"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + + assertEquals(-1, ze.getSize()); + assertEquals(-1, ze.getCompressedSize()); + + ze.setCompressedSize(0); + + assertEquals(-1, ze.getSize()); + assertEquals(0, ze.getCompressedSize()); + + out.putNextEntry(ze); + + assertEquals(0, ze.getSize()); + assertEquals(0, ze.getCompressedSize()); + } + + // Missing compressed size is copied from size. + { + ZipEntry ze = new ZipEntry("okay2"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + + assertEquals(-1, ze.getSize()); + assertEquals(-1, ze.getCompressedSize()); + + ze.setSize(0); + + assertEquals(0, ze.getSize()); + assertEquals(-1, ze.getCompressedSize()); + + out.putNextEntry(ze); + + assertEquals(0, ze.getSize()); + assertEquals(0, ze.getCompressedSize()); + } + + // Mismatched size and compressed size => failure. + try { + ZipEntry ze = new ZipEntry("a"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + ze.setCompressedSize(1); + ze.setSize(0); + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + + // Everything present => success. + ZipEntry ze = new ZipEntry("okay"); + ze.setMethod(ZipEntry.STORED); + ze.setCrc(crc.getValue()); + ze.setSize(0); + ze.setCompressedSize(0); + out.putNextEntry(ze); + + out.close(); + } + + private String makeString(int count, String ch) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; ++i) { + sb.append(ch); + } + return sb.toString(); + } + + public void testComments() throws Exception { + String expectedFileComment = "1 \u0666 2"; + String expectedEntryComment = "a \u0666 b"; + + File file = createTemporaryZipFile(); + ZipOutputStream out = createZipOutputStream(file); + + // Is file comment length checking done on bytes or characters? (Should be bytes.) + out.setComment(null); + out.setComment(makeString(0xffff, "a")); + try { + out.setComment(makeString(0xffff + 1, "a")); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + out.setComment(makeString(0xffff, "\u0666")); + fail(); + } catch (IllegalArgumentException expected) { + } + + ZipEntry ze = new ZipEntry("a"); + + // Is entry comment length checking done on bytes or characters? (Should be bytes.) + ze.setComment(null); + ze.setComment(makeString(0xffff, "a")); + try { + ze.setComment(makeString(0xffff + 1, "a")); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + ze.setComment(makeString(0xffff, "\u0666")); + fail(); + } catch (IllegalArgumentException expected) { + } + + ze.setComment(expectedEntryComment); + out.putNextEntry(ze); + out.closeEntry(); + + out.setComment(expectedFileComment); + out.close(); + + ZipFile zipFile = new ZipFile(file); + assertEquals(expectedFileComment, zipFile.getComment()); + assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment()); + zipFile.close(); + } + + public void test_getComment_unset() throws Exception { + File file = createTemporaryZipFile(); + ZipOutputStream out = createZipOutputStream(file); + ZipEntry ze = new ZipEntry("test entry"); + ze.setComment("per-entry comment"); + out.putNextEntry(ze); + out.close(); + + ZipFile zipFile = new ZipFile(file); + assertEquals(null, zipFile.getComment()); + } + + // https://code.google.com/p/android/issues/detail?id=58465 + public void test_NUL_in_filename() throws Exception { + File file = createTemporaryZipFile(); + + // We allow creation of a ZipEntry whose name contains a NUL byte, + // mainly because it's not likely to happen by accident and it's useful for testing. + ZipOutputStream out = createZipOutputStream(file); + out.putNextEntry(new ZipEntry("hello")); + out.putNextEntry(new ZipEntry("hello\u0000")); + out.close(); + + // But you can't open a ZIP file containing such an entry, because we reject it + // when we find it in the central directory. + try { + ZipFile zipFile = new ZipFile(file); + fail(); + } catch (ZipException expected) { + } + } + + public void testCrc() throws IOException { + ZipEntry ze = new ZipEntry("test"); + ze.setMethod(ZipEntry.STORED); + ze.setSize(4); + + // setCrc takes a long, not an int, so -1 isn't a valid CRC32 (because it's 64 bits). + try { + ze.setCrc(-1); + } catch (IllegalArgumentException expected) { + } + + // You can set the CRC32 to 0xffffffff if you're slightly more careful though... + ze.setCrc(0xffffffffL); + assertEquals(0xffffffffL, ze.getCrc()); + + // And it actually works, even though we use -1L to mean "no CRC set"... + ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); + out.putNextEntry(ze); + out.write(-1); + out.write(-1); + out.write(-1); + out.write(-1); + out.closeEntry(); + out.close(); + } + + /** + * RI does not allow reading of an empty zip using a {@link ZipFile}. + */ + public void testConstructorFailsWhenReadingEmptyZipArchive() throws IOException { + + File resources = Support_Resources.createTempFolder(); + File emptyZip = Support_Resources.copyFile( + resources, "java/util/zip", "EmptyArchive.zip"); + + try { + // The following should fail with an exception but if it doesn't then we need to clean + // up the resource so we need a reference to it. + ZipFile zipFile = new ZipFile(emptyZip); + + // Clean up the resource. + try { + zipFile.close(); + } catch (Exception e) { + // Ignore + } + fail(); + } catch (ZipException expected) { + // expected + } + } + + // Demonstrates http://b/18644314 : Zip entry names are relative to the point of + // extraction and can contain relative paths "../" and "./". + // + // It is left to callers of the API to perform any validation / santization to + // ensure that files are not written outside of the destination directory, where that + // is a concern. + public void testArchivesWithRelativePaths() throws IOException { + String[] entryNames = { + "../", + "../foo.bar", + "foo/../../", + "foo/../../bar.baz" + }; + + File zip = createTemporaryZipFile(); + ZipOutputStream out = createZipOutputStream(zip); + + try { + byte[] entryData = new byte[1024]; + for (String entryName : entryNames) { + ZipEntry ze = new ZipEntry(entryName); + out.putNextEntry(ze); + out.write(entryData); + out.closeEntry(); + } + } finally { + out.close(); + } + + ZipFile zf = new ZipFile(zip, ZipFile.OPEN_READ); + Enumeration<? extends ZipEntry> entries = zf.entries(); + Set<String> entryNamesFromFile = new HashSet<>(); + while (entries.hasMoreElements()) { + ZipEntry ze = entries.nextElement(); + entryNamesFromFile.add(ze.getName()); + } + + zf.close(); + + for (String entryName : entryNames) { + assertTrue(entryNamesFromFile.contains(entryName)); + } + } +} diff --git a/luni/src/test/java/libcore/java/util/zip/DeflaterTest.java b/luni/src/test/java/libcore/java/util/zip/DeflaterTest.java index 30aa7f3..1dfa775 100644 --- a/luni/src/test/java/libcore/java/util/zip/DeflaterTest.java +++ b/luni/src/test/java/libcore/java/util/zip/DeflaterTest.java @@ -82,4 +82,26 @@ public class DeflaterTest extends TestCase { assertTrue(totalDeflated > 0); // the deflated form should be non-empty assertEquals(0, totalInflated); } + + public void testDeflaterCounts() throws Exception { + deflater.setInput(new byte[] { 1, 2, 3 }); + assertEquals(11, deflater.deflate(compressed, 0, compressed.length, Deflater.FULL_FLUSH)); + assertEquals(3, deflater.getBytesRead()); + assertEquals(3, deflater.getTotalIn()); + assertEquals(11, deflater.getBytesWritten()); + assertEquals(11, deflater.getTotalOut()); + + deflater.setInput(new byte[] { 1, 2, 3 }); + assertEquals(9, deflater.deflate(compressed, 0, compressed.length, Deflater.FULL_FLUSH)); + assertEquals(6, deflater.getBytesRead()); + assertEquals(6, deflater.getTotalIn()); + assertEquals(20, deflater.getBytesWritten()); + + + deflater.reset(); + assertEquals(0, deflater.getBytesRead()); + assertEquals(0, deflater.getBytesWritten()); + assertEquals(0, deflater.getTotalIn()); + assertEquals(0, deflater.getTotalOut()); + } } diff --git a/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java b/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java index 494520a..5813753 100644 --- a/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java +++ b/luni/src/test/java/libcore/java/util/zip/GZIPInputStreamTest.java @@ -35,14 +35,46 @@ import libcore.io.Streams; public final class GZIPInputStreamTest extends TestCase { private static final byte[] HELLO_WORLD_GZIPPED = new byte[] { - 31, -117, 8, 0, 0, 0, 0, 0, 0, 0, -13, 72, -51, -55, -55, 87, 8, -49, - 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0 + 31, -117, 8, 0, 0, 0, 0, 0, 0, 0, // 10 byte header + -13, 72, -51, -55, -55, 87, 8, -49, 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0 // data + }; + + /** + * This is the same as the above, except that the 4th header byte is 2 (FHCRC flag) + * and the 2 bytes after the header make up the CRC. + * + * Constructed manually because none of the commonly used tools appear to emit header CRCs. + */ + private static final byte[] HELLO_WORLD_GZIPPED_WITH_HEADER_CRC = new byte[] { + 31, -117, 8, 2, 0, 0, 0, 0, 0, 0, // 10 byte header + 29, 38, // 2 byte CRC. + -13, 72, -51, -55, -55, 87, 8, -49, 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0 // data + }; + + /*( + * This is the same as {@code HELLO_WORLD_GZIPPED} except that the 4th header byte is 4 + * (FEXTRA flag) and that the 8 bytes after the header make up the extra. + * + * Constructed manually because none of the commonly used tools appear to emit header CRCs. + */ + private static final byte[] HELLO_WORLD_GZIPPED_WITH_EXTRA = new byte[] { + 31, -117, 8, 4, 0, 0, 0, 0, 0, 0, // 10 byte header + 6, 0, 4, 2, 4, 2, 4, 2, // 2 byte extra length + 6 byte extra. + -13, 72, -51, -55, -55, 87, 8, -49, 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0 // data }; public void testShortMessage() throws IOException { assertEquals("Hello World", new String(gunzip(HELLO_WORLD_GZIPPED), "UTF-8")); } + public void testShortMessageWithCrc() throws IOException { + assertEquals("Hello World", new String(gunzip(HELLO_WORLD_GZIPPED_WITH_HEADER_CRC), "UTF-8")); + } + + public void testShortMessageWithHeaderExtra() throws IOException { + assertEquals("Hello World", new String(gunzip(HELLO_WORLD_GZIPPED_WITH_EXTRA), "UTF-8")); + } + public void testLongMessage() throws IOException { byte[] data = new byte[1024 * 1024]; new Random().nextBytes(data); diff --git a/luni/src/test/java/libcore/java/util/zip/InflaterTest.java b/luni/src/test/java/libcore/java/util/zip/InflaterTest.java index 158b3e9..cce08f3 100644 --- a/luni/src/test/java/libcore/java/util/zip/InflaterTest.java +++ b/luni/src/test/java/libcore/java/util/zip/InflaterTest.java @@ -122,4 +122,35 @@ public class InflaterTest extends TestCase { adler32.update(bytes); return (int) adler32.getValue(); } + + public void testInflaterCounts() throws Exception { + Inflater inflater = new Inflater(); + + byte[] decompressed = new byte[32]; + byte[] compressed = deflate(new byte[] { 1, 2, 3}, null); + assertEquals(11, compressed.length); + + // Feed in bytes [0, 5) to the first iteration. + inflater.setInput(compressed, 0, 5); + inflater.inflate(decompressed, 0, decompressed.length); + assertEquals(5, inflater.getBytesRead()); + assertEquals(5, inflater.getTotalIn()); + assertEquals(2, inflater.getBytesWritten()); + assertEquals(2, inflater.getTotalOut()); + + // Feed in bytes [5, 11) to the second iteration. + assertEquals(true, inflater.needsInput()); + inflater.setInput(compressed, 5, 6); + assertEquals(1, inflater.inflate(decompressed, 0, decompressed.length)); + assertEquals(11, inflater.getBytesRead()); + assertEquals(11, inflater.getTotalIn()); + assertEquals(3, inflater.getBytesWritten()); + assertEquals(3, inflater.getTotalOut()); + + inflater.reset(); + assertEquals(0, inflater.getBytesRead()); + assertEquals(0, inflater.getTotalIn()); + assertEquals(0, inflater.getBytesWritten()); + assertEquals(0, inflater.getTotalOut()); + } } diff --git a/luni/src/test/java/libcore/java/util/zip/Zip64FileTest.java b/luni/src/test/java/libcore/java/util/zip/Zip64FileTest.java new file mode 100644 index 0000000..1b733f9 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/Zip64FileTest.java @@ -0,0 +1,102 @@ +/* + * 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 libcore.java.util.zip; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public final class Zip64FileTest extends AbstractZipFileTest { + @Override + protected ZipOutputStream createZipOutputStream(OutputStream wrapped) { + return new ZipOutputStream(wrapped, true /* forceZip64 */); + } + + public void testZip64Support_largeNumberOfEntries() throws IOException { + final File file = createZipFile(65550, 2, false /* setEntrySize */); + ZipFile zf = null; + try { + zf = new ZipFile(file); + assertEquals(65550, zf.size()); + + Enumeration<? extends ZipEntry> entries = zf.entries(); + assertTrue(entries.hasMoreElements()); + ZipEntry ze = entries.nextElement(); + assertEquals(2, ze.getSize()); + } finally { + if (zf != null) { + zf.close(); + } + } + } + + public void testZip64Support_totalLargerThan4G() throws IOException { + final File file = createZipFile(5, 1073741824L, false /* setEntrySize */); + ZipFile zf = null; + try { + zf = new ZipFile(file); + assertEquals(5, zf.size()); + Enumeration<? extends ZipEntry> entries = zf.entries(); + assertTrue(entries.hasMoreElements()); + ZipEntry ze = entries.nextElement(); + assertEquals(1073741824L, ze.getSize()); + } finally { + if (zf != null) { + zf.close(); + } + } + } + + public void testZip64Support_hugeEntry() throws IOException { + try { + createZipFile(1, 4294967410L, false /* setEntrySize */); + fail(); + } catch (IOException expected) { + } + + final File file = createZipFile(1, 4294967410L, true /* setEntrySize */); + ZipFile zf = null; + try { + zf = new ZipFile(file); + assertEquals(1, zf.size()); + Enumeration<? extends ZipEntry> entries = zf.entries(); + assertTrue(entries.hasMoreElements()); + ZipEntry ze = entries.nextElement(); + assertEquals(4294967410L, ze.getSize()); + } finally { + if (zf != null) { + zf.close(); + } + } + } + + private File createZipFile(int numEntries, long entrySize, boolean setEntrySize) + throws IOException { + File file = createTemporaryZipFile(); + // Don't force a 64 bit zip file to test that our heuristics work. + ZipOutputStream os = new ZipOutputStream( + new BufferedOutputStream(new FileOutputStream(file))); + writeEntries(os, numEntries, entrySize, setEntrySize); + return file; + } +} diff --git a/luni/src/test/java/libcore/java/util/zip/Zip64Test.java b/luni/src/test/java/libcore/java/util/zip/Zip64Test.java new file mode 100644 index 0000000..e4b5baf --- /dev/null +++ b/luni/src/test/java/libcore/java/util/zip/Zip64Test.java @@ -0,0 +1,100 @@ +/* + * 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 libcore.java.util.zip; + +import junit.framework.TestCase; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.zip.Zip64; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; + +public class Zip64Test extends TestCase { + + // We shouldn't attempt to look inside the extended info if we have valid fields + // in the regular file header / central directory entry. + public void testParseZip64ExtendedInfo_noFieldsPresent() throws Exception { + ZipEntry ze = createZipEntry(null, 100, 200, ZipEntry.STORED, 300); + Zip64.parseZip64ExtendedInfo(ze, false /* fromCentralDirectory */); + Zip64.parseZip64ExtendedInfo(ze, true /* fromCentralDirectory */); + } + + // We *should* attempt to look in the extended info if the local file header / central + // directory entry don't have the correct values. + public void testParseZip64ExtendedInfo_missingExtendedInfo() throws Exception { + ZipEntry ze = createZipEntry(null, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE, + Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE, ZipEntry.STORED, 300); + try { + Zip64.parseZip64ExtendedInfo(ze, false /* fromCentralDirectory */); + fail(); + } catch (ZipException expected) { + } + + try { + Zip64.parseZip64ExtendedInfo(ze, true /* fromCentralDirectory */); + fail(); + } catch (ZipException expected) { + } + } + + // Test the case where the compressed / uncompressed sizes are in the extended info + // but the header offset isn't. + public void testParseZip64ExtendedInfo_partialInfo() throws Exception { + byte[] extras = new byte[20]; + ByteBuffer buf = ByteBuffer.wrap(extras); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putShort((short) 0x0001); + buf.putShort((short) 16); + buf.putLong(50); + buf.putLong(100); + + ZipEntry ze = createZipEntry(extras, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE, + Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE, ZipEntry.STORED, 300); + + Zip64.parseZip64ExtendedInfo(ze, false /*fromCentralDirectory */); + assertEquals(50, ze.getSize()); + assertEquals(100, ze.getCompressedSize()); + + ze = createZipEntry(extras, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE, + Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE, ZipEntry.STORED, 300); + Zip64.parseZip64ExtendedInfo(ze, true /*fromCentralDirectory */); + assertEquals(50, ze.getSize()); + assertEquals(100, ze.getCompressedSize()); + } + + public void testInsertZip64ExtendedInfo() throws Exception { + ZipEntry ze = createZipEntry(null, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE + 300, + Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE + 500, ZipEntry.STORED, 300); + Zip64.insertZip64ExtendedInfoToExtras(ze); + + assertNotNull(ze.getExtra()); + ByteBuffer buf = ByteBuffer.wrap(ze.getExtra()); + buf.order(ByteOrder.LITTLE_ENDIAN); + assertEquals(0x0001, buf.getShort()); + assertEquals(24, buf.getShort()); + assertEquals(Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE + 300, buf.getLong()); + assertEquals(Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE + 500, buf.getLong()); + } + + private static ZipEntry createZipEntry(byte[] extras, long size, long compressedSize, + int compressionMethod, long headerOffset) { + return new ZipEntry("name", "comment", 42 /* crc */, compressedSize, size, + compressionMethod, 42 /* time */, 42 /* modDate */, extras, headerOffset, + 42 /* data offset */); + } +} diff --git a/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java b/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java index 550ddfb..c7667b9 100644 --- a/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java +++ b/luni/src/test/java/libcore/java/util/zip/ZipEntryTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.List; import java.util.jar.JarEntry; import java.util.zip.ZipEntry; +import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; @@ -132,6 +133,7 @@ public class ZipEntryTest extends junit.framework.TestCase { File f = createTemporaryZipFile(); ZipOutputStream out = createZipOutputStream(f); ZipEntry ze = new ZipEntry("x"); + ze.setSize(0); ze.setExtra(maxLengthExtra); out.putNextEntry(ze); out.closeEntry(); @@ -143,6 +145,25 @@ public class ZipEntryTest extends junit.framework.TestCase { zipFile.close(); } + public void testMaxLengthExtra_zip64() throws Exception { + // Not quite the max length (65535), but large enough that there's no space + // for the zip64 extended info header. + byte[] maxLengthExtra = new byte[65530]; + + File f = createTemporaryZipFile(); + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)), + true /* forceZip64 */); + ZipEntry ze = new ZipEntry("x"); + + ze.setExtra(maxLengthExtra); + try { + out.putNextEntry(ze); + fail(); + } catch (ZipException expected) { + } + } + + public void testTooLongComment() throws Exception { String tooLongComment = makeString(65536, "z"); ZipEntry ze = new ZipEntry("x"); @@ -176,7 +197,17 @@ public class ZipEntryTest extends junit.framework.TestCase { File f = createTemporaryZipFile(); ZipOutputStream out = createZipOutputStream(f); + + // Regular (non zip64) format. ZipEntry ze = new ZipEntry("x"); + ze.setSize(0); + ze.setExtra(extra); + ze.setComment(comment); + out.putNextEntry(ze); + out.closeEntry(); + + // An entry without a length is assumed to be zip64. + ze = new ZipEntry("y"); ze.setExtra(extra); ze.setComment(comment); out.putNextEntry(ze); @@ -188,6 +219,9 @@ public class ZipEntryTest extends junit.framework.TestCase { try { assertEquals(comment, zipFile.getEntry("x").getComment()); assertTrue(Arrays.equals(extra, zipFile.getEntry("x").getExtra())); + + assertEquals(comment, zipFile.getEntry("y").getComment()); + assertTrue(Arrays.equals(extra, zipFile.getEntry("y").getExtra())); } finally { zipFile.close(); } diff --git a/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java b/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java index a9ff56f..02210ac 100644 --- a/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java +++ b/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -11,497 +11,18 @@ * 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. + * limitations under the License */ package libcore.java.util.zip; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; -import junit.framework.TestCase; -import tests.support.resource.Support_Resources; +public final class ZipFileTest extends AbstractZipFileTest { -public final class ZipFileTest extends TestCase { - /** - * Exercise Inflater's ability to refill the zlib's input buffer. As of this - * writing, this buffer's max size is 64KiB compressed bytes. We'll write a - * full megabyte of uncompressed data, which should be sufficient to exhaust - * the buffer. http://b/issue?id=2734751 - */ - public void testInflatingFilesRequiringZipRefill() throws IOException { - int originalSize = 1024 * 1024; - byte[] readBuffer = new byte[8192]; - ZipFile zipFile = new ZipFile(createZipFile(1, originalSize)); - for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { - ZipEntry zipEntry = e.nextElement(); - assertTrue("This test needs >64 KiB of compressed data to exercise Inflater", - zipEntry.getCompressedSize() > (64 * 1024)); - InputStream is = zipFile.getInputStream(zipEntry); - while (is.read(readBuffer, 0, readBuffer.length) != -1) {} - is.close(); - } - zipFile.close(); - } - - private static void replaceBytes(byte[] buffer, byte[] original, byte[] replacement) { - // Gotcha here: original and replacement must be the same length - assertEquals(original.length, replacement.length); - boolean found; - for(int i=0; i < buffer.length - original.length; i++) { - found = false; - if (buffer[i] == original[0]) { - found = true; - for (int j=0; j < original.length; j++) { - if (buffer[i+j] != original[j]) { - found = false; - break; - } - } - } - if (found) { - for (int j=0; j < original.length; j++) { - buffer[i+j] = replacement[j]; - } - } - } - } - - private static void writeBytes(File f, byte[] bytes) throws IOException { - FileOutputStream out = new FileOutputStream(f); - out.write(bytes); - out.close(); - } - - /** - * Make sure we don't fail silently for duplicate entries. - * b/8219321 - */ - public void testDuplicateEntries() throws Exception { - String name1 = "test_file_name1"; - String name2 = "test_file_name2"; - - // Create the good zip file. - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ZipOutputStream out = new ZipOutputStream(baos); - out.putNextEntry(new ZipEntry(name2)); - out.closeEntry(); - out.putNextEntry(new ZipEntry(name1)); - out.closeEntry(); - out.close(); - - // Rewrite one of the filenames. - byte[] buffer = baos.toByteArray(); - replaceBytes(buffer, name2.getBytes(), name1.getBytes()); - - // Write the result to a file. - File badZip = createTemporaryZipFile(); - writeBytes(badZip, buffer); - - // Check that we refuse to load the modified file. - try { - ZipFile bad = new ZipFile(badZip); - fail(); - } catch (ZipException expected) { - } - } - - /** - * Make sure the size used for stored zip entires is the uncompressed size. - * b/10227498 - */ - public void testStoredEntrySize() throws Exception { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ZipOutputStream out = new ZipOutputStream(baos); - - // Set up a single stored entry. - String name = "test_file"; - int expectedLength = 5; - ZipEntry outEntry = new ZipEntry(name); - byte[] buffer = new byte[expectedLength]; - outEntry.setMethod(ZipEntry.STORED); - CRC32 crc = new CRC32(); - crc.update(buffer); - outEntry.setCrc(crc.getValue()); - outEntry.setSize(buffer.length); - - out.putNextEntry(outEntry); - out.write(buffer); - out.closeEntry(); - out.close(); - - // Write the result to a file. - byte[] outBuffer = baos.toByteArray(); - File zipFile = createTemporaryZipFile(); - writeBytes(zipFile, outBuffer); - - ZipFile zip = new ZipFile(zipFile); - // Set up the zip entry to have different compressed/uncompressed sizes. - ZipEntry ze = zip.getEntry(name); - ze.setCompressedSize(expectedLength - 1); - // Read the contents of the stream and verify uncompressed size was used. - InputStream stream = zip.getInputStream(ze); - int count = 0; - int read; - while ((read = stream.read(buffer)) != -1) { - count += read; - } - - assertEquals(expectedLength, count); - zip.close(); - } - - public void testInflatingStreamsRequiringZipRefill() throws IOException { - int originalSize = 1024 * 1024; - byte[] readBuffer = new byte[8192]; - ZipInputStream in = new ZipInputStream(new FileInputStream(createZipFile(1, originalSize))); - while (in.getNextEntry() != null) { - while (in.read(readBuffer, 0, readBuffer.length) != -1) {} - } - in.close(); - } - - public void testZipFileWithLotsOfEntries() throws IOException { - int expectedEntryCount = 64*1024 - 1; - File f = createZipFile(expectedEntryCount, 0); - ZipFile zipFile = new ZipFile(f); - int entryCount = 0; - for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { - ZipEntry zipEntry = e.nextElement(); - ++entryCount; - } - assertEquals(expectedEntryCount, entryCount); - zipFile.close(); - } - - // http://code.google.com/p/android/issues/detail?id=36187 - public void testZipFileLargerThan2GiB() throws IOException { - if (false) { // TODO: this test requires too much time and too much disk space! - File f = createZipFile(1024, 3*1024*1024); - ZipFile zipFile = new ZipFile(f); - int entryCount = 0; - for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { - ZipEntry zipEntry = e.nextElement(); - ++entryCount; - } - assertEquals(1024, entryCount); - zipFile.close(); - } - } - - public void testZip64Support() throws IOException { - try { - createZipFile(64*1024, 0); - fail(); // Make this test more like testHugeZipFile when we have Zip64 support. - } catch (ZipException expected) { - } - } - - /** - * Compresses the given number of files, each of the given size, into a .zip archive. - */ - private File createZipFile(int entryCount, int entrySize) throws IOException { - File result = createTemporaryZipFile(); - - byte[] writeBuffer = new byte[8192]; - Random random = new Random(); - - ZipOutputStream out = createZipOutputStream(result); - try { - for (int entry = 0; entry < entryCount; ++entry) { - ZipEntry ze = new ZipEntry(Integer.toHexString(entry)); - out.putNextEntry(ze); - - for (int i = 0; i < entrySize; i += writeBuffer.length) { - random.nextBytes(writeBuffer); - int byteCount = Math.min(writeBuffer.length, entrySize - i); - out.write(writeBuffer, 0, byteCount); - } - - out.closeEntry(); - } - } finally { - out.close(); - } - return result; - } - - private File createTemporaryZipFile() throws IOException { - File result = File.createTempFile("ZipFileTest", "zip"); - result.deleteOnExit(); - return result; - } - - private ZipOutputStream createZipOutputStream(File f) throws IOException { - return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f))); - } - - public void testSTORED() throws IOException { - ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); - CRC32 crc = new CRC32(); - - // Missing CRC, size, and compressed size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Missing CRC and compressed size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - ze.setSize(0); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Missing CRC and size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - ze.setSize(0); - ze.setCompressedSize(0); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Missing size and compressed size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Missing size is copied from compressed size. - { - ZipEntry ze = new ZipEntry("okay1"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - - assertEquals(-1, ze.getSize()); - assertEquals(-1, ze.getCompressedSize()); - - ze.setCompressedSize(0); - - assertEquals(-1, ze.getSize()); - assertEquals(0, ze.getCompressedSize()); - - out.putNextEntry(ze); - - assertEquals(0, ze.getSize()); - assertEquals(0, ze.getCompressedSize()); - } - - // Missing compressed size is copied from size. - { - ZipEntry ze = new ZipEntry("okay2"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - - assertEquals(-1, ze.getSize()); - assertEquals(-1, ze.getCompressedSize()); - - ze.setSize(0); - - assertEquals(0, ze.getSize()); - assertEquals(-1, ze.getCompressedSize()); - - out.putNextEntry(ze); - - assertEquals(0, ze.getSize()); - assertEquals(0, ze.getCompressedSize()); - } - - // Mismatched size and compressed size => failure. - try { - ZipEntry ze = new ZipEntry("a"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - ze.setCompressedSize(1); - ze.setSize(0); - out.putNextEntry(ze); - fail(); - } catch (ZipException expected) { - } - - // Everything present => success. - ZipEntry ze = new ZipEntry("okay"); - ze.setMethod(ZipEntry.STORED); - ze.setCrc(crc.getValue()); - ze.setSize(0); - ze.setCompressedSize(0); - out.putNextEntry(ze); - - out.close(); - } - - private String makeString(int count, String ch) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < count; ++i) { - sb.append(ch); - } - return sb.toString(); - } - - public void testComments() throws Exception { - String expectedFileComment = "1 \u0666 2"; - String expectedEntryComment = "a \u0666 b"; - - File file = createTemporaryZipFile(); - ZipOutputStream out = createZipOutputStream(file); - - // Is file comment length checking done on bytes or characters? (Should be bytes.) - out.setComment(null); - out.setComment(makeString(0xffff, "a")); - try { - out.setComment(makeString(0xffff + 1, "a")); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - out.setComment(makeString(0xffff, "\u0666")); - fail(); - } catch (IllegalArgumentException expected) { - } - - ZipEntry ze = new ZipEntry("a"); - - // Is entry comment length checking done on bytes or characters? (Should be bytes.) - ze.setComment(null); - ze.setComment(makeString(0xffff, "a")); - try { - ze.setComment(makeString(0xffff + 1, "a")); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - ze.setComment(makeString(0xffff, "\u0666")); - fail(); - } catch (IllegalArgumentException expected) { - } - - ze.setComment(expectedEntryComment); - out.putNextEntry(ze); - out.closeEntry(); - - out.setComment(expectedFileComment); - out.close(); - - ZipFile zipFile = new ZipFile(file); - assertEquals(expectedFileComment, zipFile.getComment()); - assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment()); - zipFile.close(); - } - - public void test_getComment_unset() throws Exception { - File file = createTemporaryZipFile(); - ZipOutputStream out = createZipOutputStream(file); - ZipEntry ze = new ZipEntry("test entry"); - ze.setComment("per-entry comment"); - out.putNextEntry(ze); - out.close(); - - ZipFile zipFile = new ZipFile(file); - assertEquals(null, zipFile.getComment()); - } - - // https://code.google.com/p/android/issues/detail?id=58465 - public void test_NUL_in_filename() throws Exception { - File file = createTemporaryZipFile(); - - // We allow creation of a ZipEntry whose name contains a NUL byte, - // mainly because it's not likely to happen by accident and it's useful for testing. - ZipOutputStream out = createZipOutputStream(file); - out.putNextEntry(new ZipEntry("hello")); - out.putNextEntry(new ZipEntry("hello\u0000")); - out.close(); - - // But you can't open a ZIP file containing such an entry, because we reject it - // when we find it in the central directory. - try { - ZipFile zipFile = new ZipFile(file); - fail(); - } catch (ZipException expected) { - } - } - - public void testCrc() throws IOException { - ZipEntry ze = new ZipEntry("test"); - ze.setMethod(ZipEntry.STORED); - ze.setSize(4); - - // setCrc takes a long, not an int, so -1 isn't a valid CRC32 (because it's 64 bits). - try { - ze.setCrc(-1); - } catch (IllegalArgumentException expected) { - } - - // You can set the CRC32 to 0xffffffff if you're slightly more careful though... - ze.setCrc(0xffffffffL); - assertEquals(0xffffffffL, ze.getCrc()); - - // And it actually works, even though we use -1L to mean "no CRC set"... - ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); - out.putNextEntry(ze); - out.write(-1); - out.write(-1); - out.write(-1); - out.write(-1); - out.closeEntry(); - out.close(); - } - - /** - * RI does not allow reading of an empty zip using a {@link ZipFile}. - */ - public void testConstructorFailsWhenReadingEmptyZipArchive() throws IOException { - - File resources = Support_Resources.createTempFolder(); - File emptyZip = Support_Resources.copyFile( - resources, "java/util/zip", "EmptyArchive.zip"); - - try { - // The following should fail with an exception but if it doesn't then we need to clean - // up the resource so we need a reference to it. - ZipFile zipFile = new ZipFile(emptyZip); - - // Clean up the resource. - try { - zipFile.close(); - } catch (Exception e) { - // Ignore - } - fail(); - } catch (ZipException expected) { - // expected - } + @Override + protected ZipOutputStream createZipOutputStream(OutputStream wrapped) { + return new ZipOutputStream(wrapped); } } diff --git a/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java b/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java index e69f010..15600de 100644 --- a/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java +++ b/luni/src/test/java/libcore/java/util/zip/ZipOutputStreamTest.java @@ -21,7 +21,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Random; import java.util.zip.ZipEntry; diff --git a/luni/src/test/java/libcore/javax/crypto/CipherTest.java b/luni/src/test/java/libcore/javax/crypto/CipherTest.java index c89886c..dd7d6e7 100644 --- a/luni/src/test/java/libcore/javax/crypto/CipherTest.java +++ b/luni/src/test/java/libcore/javax/crypto/CipherTest.java @@ -21,11 +21,13 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; +import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; @@ -45,6 +47,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; @@ -231,6 +234,10 @@ public final class CipherTest extends TestCase { return algorithm.startsWith("PBE"); } + private static boolean isAEAD(String algorithm) { + return "GCM".equals(algorithm) || algorithm.contains("/GCM/"); + } + private static boolean isStreamMode(String algorithm) { return algorithm.contains("/CTR/") || algorithm.contains("/OFB") || algorithm.contains("/CFB"); @@ -295,10 +302,10 @@ public final class CipherTest extends TestCase { setExpectedBlockSize("AES/ECB/PKCS5PADDING", 16); setExpectedBlockSize("AES/ECB/PKCS7PADDING", 16); setExpectedBlockSize("AES/ECB/NOPADDING", 16); + setExpectedBlockSize("AES/GCM/NOPADDING", 16); setExpectedBlockSize("AES/OFB/PKCS5PADDING", 16); setExpectedBlockSize("AES/OFB/PKCS7PADDING", 16); setExpectedBlockSize("AES/OFB/NOPADDING", 16); - setExpectedBlockSize("GCM", 16); setExpectedBlockSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16); setExpectedBlockSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16); setExpectedBlockSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16); @@ -453,9 +460,9 @@ public final class CipherTest extends TestCase { setExpectedOutputSize("AES/CTS/PKCS7PADDING", Cipher.ENCRYPT_MODE, 16); setExpectedOutputSize("AES/ECB/PKCS5PADDING", Cipher.ENCRYPT_MODE, 16); setExpectedOutputSize("AES/ECB/PKCS7PADDING", Cipher.ENCRYPT_MODE, 16); + setExpectedOutputSize("AES/GCM/NOPADDING", Cipher.ENCRYPT_MODE, GCM_TAG_SIZE_BITS / 8); setExpectedOutputSize("AES/OFB/PKCS5PADDING", Cipher.ENCRYPT_MODE, 16); setExpectedOutputSize("AES/OFB/PKCS7PADDING", Cipher.ENCRYPT_MODE, 16); - setExpectedOutputSize("GCM", Cipher.ENCRYPT_MODE, GCM_TAG_SIZE_BITS / 8); setExpectedOutputSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", 16); setExpectedOutputSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", 16); setExpectedOutputSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", 16); @@ -486,9 +493,9 @@ public final class CipherTest extends TestCase { setExpectedOutputSize("AES/CTS/PKCS7PADDING", Cipher.DECRYPT_MODE, 0); setExpectedOutputSize("AES/ECB/PKCS5PADDING", Cipher.DECRYPT_MODE, 0); setExpectedOutputSize("AES/ECB/PKCS7PADDING", Cipher.DECRYPT_MODE, 0); + setExpectedOutputSize("AES/GCM/NOPADDING", Cipher.DECRYPT_MODE, 0); setExpectedOutputSize("AES/OFB/PKCS5PADDING", Cipher.DECRYPT_MODE, 0); setExpectedOutputSize("AES/OFB/PKCS7PADDING", Cipher.DECRYPT_MODE, 0); - setExpectedOutputSize("GCM", Cipher.DECRYPT_MODE, 0); setExpectedOutputSize("PBEWITHMD5AND128BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0); setExpectedOutputSize("PBEWITHMD5AND192BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0); setExpectedOutputSize("PBEWITHMD5AND256BITAES-CBC-OPENSSL", Cipher.DECRYPT_MODE, 0); @@ -498,15 +505,8 @@ public final class CipherTest extends TestCase { setExpectedOutputSize("PBEWITHSHAAND128BITAES-CBC-BC", Cipher.DECRYPT_MODE, 0); setExpectedOutputSize("PBEWITHSHAAND192BITAES-CBC-BC", Cipher.DECRYPT_MODE, 0); setExpectedOutputSize("PBEWITHSHAAND256BITAES-CBC-BC", Cipher.DECRYPT_MODE, 0); - // AndroidOpenSSL returns the block size for the block ciphers - setExpectedOutputSize("AES/CBC/PKCS5PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 16); - setExpectedOutputSize("AES/CBC/PKCS7PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 16); - setExpectedOutputSize("AES/ECB/PKCS5PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 16); - setExpectedOutputSize("AES/ECB/PKCS7PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 16); - setExpectedOutputSize("DESEDE/CBC/PKCS5PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 8); - setExpectedOutputSize("DESEDE/CBC/PKCS7PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 8); - setExpectedOutputSize("DESEDE/ECB/PKCS5PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 8); - setExpectedOutputSize("DESEDE/ECB/PKCS7PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 8); + setExpectedOutputSize("DESEDE/CBC/PKCS5PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 0); + setExpectedOutputSize("DESEDE/CBC/PKCS7PADDING", Cipher.DECRYPT_MODE, "AndroidOpenSSL", 0); if (StandardNames.IS_RI) { setExpectedOutputSize("AESWRAP", Cipher.WRAP_MODE, 8); @@ -750,8 +750,8 @@ public final class CipherTest extends TestCase { new SecureRandom().nextBytes(salt); return new PBEParameterSpec(salt, 1024); } - if (algorithm.equals("GCM")) { - final byte[] iv = new byte[8]; + if (algorithm.equals("AES/GCM/NOPADDING")) { + final byte[] iv = new byte[12]; new SecureRandom().nextBytes(iv); return new GCMParameterSpec(GCM_TAG_SIZE_BITS, iv); } @@ -791,7 +791,7 @@ public final class CipherTest extends TestCase { } byte[] iv = encryptCipher.getIV(); if (iv != null) { - if ("GCM".equals(algorithm)) { + if ("AES/GCM/NOPADDING".equals(algorithm)) { return new GCMParameterSpec(GCM_TAG_SIZE_BITS, iv); } return new IvParameterSpec(iv); @@ -847,6 +847,24 @@ public final class CipherTest extends TestCase { } } + public void testCipher_getInstance_DoesNotSupportKeyClass_Success() throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName()); + put("Cipher.FOO SupportedKeyClasses", "None"); + } + }; + + Security.addProvider(mockProvider); + try { + Cipher c = Cipher.getInstance("FOO", mockProvider); + c.init(Cipher.ENCRYPT_MODE, new MockKey()); + assertEquals(mockProvider, c.getProvider()); + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + public void testCipher_getInstance_SuppliedProviderNotRegistered_MultipartTransform_Success() throws Exception { Provider mockProvider = new MockProvider("MockProvider") { @@ -970,14 +988,130 @@ public final class CipherTest extends TestCase { Security.addProvider(mockProviderInvalid); try { - Cipher.getInstance("FOO"); - fail("Should not find any matching providers"); - } catch (NoSuchAlgorithmException expected) { + Cipher c = Cipher.getInstance("FOO"); + c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[16], "FOO")); + fail("Should not find any matching providers; found: " + c); + } catch (ClassCastException expected) { } finally { Security.removeProvider(mockProviderInvalid.getName()); } } + public void testCipher_init_CallsInitWithParams_AlgorithmParameterSpec() throws Exception { + Provider mockProviderRejects = new MockProvider("MockProviderRejects") { + public void setup() { + put("Cipher.FOO", + MockCipherSpi.MustInitWithAlgorithmParameterSpec_RejectsAll.class.getName()); + put("Cipher.FOO SupportedKeyClasses", MockKey.class.getName()); + } + }; + Provider mockProviderAccepts = new MockProvider("MockProviderAccepts") { + public void setup() { + put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName()); + put("Cipher.FOO SupportedKeyClasses", MockKey.class.getName()); + } + }; + + Security.addProvider(mockProviderRejects); + Security.addProvider(mockProviderAccepts); + try { + Cipher c = Cipher.getInstance("FOO"); + c.init(Cipher.ENCRYPT_MODE, new MockKey(), new IvParameterSpec(new byte[12])); + assertEquals(mockProviderAccepts, c.getProvider()); + } finally { + Security.removeProvider(mockProviderRejects.getName()); + Security.removeProvider(mockProviderAccepts.getName()); + } + } + + public void testCipher_init_CallsInitWithParams_AlgorithmParameters() throws Exception { + Provider mockProviderRejects = new MockProvider("MockProviderRejects") { + public void setup() { + put("Cipher.FOO", + MockCipherSpi.MustInitWithAlgorithmParameters_RejectsAll.class.getName()); + put("Cipher.FOO SupportedKeyClasses", MockKey.class.getName()); + } + }; + Provider mockProviderAccepts = new MockProvider("MockProviderAccepts") { + public void setup() { + put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName()); + put("Cipher.FOO SupportedKeyClasses", MockKey.class.getName()); + } + }; + + Security.addProvider(mockProviderRejects); + Security.addProvider(mockProviderAccepts); + try { + Cipher c = Cipher.getInstance("FOO"); + c.init(Cipher.ENCRYPT_MODE, new MockKey(), AlgorithmParameters.getInstance("AES")); + assertEquals(mockProviderAccepts, c.getProvider()); + } finally { + Security.removeProvider(mockProviderRejects.getName()); + Security.removeProvider(mockProviderAccepts.getName()); + } + } + + public void testCipher_init_CallsInitIgnoresRuntimeException() throws Exception { + Provider mockProviderRejects = new MockProvider("MockProviderRejects") { + public void setup() { + put("Cipher.FOO", + MockCipherSpi.MustInitWithAlgorithmParameters_ThrowsNull.class.getName()); + put("Cipher.FOO SupportedKeyClasses", MockKey.class.getName()); + } + }; + Provider mockProviderAccepts = new MockProvider("MockProviderAccepts") { + public void setup() { + put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName()); + put("Cipher.FOO SupportedKeyClasses", MockKey.class.getName()); + } + }; + + Security.addProvider(mockProviderRejects); + Security.addProvider(mockProviderAccepts); + try { + Cipher c = Cipher.getInstance("FOO"); + c.init(Cipher.ENCRYPT_MODE, new MockKey(), AlgorithmParameters.getInstance("AES")); + assertEquals(mockProviderAccepts, c.getProvider()); + } finally { + Security.removeProvider(mockProviderRejects.getName()); + Security.removeProvider(mockProviderAccepts.getName()); + } + } + + public void testCipher_init_CallsInitWithMode() throws Exception { + Provider mockProviderOnlyEncrypt = new MockProvider("MockProviderOnlyEncrypt") { + public void setup() { + put("Cipher.FOO", MockCipherSpi.MustInitForEncryptModeOrRejects.class.getName()); + put("Cipher.FOO SupportedKeyClasses", MockKey.class.getName()); + } + }; + Provider mockProviderAcceptsAll = new MockProvider("MockProviderAcceptsAll") { + public void setup() { + put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName()); + put("Cipher.FOO SupportedKeyClasses", MockKey.class.getName()); + } + }; + + Security.addProvider(mockProviderOnlyEncrypt); + Security.addProvider(mockProviderAcceptsAll); + try { + { + Cipher c = Cipher.getInstance("FOO"); + c.init(Cipher.DECRYPT_MODE, new MockKey(), AlgorithmParameters.getInstance("AES")); + assertEquals(mockProviderAcceptsAll, c.getProvider()); + } + + { + Cipher c = Cipher.getInstance("FOO"); + c.init(Cipher.ENCRYPT_MODE, new MockKey(), AlgorithmParameters.getInstance("AES")); + assertEquals(mockProviderOnlyEncrypt, c.getProvider()); + } + } finally { + Security.removeProvider(mockProviderOnlyEncrypt.getName()); + Security.removeProvider(mockProviderAcceptsAll.getName()); + } + } + public void test_getInstance() throws Exception { final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream(); PrintStream out = new PrintStream(errBuffer); @@ -1125,9 +1259,9 @@ public final class CipherTest extends TestCase { c.init(encryptMode, encryptKey, encryptSpec); assertEquals(cipherID + " getBlockSize() encryptMode", - getExpectedBlockSize(algorithm, encryptMode, providerName), c.getBlockSize()); - assertEquals(cipherID + " getOutputSize(0) encryptMode", - getExpectedOutputSize(algorithm, encryptMode, providerName), c.getOutputSize(0)); + getExpectedBlockSize(algorithm, encryptMode, providerName), c.getBlockSize()); + assertTrue(cipherID + " getOutputSize(0) encryptMode", + getExpectedOutputSize(algorithm, encryptMode, providerName) <= c.getOutputSize(0)); if ((algorithm.endsWith("/PKCS5PADDING") || algorithm.endsWith("/PKCS7PADDING")) && isStreamMode(algorithm)) { assertEquals(getExpectedOutputSize(algorithm, encryptMode, providerName), @@ -1136,6 +1270,9 @@ public final class CipherTest extends TestCase { final AlgorithmParameterSpec decryptSpec = getDecryptAlgorithmParameterSpec(encryptSpec, c); int decryptMode = getDecryptMode(algorithm); + + test_Cipher_init_Decrypt_NullParameters(c, decryptMode, encryptKey, decryptSpec != null); + c.init(decryptMode, encryptKey, decryptSpec); assertEquals(cipherID + " getBlockSize() decryptMode", getExpectedBlockSize(algorithm, decryptMode, providerName), c.getBlockSize()); @@ -1188,7 +1325,7 @@ public final class CipherTest extends TestCase { // Test wrapping a key. Every cipher should be able to wrap. Except those that can't. /* Bouncycastle is broken for wrapping because getIV() fails. */ if (isSupportedForWrapping(algorithm) - && !algorithm.equals("GCM") && !providerName.equals("BC")) { + && !algorithm.equals("AES/GCM/NOPADDING") && !providerName.equals("BC")) { // Generate a small SecretKey for AES. KeyGenerator kg = KeyGenerator.getInstance("AES"); kg.init(128); @@ -1212,16 +1349,28 @@ public final class CipherTest extends TestCase { if (!isOnlyWrappingAlgorithm(algorithm)) { c.init(Cipher.ENCRYPT_MODE, encryptKey, encryptSpec); + if (isAEAD(algorithm)) { + c.updateAAD(new byte[24]); + } byte[] cipherText = c.doFinal(getActualPlainText(algorithm)); + if (isAEAD(algorithm)) { + c.updateAAD(new byte[24]); + } byte[] cipherText2 = c.doFinal(getActualPlainText(algorithm)); assertEquals(cipherID, Arrays.toString(cipherText), Arrays.toString(cipherText2)); c.init(Cipher.DECRYPT_MODE, getDecryptKey(algorithm), decryptSpec); + if (isAEAD(algorithm)) { + c.updateAAD(new byte[24]); + } byte[] decryptedPlainText = c.doFinal(cipherText); assertEquals(cipherID, Arrays.toString(getExpectedPlainText(algorithm, providerName)), Arrays.toString(decryptedPlainText)); + if (isAEAD(algorithm)) { + c.updateAAD(new byte[24]); + } byte[] decryptedPlainText2 = c.doFinal(cipherText); assertEquals(cipherID, Arrays.toString(decryptedPlainText), @@ -1268,6 +1417,53 @@ public final class CipherTest extends TestCase { } } + private void test_Cipher_init_Decrypt_NullParameters(Cipher c, int decryptMode, Key encryptKey, + boolean needsParameters) throws Exception { + try { + c.init(decryptMode, encryptKey, (AlgorithmParameterSpec) null); + if (needsParameters) { + fail("Should throw InvalidAlgorithmParameterException with null parameters"); + } + } catch (InvalidAlgorithmParameterException e) { + if (!needsParameters) { + throw e; + } + } + + try { + c.init(decryptMode, encryptKey, (AlgorithmParameterSpec) null, (SecureRandom) null); + if (needsParameters) { + fail("Should throw InvalidAlgorithmParameterException with null parameters"); + } + } catch (InvalidAlgorithmParameterException e) { + if (!needsParameters) { + throw e; + } + } + + try { + c.init(decryptMode, encryptKey, (AlgorithmParameters) null); + if (needsParameters) { + fail("Should throw InvalidAlgorithmParameterException with null parameters"); + } + } catch (InvalidAlgorithmParameterException e) { + if (!needsParameters) { + throw e; + } + } + + try { + c.init(decryptMode, encryptKey, (AlgorithmParameters) null, (SecureRandom) null); + if (needsParameters) { + fail("Should throw InvalidAlgorithmParameterException with null parameters"); + } + } catch (InvalidAlgorithmParameterException e) { + if (!needsParameters) { + throw e; + } + } + } + public void testInputPKCS1Padding() throws Exception { for (String provider : RSA_PROVIDERS) { testInputPKCS1Padding(provider); @@ -1293,11 +1489,22 @@ public final class CipherTest extends TestCase { Cipher encryptCipher = Cipher.getInstance("RSA/ECB/NoPadding", provider); encryptCipher.init(Cipher.ENCRYPT_MODE, encryptKey); byte[] cipherText = encryptCipher.doFinal(prePaddedPlainText); + encryptCipher.update(prePaddedPlainText); + encryptCipher.init(Cipher.ENCRYPT_MODE, encryptKey); + byte[] cipherText2 = encryptCipher.doFinal(prePaddedPlainText); + assertEquals(Arrays.toString(cipherText), + Arrays.toString(cipherText2)); + Cipher decryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider); decryptCipher.init(Cipher.DECRYPT_MODE, decryptKey); byte[] plainText = decryptCipher.doFinal(cipherText); assertEquals(Arrays.toString(ORIGINAL_PLAIN_TEXT), Arrays.toString(plainText)); + decryptCipher.update(prePaddedPlainText); + decryptCipher.init(Cipher.DECRYPT_MODE, decryptKey); + byte[] plainText2 = decryptCipher.doFinal(cipherText); + assertEquals(Arrays.toString(plainText), + Arrays.toString(plainText2)); } public void testOutputPKCS1Padding() throws Exception { @@ -2423,6 +2630,80 @@ public final class CipherTest extends TestCase { }; /* + * Taken from BoringSSL test vectors. + */ + private static final byte[] AES_128_GCM_TestVector_1_Key = new byte[] { + (byte) 0xca, (byte) 0xbd, (byte) 0xcf, (byte) 0x54, (byte) 0x1a, (byte) 0xeb, + (byte) 0xf9, (byte) 0x17, (byte) 0xba, (byte) 0xc0, (byte) 0x19, (byte) 0xf1, + (byte) 0x39, (byte) 0x25, (byte) 0xd2, (byte) 0x67, + }; + + /* + * Taken from BoringSSL test vectors. + */ + private static final byte[] AES_128_GCM_TestVector_1_IV = new byte[] { + (byte) 0x2c, (byte) 0x34, (byte) 0xc0, (byte) 0x0c, (byte) 0x42, (byte) 0xda, + (byte) 0xe3, (byte) 0x82, (byte) 0x27, (byte) 0x9d, (byte) 0x79, (byte) 0x74, + }; + + /* + * Taken from BoringSSL test vectors. + */ + private static final byte[] AES_128_GCM_TestVector_1_AAD = new byte[] { + (byte) 0xdd, (byte) 0x10, (byte) 0xe3, (byte) 0x71, (byte) 0xb2, (byte) 0x2e, + (byte) 0x15, (byte) 0x67, (byte) 0x1c, (byte) 0x31, (byte) 0xaf, (byte) 0xee, + (byte) 0x55, (byte) 0x2b, (byte) 0xf1, (byte) 0xde, (byte) 0xa0, (byte) 0x7c, + (byte) 0xbb, (byte) 0xf6, (byte) 0x85, (byte) 0xe2, (byte) 0xca, (byte) 0xa0, + (byte) 0xe0, (byte) 0x36, (byte) 0x37, (byte) 0x16, (byte) 0xa2, (byte) 0x76, + (byte) 0xe1, (byte) 0x20, (byte) 0xc6, (byte) 0xc0, (byte) 0xeb, (byte) 0x4a, + (byte) 0xcb, (byte) 0x1a, (byte) 0x4d, (byte) 0x1b, (byte) 0xa7, (byte) 0x3f, + (byte) 0xde, (byte) 0x66, (byte) 0x15, (byte) 0xf7, (byte) 0x08, (byte) 0xaa, + (byte) 0xa4, (byte) 0x6b, (byte) 0xc7, (byte) 0x6c, (byte) 0x7f, (byte) 0xf3, + (byte) 0x45, (byte) 0xa4, (byte) 0xf7, (byte) 0x6b, (byte) 0xda, (byte) 0x11, + (byte) 0x7f, (byte) 0xe5, (byte) 0x6f, (byte) 0x0d, (byte) 0xc9, (byte) 0xb9, + (byte) 0x39, (byte) 0x04, (byte) 0x0d, (byte) 0xdd, + }; + + /* + * Taken from BoringSSL test vectors. + */ + private static final byte[] AES_128_GCM_TestVector_1_Plaintext = new byte[] { + (byte) 0x88, (byte) 0xcc, (byte) 0x1e, (byte) 0x07, (byte) 0xdf, (byte) 0xde, + (byte) 0x8e, (byte) 0x08, (byte) 0x08, (byte) 0x2e, (byte) 0x67, (byte) 0x66, + (byte) 0xe0, (byte) 0xa8, (byte) 0x81, (byte) 0x03, (byte) 0x38, (byte) 0x47, + (byte) 0x42, (byte) 0xaf, (byte) 0x37, (byte) 0x8d, (byte) 0x7b, (byte) 0x6b, + (byte) 0x8a, (byte) 0x87, (byte) 0xfc, (byte) 0xe0, (byte) 0x36, (byte) 0xaf, + (byte) 0x74, (byte) 0x41, (byte) 0xc1, (byte) 0x39, (byte) 0x61, (byte) 0xc2, + (byte) 0x5a, (byte) 0xfe, (byte) 0xa7, (byte) 0xf6, (byte) 0xe5, (byte) 0x61, + (byte) 0x93, (byte) 0xf5, (byte) 0x4b, (byte) 0xee, (byte) 0x00, (byte) 0x11, + (byte) 0xcb, (byte) 0x78, (byte) 0x64, (byte) 0x2c, (byte) 0x3a, (byte) 0xb9, + (byte) 0xe6, (byte) 0xd5, (byte) 0xb2, (byte) 0xe3, (byte) 0x58, (byte) 0x33, + (byte) 0xec, (byte) 0x16, (byte) 0xcd, (byte) 0x35, (byte) 0x55, (byte) 0x15, + (byte) 0xaf, (byte) 0x1a, (byte) 0x19, (byte) 0x0f, + }; + + /* + * Taken from BoringSSL test vectors. + */ + private static final byte[] AES_128_GCM_TestVector_1_Encrypted = new byte[] { + (byte) 0x04, (byte) 0x94, (byte) 0x53, (byte) 0xba, (byte) 0xf1, (byte) 0x57, + (byte) 0x87, (byte) 0x87, (byte) 0xd6, (byte) 0x8e, (byte) 0xd5, (byte) 0x47, + (byte) 0x87, (byte) 0x26, (byte) 0xc0, (byte) 0xb8, (byte) 0xa6, (byte) 0x36, + (byte) 0x33, (byte) 0x7a, (byte) 0x0b, (byte) 0x8a, (byte) 0x82, (byte) 0xb8, + (byte) 0x68, (byte) 0x36, (byte) 0xf9, (byte) 0x1c, (byte) 0xde, (byte) 0x25, + (byte) 0xe6, (byte) 0xe4, (byte) 0x4c, (byte) 0x34, (byte) 0x59, (byte) 0x40, + (byte) 0xe8, (byte) 0x19, (byte) 0xa0, (byte) 0xc5, (byte) 0x05, (byte) 0x75, + (byte) 0x1e, (byte) 0x60, (byte) 0x3c, (byte) 0xb8, (byte) 0xf8, (byte) 0xc4, + (byte) 0xfe, (byte) 0x98, (byte) 0x71, (byte) 0x91, (byte) 0x85, (byte) 0x56, + (byte) 0x27, (byte) 0x94, (byte) 0xa1, (byte) 0x85, (byte) 0xe5, (byte) 0xde, + (byte) 0xc4, (byte) 0x15, (byte) 0xc8, (byte) 0x1f, (byte) 0x2f, (byte) 0x16, + (byte) 0x2c, (byte) 0xdc, (byte) 0xd6, (byte) 0x50, (byte) 0xdc, (byte) 0xe7, + (byte) 0x19, (byte) 0x87, (byte) 0x28, (byte) 0xbf, (byte) 0xc1, (byte) 0xb5, + (byte) 0xf9, (byte) 0x49, (byte) 0xb9, (byte) 0xb5, (byte) 0x37, (byte) 0x41, + (byte) 0x99, (byte) 0xc6, + }; + + /* * Test key generation: * openssl rand -hex 16 * echo 'ceaa31952dfd3d0f5af4b2042ba06094' | sed 's/\(..\)/(byte) 0x\1, /g' @@ -2491,17 +2772,20 @@ public final class CipherTest extends TestCase { public final byte[] iv; + public final byte[] aad; + public final byte[] plaintext; public final byte[] ciphertext; public final byte[] plaintextPadded; - public CipherTestParam(String transformation, byte[] key, byte[] iv, byte[] plaintext, - byte[] plaintextPadded, byte[] ciphertext) { - this.transformation = transformation; + public CipherTestParam(String transformation, byte[] key, byte[] iv, byte[] aad, + byte[] plaintext, byte[] plaintextPadded, byte[] ciphertext) { + this.transformation = transformation.toUpperCase(Locale.ROOT); this.key = key; this.iv = iv; + this.aad = aad; this.plaintext = plaintext; this.plaintextPadded = plaintextPadded; this.ciphertext = ciphertext; @@ -2512,23 +2796,34 @@ public final class CipherTest extends TestCase { static { CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/ECB/PKCS5Padding", AES_128_KEY, null, + null, AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext, AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded, AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted)); // PKCS#5 is assumed to be equivalent to PKCS#7 -- same test vectors are thus used for both. CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/ECB/PKCS7Padding", AES_128_KEY, null, + null, AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext, AES_128_ECB_PKCS5Padding_TestVector_1_Plaintext_Padded, AES_128_ECB_PKCS5Padding_TestVector_1_Encrypted)); + CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/GCM/NOPADDING", + AES_128_GCM_TestVector_1_Key, + AES_128_GCM_TestVector_1_IV, + AES_128_GCM_TestVector_1_AAD, + AES_128_GCM_TestVector_1_Plaintext, + AES_128_GCM_TestVector_1_Plaintext, + AES_128_GCM_TestVector_1_Encrypted)); if (IS_UNLIMITED) { CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/CBC/PKCS5Padding", AES_256_KEY, AES_256_CBC_PKCS5Padding_TestVector_1_IV, + null, AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext, AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded, AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext)); CIPHER_TEST_PARAMS.add(new CipherTestParam("AES/CBC/PKCS7Padding", AES_256_KEY, AES_256_CBC_PKCS5Padding_TestVector_1_IV, + null, AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext, AES_256_CBC_PKCS5Padding_TestVector_1_Plaintext_Padded, AES_256_CBC_PKCS5Padding_TestVector_1_Ciphertext)); @@ -2564,62 +2859,98 @@ public final class CipherTest extends TestCase { private void checkCipher(CipherTestParam p, String provider) throws Exception { SecretKey key = new SecretKeySpec(p.key, "AES"); Cipher c = Cipher.getInstance(p.transformation, provider); + AlgorithmParameterSpec spec = null; if (p.iv != null) { - spec = new IvParameterSpec(p.iv); + if (isAEAD(p.transformation)) { + spec = new GCMParameterSpec((p.ciphertext.length - p.plaintext.length) * 8, p.iv); + } else { + spec = new IvParameterSpec(p.iv); + } } + c.init(Cipher.ENCRYPT_MODE, key, spec); + if (p.aad != null) { + c.updateAAD(p.aad); + } final byte[] actualCiphertext = c.doFinal(p.plaintext); - assertEquals(Arrays.toString(p.ciphertext), Arrays.toString(actualCiphertext)); + assertEquals(p.transformation + " " + provider, Arrays.toString(p.ciphertext), + Arrays.toString(actualCiphertext)); + c = Cipher.getInstance(p.transformation, provider); + c.init(Cipher.ENCRYPT_MODE, key, spec); byte[] emptyCipherText = c.doFinal(); assertNotNull(emptyCipherText); c.init(Cipher.DECRYPT_MODE, key, spec); - try { - c.updateAAD(new byte[8]); - fail("Cipher should not support AAD"); - } catch (UnsupportedOperationException expected) { + if (!isAEAD(p.transformation)) { + try { + c.updateAAD(new byte[8]); + fail("Cipher should not support AAD"); + } catch (UnsupportedOperationException | IllegalStateException expected) { + } } - byte[] emptyPlainText = c.doFinal(emptyCipherText); - assertEquals(Arrays.toString(new byte[0]), Arrays.toString(emptyPlainText)); + try { + byte[] emptyPlainText = c.doFinal(emptyCipherText); + assertEquals(Arrays.toString(new byte[0]), Arrays.toString(emptyPlainText)); + } catch (AEADBadTagException e) { + if (!"AndroidOpenSSL".equals(provider) || !isAEAD(p.transformation)) { + throw e; + } + } // empty decrypt { - if (StandardNames.IS_RI) { + if (!isAEAD(p.transformation) + && (StandardNames.IS_RI || provider.equals("AndroidOpenSSL"))) { 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")) { + } else if (provider.equals("BC") || isAEAD(p.transformation)) { try { c.doFinal(); fail(); - } catch (IllegalBlockSizeException expected) { + } catch (IllegalBlockSizeException maybe) { + if (isAEAD(p.transformation)) { + throw maybe; + } + } catch (AEADBadTagException maybe) { + if (!isAEAD(p.transformation)) { + throw maybe; + } } try { c.update(new byte[0]); c.doFinal(); fail(); - } catch (IllegalBlockSizeException expected) { + } catch (IllegalBlockSizeException maybe) { + if (isAEAD(p.transformation)) { + throw maybe; + } + } catch (AEADBadTagException maybe) { + if (!isAEAD(p.transformation)) { + throw maybe; + } } - } else if (provider.equals("AndroidOpenSSL")) { - assertNull(c.doFinal()); - - c.update(new byte[0]); - assertNull(c.doFinal()); } else { throw new AssertionError("Define your behavior here for " + provider); } } + // Cipher might be in unspecified state from failures above. + c.init(Cipher.DECRYPT_MODE, key, 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)); } @@ -2629,6 +2960,12 @@ public final class CipherTest extends TestCase { 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); + c.updateAAD(largerThanAad, 50, p.aad.length); + } + final byte[] actualPlaintext = new byte[c.getOutputSize(p.ciphertext.length)]; assertEquals(p.plaintext.length, c.doFinal(largerThanCiphertext, 5, p.ciphertext.length, actualPlaintext)); @@ -2641,6 +2978,12 @@ public final class CipherTest extends TestCase { 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)); @@ -2648,13 +2991,18 @@ public final class CipherTest extends TestCase { Arrays.toString(Arrays.copyOfRange(actualPlaintext, 1, p.plaintext.length + 1))); } - Cipher cNoPad = Cipher.getInstance( - getCipherTransformationWithNoPadding(p.transformation), provider); - cNoPad.init(Cipher.DECRYPT_MODE, key, spec); + if (!p.transformation.endsWith("NOPADDING")) { + Cipher cNoPad = Cipher.getInstance( + getCipherTransformationWithNoPadding(p.transformation), provider); + cNoPad.init(Cipher.DECRYPT_MODE, key, spec); - final byte[] actualPlaintextPadded = cNoPad.doFinal(p.ciphertext); - assertEquals(provider + ":" + cNoPad.getAlgorithm(), Arrays.toString(p.plaintextPadded), - Arrays.toString(actualPlaintextPadded)); + 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. { @@ -2664,6 +3012,7 @@ public final class CipherTest extends TestCase { SecretKey sk = kg.generateKey(); // Wrap it + c = Cipher.getInstance(p.transformation, provider); c.init(Cipher.WRAP_MODE, key, spec); byte[] cipherText = c.wrap(sk); @@ -2770,6 +3119,27 @@ public final class CipherTest extends TestCase { fail("should not be able to call updateAAD with too large length"); } catch (IllegalArgumentException expected) { } + + try { + c.updateAAD(new byte[8]); + fail("should not be able to call updateAAD on non-AEAD cipher"); + } catch (UnsupportedOperationException | IllegalStateException expected) { + } + } + + public void testCipher_updateAAD_AfterInit_WithGcm_Success() throws Exception { + Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); + c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[128 / 8], "AES")); + c.updateAAD(new byte[8]); + c.updateAAD(new byte[8]); + } + + public void testCipher_updateAAD_AfterUpdate_WithGcm_Sucess() throws Exception { + Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); + c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[128 / 8], "AES")); + c.updateAAD(new byte[8]); + c.update(new byte[8]); + c.updateAAD(new byte[8]); } public void testCipher_ShortBlock_Failure() throws Exception { @@ -2797,7 +3167,24 @@ public final class CipherTest extends TestCase { } } + public void testCipher_Update_WithZeroLengthInput_ReturnsNull() throws Exception { + SecretKey key = new SecretKeySpec(AES_128_KEY, "AES"); + Cipher c = Cipher.getInstance("AES/ECB/NoPadding"); + c.init(Cipher.ENCRYPT_MODE, key); + assertNull(c.update(new byte[0])); + assertNull(c.update(new byte[c.getBlockSize() * 2], 0, 0)); + + // Try with non-zero offset just in case the implementation mixes up offset and inputLen + assertNull(c.update(new byte[c.getBlockSize() * 2], 16, 0)); + } + private void checkCipher_ShortBlock_Failure(CipherTestParam p, String provider) throws Exception { + // Do not try to test ciphers with no padding already. + String noPaddingTransform = getCipherTransformationWithNoPadding(p.transformation); + if (p.transformation.equals(noPaddingTransform)) { + return; + } + SecretKey key = new SecretKeySpec(p.key, "AES"); Cipher c = Cipher.getInstance( getCipherTransformationWithNoPadding(p.transformation), provider); @@ -2805,12 +3192,14 @@ public final class CipherTest extends TestCase { return; } - c.init(Cipher.ENCRYPT_MODE, key); - try { - c.doFinal(new byte[] { 0x01, 0x02, 0x03 }); - fail("Should throw IllegalBlockSizeException on wrong-sized block; provider=" - + provider); - } catch (IllegalBlockSizeException expected) { + if (!p.transformation.endsWith("NOPADDING")) { + c.init(Cipher.ENCRYPT_MODE, key); + try { + c.doFinal(new byte[] { 0x01, 0x02, 0x03 }); + fail("Should throw IllegalBlockSizeException on wrong-sized block; transform=" + + p.transformation + " provider=" + provider); + } catch (IllegalBlockSizeException expected) { + } } } @@ -2985,4 +3374,145 @@ public final class CipherTest extends TestCase { } } } + + /** + * Several exceptions can be thrown by init. Check that in this case we throw the right one, + * as the error could fall under the umbrella of other exceptions. + * http://b/18987633 + */ + public void testCipher_init_DoesNotSupportKeyClass_throwsInvalidKeyException() + throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName()); + put("Cipher.FOO SupportedKeyClasses", "none"); + } + }; + + Security.addProvider(mockProvider); + try { + Cipher c = Cipher.getInstance("FOO"); + c.init(Cipher.DECRYPT_MODE, new MockKey()); + fail("Expected InvalidKeyException"); + } catch (InvalidKeyException expected) { + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + + /* + * When in decrypt mode and using padding, the buffer shouldn't necessarily have room for an + * extra block when using padding. + * http://b/19186852 + */ + public void testDecryptBufferMultipleBlockSize_mustNotThrowException() throws Exception { + String testString = "Hello, World!"; + byte[] testKey = "0123456789012345".getBytes(StandardCharsets.US_ASCII); + String testedCipher = "AES/ECB/PKCS7Padding"; + + Cipher encCipher = Cipher.getInstance(testedCipher); + encCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(testKey, "AES")); + byte[] plainBuffer = testString.getBytes(StandardCharsets.US_ASCII); + byte[] encryptedBuffer = new byte[16]; + int encryptedLength = encCipher.doFinal( + plainBuffer, 0, plainBuffer.length, encryptedBuffer); + assertEquals(16, encryptedLength); + + Cipher cipher = Cipher.getInstance(testedCipher); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(testKey, "AES")); + // Must not throw exception. + int unencryptedBytes = cipher.doFinal( + encryptedBuffer, 0, encryptedBuffer.length, encryptedBuffer); + assertEquals(testString, + new String(encryptedBuffer, 0, unencryptedBytes, StandardCharsets.US_ASCII)); + } + + /** + * When using padding in decrypt mode, ensure that empty buffers decode to empty strings + * (no padding needed for the empty buffer). + * http://b/19186852 + */ + public void testDecryptBufferZeroSize_mustDecodeToEmptyString() throws Exception { + String[] androidOpenSSLCiphers = { "AES/CBC/PKCS5PADDING", "AES/CBC/PKCS7PADDING", + "AES/ECB/PKCS5PADDING", "AES/ECB/PKCS7PADDING", "DESEDE/CBC/PKCS5PADDING", + "DESEDE/CBC/PKCS7PADDING" }; + for (String c : androidOpenSSLCiphers) { + Cipher cipher = Cipher.getInstance(c); + if (c.contains("/CBC/")) { + cipher.init(Cipher.DECRYPT_MODE, + new SecretKeySpec("0123456789012345".getBytes(StandardCharsets.US_ASCII), + (c.startsWith("AES/")) ? "AES" : "DESEDE"), + new IvParameterSpec( + ("01234567" + ((c.startsWith("AES/")) ? "89012345" : "")) + .getBytes(StandardCharsets.US_ASCII))); + } else { + cipher.init(Cipher.DECRYPT_MODE, + new SecretKeySpec("0123456789012345".getBytes(StandardCharsets.US_ASCII), + (c.startsWith("AES/")) ? "AES" : "DESEDE")); + } + + byte[] buffer = new byte[0]; + int bytesProduced = cipher.doFinal(buffer, 0, buffer.length, buffer); + assertEquals("", new String(buffer, 0, bytesProduced, StandardCharsets.US_ASCII)); + } + } + + /** + * If a provider rejects a key for "Cipher/Mode/Padding"", there might be another that + * accepts the key for "Cipher". Don't throw InvalidKeyException when trying the first one. + * http://b/22208820 + */ + public void testCipher_init_tryAllCombinationsBeforeThrowingInvalidKey() + throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Cipher.FOO/FOO/FOO", MockCipherSpi.AllKeyTypes.class.getName()); + put("Cipher.FOO/FOO/FOO SupportedKeyClasses", "none"); + } + }; + + Provider mockProvider2 = new MockProvider("MockProvider2") { + public void setup() { + put("Cipher.FOO", MockCipherSpi.AllKeyTypes.class.getName()); + } + }; + + Security.addProvider(mockProvider); + + try { + try { + // The provider installed doesn't accept the key. + Cipher c = Cipher.getInstance("FOO/FOO/FOO"); + c.init(Cipher.DECRYPT_MODE, new MockKey()); + fail("Expected InvalidKeyException"); + } catch (InvalidKeyException expected) { + } + + Security.addProvider(mockProvider2); + + try { + // The new provider accepts "FOO" with this key. Use it despite the other provider + // accepts "FOO/FOO/FOO" but doesn't accept the key. + Cipher c = Cipher.getInstance("FOO/FOO/FOO"); + c.init(Cipher.DECRYPT_MODE, new MockKey()); + assertEquals("MockProvider2", c.getProvider().getName()); + } finally { + Security.removeProvider(mockProvider2.getName()); + } + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + + /** + * Check that RSA with OAEPPadding is supported. + * http://b/22208820 + */ + public void test_RSA_OAEPPadding() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(1024, SecureRandom.getInstance("SHA1PRNG")); + Cipher cipher = Cipher.getInstance("RSA/NONE/OAEPPadding"); + cipher.init(Cipher.ENCRYPT_MODE, keyGen.generateKeyPair().getPublic()); + cipher.doFinal(new byte[] {1,2,3,4}); + } } diff --git a/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java b/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java index cabe5c9..cc29640 100644 --- a/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java +++ b/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java @@ -38,7 +38,9 @@ import java.security.spec.ECGenParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; +import java.util.ArrayList; import java.util.Comparator; +import java.util.List; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; @@ -374,8 +376,8 @@ public class ECDHKeyAgreementTest extends TestCase { if (providers == null) { return new Provider[0]; } - // Sort providers by name to guarantee non-determinism in the order in which providers are - // used in the tests. + // Sort providers by name to guarantee deterministic order in which providers are used in + // the tests. return sortByName(providers); } @@ -384,8 +386,21 @@ public class ECDHKeyAgreementTest extends TestCase { if (providers == null) { return new Provider[0]; } - // Sort providers by name to guarantee non-determinism in the order in which providers are - // used in the tests. + + // Do not test AndroidKeyStore's KeyFactory. It only handles Android Keystore-backed keys. + // It's OKish not to test AndroidKeyStore's KeyFactory here because it's tested by + // cts/tests/test/keystore. + List<Provider> filteredProvidersList = new ArrayList<Provider>(providers.length); + for (Provider provider : providers) { + if ("AndroidKeyStore".equals(provider.getName())) { + continue; + } + filteredProvidersList.add(provider); + } + providers = filteredProvidersList.toArray(new Provider[filteredProvidersList.size()]); + + // Sort providers by name to guarantee deterministic order in which providers are used in + // the tests. return sortByName(providers); } diff --git a/luni/src/test/java/libcore/javax/crypto/KeyAgreementTest.java b/luni/src/test/java/libcore/javax/crypto/KeyAgreementTest.java new file mode 100644 index 0000000..9281b43 --- /dev/null +++ b/luni/src/test/java/libcore/javax/crypto/KeyAgreementTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 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 libcore.javax.crypto; + +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.Security; + +import javax.crypto.KeyAgreement; + +import junit.framework.TestCase; + +public class KeyAgreementTest extends TestCase { + private static abstract class MockProvider extends Provider { + public MockProvider(String name) { + super(name, 1.0, "Mock provider used for testing"); + setup(); + } + + public abstract void setup(); + } + + public void testKeyAgreement_getInstance_SuppliedProviderNotRegistered_Success() + throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("KeyAgreement.FOO", MockKeyAgreementSpi.AllKeyTypes.class.getName()); + } + }; + + { + KeyAgreement c = KeyAgreement.getInstance("FOO", mockProvider); + c.init(new MockKey()); + assertEquals(mockProvider, c.getProvider()); + } + } + + public void testKeyAgreement_getInstance_DoesNotSupportKeyClass_Success() + throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("KeyAgreement.FOO", MockKeyAgreementSpi.AllKeyTypes.class.getName()); + put("KeyAgreement.FOO SupportedKeyClasses", "none"); + } + }; + + Security.addProvider(mockProvider); + try { + KeyAgreement c = KeyAgreement.getInstance("FOO", mockProvider); + c.init(new MockKey()); + assertEquals(mockProvider, c.getProvider()); + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + + /** + * Several exceptions can be thrown by init. Check that in this case we throw the right one, + * as the error could fall under the umbrella of other exceptions. + * http://b/18987633 + */ + public void testKeyAgreement_init_DoesNotSupportKeyClass_throwsInvalidKeyException() + throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("KeyAgreement.FOO", MockKeyAgreementSpi.AllKeyTypes.class.getName()); + put("KeyAgreement.FOO SupportedKeyClasses", "none"); + } + }; + + Security.addProvider(mockProvider); + try { + KeyAgreement c = KeyAgreement.getInstance("FOO"); + c.init(new MockKey()); + fail("Expected InvalidKeyException"); + } catch (InvalidKeyException expected) { + } finally { + Security.removeProvider(mockProvider.getName()); + } + } +} diff --git a/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java b/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java index 8bbd548..5763562 100644 --- a/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java +++ b/luni/src/test/java/libcore/javax/crypto/KeyGeneratorTest.java @@ -44,6 +44,15 @@ public class KeyGeneratorTest extends TestCase { if (!type.equals("KeyGenerator")) { continue; } + + // Do not test AndroidKeyStore's KeyGenerator. It cannot be initialized without + // providing AndroidKeyStore-specific algorithm parameters. + // It's OKish not to test AndroidKeyStore's KeyGenerator here because it's tested + // by cts/tests/test/keystore. + if ("AndroidKeyStore".equals(provider.getName())) { + continue; + } + String algorithm = service.getAlgorithm(); try { // KeyGenerator.getInstance(String) diff --git a/luni/src/test/java/libcore/javax/crypto/MacTest.java b/luni/src/test/java/libcore/javax/crypto/MacTest.java new file mode 100644 index 0000000..314a564 --- /dev/null +++ b/luni/src/test/java/libcore/javax/crypto/MacTest.java @@ -0,0 +1,62 @@ +/* + * 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 libcore.javax.crypto; + +import junit.framework.TestCase; + +import java.security.InvalidKeyException; +import java.security.Provider; +import java.security.Security; + +import javax.crypto.Mac; + +public class MacTest extends TestCase { + private static abstract class MockProvider extends Provider { + public MockProvider(String name) { + super(name, 1.0, "Mock provider used for testing"); + setup(); + } + + public abstract void setup(); + } + + /** + * Several exceptions can be thrown by init. Check that in this case we throw the right one, + * as the error could fall under the umbrella of other exceptions. + * http://b/18987633 + */ + public void testMac_init_DoesNotSupportKeyClass_throwsInvalidKeyException() + throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Mac.FOO", MockMacSpi.AllKeyTypes.class.getName()); + put("Mac.FOO SupportedKeyClasses", "none"); + + } + }; + + Security.addProvider(mockProvider); + try { + Mac c = Mac.getInstance("FOO"); + c.init(new MockKey()); + fail("Expected InvalidKeyException"); + } catch (InvalidKeyException expected) { + } finally { + Security.removeProvider(mockProvider.getName()); + } + } +} diff --git a/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java b/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java index 6742cf3..c1b1bd2 100644 --- a/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java +++ b/luni/src/test/java/libcore/javax/crypto/MockCipherSpi.java @@ -25,6 +25,7 @@ import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; import javax.crypto.CipherSpi; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; @@ -56,6 +57,92 @@ public class MockCipherSpi extends CipherSpi { public static class AllKeyTypes extends MockCipherSpi { } + public static class MustInitWithAlgorithmParameterSpec_RejectsAll extends MockCipherSpi { + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + throw new AssertionError("Must have AlgorithmParameterSpec"); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new InvalidAlgorithmParameterException("expected rejection"); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new AssertionError("Must have AlgorithmParameterSpec"); + } + } + + public static class MustInitWithAlgorithmParameters_RejectsAll extends MockCipherSpi { + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + throw new AssertionError("Must have AlgorithmParameterSpec"); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new AssertionError("Must have AlgorithmParameterSpec"); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new InvalidAlgorithmParameterException("expected rejection"); + } + } + + public static class MustInitWithAlgorithmParameters_ThrowsNull extends MockCipherSpi { + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + throw new NullPointerException("expected rejection"); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new NullPointerException("expected rejection"); + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new NullPointerException("expected rejection"); + } + } + + public static class MustInitForEncryptModeOrRejects extends MockCipherSpi { + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + if (opmode != Cipher.ENCRYPT_MODE) { + throw new InvalidKeyException("expected rejection"); + } + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + if (opmode != Cipher.ENCRYPT_MODE) { + throw new InvalidKeyException("expected rejection"); + } + } + + @Override + protected void engineInit(int opmode, Key key, AlgorithmParameters params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + if (opmode != Cipher.ENCRYPT_MODE) { + throw new InvalidKeyException("expected rejection"); + } + } + } + public void checkKeyType(Key key) throws InvalidKeyException { } diff --git a/luni/src/test/java/libcore/javax/crypto/MockKey.java b/luni/src/test/java/libcore/javax/crypto/MockKey.java index 248e2de..1c758f3 100644 --- a/luni/src/test/java/libcore/javax/crypto/MockKey.java +++ b/luni/src/test/java/libcore/javax/crypto/MockKey.java @@ -25,7 +25,7 @@ import java.security.Key; public class MockKey implements Key { @Override public String getAlgorithm() { - throw new UnsupportedOperationException("not implemented"); + return "MOCK"; } @Override diff --git a/luni/src/test/java/libcore/javax/crypto/MockKeyAgreementSpi.java b/luni/src/test/java/libcore/javax/crypto/MockKeyAgreementSpi.java new file mode 100644 index 0000000..574dbeb --- /dev/null +++ b/luni/src/test/java/libcore/javax/crypto/MockKeyAgreementSpi.java @@ -0,0 +1,91 @@ +/* + * Copyright 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 libcore.javax.crypto; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import javax.crypto.KeyAgreementSpi; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; + +/** + * Mock KeyAgreementSpi used by {@link KeyAgreementTest}. + */ +public class MockKeyAgreementSpi extends KeyAgreementSpi { + public static class SpecificKeyTypes extends MockKeyAgreementSpi { + @Override + public void checkKeyType(Key key) throws InvalidKeyException { + if (!(key instanceof MockKey)) { + throw new InvalidKeyException("Must be MockKey!"); + } + } + } + + public static class SpecificKeyTypes2 extends MockKeyAgreementSpi { + @Override + public void checkKeyType(Key key) throws InvalidKeyException { + System.err.println("Checking key of type " + key.getClass().getName()); + if (!(key instanceof MockKey2)) { + throw new InvalidKeyException("Must be MockKey2!"); + } + } + } + + public static class AllKeyTypes extends MockKeyAgreementSpi { + } + + public void checkKeyType(Key key) throws InvalidKeyException { + } + + @Override + protected Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException, + IllegalStateException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected byte[] engineGenerateSecret() throws IllegalStateException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected int engineGenerateSecret(byte[] sharedSecret, int offset) + throws IllegalStateException, ShortBufferException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected SecretKey engineGenerateSecret(String algorithm) throws IllegalStateException, + NoSuchAlgorithmException, InvalidKeyException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException { + checkKeyType(key); + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + checkKeyType(key); + } +} diff --git a/luni/src/test/java/libcore/javax/crypto/MockMacSpi.java b/luni/src/test/java/libcore/javax/crypto/MockMacSpi.java new file mode 100644 index 0000000..0edeba7 --- /dev/null +++ b/luni/src/test/java/libcore/javax/crypto/MockMacSpi.java @@ -0,0 +1,92 @@ +/* + * 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 libcore.javax.crypto; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.BadPaddingException; +import javax.crypto.MacSpi; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; + +/** + * Mock CipherSpi used by {@link libcore.javax.crypto.CipherTest}. + */ +public class MockMacSpi extends MacSpi { + public static class SpecificKeyTypes extends MockMacSpi { + @Override + public void checkKeyType(Key key) throws InvalidKeyException { + if (!(key instanceof MockKey)) { + throw new InvalidKeyException("Must be MockKey!"); + } + } + } + + public static class SpecificKeyTypes2 extends MockMacSpi { + @Override + public void checkKeyType(Key key) throws InvalidKeyException { + System.err.println("Checking key of type " + key.getClass().getName()); + if (!(key instanceof MockKey2)) { + throw new InvalidKeyException("Must be MockKey2!"); + } + } + } + + public static class AllKeyTypes extends MockMacSpi { + } + + public void checkKeyType(Key key) throws InvalidKeyException { + } + + @Override + protected int engineGetMacLength() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) + throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected void engineUpdate(byte input) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected void engineUpdate(byte[] input, int inputOffset, int inputLen) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected byte[] engineDoFinal() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + protected void engineReset() { + throw new UnsupportedOperationException("not implemented"); + } +} diff --git a/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java b/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java index feecebe..07ecd12 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/DefaultHostnameVerifierTest.java @@ -22,6 +22,7 @@ import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.Principal; import java.security.PublicKey; +import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -30,21 +31,32 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Set; -import javax.net.ssl.DefaultHostnameVerifier; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; import javax.security.auth.x500.X500Principal; import junit.framework.TestCase; +/** + * Tests for the platform-default {@link HostnameVerifier} as provided by + * {@link HttpsURLConnection#getDefaultHostnameVerifier()}. + */ public final class DefaultHostnameVerifierTest extends TestCase { private static final int ALT_UNKNOWN = 0; private static final int ALT_DNS_NAME = 2; private static final int ALT_IPA_NAME = 7; - private final DefaultHostnameVerifier verifier = new DefaultHostnameVerifier(); + private final HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier(); public void testVerify() { - assertTrue(verifier.verify("imap.g.com", new StubX509Certificate("cn=imap.g.com"))); - assertFalse(verifier.verify("imap.g.com", new StubX509Certificate("cn=imap2.g.com"))); - assertFalse(verifier.verify("imap.g.com", new StubX509Certificate("cn=sub.imap.g.com"))); + assertTrue(verifyWithServerCertificate( + "imap.g.com", new StubX509Certificate("cn=imap.g.com"))); + assertFalse(verifyWithServerCertificate( + "imap.g.com", new StubX509Certificate("cn=imap2.g.com"))); + assertFalse(verifyWithServerCertificate( + "imap.g.com", new StubX509Certificate("cn=sub.imap.g.com"))); } /** @@ -52,32 +64,33 @@ public final class DefaultHostnameVerifierTest extends TestCase { * be used as the identity and the CN should be ignored. */ public void testSubjectAltNameAndCn() { - assertFalse(verifier.verify("imap.g.com", new StubX509Certificate("") - .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com"))); - assertFalse(verifier.verify("imap.g.com", new StubX509Certificate("cn=imap.g.com") + assertFalse(verifyWithServerCertificate("imap.g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com"))); - assertTrue(verifier.verify("imap.g.com", new StubX509Certificate("") + assertFalse( + verifyWithServerCertificate("imap.g.com", new StubX509Certificate("cn=imap.g.com") + .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com"))); + assertTrue(verifyWithServerCertificate("imap.g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com"))); } public void testSubjectAltNameWithWildcard() { - assertTrue(verifier.verify("imap.g.com", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("imap.g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_DNS_NAME, "*.g.com"))); } public void testSubjectAltNameWithIpAddress() { - assertTrue(verifier.verify("1.2.3.4", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("1.2.3.4", new StubX509Certificate("") .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4"))); - assertFalse(verifier.verify("1.2.3.5", new StubX509Certificate("") + assertFalse(verifyWithServerCertificate("1.2.3.5", new StubX509Certificate("") .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4"))); - assertTrue(verifier.verify("192.168.100.1", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("192.168.100.1", new StubX509Certificate("") .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4") .addSubjectAlternativeName(ALT_IPA_NAME, "192.168.100.1"))); } public void testUnknownSubjectAltName() { // Has unknown subject alternative names - assertTrue(verifier.verify("imap.g.com", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("imap.g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") @@ -85,7 +98,7 @@ public final class DefaultHostnameVerifierTest extends TestCase { .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3"))); - assertTrue(verifier.verify("2.33.44.55", new StubX509Certificate("") + assertTrue(verifyWithServerCertificate("2.33.44.55", new StubX509Certificate("") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") @@ -93,7 +106,7 @@ public final class DefaultHostnameVerifierTest extends TestCase { .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3"))); - assertFalse(verifier.verify("g.com", new StubX509Certificate("") + assertFalse(verifyWithServerCertificate("g.com", new StubX509Certificate("") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") @@ -101,7 +114,7 @@ public final class DefaultHostnameVerifierTest extends TestCase { .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com") .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3"))); - assertFalse(verifier.verify("2.33.44.1", new StubX509Certificate("") + assertFalse(verifyWithServerCertificate("2.33.44.1", new StubX509Certificate("") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1") .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2") .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d") @@ -111,40 +124,129 @@ public final class DefaultHostnameVerifierTest extends TestCase { .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3"))); } - public void testWildcardMatchesWildcardSuffix() { - assertTrue(verifier.verifyHostName("b.c.d", "*.b.c.d")); - assertTrue(verifier.verifyHostName("imap.google.com", "*.imap.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com.au", "*.imap.google.com")); + public void testWildcardsRejectedForIpAddress() { + assertFalse(verifyWithServerCertificate("1.2.3.4", new StubX509Certificate("cn=*.2.3.4"))); + assertFalse(verifyWithServerCertificate("1.2.3.4", new StubX509Certificate("cn=*.2.3.4") + .addSubjectAlternativeName(ALT_IPA_NAME, "*.2.3.4") + .addSubjectAlternativeName(ALT_DNS_NAME, "*.2.3.4"))); + assertFalse(verifyWithServerCertificate( + "2001:1234::1", new StubX509Certificate("cn=*:1234::1"))); + assertFalse(verifyWithServerCertificate( + "2001:1234::1", new StubX509Certificate("cn=*:1234::1") + .addSubjectAlternativeName(ALT_IPA_NAME, "*:1234::1") + .addSubjectAlternativeName(ALT_DNS_NAME, "*:1234::1"))); + } + + public void testNullParameters() { + // Confirm that neither of the parameters used later in the test cause the verifier to blow + // up + String hostname = "www.example.com"; + StubSSLSession session = new StubSSLSession(); + session.peerCertificates = + new Certificate[] {new StubX509Certificate("cn=www.example.com")}; + verifier.verify(hostname, session); + + try { + verifier.verify(hostname, null); + fail(); + } catch (NullPointerException expected) { + } + + try { + verifier.verify(null, session); + fail(); + } catch (NullPointerException expected) { + } + } + + public void testInvalidDomainNames() { + assertFalse(verifyWithDomainNamePattern("", "")); + assertFalse(verifyWithDomainNamePattern(".test.example.com", ".test.example.com")); + assertFalse(verifyWithDomainNamePattern("ex*ample.com", "ex*ample.com")); + assertFalse(verifyWithDomainNamePattern("example.com..", "example.com.")); + assertFalse(verifyWithDomainNamePattern("example.com.", "example.com..")); + } + + public void testWildcardCharacterMustBeLeftMostLabelOnly() { + assertFalse(verifyWithDomainNamePattern("test.www.example.com", "test.*.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "www.*.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "www.example.*")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "*www.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "*w.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "w*w.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "w*.example.com")); + assertFalse(verifyWithDomainNamePattern("www.example.com", "www*.example.com")); } - public void testWildcardMatchingSubstring() { - assertTrue(verifier.verifyHostName("b.c.d", "b*.c.d")); - assertTrue(verifier.verifyHostName("imap.google.com", "ima*.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com.au", "ima*.google.com")); + public void testWildcardCannotMatchEmptyLabel() { + assertFalse(verifyWithDomainNamePattern("example.com", "*.example.com")); + assertFalse(verifyWithDomainNamePattern(".example.com", "*.example.com")); } - public void testWildcardMatchingEmptySubstring() { - assertTrue(verifier.verifyHostName("imap.google.com", "imap*.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com.au", "imap*.google.com")); + public void testWildcardCannotMatchChildDomain() { + assertFalse(verifyWithDomainNamePattern("sub.www.example.com", "*.example.com")); } - public void testWildcardMatchesChildDomain() { - assertFalse(verifier.verifyHostName("a.b.c.d", "*.c.d")); + public void testWildcardRejectedForSingleLabelPatterns() { + assertFalse(verifyWithDomainNamePattern("d", "*")); + assertFalse(verifyWithDomainNamePattern("d.", "*.")); + assertFalse(verifyWithDomainNamePattern("d", "d*")); + assertFalse(verifyWithDomainNamePattern("d.", "d*.")); + assertFalse(verifyWithDomainNamePattern("d", "*d")); + assertFalse(verifyWithDomainNamePattern("d.", "*d.")); + assertFalse(verifyWithDomainNamePattern("ddd", "d*d")); + assertFalse(verifyWithDomainNamePattern("ddd.", "d*d.")); + } + + public void testNoPrefixMatch() { + assertFalse(verifyWithDomainNamePattern("imap.google.com.au", "imap.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com.au", "*.google.com")); } public void testVerifyHostName() { - assertTrue(verifier.verifyHostName("a.b.c.d", "a.b.c.d")); - assertTrue(verifier.verifyHostName("a.b.c.d", "*.b.c.d")); - assertFalse(verifier.verifyHostName("a.b.c.d", "*.*.c.d")); - assertTrue(verifier.verifyHostName("imap.google.com", "imap.google.com")); - assertFalse(verifier.verifyHostName("imap2.google.com", "imap.google.com")); - assertTrue(verifier.verifyHostName("imap.google.com", "*.google.com")); - assertTrue(verifier.verifyHostName("imap2.google.com", "*.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com", "*.googl.com")); - assertFalse(verifier.verifyHostName("imap2.google2.com", "*.google3.com")); - assertFalse(verifier.verifyHostName("imap.google.com", "a*.google.com")); - assertFalse(verifier.verifyHostName("imap.google.com", "ix*.google.com")); - assertTrue(verifier.verifyHostName("imap.google.com", "iMap.Google.Com")); + assertTrue(verifyWithDomainNamePattern("a.b.c.d", "a.b.c.d")); + assertTrue(verifyWithDomainNamePattern("a.b.c.d", "*.b.c.d")); + assertFalse(verifyWithDomainNamePattern("a.b.c.d", "*.*.c.d")); + assertTrue(verifyWithDomainNamePattern("imap.google.com", "imap.google.com")); + assertFalse(verifyWithDomainNamePattern("imap2.google.com", "imap.google.com")); + assertTrue(verifyWithDomainNamePattern("imap.google.com", "*.google.com")); + assertTrue(verifyWithDomainNamePattern("imap2.google.com", "*.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com", "*.googl.com")); + assertFalse(verifyWithDomainNamePattern("imap2.google2.com", "*.google3.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com", "a*.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com", "ix*.google.com")); + assertTrue(verifyWithDomainNamePattern("imap.google.com", "iMap.Google.Com")); + assertTrue(verifyWithDomainNamePattern("weird", "weird")); + assertTrue(verifyWithDomainNamePattern("weird", "weird.")); + + // Wildcards rejected for domain names consisting of fewer than two labels (excluding root). + assertFalse(verifyWithDomainNamePattern("weird", "weird*")); + assertFalse(verifyWithDomainNamePattern("weird", "*weird")); + assertFalse(verifyWithDomainNamePattern("weird", "weird*.")); + assertFalse(verifyWithDomainNamePattern("weird", "weird.*")); + } + + public void testVerifyAbsoluteHostName() { + assertTrue(verifyWithDomainNamePattern("a.b.c.d.", "a.b.c.d")); + assertTrue(verifyWithDomainNamePattern("a.b.c.d.", "*.b.c.d")); + assertFalse(verifyWithDomainNamePattern("a.b.c.d.", "*.*.c.d")); + assertTrue(verifyWithDomainNamePattern("imap.google.com.", "imap.google.com")); + assertFalse(verifyWithDomainNamePattern("imap2.google.com.", "imap.google.com")); + assertTrue(verifyWithDomainNamePattern("imap.google.com.", "*.google.com")); + assertTrue(verifyWithDomainNamePattern("imap2.google.com.", "*.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com.", "*.googl.com")); + assertFalse(verifyWithDomainNamePattern("imap2.google2.com.", "*.google3.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com.", "a*.google.com")); + assertFalse(verifyWithDomainNamePattern("imap.google.com.", "ix*.google.com")); + assertTrue(verifyWithDomainNamePattern("imap.google.com.", "iMap.Google.Com")); + assertTrue(verifyWithDomainNamePattern("weird.", "weird")); + assertTrue(verifyWithDomainNamePattern("weird.", "weird.")); + + // Wildcards rejected for domain names consisting of fewer than two labels (excluding root). + assertFalse(verifyWithDomainNamePattern("weird.", "*weird")); + assertFalse(verifyWithDomainNamePattern("weird.", "weird*")); + assertFalse(verifyWithDomainNamePattern("weird.", "weird*.")); + assertFalse(verifyWithDomainNamePattern("weird.", "weird.*")); } public void testSubjectOnlyCert() throws Exception { @@ -168,8 +270,8 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "rs2oQLwOLnuifH52ey9+tJguabo+brlYYigAuWWFEzJfBzikDkIwnE/L7wlrypIk\n" + "taXDWI4=\n" + "-----END CERTIFICATE-----"); - assertTrue(verifier.verify("www.example.com", cert)); - assertFalse(verifier.verify("www2.example.com", cert)); + assertTrue(verifyWithServerCertificate("www.example.com", cert)); + assertFalse(verifyWithServerCertificate("www2.example.com", cert)); } public void testSubjectAltOnlyCert() throws Exception { @@ -192,8 +294,8 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "JPRynf9244Pn0Sr/wsnmdsTRFIFYynrc51hQ7DkwbUxpcaewkZzilru/SwZ3+pPT\n" + "9JSqm5hJ1pg5WDlPkW7c/1VA0/141N52Q8MIU+2ZpuOj\n" + "-----END CERTIFICATE-----"); - assertTrue(verifier.verify("www.example.com", cert)); - assertFalse(verifier.verify("www2.example.com", cert)); + assertTrue(verifyWithServerCertificate("www.example.com", cert)); + assertFalse(verifyWithServerCertificate("www2.example.com", cert)); } public void testSubjectWithAltNamesCert() throws Exception { @@ -219,10 +321,10 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "hrTVypLSoRXuTB2aWilu4p6aNh84xTdyqo2avtNr2MiQMZIcdamBq8LdBIAShFXI\n" + "h5G2eVGXH/Y=\n" + "-----END CERTIFICATE-----"); - assertFalse(verifier.verify("www.example.com", cert)); - assertTrue(verifier.verify("www2.example.com", cert)); - assertTrue(verifier.verify("www3.example.com", cert)); - assertFalse(verifier.verify("www4.example.com", cert)); + assertFalse(verifyWithServerCertificate("www.example.com", cert)); + assertTrue(verifyWithServerCertificate("www2.example.com", cert)); + assertTrue(verifyWithServerCertificate("www3.example.com", cert)); + assertFalse(verifyWithServerCertificate("www4.example.com", cert)); } public void testSubjectWithWildAltNamesCert() throws Exception { @@ -247,11 +349,11 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "Y3R0HZvKzNIU3pwAm69HCJoG+/9MZEIDJb0WJc5UygxDT45XE9zQMQe4dBOTaNXT\n" + "+ntgaB62kE10HzrzpqXAgoAWxWK4RzFcUpBWw9qYq9xOCewJ\n" + "-----END CERTIFICATE-----"); - assertFalse(verifier.verify("www.example.com", cert)); - assertFalse(verifier.verify("www2.example.com", cert)); - assertTrue(verifier.verify("www.example2.com", cert)); - assertTrue(verifier.verify("abc.example2.com", cert)); - assertFalse(verifier.verify("www.example3.com", cert)); + assertFalse(verifyWithServerCertificate("www.example.com", cert)); + assertFalse(verifyWithServerCertificate("www2.example.com", cert)); + assertTrue(verifyWithServerCertificate("www.example2.com", cert)); + assertTrue(verifyWithServerCertificate("abc.example2.com", cert)); + assertFalse(verifyWithServerCertificate("www.example3.com", cert)); } public void testWildAltNameOnlyCert() throws Exception { @@ -274,9 +376,9 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "va++ow5r1VxQXFJc0ZPzsDo+6TlktoDHaRQJGMqQomqHWT4i7F5UZgf6BHGfEUPU\n" + "qep+GsF3QRHSBtpObWkVDZNFvky3a1iZ2q25+hFIqQ==\n" + "-----END CERTIFICATE-----"); - assertTrue(verifier.verify("www.example.com", cert)); - assertTrue(verifier.verify("www2.example.com", cert)); - assertFalse(verifier.verify("www.example2.com", cert)); + assertTrue(verifyWithServerCertificate("www.example.com", cert)); + assertTrue(verifyWithServerCertificate("www2.example.com", cert)); + assertFalse(verifyWithServerCertificate("www.example2.com", cert)); } public void testAltIpOnlyCert() throws Exception { @@ -299,8 +401,48 @@ public final class DefaultHostnameVerifierTest extends TestCase { + "WPjHQcWfpkFzAF5wyOq0kveVfx0g5xPhOVDd+U+q7WastbXICpCoHp9FxISmZVik\n" + "sAyifp8agkYdzaSh55fFmKXlFnRsQw==\n" + "-----END CERTIFICATE-----"); - assertTrue(verifier.verify("192.168.10.1", cert)); - assertFalse(verifier.verify("192.168.10.2", cert)); + assertTrue(verifyWithServerCertificate("192.168.10.1", cert)); + assertFalse(verifyWithServerCertificate("192.168.10.2", cert)); + } + + /** + * Verifies the provided hostname against the provided domain name pattern from server + * certificate. + */ + private boolean verifyWithDomainNamePattern(String hostname, String pattern) { + StubSSLSession session = new StubSSLSession(); + + // Verify using a certificate where the pattern is in the CN + session.peerCertificates = new Certificate[] { + new StubX509Certificate("cn=\"" + pattern + "\"") + }; + boolean resultWhenPatternInCn = verifier.verify(hostname, session); + + // Verify using a certificate where the pattern is in a DNS SubjectAltName + session.peerCertificates = new Certificate[] { + new StubX509Certificate("ou=test") + .addSubjectAlternativeName(ALT_DNS_NAME, pattern) + }; + boolean resultWhenPatternInSubjectAltName = verifier.verify(hostname, session); + + // Assert that in both cases the verifier gives the same result + if (resultWhenPatternInCn != resultWhenPatternInSubjectAltName) { + fail("Different results between pattern in CN and SubjectAltName." + + " hostname : " + hostname + ", pattern: " + pattern + + ", when pattern in CN: " + resultWhenPatternInCn + + ", when pattern in SubjectAltName: " + resultWhenPatternInSubjectAltName); + } + return resultWhenPatternInCn; + } + + /** + * Verifies the provided hostname against the provided server certificate. + */ + private boolean verifyWithServerCertificate(String hostname, X509Certificate certificate) { + StubSSLSession session = new StubSSLSession(); + session.peerCertificates = + (certificate != null) ? new Certificate[] {certificate} : new Certificate[0]; + return verifier.verify(hostname, session); } X509Certificate parseCertificate(String encoded) throws Exception { @@ -308,6 +450,117 @@ public final class DefaultHostnameVerifierTest extends TestCase { return (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(in); } + private static class StubSSLSession implements SSLSession { + + public Certificate[] peerCertificates = new Certificate[0]; + + @Override + public int getApplicationBufferSize() { + throw new UnsupportedOperationException(); + } + + @Override + public String getCipherSuite() { + throw new UnsupportedOperationException(); + } + + @Override + public long getCreationTime() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] getId() { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastAccessedTime() { + throw new UnsupportedOperationException(); + } + + @Override + public Certificate[] getLocalCertificates() { + throw new UnsupportedOperationException(); + } + + @Override + public Principal getLocalPrincipal() { + throw new UnsupportedOperationException(); + } + + @Override + public int getPacketBufferSize() { + throw new UnsupportedOperationException(); + } + + @Override + public javax.security.cert.X509Certificate[] getPeerCertificateChain() + throws SSLPeerUnverifiedException { + throw new UnsupportedOperationException(); + } + + @Override + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + return peerCertificates; + } + + @Override + public String getPeerHost() { + throw new UnsupportedOperationException(); + } + + @Override + public int getPeerPort() { + throw new UnsupportedOperationException(); + } + + @Override + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + throw new UnsupportedOperationException(); + } + + @Override + public String getProtocol() { + throw new UnsupportedOperationException(); + } + + @Override + public SSLSessionContext getSessionContext() { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] getValueNames() { + throw new UnsupportedOperationException(); + } + + @Override + public void invalidate() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public void putValue(String name, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeValue(String name) { + throw new UnsupportedOperationException(); + } + } + private static class StubX509Certificate extends X509Certificate { private final X500Principal subjectX500Principal; private Collection<List<?>> subjectAlternativeNames; diff --git a/luni/src/test/java/libcore/javax/net/ssl/HttpsURLConnectionTest.java b/luni/src/test/java/libcore/javax/net/ssl/HttpsURLConnectionTest.java new file mode 100644 index 0000000..cbaea20 --- /dev/null +++ b/luni/src/test/java/libcore/javax/net/ssl/HttpsURLConnectionTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2014 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 libcore.javax.net.ssl; + +import junit.framework.TestCase; + +import java.io.IOException; +import java.net.URL; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; + +public class HttpsURLConnectionTest extends TestCase { + + /** + * HTTPS URL which cannot be resolved and is thus safe to use in tests where network traffic + * should be avoided. + */ + private static final String UNRESOLVABLE_HTTPS_URL = "https:///"; + + public void testDefaultHostnameVerifierNotNull() { + assertNotNull(HttpsURLConnection.getDefaultHostnameVerifier()); + } + + public void testDefaultHostnameVerifierUsedForNewConnectionsByDefault() throws IOException { + HostnameVerifier originalHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); + HttpsURLConnection connection = + (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(originalHostnameVerifier, connection.getHostnameVerifier()); + } finally { + connection.disconnect(); + } + + HostnameVerifier anotherVerifier = new FakeHostnameVerifier(); + try { + HttpsURLConnection.setDefaultHostnameVerifier(anotherVerifier); + connection = (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(anotherVerifier, connection.getHostnameVerifier()); + } finally { + connection.disconnect(); + } + + HttpsURLConnection.setDefaultHostnameVerifier(originalHostnameVerifier); + connection = (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(originalHostnameVerifier, connection.getHostnameVerifier()); + } finally { + connection.disconnect(); + } + } finally { + HttpsURLConnection.setDefaultHostnameVerifier(originalHostnameVerifier); + } + } + + public void testDefaultSSLSocketFactoryNotNull() { + assertNotNull(HttpsURLConnection.getDefaultSSLSocketFactory()); + } + + public void testDefaultSSLSocketFactoryUsedForNewConnectionsByDefault() throws IOException { + SSLSocketFactory originalFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); + HttpsURLConnection connection = + (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(originalFactory, connection.getSSLSocketFactory()); + } finally { + connection.disconnect(); + } + + SSLSocketFactory anotherFactory = new SSLSocketFactoryTest.FakeSSLSocketFactory(); + try { + HttpsURLConnection.setDefaultSSLSocketFactory(anotherFactory); + connection = (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(anotherFactory, connection.getSSLSocketFactory()); + } finally { + connection.disconnect(); + } + + HttpsURLConnection.setDefaultSSLSocketFactory(originalFactory); + connection = (HttpsURLConnection) new URL(UNRESOLVABLE_HTTPS_URL).openConnection(); + try { + assertSame(originalFactory, connection.getSSLSocketFactory()); + } finally { + connection.disconnect(); + } + } finally { + HttpsURLConnection.setDefaultSSLSocketFactory(originalFactory); + } + } + + private static class FakeHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } +} diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLContextTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLContextTest.java index dccadbd..533849c 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLContextTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLContextTest.java @@ -26,7 +26,6 @@ import java.security.Security; import java.security.UnrecoverableKeyException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import libcore.io.IoUtils; @@ -82,14 +81,14 @@ public class SSLContextTest extends TestCase { } public void test_SSLContext_defaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLContext(SSLContext.getDefault()); + SSLConfigurationAsserts.assertSSLContextDefaultConfiguration(SSLContext.getDefault()); for (String protocol : StandardNames.SSL_CONTEXT_PROTOCOLS) { SSLContext sslContext = SSLContext.getInstance(protocol); if (!protocol.equals(StandardNames.SSL_CONTEXT_PROTOCOLS_DEFAULT)) { sslContext.init(null, null, null); } - SSLDefaultConfigurationAsserts.assertSSLContext(sslContext); + SSLConfigurationAsserts.assertSSLContextDefaultConfiguration(sslContext); } } @@ -149,6 +148,27 @@ public class SSLContextTest extends TestCase { sslContext); } + public void test_SSLContext_init_correctProtocolVersionsEnabled() throws Exception { + for (String tlsVersion : StandardNames.SSL_CONTEXT_PROTOCOLS) { + // Don't test the "Default" instance. + if (StandardNames.SSL_CONTEXT_PROTOCOLS_DEFAULT.equals(tlsVersion)) { + continue; + } + + SSLContext context = SSLContext.getInstance(tlsVersion); + context.init(null, null, null); + + StandardNames.assertSSLContextEnabledProtocols(tlsVersion, ((SSLSocket) (context.getSocketFactory() + .createSocket())).getEnabledProtocols()); + StandardNames.assertSSLContextEnabledProtocols(tlsVersion, ((SSLServerSocket) (context + .getServerSocketFactory().createServerSocket())).getEnabledProtocols()); + StandardNames.assertSSLContextEnabledProtocols(tlsVersion, context.getDefaultSSLParameters() + .getProtocols()); + StandardNames.assertSSLContextEnabledProtocols(tlsVersion, context.createSSLEngine() + .getEnabledProtocols()); + } + } + private static void assertEnabledCipherSuites( List<String> expectedCipherSuites, SSLContext sslContext) throws Exception { assertContentsInOrder( diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java index df4585f..5e3a3d5 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java @@ -65,7 +65,7 @@ public class SSLEngineTest extends TestCase { } public void test_SSLEngine_defaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLEngine( + SSLConfigurationAsserts.assertSSLEngineDefaultConfiguration( TestSSLContext.create().clientContext.createSSLEngine()); } @@ -203,24 +203,31 @@ public class SSLEngineTest extends TestCase { : new String[] { cipherSuite }); // Check that handshake succeeds. - TestSSLEnginePair pair = TestSSLEnginePair.create(c, new TestSSLEnginePair.Hooks() { - @Override - void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - client.setEnabledCipherSuites(cipherSuiteArray); - server.setEnabledCipherSuites(cipherSuiteArray); + TestSSLEnginePair pair = null; + try { + pair = TestSSLEnginePair.create(c, new TestSSLEnginePair.Hooks() { + @Override + void beforeBeginHandshake(SSLEngine client, SSLEngine server) { + client.setEnabledCipherSuites(cipherSuiteArray); + server.setEnabledCipherSuites(cipherSuiteArray); + } + }); + assertConnected(pair); + + boolean needsRecordSplit = + ("TLS".equalsIgnoreCase(c.clientContext.getProtocol()) + || "SSLv3".equalsIgnoreCase(c.clientContext.getProtocol())) + && cipherSuite.contains("_CBC_"); + + assertSendsCorrectly("This is the client. Hello!".getBytes(), + pair.client, pair.server, needsRecordSplit); + assertSendsCorrectly("This is the server. Hi!".getBytes(), + pair.server, pair.client, needsRecordSplit); + } finally { + if (pair != null) { + pair.close(); } - }); - assertConnected(pair); - - boolean needsRecordSplit = - ("TLS".equalsIgnoreCase(c.clientContext.getProtocol()) - || "SSLv3".equalsIgnoreCase(c.clientContext.getProtocol())) - && cipherSuite.contains("_CBC_"); - - assertSendsCorrectly("This is the client. Hello!".getBytes(), - pair.client, pair.server, needsRecordSplit); - assertSendsCorrectly("This is the server. Hi!".getBytes(), - pair.server, pair.client, needsRecordSplit); + } // Check that handshake fails when the server does not possess the private key // corresponding to the server's certificate. This is achieved by using SSLContext @@ -234,17 +241,23 @@ public class SSLEngineTest extends TestCase { serverAuthenticatedUsingPublicKey = false; } if (serverAuthenticatedUsingPublicKey) { + TestSSLEnginePair p = null; try { - TestSSLEnginePair p = TestSSLEnginePair.create( + p = TestSSLEnginePair.create( cWithWrongPrivateKeys, new TestSSLEnginePair.Hooks() { - @Override + @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - client.setEnabledCipherSuites(cipherSuiteArray); - server.setEnabledCipherSuites(cipherSuiteArray); - } - }); + client.setEnabledCipherSuites(cipherSuiteArray); + server.setEnabledCipherSuites(cipherSuiteArray); + } + }); assertNotConnected(p); - } catch (IOException expected) {} + } catch (IOException expected) { + } finally { + if (p != null) { + p.close(); + } + } } } catch (Exception e) { String message = ("Problem trying to connect cipher suite " + cipherSuite); @@ -432,21 +445,28 @@ public class SSLEngineTest extends TestCase { fail(); } catch (IllegalStateException expected) { } + c.close(); - assertConnected(TestSSLEnginePair.create(null)); + TestSSLEnginePair p = TestSSLEnginePair.create(null); + assertConnected(p); + p.close(); - c.close(); } public void test_SSLEngine_beginHandshake_noKeyStore() throws Exception { TestSSLContext c = TestSSLContext.create(null, null, null, null, null, null, null, null, SSLContext.getDefault(), SSLContext.getDefault()); + SSLEngine[] p = null; try { // TODO Fix KnownFailure AlertException "NO SERVER CERTIFICATE FOUND" // ServerHandshakeImpl.selectSuite should not select a suite without a required cert - TestSSLEnginePair.connect(c, null); + p = TestSSLEnginePair.connect(c, null); fail(); } catch (SSLHandshakeException expected) { + } finally { + if (p != null) { + TestSSLEnginePair.close(p); + } } c.close(); } @@ -456,6 +476,7 @@ public class SSLEngineTest extends TestCase { SSLEngine[] engines = TestSSLEnginePair.connect(c, null); assertConnected(engines[0], engines[1]); c.close(); + TestSSLEnginePair.close(engines); } public void test_SSLEngine_getUseClientMode() throws Exception { @@ -467,33 +488,47 @@ public class SSLEngineTest extends TestCase { public void test_SSLEngine_setUseClientMode() throws Exception { boolean[] finished; + TestSSLEnginePair p = null; // client is client, server is server finished = new boolean[2]; - assertConnected(test_SSLEngine_setUseClientMode(true, false, finished)); + p = test_SSLEngine_setUseClientMode(true, false, finished); + assertConnected(p); assertTrue(finished[0]); assertTrue(finished[1]); + p.close(); // client is server, server is client finished = new boolean[2]; - assertConnected(test_SSLEngine_setUseClientMode(false, true, finished)); + p = test_SSLEngine_setUseClientMode(false, true, finished); + assertConnected(p); assertTrue(finished[0]); assertTrue(finished[1]); + p.close(); // both are client /* * Our implementation throws an SSLHandshakeException, but RI just * stalls forever */ + p = null; try { - assertNotConnected(test_SSLEngine_setUseClientMode(true, true, null)); + p = test_SSLEngine_setUseClientMode(true, true, null); + assertNotConnected(p); assertTrue(StandardNames.IS_RI); } catch (SSLHandshakeException maybeExpected) { assertFalse(StandardNames.IS_RI); + } finally { + if (p != null) { + p.close(); + } + } + p = test_SSLEngine_setUseClientMode(false, false, null); // both are server - assertNotConnected(test_SSLEngine_setUseClientMode(false, false, null)); + assertNotConnected(p); + p.close(); } public void test_SSLEngine_setUseClientMode_afterHandshake() throws Exception { @@ -510,6 +545,7 @@ public class SSLEngineTest extends TestCase { fail(); } catch (IllegalArgumentException expected) { } + pair.close(); } private TestSSLEnginePair test_SSLEngine_setUseClientMode(final boolean clientClientMode, @@ -572,6 +608,7 @@ public class SSLEngineTest extends TestCase { p.client.getSession().getLocalCertificates()); clientAuthContext.close(); c.close(); + p.close(); } /** @@ -591,6 +628,7 @@ public class SSLEngineTest extends TestCase { }); assertConnected(p); clientAuthContext.close(); + p.close(); } /** @@ -604,8 +642,9 @@ public class SSLEngineTest extends TestCase { TestSSLContext clientAuthContext = TestSSLContext.create(TestKeyStore.getClient(), TestKeyStore.getServer()); + TestSSLEnginePair p = null; try { - TestSSLEnginePair.create(clientAuthContext, + p = TestSSLEnginePair.create(clientAuthContext, new TestSSLEnginePair.Hooks() { @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { @@ -616,6 +655,9 @@ public class SSLEngineTest extends TestCase { } catch (SSLHandshakeException expected) { } finally { clientAuthContext.close(); + if (p != null) { + p.close(); + } } } @@ -624,11 +666,13 @@ public class SSLEngineTest extends TestCase { SSLEngine e = c.clientContext.createSSLEngine(); assertTrue(e.getEnableSessionCreation()); c.close(); + TestSSLEnginePair.close(new SSLEngine[] { e }); } public void test_SSLEngine_setEnableSessionCreation_server() throws Exception { + TestSSLEnginePair p = null; try { - TestSSLEnginePair p = TestSSLEnginePair.create(new TestSSLEnginePair.Hooks() { + p = TestSSLEnginePair.create(new TestSSLEnginePair.Hooks() { @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { server.setEnableSessionCreation(false); @@ -639,12 +683,17 @@ public class SSLEngineTest extends TestCase { assertNotConnected(p); } catch (SSLException maybeExpected) { assertFalse(StandardNames.IS_RI); + } finally { + if (p != null) { + p.close(); + } } } public void test_SSLEngine_setEnableSessionCreation_client() throws Exception { + TestSSLEnginePair p = null; try { - TestSSLEnginePair.create(new TestSSLEnginePair.Hooks() { + p = TestSSLEnginePair.create(new TestSSLEnginePair.Hooks() { @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { client.setEnableSessionCreation(false); @@ -652,6 +701,10 @@ public class SSLEngineTest extends TestCase { }); fail(); } catch (SSLException expected) { + } finally { + if (p != null) { + p.close(); + } } } @@ -735,5 +788,6 @@ public class SSLEngineTest extends TestCase { assertNotNull(test.server); assertNotNull(test.client); assertConnected(test); + test.close(); } } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketFactoryTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketFactoryTest.java index ea9c3f0..cda1fb8 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketFactoryTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketFactoryTest.java @@ -22,7 +22,7 @@ import junit.framework.TestCase; public class SSLServerSocketFactoryTest extends TestCase { public void testDefaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLServerSocketFactory( + SSLConfigurationAsserts.assertSSLServerSocketFactoryDefaultConfiguration( (SSLServerSocketFactory) SSLServerSocketFactory.getDefault()); } } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketTest.java index 59c44c1..d2c0f48 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLServerSocketTest.java @@ -24,7 +24,7 @@ import java.util.Arrays; public class SSLServerSocketTest extends TestCase { public void testDefaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLServerSocket( + SSLConfigurationAsserts.assertSSLServerSocketDefaultConfiguration( (SSLServerSocket) SSLServerSocketFactory.getDefault().createServerSocket()); } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSessionTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSessionTest.java index a434d94..bc2b626 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSessionTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSessionTest.java @@ -99,7 +99,7 @@ public class SSLSessionTest extends TestCase { assertTrue("s.server.getLastAccessedTime()=" + s.server.getLastAccessedTime() + " " + "s.client.getLastAccessedTime()=" + s.client.getLastAccessedTime(), Math.abs(s.server.getLastAccessedTime() - - s.client.getLastAccessedTime()) < 1 * 1000); + - s.client.getLastAccessedTime()) <= 1 * 1000); assertTrue(s.server.getLastAccessedTime() >= s.server.getCreationTime()); assertTrue(s.client.getLastAccessedTime() >= diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketFactoryTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketFactoryTest.java index acf69c0..83b690b 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketFactoryTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketFactoryTest.java @@ -210,7 +210,7 @@ public class SSLSocketFactoryTest extends TestCase { } public void test_SSLSocketFactory_defaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLSocketFactory( + SSLConfigurationAsserts.assertSSLSocketFactoryDefaultConfiguration( (SSLSocketFactory) SSLSocketFactory.getDefault()); } diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java index 4af7f5a..bf2d0f8 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java @@ -16,6 +16,8 @@ package libcore.javax.net.ssl; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -26,15 +28,18 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.net.SocketAddress; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -66,11 +71,23 @@ import libcore.io.IoUtils; import libcore.io.Streams; import libcore.java.security.StandardNames; import libcore.java.security.TestKeyStore; +import libcore.tlswire.handshake.CipherSuite; +import libcore.tlswire.handshake.ClientHello; +import libcore.tlswire.handshake.CompressionMethod; +import libcore.tlswire.handshake.HandshakeMessage; +import libcore.tlswire.handshake.HelloExtension; +import libcore.tlswire.handshake.ServerNameHelloExtension; +import libcore.tlswire.record.TlsProtocols; +import libcore.tlswire.record.TlsRecord; +import libcore.tlswire.util.TlsProtocolVersion; +import tests.util.ForEachRunner; +import tests.util.DelegatingSSLSocketFactory; +import tests.util.Pair; public class SSLSocketTest extends TestCase { public void test_SSLSocket_defaultConfiguration() throws Exception { - SSLDefaultConfigurationAsserts.assertSSLSocket( + SSLConfigurationAsserts.assertSSLSocketDefaultConfiguration( (SSLSocket) SSLSocketFactory.getDefault().createSocket()); } @@ -403,6 +420,37 @@ public class SSLSocketTest extends TestCase { c.close(); } + public void test_SSLSocket_NoEnabledCipherSuites_Failure() throws Exception { + TestSSLContext c = TestSSLContext.create(null, null, null, null, null, null, null, null, + SSLContext.getDefault(), SSLContext.getDefault()); + SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, + c.port); + client.setEnabledCipherSuites(new String[0]); + final SSLSocket server = (SSLSocket) c.serverSocket.accept(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future<Void> future = executor.submit(new Callable<Void>() { + @Override + public Void call() throws Exception { + try { + server.startHandshake(); + fail(); + } catch (SSLHandshakeException expected) { + } + return null; + } + }); + executor.shutdown(); + try { + client.startHandshake(); + fail(); + } catch (SSLHandshakeException expected) { + } + future.get(); + server.close(); + client.close(); + c.close(); + } + public void test_SSLSocket_startHandshake_noKeyStore() throws Exception { TestSSLContext c = TestSSLContext.create(null, null, null, null, null, null, null, null, SSLContext.getDefault(), SSLContext.getDefault()); @@ -1458,11 +1506,147 @@ public class SSLSocketTest extends TestCase { test.close(); } - public void test_SSLSocket_ClientHello_size() throws Exception { + public void test_SSLSocket_ClientHello_record_size() throws Exception { // This test checks the size of ClientHello of the default SSLSocket. TLS/SSL handshakes // with older/unpatched F5/BIG-IP appliances are known to stall and time out when // the fragment containing ClientHello is between 256 and 511 (inclusive) bytes long. - // + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, null, null); + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + sslSocketFactory = new DelegatingSSLSocketFactory(sslSocketFactory) { + @Override + protected void configureSocket(SSLSocket socket) { + // Enable SNI extension on the socket (this is typically enabled by default) + // to increase the size of ClientHello. + try { + Method setHostname = + socket.getClass().getMethod("setHostname", String.class); + setHostname.invoke(socket, "sslsockettest.androidcts.google.com"); + } catch (NoSuchMethodException ignored) { + } catch (Exception e) { + throw new RuntimeException("Failed to enable SNI", e); + } + + // Enable Session Tickets extension on the socket (this is typically enabled + // by default) to increase the size of ClientHello. + try { + Method setUseSessionTickets = + socket.getClass().getMethod( + "setUseSessionTickets", boolean.class); + setUseSessionTickets.invoke(socket, true); + } catch (NoSuchMethodException ignored) { + } catch (Exception e) { + throw new RuntimeException("Failed to enable Session Tickets", e); + } + } + }; + + TlsRecord firstReceivedTlsRecord = captureTlsHandshakeFirstTlsRecord(sslSocketFactory); + assertEquals("TLS record type", TlsProtocols.HANDSHAKE, firstReceivedTlsRecord.type); + HandshakeMessage handshakeMessage = HandshakeMessage.read( + new DataInputStream(new ByteArrayInputStream(firstReceivedTlsRecord.fragment))); + assertEquals("HandshakeMessage type", + HandshakeMessage.TYPE_CLIENT_HELLO, handshakeMessage.type); + int fragmentLength = firstReceivedTlsRecord.fragment.length; + if ((fragmentLength >= 256) && (fragmentLength <= 511)) { + fail("Fragment containing ClientHello is of dangerous length: " + + fragmentLength + " bytes"); + } + } + + public void test_SSLSocket_ClientHello_cipherSuites() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + String[] cipherSuites = new String[clientHello.cipherSuites.size()]; + for (int i = 0; i < clientHello.cipherSuites.size(); i++) { + CipherSuite cipherSuite = clientHello.cipherSuites.get(i); + cipherSuites[i] = cipherSuite.getAndroidName(); + } + StandardNames.assertDefaultCipherSuites(cipherSuites); + } + }, getSSLSocketFactoriesToTest()); + } + + public void test_SSLSocket_ClientHello_clientProtocolVersion() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + assertEquals(TlsProtocolVersion.TLSv1_2, clientHello.clientVersion); + } + }, getSSLSocketFactoriesToTest()); + } + + public void test_SSLSocket_ClientHello_compressionMethods() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + assertEquals(Arrays.asList(CompressionMethod.NULL), clientHello.compressionMethods); + } + }, getSSLSocketFactoriesToTest()); + } + + public void test_SSLSocket_ClientHello_SNI() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + ServerNameHelloExtension sniExtension = (ServerNameHelloExtension) + clientHello.findExtensionByType(HelloExtension.TYPE_SERVER_NAME); + assertNotNull(sniExtension); + assertEquals(Arrays.asList("localhost.localdomain"), sniExtension.hostnames); + } + }, getSSLSocketFactoriesToTest()); + } + + private List<Pair<String, SSLSocketFactory>> getSSLSocketFactoriesToTest() + throws NoSuchAlgorithmException, KeyManagementException { + List<Pair<String, SSLSocketFactory>> result = + new ArrayList<Pair<String, SSLSocketFactory>>(); + result.add(Pair.of("default", (SSLSocketFactory) SSLSocketFactory.getDefault())); + for (String sslContextProtocol : StandardNames.SSL_CONTEXT_PROTOCOLS) { + SSLContext sslContext = SSLContext.getInstance(sslContextProtocol); + if (StandardNames.SSL_CONTEXT_PROTOCOLS_DEFAULT.equals(sslContextProtocol)) { + continue; + } + sslContext.init(null, null, null); + result.add(Pair.of( + "SSLContext(\"" + sslContext.getProtocol() + "\")", + sslContext.getSocketFactory())); + } + return result; + } + + private ClientHello captureTlsHandshakeClientHello(SSLSocketFactory sslSocketFactory) + throws Exception { + TlsRecord record = captureTlsHandshakeFirstTlsRecord(sslSocketFactory); + assertEquals("TLS record type", TlsProtocols.HANDSHAKE, record.type); + ByteArrayInputStream fragmentIn = new ByteArrayInputStream(record.fragment); + HandshakeMessage handshakeMessage = HandshakeMessage.read(new DataInputStream(fragmentIn)); + assertEquals("HandshakeMessage type", + HandshakeMessage.TYPE_CLIENT_HELLO, handshakeMessage.type); + // Assert that the fragment does not contain any more messages + assertEquals(0, fragmentIn.available()); + + return (ClientHello) handshakeMessage; + } + + private TlsRecord captureTlsHandshakeFirstTlsRecord(SSLSocketFactory sslSocketFactory) + throws Exception { + byte[] firstReceivedChunk = captureTlsHandshakeFirstTransmittedChunkBytes(sslSocketFactory); + ByteArrayInputStream firstReceivedChunkIn = new ByteArrayInputStream(firstReceivedChunk); + TlsRecord record = TlsRecord.read(new DataInputStream(firstReceivedChunkIn)); + // Assert that the chunk does not contain any more data + assertEquals(0, firstReceivedChunkIn.available()); + + return record; + } + + private byte[] captureTlsHandshakeFirstTransmittedChunkBytes( + final SSLSocketFactory sslSocketFactory) throws Exception { // Since there's no straightforward way to obtain a ClientHello from SSLSocket, this test // does the following: // 1. Creates a listening server socket (a plain one rather than a TLS/SSL one). @@ -1506,61 +1690,31 @@ public class SSLSocketTest extends TestCase { executorService.submit(new Callable<Void>() { @Override public Void call() throws Exception { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, null, null); - SSLSocket client = (SSLSocket) sslContext.getSocketFactory().createSocket(); + Socket client = new Socket(); sockets[0] = client; try { - // Enable SNI extension on the socket (this is typically enabled by default) - // to increase the size of ClientHello. - try { - Method setHostname = - client.getClass().getMethod("setHostname", String.class); - setHostname.invoke(client, "sslsockettest.androidcts.google.com"); - } catch (NoSuchMethodException ignored) {} - - // Enable Session Tickets extension on the socket (this is typically enabled - // by default) to increase the size of ClientHello. - try { - Method setUseSessionTickets = - client.getClass().getMethod( - "setUseSessionTickets", boolean.class); - setUseSessionTickets.invoke(client, true); - } catch (NoSuchMethodException ignored) {} - client.connect(finalListeningSocket.getLocalSocketAddress()); // Initiate the TLS/SSL handshake which is expected to fail as soon as the // server socket receives a ClientHello. try { - client.startHandshake(); + SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket( + client, + "localhost.localdomain", + finalListeningSocket.getLocalPort(), + true); + sslSocket.startHandshake(); fail(); return null; } catch (IOException expected) {} return null; } finally { IoUtils.closeQuietly(client); - - // Cancel the reading task. If this task succeeded, then the reading task - // is done and this will have no effect. If this task failed prematurely, - // then the reading task might get unblocked (we're interrupting the thread - // it's running on), will fail early, and we'll thus save some time in this - // test. - readFirstReceivedChunkFuture.cancel(true); } } }); // Wait for the ClientHello to arrive - byte[] clientHello = readFirstReceivedChunkFuture.get(10, TimeUnit.SECONDS); - - // Check for ClientHello length that may cause handshake to fail/time out with older - // F5/BIG-IP appliances. - assertEquals("TLS record type: handshake", 22, clientHello[0]); - int fragmentLength = ((clientHello[3] & 0xff) << 8) | (clientHello[4] & 0xff); - if ((fragmentLength >= 256) && (fragmentLength <= 511)) { - fail("Fragment containing ClientHello is of dangerous length: " - + fragmentLength + " bytes"); - } + return readFirstReceivedChunkFuture.get(10, TimeUnit.SECONDS); } finally { executorService.shutdownNow(); IoUtils.closeQuietly(listeningSocket); @@ -1668,6 +1822,11 @@ public class SSLSocketTest extends TestCase { context.close(); } + private static void assertInappropriateFallbackIsCause(Throwable cause) { + assertTrue(cause.getMessage(), cause.getMessage().contains("inappropriate fallback") + || cause.getMessage().contains("INAPPROPRIATE_FALLBACK")); + } + public void test_SSLSocket_sendsTlsFallbackScsv_InappropriateFallback_Failure() throws Exception { TestSSLContext context = TestSSLContext.create(); @@ -1693,8 +1852,7 @@ public class SSLSocketTest extends TestCase { } catch (SSLHandshakeException expected) { Throwable cause = expected.getCause(); assertEquals(SSLProtocolException.class, cause.getClass()); - assertTrue(cause.getMessage(), - cause.getMessage().contains("inappropriate fallback")); + assertInappropriateFallbackIsCause(cause); } return null; } @@ -1709,8 +1867,7 @@ public class SSLSocketTest extends TestCase { } catch (SSLHandshakeException expected) { Throwable cause = expected.getCause(); assertEquals(SSLProtocolException.class, cause.getClass()); - assertTrue(cause.getMessage(), - cause.getMessage().contains("inappropriate fallback")); + assertInappropriateFallbackIsCause(cause); } return null; } diff --git a/luni/src/test/java/libcore/net/MimeUtilsTest.java b/luni/src/test/java/libcore/net/MimeUtilsTest.java index 9bfb375..ff22632 100644 --- a/luni/src/test/java/libcore/net/MimeUtilsTest.java +++ b/luni/src/test/java/libcore/net/MimeUtilsTest.java @@ -27,6 +27,12 @@ public class MimeUtilsTest extends TestCase { assertEquals("flac", MimeUtils.guessExtensionFromMimeType("application/x-flac")); } + // https://code.google.com/p/android/issues/detail?id=78909 + public void test_78909() { + assertEquals("mka", MimeUtils.guessExtensionFromMimeType("audio/x-matroska")); + assertEquals("mkv", MimeUtils.guessExtensionFromMimeType("video/x-matroska")); + } + public void test_16978217() { assertEquals("image/x-ms-bmp", MimeUtils.guessMimeTypeFromExtension("bmp")); assertEquals("image/x-icon", MimeUtils.guessMimeTypeFromExtension("ico")); diff --git a/luni/src/test/java/libcore/net/NetworkSecurityPolicyTest.java b/luni/src/test/java/libcore/net/NetworkSecurityPolicyTest.java new file mode 100644 index 0000000..d04e2ba --- /dev/null +++ b/luni/src/test/java/libcore/net/NetworkSecurityPolicyTest.java @@ -0,0 +1,315 @@ +/* + * 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 libcore.net; + +import junit.framework.TestCase; +import libcore.io.IoUtils; +import java.io.Closeable; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.ErrorManager; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.SocketHandler; + +public class NetworkSecurityPolicyTest extends TestCase { + + private boolean mCleartextTrafficPermittedOriginalState; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mCleartextTrafficPermittedOriginalState = + NetworkSecurityPolicy.isCleartextTrafficPermitted(); + } + + @Override + protected void tearDown() throws Exception { + try { + NetworkSecurityPolicy.setCleartextTrafficPermitted( + mCleartextTrafficPermittedOriginalState); + } finally { + super.tearDown(); + } + } + + public void testCleartextTrafficPolicySetterAndGetter() { + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + assertEquals(false, NetworkSecurityPolicy.isCleartextTrafficPermitted()); + + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + assertEquals(true, NetworkSecurityPolicy.isCleartextTrafficPermitted()); + + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + assertEquals(false, NetworkSecurityPolicy.isCleartextTrafficPermitted()); + + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + assertEquals(true, NetworkSecurityPolicy.isCleartextTrafficPermitted()); + } + + public void testCleartextTrafficPolicyWithHttpURLConnection() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + try (CapturingServerSocket server = new CapturingServerSocket()) { + URL url = new URL("http://localhost:" + server.getPort() + "/test.txt"); + try { + url.openConnection().getContent(); + fail(); + } catch (IOException expected) { + } + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted and + // that URLConnection.openConnection or getContent fail with an IOException. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket()) { + URL url = new URL("http://localhost:" + server.getPort() + "/test.txt"); + try { + url.openConnection().getContent(); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + public void testCleartextTrafficPolicyWithFtpURLConnection() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + byte[] serverReplyOnConnect = "220\r\n".getBytes("US-ASCII"); + try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { + URL url = new URL("ftp://localhost:" + server.getPort() + "/test.txt"); + try { + url.openConnection().getContent(); + fail(); + } catch (IOException expected) { + } + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted and + // that URLConnection.openConnection or getContent fail with an IOException. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { + URL url = new URL("ftp://localhost:" + server.getPort() + "/test.txt"); + try { + url.openConnection().getContent(); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + public void testCleartextTrafficPolicyWithJarHttpURLConnection() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + try (CapturingServerSocket server = new CapturingServerSocket()) { + URL url = new URL("jar:http://localhost:" + server.getPort() + "/test.jar!/"); + try { + ((JarURLConnection) url.openConnection()).getManifest(); + fail(); + } catch (IOException expected) { + } + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted and + // that JarURLConnection.openConnection or getManifest fail with an IOException. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket()) { + URL url = new URL("jar:http://localhost:" + server.getPort() + "/test.jar!/"); + try { + ((JarURLConnection) url.openConnection()).getManifest(); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + public void testCleartextTrafficPolicyWithJarFtpURLConnection() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + byte[] serverReplyOnConnect = "220\r\n".getBytes("US-ASCII"); + try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { + URL url = new URL("jar:ftp://localhost:" + server.getPort() + "/test.jar!/"); + try { + ((JarURLConnection) url.openConnection()).getManifest(); + fail(); + } catch (IOException expected) { + } + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted and + // that JarURLConnection.openConnection or getManifest fail with an IOException. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { + URL url = new URL("jar:ftp://localhost:" + server.getPort() + "/test.jar!/"); + try { + ((JarURLConnection) url.openConnection()).getManifest(); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + public void testCleartextTrafficPolicyWithLoggingSocketHandler() throws Exception { + // Assert that client transmits some data when cleartext traffic is permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(true); + try (CapturingServerSocket server = new CapturingServerSocket()) { + SocketHandler logger = new SocketHandler("localhost", server.getPort()); + MockErrorManager mockErrorManager = new MockErrorManager(); + logger.setErrorManager(mockErrorManager); + logger.setLevel(Level.ALL); + LogRecord record = new LogRecord(Level.INFO, "A log record"); + assertTrue(logger.isLoggable(record)); + logger.publish(record); + assertNull(mockErrorManager.getMostRecentException()); + server.assertDataTransmittedByClient(); + } + + // Assert that client does not transmit any data when cleartext traffic is not permitted. + NetworkSecurityPolicy.setCleartextTrafficPermitted(false); + try (CapturingServerSocket server = new CapturingServerSocket()) { + try { + new SocketHandler("localhost", server.getPort()); + fail(); + } catch (IOException expected) { + } + server.assertNoDataTransmittedByClient(); + } + } + + /** + * Server socket which listens on a local port and captures the first chunk of data transmitted + * by the client. + */ + private static class CapturingServerSocket implements Closeable { + private final ServerSocket mSocket; + private final int mPort; + private final Thread mListeningThread; + private final FutureTask<byte[]> mFirstChunkReceivedFuture; + + /** + * Constructs a new socket listening on a local port. + */ + public CapturingServerSocket() throws IOException { + this(null); + } + + /** + * Constructs a new socket listening on a local port, which sends the provided reply as + * soon as a client connects to it. + */ + public CapturingServerSocket(final byte[] replyOnConnect) throws IOException { + mSocket = new ServerSocket(0); + mPort = mSocket.getLocalPort(); + mFirstChunkReceivedFuture = new FutureTask<byte[]>(new Callable<byte[]>() { + @Override + public byte[] call() throws Exception { + try (Socket client = mSocket.accept()) { + // Reply (if requested) + if (replyOnConnect != null) { + client.getOutputStream().write(replyOnConnect); + client.getOutputStream().flush(); + } + + // Read request + byte[] buf = new byte[64 * 1024]; + int chunkSize = client.getInputStream().read(buf); + if (chunkSize == -1) { + // Connection closed without any data received + return new byte[0]; + } + // Received some data + return Arrays.copyOf(buf, chunkSize); + } finally { + IoUtils.closeQuietly(mSocket); + } + } + }); + mListeningThread = new Thread(mFirstChunkReceivedFuture); + mListeningThread.start(); + } + + public int getPort() { + return mPort; + } + + public Future<byte[]> getFirstReceivedChunkFuture() { + return mFirstChunkReceivedFuture; + } + + @Override + public void close() { + IoUtils.closeQuietly(mSocket); + mListeningThread.interrupt(); + } + + private void assertDataTransmittedByClient() + throws Exception { + byte[] firstChunkFromClient = getFirstReceivedChunkFuture().get(2, TimeUnit.SECONDS); + if ((firstChunkFromClient == null) || (firstChunkFromClient.length == 0)) { + fail("Client did not transmit any data to server"); + } + } + + private void assertNoDataTransmittedByClient() + throws Exception { + byte[] firstChunkFromClient; + try { + firstChunkFromClient = getFirstReceivedChunkFuture().get(2, TimeUnit.SECONDS); + } catch (TimeoutException expected) { + return; + } + if ((firstChunkFromClient != null) && (firstChunkFromClient.length > 0)) { + fail("Client transmitted " + firstChunkFromClient.length+ " bytes: " + + new String(firstChunkFromClient, "US-ASCII")); + } + } + } + + private static class MockErrorManager extends ErrorManager { + private Exception mMostRecentException; + + public Exception getMostRecentException() { + synchronized (this) { + return mMostRecentException; + } + } + + @Override + public void error(String message, Exception exception, int errorCode) { + synchronized (this) { + mMostRecentException = exception; + } + } + } +} diff --git a/luni/src/test/java/libcore/net/http/ResponseUtilsTest.java b/luni/src/test/java/libcore/net/http/ResponseUtilsTest.java new file mode 100644 index 0000000..16745ee --- /dev/null +++ b/luni/src/test/java/libcore/net/http/ResponseUtilsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 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 libcore.net.http; + +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import junit.framework.TestCase; +import static libcore.net.http.ResponseUtils.responseCharset; + +public class ResponseUtilsTest extends TestCase { + public void test_responseCharset_missing() { + assertEquals(StandardCharsets.UTF_8, responseCharset(null)); + assertEquals(StandardCharsets.UTF_8, responseCharset("text/plain")); + assertEquals(StandardCharsets.UTF_8, responseCharset("text/plain;foo=bar;baz=bal")); + assertEquals(StandardCharsets.UTF_8, responseCharset("text/plain;charset=")); + } + + public void test_responseCharset_valid() { + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain;charset=ISO-8859-1")); + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain;CHARSET=ISO-8859-1")); + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain; charset = ISO-8859-1")); + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain; foo=bar;baz=bag;charset=ISO-8859-1")); + assertEquals(StandardCharsets.ISO_8859_1, + responseCharset("text/plain;charset=ISO-8859-1;;==,==")); + } + + public void test_responseCharset_invalid() { + try { + responseCharset("text/plain;charset=unsupportedCharset"); + fail(); + } catch (UnsupportedCharsetException expected) { + } + } +} diff --git a/luni/src/test/java/libcore/util/HexEncodingTest.java b/luni/src/test/java/libcore/util/HexEncodingTest.java new file mode 100644 index 0000000..f5cfb3e --- /dev/null +++ b/luni/src/test/java/libcore/util/HexEncodingTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 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 libcore.util; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import junit.framework.TestCase; +import static libcore.util.HexEncoding.decode; +import static libcore.util.HexEncoding.encode; + +public class HexEncodingTest extends TestCase { + public void testEncode() { + final byte[] avocados = "avocados".getBytes(StandardCharsets.UTF_8); + + assertArraysEqual("61766F6361646F73".toCharArray(), encode(avocados)); + assertArraysEqual(avocados, decode(encode(avocados), false)); + // Make sure we can handle lower case hex encodings as well. + assertArraysEqual(avocados, decode("61766f6361646f73".toCharArray(), false)); + } + + public void testDecode_allow4Bit() { + assertArraysEqual(new byte[] { 6 }, decode("6".toCharArray(), true)); + assertArraysEqual(new byte[] { 6, 'v' }, decode("676".toCharArray(), true)); + } + + public void testDecode_disallow4Bit() { + try { + decode("676".toCharArray(), false); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testDecode_invalid() { + try { + decode("DEADBARD".toCharArray(), false); + fail(); + } catch (IllegalArgumentException expected) { + } + + // This demonstrates a difference in behaviour from apache commons : apache + // commons uses Character.isDigit and would successfully decode a string with + // arabic and devanagari characters. + try { + decode("६१٧٥٥F6361646F73".toCharArray(), false); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + decode("#%6361646F73".toCharArray(), false); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + private static void assertArraysEqual(char[] lhs, char[] rhs) { + assertEquals(new String(lhs), new String(rhs)); + } + + private static void assertArraysEqual(byte[] lhs, byte[] rhs) { + assertEquals(Arrays.toString(lhs), Arrays.toString(rhs)); + } +} diff --git a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java index 9875647..a90bb8e 100644 --- a/luni/src/test/java/libcore/util/ZoneInfoDBTest.java +++ b/luni/src/test/java/libcore/util/ZoneInfoDBTest.java @@ -98,6 +98,13 @@ public class ZoneInfoDBTest extends junit.framework.TestCase { public void testMakeTimeZone_notFound() throws Exception { ZoneInfoDB.TzData data = new ZoneInfoDB.TzData(TZDATA_IN_ROOT); assertNull(data.makeTimeZone("THIS_TZ_DOES_NOT_EXIST")); + assertFalse(data.hasTimeZone("THIS_TZ_DOES_NOT_EXIST")); + } + + public void testMakeTimeZone_found() throws Exception { + ZoneInfoDB.TzData data = new ZoneInfoDB.TzData(TZDATA_IN_ROOT); + assertNotNull(data.makeTimeZone("Europe/London")); + assertTrue(data.hasTimeZone("Europe/London")); } private static String makeCorruptFile() throws Exception { diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/ExemptionMechanismTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/ExemptionMechanismTest.java index 87b2913..17fb127 100644 --- a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/ExemptionMechanismTest.java +++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/ExemptionMechanismTest.java @@ -28,7 +28,6 @@ import java.security.Provider; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; -import java.util.Vector; import javax.crypto.ExemptionMechanism; import javax.crypto.ExemptionMechanismException; @@ -37,7 +36,6 @@ import javax.crypto.KeyGenerator; import javax.crypto.ShortBufferException; import org.apache.harmony.crypto.tests.support.MyExemptionMechanismSpi; -import org.apache.harmony.crypto.tests.support.MyExemptionMechanismSpi.tmpKey; import org.apache.harmony.security.tests.support.SpiEngUtils; import junit.framework.TestCase; @@ -170,45 +168,6 @@ public class ExemptionMechanismTest extends TestCase { em.genExemptionBlob(new byte[10], -5); } - static boolean flag = false; - - class Mock_ExemptionMechanism extends ExemptionMechanism { - protected Mock_ExemptionMechanism(ExemptionMechanismSpi exmechSpi, Provider provider, String mechanism) { - super(exmechSpi, provider, mechanism); - } - - @Override - protected void finalize() { - flag = true; - super.finalize(); - } - } - - // Side Effect: Causes OutOfMemoryError to test finalization - public void test_finalize () { - Mock_ExemptionMechanism mem = new Mock_ExemptionMechanism(null, null, "Name"); - assertNotNull(mem); - mem = null; - assertFalse(flag); - Vector v = new Vector(); - int capacity; - try { - while(true) { - v.add(this); - } - } catch (OutOfMemoryError e) { - capacity = v.size(); - v = null; - } - - v = new Vector(); - for (int i = 0; i < capacity/2; i++) { - v.add(this); - } - v = null; - assertTrue(flag); - } - class Mock_ExemptionMechanismSpi extends MyExemptionMechanismSpi { @Override protected byte[] engineGenExemptionBlob() diff --git a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java index ddd0695..48d945b 100644 --- a/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java +++ b/luni/src/test/java/org/apache/harmony/crypto/tests/javax/crypto/MacTest.java @@ -76,8 +76,13 @@ public class MacTest extends TestCase { static { for (int i = 0; i < validAlgorithmsMac.length; i++) { - defaultProvider = SpiEngUtils.isSupport(validAlgorithmsMac[i], - srvMac); + try { + Mac mac = Mac.getInstance(validAlgorithmsMac[i]); + mac.init(new SecretKeySpec(new byte[64], validAlgorithmsMac[i])); + defaultProvider = mac.getProvider(); + } catch (NoSuchAlgorithmException ignored) { + } catch (InvalidKeyException ignored) {} + DEFSupported = (defaultProvider != null); if (DEFSupported) { defaultAlgorithm = validAlgorithmsMac[i]; @@ -100,6 +105,12 @@ public class MacTest extends TestCase { macList.add(Mac.getInstance(defaultAlgorithm, defaultProvider)); macList.add(Mac.getInstance(defaultAlgorithm, defaultProviderName)); for (Provider p : Security.getProviders("Mac." + defaultAlgorithm)) { + // Do not test AndroidKeyStore's Mac. It cannot be initialized without providing an + // AndroidKeyStore-backed SecretKey instance. It's OKish not to test here because it's + // tested by cts/tests/test/keystore. + if (p.getName().startsWith("AndroidKeyStore")) { + continue; + } macList.add(Mac.getInstance(defaultAlgorithm, p)); } return macList.toArray(new Mac[macList.size()]); @@ -845,6 +856,13 @@ public class MacTest extends TestCase { byte[] output = null; byte[] output2 = null; for (int i = 0; i < providers.length; i++) { + // Do not test AndroidKeyStore's Mac. It cannot be initialized without providing an + // AndroidKeyStore-backed SecretKey instance. It's OKish not to test here because it's + // tested by cts/tests/test/keystore. + if (providers[i].getName().startsWith("AndroidKeyStore")) { + continue; + } + System.out.println("provider = " + providers[i].getName()); Mac mac = Mac.getInstance("HmacMD5", providers[i]); mac.init(key); @@ -884,6 +902,24 @@ public class MacTest extends TestCase { public abstract void setup(); } + public void testMac_getInstance_DoesNotSupportKeyClass_Success() throws Exception { + Provider mockProvider = new MockProvider("MockProvider") { + public void setup() { + put("Mac.FOO", MockMacSpi.AllKeyTypes.class.getName()); + put("Mac.FOO SupportedKeyClasses", "None"); + } + }; + + Security.addProvider(mockProvider); + try { + Mac s = Mac.getInstance("FOO", mockProvider); + s.init(new MockKey()); + assertEquals(mockProvider, s.getProvider()); + } finally { + Security.removeProvider(mockProvider.getName()); + } + } + public void testMac_getInstance_SuppliedProviderNotRegistered_Success() throws Exception { Provider mockProvider = new MockProvider("MockProvider") { public void setup() { diff --git a/luni/src/test/java/org/apache/harmony/security/tests/java/security/Signature2Test.java b/luni/src/test/java/org/apache/harmony/security/tests/java/security/Signature2Test.java index ad084e1..22e6795 100644 --- a/luni/src/test/java/org/apache/harmony/security/tests/java/security/Signature2Test.java +++ b/luni/src/test/java/org/apache/harmony/security/tests/java/security/Signature2Test.java @@ -478,17 +478,10 @@ public class Signature2Test extends junit.framework.TestCase { } catch (IllegalArgumentException expected) { } - if (StandardNames.IS_RI) { - try { - sig.verify(signature, signature.length, 0); - fail(); - } catch (SignatureException expected) { - } - } else { - // Calling Signature.verify a second time should not throw - // http://code.google.com/p/android/issues/detail?id=34933 - boolean verified = sig.verify(signature, signature.length, 0); - assertFalse(verified); + try { + sig.verify(signature, signature.length, 0); + fail(); + } catch (SignatureException expected) { } try { diff --git a/luni/src/test/java/tests/security/cert/CertificateTest.java b/luni/src/test/java/tests/security/cert/CertificateTest.java index d13e16b..194bfdb 100644 --- a/luni/src/test/java/tests/security/cert/CertificateTest.java +++ b/luni/src/test/java/tests/security/cert/CertificateTest.java @@ -300,17 +300,10 @@ public class MyModifiablePublicKey implements PublicKey { private Certificate cert; - private Provider wrongProvider; - - private Provider usefulProvider; - public void setUp() throws Exception { super.setUp(); TestUtils.initCertPathSSCertChain(); cert = TestUtils.rootCertificateSS; - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - wrongProvider = cf.getProvider(); - usefulProvider = Signature.getInstance("SHA1WithRSA").getProvider(); } /** @@ -326,8 +319,11 @@ public class MyModifiablePublicKey implements PublicKey { CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException { + final Signature sig = Signature.getInstance("SHA1WithRSA"); + sig.initVerify(cert.getPublicKey()); + final Provider provider = sig.getProvider(); // real test - cert.verify(cert.getPublicKey(), usefulProvider.getName()); + cert.verify(cert.getPublicKey(), provider.getName()); // Exception tests @@ -342,6 +338,9 @@ public class MyModifiablePublicKey implements PublicKey { // a new provider, test if it works, then remove it and test if the // exception is thrown. // + // CertificateFactory cf = CertificateFactory.getInstance("X.509"); + // Provider wrongProvider = cf.getProvider(); + // // Security.removeProvider(wrongProvider.getName()); // // try { diff --git a/luni/src/test/java/tests/security/interfaces/DSAPrivateKeyTest.java b/luni/src/test/java/tests/security/interfaces/DSAPrivateKeyTest.java index 6cebda5..5f4abdd 100644 --- a/luni/src/test/java/tests/security/interfaces/DSAPrivateKeyTest.java +++ b/luni/src/test/java/tests/security/interfaces/DSAPrivateKeyTest.java @@ -32,9 +32,7 @@ public class DSAPrivateKeyTest extends TestCase { */ public void test_getX() throws Exception { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA"); - keyGen.initialize(new DSAParameterSpec(Util.P, Util.Q, Util.G), - new SecureRandom(new MySecureRandomSpi(), null) { - }); + keyGen.initialize(new DSAParameterSpec(Util.P, Util.Q, Util.G), new SecureRandom()); KeyPair keyPair = keyGen.generateKeyPair(); DSAPrivateKey key = (DSAPrivateKey) keyPair.getPrivate(); assertNotNull("Invalid X value", key.getX()); diff --git a/luni/src/test/java/tests/security/interfaces/DSAPublicKeyTest.java b/luni/src/test/java/tests/security/interfaces/DSAPublicKeyTest.java index 9fe4910..09e936d 100644 --- a/luni/src/test/java/tests/security/interfaces/DSAPublicKeyTest.java +++ b/luni/src/test/java/tests/security/interfaces/DSAPublicKeyTest.java @@ -42,9 +42,7 @@ public class DSAPublicKeyTest extends TestCase { // Case 1: check with predefined p, q, g, x keyGen = KeyPairGenerator.getInstance("DSA"); - keyGen.initialize(new DSAParameterSpec(Util.P, Util.Q, Util.G), - new SecureRandom(new MySecureRandomSpi(), null) { - }); + keyGen.initialize(new DSAParameterSpec(Util.P, Util.Q, Util.G), new SecureRandom()); keys = keyGen.generateKeyPair(); priv = (DSAPrivateKey) keys.getPrivate(); publ = (DSAPublicKey) keys.getPublic(); diff --git a/luni/src/test/native/libcore_io_Memory_test.cpp b/luni/src/test/native/libcore_io_Memory_test.cpp new file mode 100644 index 0000000..2d95155 --- /dev/null +++ b/luni/src/test/native/libcore_io_Memory_test.cpp @@ -0,0 +1,134 @@ +/* + * 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. + */ + +#include "luni/src/main/native/libcore_io_Memory.cpp" + +#include <stdlib.h> + +#include <functional> + +#include <gtest/gtest.h> + +#define ALIGNMENT 8 + +template<typename T, size_t NUM_ELEMENTS> +void swap_align_test(void (*swap_func)(T*, const T*, size_t), + std::function<void (T*, T*, uint64_t)> init_func) { + uint8_t* dst = nullptr; + uint8_t* src = nullptr; + ASSERT_EQ(0, posix_memalign(reinterpret_cast<void**>(&dst), ALIGNMENT, + sizeof(T) * NUM_ELEMENTS + ALIGNMENT)); + ASSERT_EQ(0, posix_memalign(reinterpret_cast<void**>(&src), ALIGNMENT, + sizeof(T) * NUM_ELEMENTS + ALIGNMENT)); + + T src_buf[NUM_ELEMENTS]; + T dst_buf[NUM_ELEMENTS]; + for (uint64_t i = 0; i < NUM_ELEMENTS; i++) { + init_func(&src_buf[i], &dst_buf[i], i); + } + + // Vary a few alignments. + for (size_t dst_align = 0; dst_align < ALIGNMENT; dst_align++) { + T* dst_aligned = reinterpret_cast<T*>(&dst[dst_align]); + for (size_t src_align = 0; src_align < ALIGNMENT; src_align++) { + T* src_aligned = reinterpret_cast<T*>(&src[src_align]); + memset(dst_aligned, 0, sizeof(T) * NUM_ELEMENTS); + memcpy(src_aligned, src_buf, sizeof(T) * NUM_ELEMENTS); + swap_func(dst_aligned, src_aligned, NUM_ELEMENTS); + ASSERT_EQ(0, memcmp(dst_buf, dst_aligned, sizeof(T) * NUM_ELEMENTS)) + << "Failed at dst align " << dst_align << " src align " << src_align; + } + } + free(dst); + free(src); +} + +TEST(libcore, swapShorts_align_test) { + // Use an odd number to guarantee that the last 16-bit swap code + // is executed. + swap_align_test<jshort, 9> (swapShorts, [] (jshort* src, jshort* dst, uint64_t i) { + *src = ((2*i) << 8) | (2*(i+1)); + *dst = (2*i) | ((2*(i+1)) << 8); + }); +} + +TEST(libcore, swapInts_align_test) { + swap_align_test<jint, 10> (swapInts, [] (jint* src, jint* dst, uint64_t i) { + *src = ((4*i) << 24) | ((4*(i+1)) << 16) | ((4*(i+2)) << 8) | (4*(i+3)); + *dst = (4*i) | ((4*(i+1)) << 8) | ((4*(i+2)) << 16) | ((4*(i+3)) << 24); + }); +} + +TEST(libcore, swapLongs_align_test) { + swap_align_test<jlong, 10> (swapLongs, [] (jlong* src, jlong* dst, uint64_t i) { + *src = ((8*i) << 56) | ((8*(i+1)) << 48) | ((8*(i+2)) << 40) | ((8*(i+3)) << 32) | + ((8*(i+4)) << 24) | ((8*(i+5)) << 16) | ((8*(i+6)) << 8) | (8*(i+7)); + *dst = (8*i) | ((8*(i+1)) << 8) | ((8*(i+2)) << 16) | ((8*(i+3)) << 24) | + ((8*(i+4)) << 32) | ((8*(i+5)) << 40) | ((8*(i+6)) << 48) | ((8*(i+7)) << 56); + }); +} + +template<typename T> +void memory_peek_test(T (*peek_func)(JNIEnv*, jclass, jlong), T value) { + T* src = nullptr; + ASSERT_EQ(0, posix_memalign(reinterpret_cast<void**>(&src), ALIGNMENT, + sizeof(T) + ALIGNMENT)); + for (size_t i = 0; i < ALIGNMENT; i++) { + jlong src_aligned = reinterpret_cast<jlong>(src) + i; + memcpy(reinterpret_cast<void*>(src_aligned), &value, sizeof(T)); + T result = peek_func(nullptr, nullptr, src_aligned); + ASSERT_EQ(value, result); + } + free(src); +} + +TEST(libcore, Memory_peekShortNative_align_check) { + memory_peek_test<jshort>(Memory_peekShortNative, 0x0102); +} + +TEST(libcore, Memory_peekIntNative_align_check) { + memory_peek_test<jint>(Memory_peekIntNative, 0x01020304); +} + +TEST(libcore, Memory_peekLongNative_align_check) { + memory_peek_test<jlong>(Memory_peekLongNative, 0x01020405060708ULL); +} + +template<typename T> +void memory_poke_test(void (*poke_func)(JNIEnv*, jclass, jlong, T), T value) { + T* dst = nullptr; + ASSERT_EQ(0, posix_memalign(reinterpret_cast<void**>(&dst), ALIGNMENT, + sizeof(T) + ALIGNMENT)); + for(size_t i = 0; i < ALIGNMENT; i++) { + memset(dst, 0, sizeof(T) + ALIGNMENT); + jlong dst_aligned = reinterpret_cast<jlong>(dst) + i; + poke_func(nullptr, nullptr, dst_aligned, value); + ASSERT_EQ(0, memcmp(reinterpret_cast<void*>(dst_aligned), &value, sizeof(T))); + } + free(dst); +} + +TEST(libcore, Memory_pokeShortNative_align_check) { + memory_poke_test<jshort>(Memory_pokeShortNative, 0x0102); +} + +TEST(libcore, Memory_pokeIntNative_align_check) { + memory_poke_test<jint>(Memory_pokeIntNative, 0x01020304); +} + +TEST(libcore, Memory_pokeLongNative_align_check) { + memory_poke_test<jlong>(Memory_pokeLongNative, 0x0102030405060708ULL); +} diff --git a/luni/src/test/native/test_openssl_engine.cpp b/luni/src/test/native/test_openssl_engine.cpp deleted file mode 100644 index 9a0f3b3..0000000 --- a/luni/src/test/native/test_openssl_engine.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2013 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. - */ - -#include "UniquePtr.h" - -#include <stdarg.h> -#include <string.h> -#include <unistd.h> - -#include <openssl/objects.h> -#include <openssl/engine.h> -#include <openssl/evp.h> -#include <openssl/pem.h> - -#define DYNAMIC_ENGINE -#define TEST_ENGINE_ID "javacoretests" -#define TEST_ENGINE_NAME "libcore test engine" - -struct RSA_Delete { - void operator()(RSA* p) const { - RSA_free(p); - } -}; -typedef UniquePtr<RSA, RSA_Delete> Unique_RSA; - -static const char* HMAC_TAG = "-HMAC-"; -static const size_t HMAC_TAG_LEN = strlen(HMAC_TAG); - -static EVP_PKEY *test_load_key(ENGINE* e, const char *key_id, - EVP_PKEY* (*read_func)(BIO*, EVP_PKEY**, pem_password_cb*, void*)) { - void* data = static_cast<void*>(const_cast<char*>(key_id)); - - EVP_PKEY *key = NULL; - - const size_t key_len = strlen(key_id); - if (key_len > HMAC_TAG_LEN && !strncmp(key_id, HMAC_TAG, HMAC_TAG_LEN)) { - key = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, e, reinterpret_cast<const unsigned char*>(key_id), - key_len); - } else { - BIO* in = BIO_new_mem_buf(data, strlen(key_id)); - if (!in) { - return NULL; - } - key = read_func(in, NULL, 0, NULL); - BIO_free(in); - - if (key != NULL && EVP_PKEY_type(key->type) == EVP_PKEY_RSA) { - ENGINE_init(e); - - Unique_RSA rsa(EVP_PKEY_get1_RSA(key)); - rsa->engine = e; - rsa->flags |= RSA_FLAG_EXT_PKEY; - } - } - - return key; -} - -static EVP_PKEY* test_load_privkey(ENGINE* e, const char* key_id, UI_METHOD*, void*) { - return test_load_key(e, key_id, PEM_read_bio_PrivateKey); -} - -static EVP_PKEY* test_load_pubkey(ENGINE* e, const char* key_id, UI_METHOD*, void*) { - return test_load_key(e, key_id, PEM_read_bio_PUBKEY); -} - -static const int meths[] = { - EVP_PKEY_HMAC, -}; - -static int pkey_meths(ENGINE*, EVP_PKEY_METHOD** meth, const int** nids, int nid) { - if (nid == EVP_PKEY_HMAC) { - *meth = const_cast<EVP_PKEY_METHOD*>(EVP_PKEY_meth_find(nid)); - return 1; - } else if (nid != 0) { - return 0; - } - - if (nids != NULL) { - *nids = meths; - return 1; - } - - return 0; -} - -static int test_engine_setup(ENGINE* e) { - if (!ENGINE_set_id(e, TEST_ENGINE_ID) - || !ENGINE_set_name(e, TEST_ENGINE_NAME) - || !ENGINE_set_flags(e, 0) - || !ENGINE_set_RSA(e, RSA_get_default_method()) - || !ENGINE_set_load_privkey_function(e, test_load_privkey) - || !ENGINE_set_load_pubkey_function(e, test_load_pubkey) - || !ENGINE_set_pkey_meths(e, pkey_meths)) { - return 0; - } - - return 1; -} - -static int test_engine_bind_fn(ENGINE *e, const char *id) { - if (id && (strcmp(id, TEST_ENGINE_ID) != 0)) { - return 0; - } - - if (!test_engine_setup(e)) { - return 0; - } - - return 1; -} - -extern "C" { -#undef OPENSSL_EXPORT -#define OPENSSL_EXPORT extern __attribute__ ((visibility ("default"))) - -IMPLEMENT_DYNAMIC_CHECK_FN() -IMPLEMENT_DYNAMIC_BIND_FN(test_engine_bind_fn) -}; diff --git a/luni/src/test/resources/META-INF/services/java.nio.charset.spi.CharsetProvider b/luni/src/test/resources/META-INF/services/java.nio.charset.spi.CharsetProvider new file mode 100644 index 0000000..1a53312 --- /dev/null +++ b/luni/src/test/resources/META-INF/services/java.nio.charset.spi.CharsetProvider @@ -0,0 +1 @@ +libcore.java.nio.charset.SettableCharsetProvider |