Merge "Fix CertificateFactoryTest API to BC"
diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt
index c6d3dd7..77d301e 100644
--- a/expectations/knownfailures.txt
+++ b/expectations/knownfailures.txt
@@ -1492,5 +1492,14 @@
   names: [
     "org.apache.harmony.tests.java.nio.charset.CharsetTest#testForName_withProviderWithRecursiveCall"
   ]
+},
+{
+  description: "Zip64 tests take too long to execute and need more than 5GB of space to run.",
+  result: EXEC_FAILED,
+  names: [
+    "libcore.java.util.zip.ZipFileTest#testZip64Support_largeNumberOfEntries",
+    "libcore.java.util.zip.ZipFileTest#testZip64Support_totalLargerThan4G",
+    "libcore.java.util.zip.ZipFileTest#testZip64Support_hugeEntry"
+  ]
 }
 ]
diff --git a/libart/src/main/java/java/lang/reflect/Proxy.java b/libart/src/main/java/java/lang/reflect/Proxy.java
old mode 100644
new mode 100755
index 31f9cd9..e47b27b
--- a/libart/src/main/java/java/lang/reflect/Proxy.java
+++ b/libart/src/main/java/java/lang/reflect/Proxy.java
@@ -179,7 +179,7 @@
 
         Class<?> result;
         synchronized (loader.proxyCache) {
-            result = loader.proxyCache.get(interfaceSet);
+            result = loader.proxyCache.get(interfaceList);
             if (result == null) {
                 String name = baseName + nextClassNameIndex++;
                 result = generateProxy(name, interfaces, loader, methodsArray, exceptionsArray);
diff --git a/luni/src/main/java/android/system/OsConstants.java b/luni/src/main/java/android/system/OsConstants.java
index 55870ec..8389e5f 100644
--- a/luni/src/main/java/android/system/OsConstants.java
+++ b/luni/src/main/java/android/system/OsConstants.java
@@ -434,6 +434,15 @@
     public static final int STDERR_FILENO = placeholder();
     public static final int STDIN_FILENO = placeholder();
     public static final int STDOUT_FILENO = placeholder();
+    /** @hide */ public static final int ST_MANDLOCK = placeholder();
+    /** @hide */ public static final int ST_NOATIME = placeholder();
+    /** @hide */ public static final int ST_NODEV = placeholder();
+    /** @hide */ public static final int ST_NODIRATIME = placeholder();
+    /** @hide */ public static final int ST_NOEXEC = placeholder();
+    /** @hide */ public static final int ST_NOSUID = placeholder();
+    /** @hide */ public static final int ST_RDONLY = placeholder();
+    /** @hide */ public static final int ST_RELATIME = placeholder();
+    /** @hide */ public static final int ST_SYNCHRONOUS = placeholder();
     public static final int S_IFBLK = placeholder();
     public static final int S_IFCHR = placeholder();
     public static final int S_IFDIR = placeholder();
diff --git a/luni/src/main/java/java/util/zip/Zip64.java b/luni/src/main/java/java/util/zip/Zip64.java
new file mode 100644
index 0000000..9be3d1c
--- /dev/null
+++ b/luni/src/main/java/java/util/zip/Zip64.java
@@ -0,0 +1,401 @@
+/*
+ * 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 java.util.zip;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static java.util.zip.ZipOutputStream.writeIntAsUint16;
+import static java.util.zip.ZipOutputStream.writeLongAsUint32;
+import static java.util.zip.ZipOutputStream.writeLongAsUint64;
+
+/**
+ * @hide
+ */
+public class Zip64 {
+
+    /* Non instantiable */
+    private Zip64() {}
+
+    /**
+     * The maximum supported entry / archive size for standard (non zip64) entries and archives.
+     */
+    static final long MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE = 0x00000000ffffffffL;
+
+    /**
+     * The header ID of the zip64 extended info header. This value is used to identify
+     * zip64 data in the "extra" field in the file headers.
+     */
+    private static final short ZIP64_EXTENDED_INFO_HEADER_ID = 0x0001;
+
+    /**
+     * The minimum size of the zip64 extended info header. This excludes the 2 byte header ID
+     * and the 2 byte size.
+     */
+    private static final int ZIP64_EXTENDED_INFO_MIN_SIZE = 28;
+
+    /*
+     * Size (in bytes) of the zip64 end of central directory locator. This will be located
+     * immediately before the end of central directory record if a given zipfile is in the
+     * zip64 format.
+     */
+    private static final int ZIP64_LOCATOR_SIZE = 20;
+
+    /**
+     * The zip64 end of central directory locator signature (4 bytes wide).
+     */
+    private static final int ZIP64_LOCATOR_SIGNATURE = 0x07064b50;
+
+    /**
+     * The zip64 end of central directory record singature (4 bytes wide).
+     */
+    private static final int ZIP64_EOCD_RECORD_SIGNATURE = 0x06064b50;
+
+    /**
+     * The "effective" size of the zip64 eocd record. This excludes the fields that
+     * are proprietary, signature, or fields we aren't interested in. We include the
+     * following (contiguous) fields in this calculation :
+     * - disk number (4 bytes)
+     * - disk with start of central directory (4 bytes)
+     * - number of central directory entries on this disk (8 bytes)
+     * - total number of central directory entries (8 bytes)
+     * - size of the central directory (8 bytes)
+     * - offset of the start of the central directory (8 bytes)
+     */
+    private static final int ZIP64_EOCD_RECORD_EFFECTIVE_SIZE = 40;
+
+    /**
+     * Parses the zip64 end of central directory record locator. The locator
+     * must be placed immediately before the end of central directory (eocd) record
+     * starting at {@code eocdOffset}.
+     *
+     * The position of the file cursor for {@code raf} after a call to this method
+     * is undefined an callers must reposition it after each call to this method.
+     */
+    public static long parseZip64EocdRecordLocator(RandomAccessFile raf, long eocdOffset)
+            throws IOException {
+        // The spec stays curiously silent about whether a zip file with an EOCD record,
+        // a zip64 locator and a zip64 eocd record is considered "empty". In our implementation,
+        // we parse all records and read the counts from them instead of drawing any size or
+        // layout based information.
+        if (eocdOffset > ZIP64_LOCATOR_SIZE) {
+            raf.seek(eocdOffset - ZIP64_LOCATOR_SIZE);
+            if (Integer.reverseBytes(raf.readInt()) == ZIP64_LOCATOR_SIGNATURE) {
+                byte[] zip64EocdLocator = new byte[ZIP64_LOCATOR_SIZE  - 4];
+                raf.readFully(zip64EocdLocator);
+                ByteBuffer buf = ByteBuffer.wrap(zip64EocdLocator).order(ByteOrder.LITTLE_ENDIAN);
+
+                final int diskWithCentralDir = buf.getInt();
+                final long zip64EocdRecordOffset = buf.getLong();
+                final int numDisks = buf.getInt();
+
+                if (numDisks != 1 || diskWithCentralDir != 0) {
+                    throw new ZipException("Spanned archives not supported");
+                }
+
+                return zip64EocdRecordOffset;
+            }
+        }
+
+        return -1;
+    }
+
+    public static ZipFile.EocdRecord parseZip64EocdRecord(RandomAccessFile raf,
+            long eocdRecordOffset, int commentLength) throws IOException {
+        raf.seek(eocdRecordOffset);
+        final int signature = Integer.reverseBytes(raf.readInt());
+        if (signature != ZIP64_EOCD_RECORD_SIGNATURE) {
+            throw new ZipException("Invalid zip64 eocd record offset, sig="
+                    + Integer.toHexString(signature) + " offset=" + eocdRecordOffset);
+        }
+
+        // The zip64 eocd record specifies its own size as an 8 byte integral type. It is variable
+        // length because of the "zip64 extensible data sector" but that field is reserved for
+        // pkware's proprietary use. We therefore disregard it altogether and treat the end of
+        // central directory structure as fixed length.
+        //
+        // We also skip "version made by" (2 bytes) and "version needed to extract" (2 bytes)
+        // fields. We perform additional validation at the ZipEntry level, where applicable.
+        //
+        // That's a total of 12 bytes to skip
+        raf.skipBytes(12);
+
+        byte[] zip64Eocd = new byte[ZIP64_EOCD_RECORD_EFFECTIVE_SIZE];
+        raf.readFully(zip64Eocd);
+
+        ByteBuffer buf = ByteBuffer.wrap(zip64Eocd).order(ByteOrder.LITTLE_ENDIAN);
+        try {
+            int diskNumber = buf.getInt();
+            int diskWithCentralDirStart = buf.getInt();
+            long numEntries = buf.getLong();
+            long totalNumEntries = buf.getLong();
+            buf.getLong(); // Ignore the size of the central directory
+            long centralDirOffset = buf.getLong();
+
+            if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDirStart != 0) {
+                throw new ZipException("Spanned archives not supported :" +
+                        " numEntries=" + numEntries + ", totalNumEntries=" + totalNumEntries +
+                        ", diskNumber=" + diskNumber + ", diskWithCentralDirStart=" +
+                        diskWithCentralDirStart);
+            }
+
+            return new ZipFile.EocdRecord(numEntries, centralDirOffset, commentLength);
+        } catch (BufferUnderflowException bue) {
+            ZipException zipException = new ZipException("Error parsing zip64 eocd record.");
+            zipException.initCause(bue);
+            throw zipException;
+        }
+    }
+
+    /**
+     * Parse the zip64 extended info record from the extras present in {@code ze}.
+     *
+     * If {@code fromCentralDirectory} is true, we assume we're parsing a central directory
+     * record. We assume a local file header otherwise. The difference between the two is that
+     * a central directory entry is required to be complete, whereas a local file header isn't.
+     * This is due to the presence of an optional data descriptor after the file content.
+     *
+     * @return {@code} true iff. a zip64 extended info record was found.
+     */
+    public static boolean parseZip64ExtendedInfo(ZipEntry ze, boolean fromCentralDirectory)
+            throws ZipException {
+        int extendedInfoSize = -1;
+        int extendedInfoStart = -1;
+        // If this file contains a zip64 central directory locator, entries might
+        // optionally contain a zip64 extended information extra entry.
+        if (ze.extra != null && ze.extra.length > 0) {
+            // Extensible data fields are of the form header1+data1 + header2+data2 and so
+            // on, where each header consists of a 2 byte header ID followed by a 2 byte size.
+            // We need to iterate through the entire list of headers to find the header ID
+            // for the zip64 extended information extra field (0x0001).
+            final ByteBuffer buf = ByteBuffer.wrap(ze.extra).order(ByteOrder.LITTLE_ENDIAN);
+            extendedInfoSize = getZip64ExtendedInfoSize(buf);
+            if (extendedInfoSize != -1) {
+                extendedInfoStart = buf.position();
+                try {
+                    if (extendedInfoSize < ZIP64_EXTENDED_INFO_MIN_SIZE) {
+                        throw new ZipException("Invalid zip64 extended info size: " + extendedInfoSize);
+                    }
+
+                    // The size & compressed size only make sense in the central directory *or* if
+                    // we know them beforehand. If we don't know them beforehand, they're stored in
+                    // the data descriptor and should be read from there.
+                    if (fromCentralDirectory || (ze.getMethod() == ZipEntry.STORED)) {
+                        final long zip64Size = buf.getLong();
+                        if (ze.size == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) {
+                            ze.size = zip64Size;
+                        }
+
+                        final long zip64CompressedSize = buf.getLong();
+                        if (ze.compressedSize == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) {
+                            ze.compressedSize = zip64CompressedSize;
+                        }
+                    }
+
+                    // The local header offset is significant only in the central directory. It makes no
+                    // sense within the local header itself.
+                    if (fromCentralDirectory) {
+                        final long zip64LocalHeaderRelOffset = buf.getLong();
+                        if (ze.localHeaderRelOffset == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) {
+                            ze.localHeaderRelOffset = zip64LocalHeaderRelOffset;
+                        }
+                    }
+                } catch (BufferUnderflowException bue) {
+                    ZipException zipException = new ZipException("Error parsing extendend info ");
+                    zipException.initCause(bue);
+                    throw zipException;
+                }
+            }
+        }
+
+        // This entry doesn't contain a zip64 extended information data entry header.
+        // We have to check that the compressedSize / size / localHeaderRelOffset values
+        // are valid and don't require the presence of the extended header.
+        if (extendedInfoSize == -1) {
+            if (ze.compressedSize == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE ||
+                    ze.size == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE ||
+                    ze.localHeaderRelOffset == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) {
+                throw new ZipException("File contains no zip64 extended information: "
+                        + "name=" + ze.name + "compressedSize=" + ze.compressedSize + ", size="
+                        + ze.size + ", localHeader=" + ze.localHeaderRelOffset);
+            }
+
+            return false;
+        } else {
+            // If we're parsed the zip64 extended info header, we remove it from the extras
+            // so that applications that set their own extras will see the data they set.
+
+            // This is an unfortunate workaround needed due to a gap in the spec. The spec demands
+            // that extras are present in the "extensible" format, which means that each extra field
+            // must be prefixed with a header ID and a length. However, earlier versions of the spec
+            // made no mention of this, nor did any existing API enforce it. This means users could
+            // set "free form" extras without caring very much whether the implementation wanted to
+            // extend or add to them.
+
+            // The start of the extended info header.
+            final int extendedInfoHeaderStart = extendedInfoStart - 4;
+            // The total size of the extended info, including the header.
+            final int extendedInfoTotalSize = extendedInfoSize + 4;
+
+            final int extrasLen = ze.extra.length - extendedInfoTotalSize;
+            byte[] extrasWithoutZip64 = new byte[extrasLen];
+
+            System.arraycopy(ze.extra, 0, extrasWithoutZip64, 0, extendedInfoHeaderStart);
+            System.arraycopy(ze.extra, extendedInfoHeaderStart + extendedInfoTotalSize,
+                    extrasWithoutZip64, extendedInfoHeaderStart, (extrasLen - extendedInfoHeaderStart));
+
+            ze.extra = extrasWithoutZip64;
+            return true;
+        }
+    }
+
+    /**
+     * Appends a zip64 extended info record to the extras contained in {@code ze}. If {@code ze}
+     * contains no extras, a new extras array is created.
+     */
+    public static void insertZip64ExtendedInfoToExtras(ZipEntry ze) throws ZipException {
+        final byte[] output;
+        // We add 4 to ZIP64_EXTENDED_INFO_MIN_SIZE to account for the 2 byte header and length.
+        final int extendedInfoSize = ZIP64_EXTENDED_INFO_MIN_SIZE + 4;
+        if (ze.extra == null) {
+            output = new byte[extendedInfoSize];
+        } else {
+            // If the existing extras are already too big, we have no choice but to throw
+            // an error.
+            if (ze.extra.length + extendedInfoSize > 65535) {
+                throw new ZipException("No space in extras for zip64 extended entry info");
+            }
+
+            // We copy existing extras over and put the zip64 extended info at the beginning. This
+            // is to avoid breakages in the presence of "old style" extras which don't contain
+            // headers and lengths. The spec is again silent about these inconsistencies.
+            //
+            // This means that people that for ZipOutputStream users, the value ZipEntry.getExtra
+            // after an entry is written will be different from before. This shouldn't be an issue
+            // in practice.
+            output = new byte[ze.extra.length + ZIP64_EXTENDED_INFO_MIN_SIZE + 4];
+            System.arraycopy(ze.extra, 0, output,  ZIP64_EXTENDED_INFO_MIN_SIZE + 4, ze.extra.length);
+        }
+
+        ByteBuffer bb = ByteBuffer.wrap(output).order(ByteOrder.LITTLE_ENDIAN);
+        bb.putShort(ZIP64_EXTENDED_INFO_HEADER_ID);
+        bb.putShort((short) ZIP64_EXTENDED_INFO_MIN_SIZE);
+
+        if (ze.getMethod() == ZipEntry.STORED) {
+            bb.putLong(ze.size);
+            bb.putLong(ze.compressedSize);
+        } else {
+            // Store these fields in the data descriptor instead.
+            bb.putLong(0); // size.
+            bb.putLong(0); // compressed size.
+        }
+
+        // The offset is only relevant in the central directory entry, but we write it out here
+        // anyway, since we know what it is.
+        bb.putLong(ze.localHeaderRelOffset);
+        bb.putInt(0);  //  disk number
+
+        ze.extra = output;
+    }
+
+    /**
+     * Returns the size of the extended info record if {@code extras} contains a zip64 extended info
+     * record, {@code -1} otherwise. The buffer will be positioned at the start of the extended info
+     * record.
+     */
+    private static int getZip64ExtendedInfoSize(ByteBuffer extras) {
+        try {
+            while (extras.hasRemaining()) {
+                final int headerId = extras.getShort() & 0xffff;
+                final int length = extras.getShort() & 0xffff;
+                if (headerId == ZIP64_EXTENDED_INFO_HEADER_ID) {
+                    if (extras.remaining() >= length) {
+                        return length;
+                    } else {
+                        return -1;
+                    }
+                } else {
+                    extras.position(extras.position() + length);
+                }
+            }
+
+            return -1;
+        } catch (BufferUnderflowException bue) {
+            // We'll underflow if we have an incomplete header in our extras.
+            return -1;
+        } catch (IllegalArgumentException iae) {
+            // ByteBuffer.position() will throw if we have a truncated extra or
+            // an invalid length in the header.
+            return -1;
+        }
+    }
+
+    /**
+     * Copy the size, compressed size and local header offset fields from {@code ze} to
+     * inside {@code ze}'s extended info record. This is additional step is necessary when
+     * we could calculate the correct sizes only after writing out the entry. In this case,
+     * the local file header would not contain real sizes, and they would be present in the
+     * data descriptor and the central directory only.
+     */
+    public static void refreshZip64ExtendedInfo(ZipEntry ze) {
+        if (ze.extra == null || ze.extra.length < ZIP64_EXTENDED_INFO_MIN_SIZE) {
+            throw new IllegalStateException("Zip64 entry has no available extras: " + ze);
+        }
+
+
+        ByteBuffer buf = ByteBuffer.wrap(ze.extra).order(ByteOrder.LITTLE_ENDIAN);
+        if (getZip64ExtendedInfoSize(buf) == -1) {
+            throw new IllegalStateException(
+                    "Zip64 entry extras has no zip64 extended info record: " + ze);
+        }
+
+        buf.putLong(ze.size);
+        buf.putLong(ze.compressedSize);
+        buf.putLong(ze.localHeaderRelOffset);
+        buf.putInt(0); // disk number.
+    }
+
+    public static void writeZip64EocdRecordAndLocator(ByteArrayOutputStream baos,
+            long numEntries, long offset, long cDirSize) throws IOException {
+        // Step 1: Write out the zip64 EOCD record.
+        writeLongAsUint32(baos, ZIP64_EOCD_RECORD_SIGNATURE);
+        // The size of the zip64 eocd record. This is the effective size + the
+        // size of the "version made by" (2 bytes) and the "version needed to extract" (2 bytes)
+        // fields.
+        writeLongAsUint64(baos, ZIP64_EOCD_RECORD_EFFECTIVE_SIZE + 4);
+        // TODO: What values should we put here ? The pre-zip64 values we've chosen don't
+        // seem to make much sense either.
+        writeIntAsUint16(baos, 20);
+        writeIntAsUint16(baos, 20);
+        writeLongAsUint32(baos, 0L); // number of disk
+        writeLongAsUint32(baos, 0L); // number of disk with start of central dir.
+        writeLongAsUint64(baos, numEntries); // number of entries in this disk.
+        writeLongAsUint64(baos, numEntries); // number of entries in total.
+        writeLongAsUint64(baos, cDirSize); // size of the central directory.
+        writeLongAsUint64(baos, offset); // offset of the central directory wrt. this file.
+
+        // Step 2: Write out the zip64 EOCD record locator.
+        writeLongAsUint32(baos, ZIP64_LOCATOR_SIGNATURE);
+        writeLongAsUint32(baos, 0); // number of disk with start of central dir.
+        writeLongAsUint64(baos, offset + cDirSize); // offset of the eocd record wrt. this file.
+        writeLongAsUint32(baos, 1); // total number of disks.
+    }
+}
diff --git a/luni/src/main/java/java/util/zip/ZipEntry.java b/luni/src/main/java/java/util/zip/ZipEntry.java
index 771af83..26f6863 100644
--- a/luni/src/main/java/java/util/zip/ZipEntry.java
+++ b/luni/src/main/java/java/util/zip/ZipEntry.java
@@ -268,17 +268,15 @@
     /**
      * Sets the uncompressed size of this {@code ZipEntry}.
      *
-     * @param value
-     *            the uncompressed size for this entry.
-     * @throws IllegalArgumentException
-     *             if {@code value} < 0 or {@code value} > 0xFFFFFFFFL.
+     * @param value the uncompressed size for this entry.
+     * @throws IllegalArgumentException if {@code value < 0}.
      */
     public void setSize(long value) {
-        if (value >= 0 && value <= 0xFFFFFFFFL) {
-            size = value;
-        } else {
+        if (value < 0) {
             throw new IllegalArgumentException("Bad size: " + value);
         }
+
+        size = value;
     }
 
     /**
@@ -380,7 +378,7 @@
      * On exit, "in" will be positioned at the start of the next entry
      * in the Central Directory.
      */
-    ZipEntry(byte[] cdeHdrBuf, InputStream cdStream, Charset defaultCharset) throws IOException {
+    ZipEntry(byte[] cdeHdrBuf, InputStream cdStream, Charset defaultCharset, boolean isZip64) throws IOException {
         Streams.readFully(cdStream, cdeHdrBuf, 0, cdeHdrBuf.length);
 
         BufferIterator it = HeapBufferIterator.iterator(cdeHdrBuf, 0, cdeHdrBuf.length,
@@ -439,6 +437,10 @@
             Streams.readFully(cdStream, commentBytes, 0, commentByteCount);
             comment = new String(commentBytes, 0, commentBytes.length, charset);
         }
+
+        if (isZip64) {
+            Zip64.parseZip64ExtendedInfo(this, true /* from central directory */);
+        }
     }
 
     private static boolean containsNulByte(byte[] bytes) {
diff --git a/luni/src/main/java/java/util/zip/ZipFile.java b/luni/src/main/java/java/util/zip/ZipFile.java
index b44156e..307e7fe 100644
--- a/luni/src/main/java/java/util/zip/ZipFile.java
+++ b/luni/src/main/java/java/util/zip/ZipFile.java
@@ -107,6 +107,18 @@
 
     private final CloseGuard guard = CloseGuard.get();
 
+    static class EocdRecord {
+        final long numEntries;
+        final long centralDirOffset;
+        final int commentLength;
+
+        EocdRecord(long numEntries, long centralDirOffset, int commentLength) {
+            this.numEntries = numEntries;
+            this.centralDirOffset = centralDirOffset;
+            this.commentLength = commentLength;
+        }
+    }
+
     /**
      * Constructs a new {@code ZipFile} allowing read access to the contents of the given file.
      *
@@ -390,9 +402,11 @@
             stopOffset = 0;
         }
 
+        long eocdOffset;
         while (true) {
             raf.seek(scanOffset);
             if (Integer.reverseBytes(raf.readInt()) == ENDSIG) {
+                eocdOffset = scanOffset;
                 break;
             }
 
@@ -402,41 +416,35 @@
             }
         }
 
-        // Read the End Of Central Directory. ENDHDR includes the signature bytes,
-        // which we've already read.
-        byte[] eocd = new byte[ENDHDR - 4];
-        raf.readFully(eocd);
+        final long zip64EocdRecordOffset = Zip64.parseZip64EocdRecordLocator(raf, eocdOffset);
 
-        // Pull out the information we need.
-        BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN);
-        int diskNumber = it.readShort() & 0xffff;
-        int diskWithCentralDir = it.readShort() & 0xffff;
-        int numEntries = it.readShort() & 0xffff;
-        int totalNumEntries = it.readShort() & 0xffff;
-        it.skip(4); // Ignore centralDirSize.
-        long centralDirOffset = ((long) it.readInt()) & 0xffffffffL;
-        int commentLength = it.readShort() & 0xffff;
-
-        if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
-            throw new ZipException("Spanned archives not supported");
-        }
-
-        if (commentLength > 0) {
-            byte[] commentBytes = new byte[commentLength];
+        // Seek back past the eocd signature so that we can continue with our search.
+        // Note that we add 4 bytes to the offset to skip past the signature.
+        EocdRecord record = parseEocdRecord(raf, eocdOffset + 4, (zip64EocdRecordOffset != -1) /* isZip64 */);
+        // Read the comment now to avoid an additional seek. We also know the commentLength
+        // won't change because that information isn't present in the zip64 eocd record.
+        if (record.commentLength > 0) {
+            byte[] commentBytes = new byte[record.commentLength];
             raf.readFully(commentBytes);
             comment = new String(commentBytes, 0, commentBytes.length, StandardCharsets.UTF_8);
         }
 
+        // We have a zip64 eocd record : use that for getting the information we need.
+        if (zip64EocdRecordOffset != -1) {
+            record = Zip64.parseZip64EocdRecord(raf, zip64EocdRecordOffset, record.commentLength);
+        }
+
         // Seek to the first CDE and read all entries.
         // We have to do this now (from the constructor) rather than lazily because the
         // public API doesn't allow us to throw IOException except from the constructor
         // or from getInputStream.
-        RAFStream rafStream = new RAFStream(raf, centralDirOffset);
+        RAFStream rafStream = new RAFStream(raf, record.centralDirOffset);
         BufferedInputStream bufferedStream = new BufferedInputStream(rafStream, 4096);
         byte[] hdrBuf = new byte[CENHDR]; // Reuse the same buffer for each entry.
-        for (int i = 0; i < numEntries; ++i) {
-            ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream, StandardCharsets.UTF_8);
-            if (newEntry.localHeaderRelOffset >= centralDirOffset) {
+        for (long i = 0; i < record.numEntries; ++i) {
+            ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream, StandardCharsets.UTF_8,
+                    (zip64EocdRecordOffset != -1) /* isZip64 */);
+            if (newEntry.localHeaderRelOffset >= record.centralDirOffset) {
                 throw new ZipException("Local file header offset is after central directory");
             }
             String entryName = newEntry.getName();
@@ -446,6 +454,45 @@
         }
     }
 
+    private static EocdRecord parseEocdRecord(RandomAccessFile raf, long offset, boolean isZip64) throws IOException {
+        raf.seek(offset);
+
+        // Read the End Of Central Directory. ENDHDR includes the signature bytes,
+        // which we've already read.
+        byte[] eocd = new byte[ENDHDR - 4];
+        raf.readFully(eocd);
+
+        BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN);
+        final long numEntries;
+        final long centralDirOffset;
+        if (isZip64) {
+            numEntries = -1;
+            centralDirOffset = -1;
+
+            // If we have a zip64 end of central directory record, we skip through the regular
+            // end of central directory record and use the information from the zip64 eocd record.
+            // We're still forced to read the comment length (below) since it isn't present in the
+            // zip64 eocd record.
+            it.skip(16);
+        } else {
+            // If we don't have a zip64 eocd record, we read values from the "regular"
+            // eocd record.
+            int diskNumber = it.readShort() & 0xffff;
+            int diskWithCentralDir = it.readShort() & 0xffff;
+            numEntries = it.readShort() & 0xffff;
+            int totalNumEntries = it.readShort() & 0xffff;
+            it.skip(4); // Ignore centralDirSize.
+
+            centralDirOffset = ((long) it.readInt()) & 0xffffffffL;
+            if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
+                throw new ZipException("Spanned archives not supported");
+            }
+        }
+
+        final int commentLength = it.readShort() & 0xffff;
+        return new EocdRecord(numEntries, centralDirOffset, commentLength);
+    }
+
     static void throwZipException(String msg, int magic) throws ZipException {
         final String hexString = IntegralToString.intToHexString(magic, true, 8);
         throw new ZipException(msg + " signature not found; was " + hexString);
diff --git a/luni/src/main/java/java/util/zip/ZipInputStream.java b/luni/src/main/java/java/util/zip/ZipInputStream.java
index 4c0034e..f3ca74e 100644
--- a/luni/src/main/java/java/util/zip/ZipInputStream.java
+++ b/luni/src/main/java/java/util/zip/ZipInputStream.java
@@ -81,7 +81,9 @@
 
     private ZipEntry currentEntry;
 
-    private final byte[] hdrBuf = new byte[LOCHDR - LOCVER];
+    private boolean currentEntryIsZip64;
+
+    private final byte[] hdrBuf = new byte[LOCHDR - LOCVER + 8];
 
     private final CRC32 crc = new CRC32();
 
@@ -159,7 +161,7 @@
         }
 
         try {
-            readAndVerifyDataDescriptor(inB, out);
+            readAndVerifyDataDescriptor(inB, out, currentEntryIsZip64);
         } catch (Exception e) {
             if (failure == null) { // otherwise we're already going to throw
                 failure = e;
@@ -183,16 +185,31 @@
         }
     }
 
-    private void readAndVerifyDataDescriptor(int inB, int out) throws IOException {
+    private void readAndVerifyDataDescriptor(long inB, long out, boolean isZip64) throws IOException {
         if (hasDD) {
-            Streams.readFully(in, hdrBuf, 0, EXTHDR);
+            if (isZip64) {
+                // 8 additional bytes since the compressed / uncompressed size fields
+                // in the extended header are 8 bytes each, instead of 4 bytes each.
+                Streams.readFully(in, hdrBuf, 0, EXTHDR + 8);
+            } else {
+                Streams.readFully(in, hdrBuf, 0, EXTHDR);
+            }
+
             int sig = Memory.peekInt(hdrBuf, 0, ByteOrder.LITTLE_ENDIAN);
             if (sig != (int) EXTSIG) {
                 throw new ZipException(String.format("unknown format (EXTSIG=%x)", sig));
             }
             currentEntry.crc = ((long) Memory.peekInt(hdrBuf, EXTCRC, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL;
-            currentEntry.compressedSize = ((long) Memory.peekInt(hdrBuf, EXTSIZ, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL;
-            currentEntry.size = ((long) Memory.peekInt(hdrBuf, EXTLEN, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL;
+
+            if (isZip64) {
+                currentEntry.compressedSize = Memory.peekLong(hdrBuf, EXTSIZ, ByteOrder.LITTLE_ENDIAN);
+                // Note that we apply an adjustment of 4 bytes to the offset of EXTLEN to account
+                // for the 8 byte size for zip64.
+                currentEntry.size = Memory.peekLong(hdrBuf, EXTLEN + 4, ByteOrder.LITTLE_ENDIAN);
+            } else {
+                currentEntry.compressedSize = ((long) Memory.peekInt(hdrBuf, EXTSIZ, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL;
+                currentEntry.size = ((long) Memory.peekInt(hdrBuf, EXTLEN, ByteOrder.LITTLE_ENDIAN)) & 0xffffffffL;
+            }
         }
         if (currentEntry.crc != crc.getValue()) {
             throw new ZipException("CRC mismatch");
@@ -266,7 +283,11 @@
             byte[] extraData = new byte[extraLength];
             Streams.readFully(in, extraData, 0, extraLength);
             currentEntry.setExtra(extraData);
+            currentEntryIsZip64 = Zip64.parseZip64ExtendedInfo(currentEntry, false /* from central directory */);
+        } else {
+            currentEntryIsZip64 = false;
         }
+
         return currentEntry;
     }
 
diff --git a/luni/src/main/java/java/util/zip/ZipOutputStream.java b/luni/src/main/java/java/util/zip/ZipOutputStream.java
index 8278355..4995f5b 100644
--- a/luni/src/main/java/java/util/zip/ZipOutputStream.java
+++ b/luni/src/main/java/java/util/zip/ZipOutputStream.java
@@ -23,6 +23,8 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.HashSet;
+
+import libcore.util.CountingOutputStream;
 import libcore.util.EmptyArray;
 
 /**
@@ -85,7 +87,7 @@
 
     private final CRC32 crc = new CRC32();
 
-    private int offset = 0, curOffset = 0;
+    private long offset = 0;
 
     /** The charset-encoded name for the current entry. */
     private byte[] nameBytes;
@@ -93,6 +95,31 @@
     /** The charset-encoded comment for the current entry. */
     private byte[] entryCommentBytes;
 
+    private static final byte[] ZIP64_PLACEHOLDER_BYTES =
+            new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+
+    /**
+     * Whether this zip file needs a Zip64 EOCD record / zip64 EOCD record locator. This
+     * will be true if we wrote an entry whose size or compressed size was too large for
+     * the standard zip format or if we exceeded the maximum number of entries allowed
+     * in the standard format.
+     */
+    private boolean archiveNeedsZip64EocdRecord;
+
+    /**
+     * Whether the current entry being processed needs a zip64 extended info record. This
+     * will be true if the entry is too large for the standard zip format or if the offset
+     * to the start of the current entry header is greater than 0xFFFFFFFF.
+     */
+    private boolean currentEntryNeedsZip64;
+
+    /**
+     * Whether we force all entries in this archive to have a zip64 extended info record.
+     * This of course implies that the {@code currentEntryNeedsZip64} and
+     * {@code archiveNeedsZip64EocdRecord} are always {@code true}.
+     */
+    private final boolean forceZip64;
+
     /**
      * Constructs a new {@code ZipOutputStream} that writes a zip file to the given
      * {@code OutputStream}.
@@ -100,7 +127,15 @@
      * <p>UTF-8 will be used to encode the file comment, entry names and comments.
      */
     public ZipOutputStream(OutputStream os) {
-        super(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
+        this(os, false /* forceZip64 */);
+    }
+
+    /**
+     * @hide for testing only.
+     */
+    public ZipOutputStream(OutputStream os, boolean forceZip64) {
+        super(new CountingOutputStream(os), new Deflater(Deflater.DEFAULT_COMPRESSION, true));
+        this.forceZip64 = forceZip64;
     }
 
     /**
@@ -146,15 +181,30 @@
                 throw new ZipException("Size mismatch");
             }
         }
-        curOffset = LOCHDR;
+
+        long curOffset = LOCHDR;
 
         // Write the DataDescriptor
         if (currentEntry.getMethod() != STORED) {
             curOffset += EXTHDR;
-            writeLong(out, EXTSIG);
-            writeLong(out, currentEntry.crc = crc.getValue());
-            writeLong(out, currentEntry.compressedSize = def.getTotalOut());
-            writeLong(out, currentEntry.size = def.getTotalIn());
+
+            // Data descriptor signature and CRC are 4 bytes each for both zip and zip64.
+            writeLongAsUint32(out, EXTSIG);
+            writeLongAsUint32(out, currentEntry.crc = crc.getValue());
+
+            currentEntry.compressedSize = def.getBytesWritten();
+            currentEntry.size = def.getBytesRead();
+
+            if (currentEntryNeedsZip64) {
+                // We need an additional 8 bytes to store 8 byte compressed / uncompressed
+                // sizes.
+                curOffset += 8;
+                writeLongAsUint64(out, currentEntry.compressedSize);
+                writeLongAsUint64(out, currentEntry.size);
+            } else {
+                writeLongAsUint32(out, currentEntry.compressedSize);
+                writeLongAsUint32(out, currentEntry.size);
+            }
         }
         // Update the CentralDirectory
         // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
@@ -163,33 +213,54 @@
         // Some tools insist that the central directory have the UTF-8 flag.
         // http://code.google.com/p/android/issues/detail?id=20214
         flags |= ZipFile.GPBF_UTF8_FLAG;
-        writeLong(cDir, CENSIG);
-        writeShort(cDir, ZIP_VERSION_2_0); // Version this file was made by.
-        writeShort(cDir, ZIP_VERSION_2_0); // Minimum version needed to extract.
-        writeShort(cDir, flags);
-        writeShort(cDir, currentEntry.getMethod());
-        writeShort(cDir, currentEntry.time);
-        writeShort(cDir, currentEntry.modDate);
-        writeLong(cDir, crc.getValue());
+        writeLongAsUint32(cDir, CENSIG);
+        writeIntAsUint16(cDir, ZIP_VERSION_2_0); // Version this file was made by.
+        writeIntAsUint16(cDir, ZIP_VERSION_2_0); // Minimum version needed to extract.
+        writeIntAsUint16(cDir, flags);
+        writeIntAsUint16(cDir, currentEntry.getMethod());
+        writeIntAsUint16(cDir, currentEntry.time);
+        writeIntAsUint16(cDir, currentEntry.modDate);
+        writeLongAsUint32(cDir, crc.getValue());
+
         if (currentEntry.getMethod() == DEFLATED) {
-            curOffset += writeLong(cDir, def.getTotalOut());
-            writeLong(cDir, def.getTotalIn());
+            currentEntry.setCompressedSize(def.getBytesWritten());
+            currentEntry.setSize(def.getBytesRead());
+            curOffset += currentEntry.getCompressedSize();
         } else {
-            curOffset += writeLong(cDir, crc.tbytes);
-            writeLong(cDir, crc.tbytes);
-        }
-        curOffset += writeShort(cDir, nameBytes.length);
-        if (currentEntry.extra != null) {
-            curOffset += writeShort(cDir, currentEntry.extra.length);
-        } else {
-            writeShort(cDir, 0);
+            currentEntry.setCompressedSize(crc.tbytes);
+            currentEntry.setSize(crc.tbytes);
+            curOffset += currentEntry.getSize();
         }
 
-        writeShort(cDir, entryCommentBytes.length); // Comment length.
-        writeShort(cDir, 0); // Disk Start
-        writeShort(cDir, 0); // Internal File Attributes
-        writeLong(cDir, 0); // External File Attributes
-        writeLong(cDir, offset);
+        if (currentEntryNeedsZip64) {
+            // Refresh the extended info with the compressed size / size before
+            // writing it to the central directory.
+            Zip64.refreshZip64ExtendedInfo(currentEntry);
+
+            // NOTE: We would've written out the zip64 extended info locator to the entry
+            // extras while constructing the local file header. There's no need to do it again
+            // here. If we do, there will be a size mismatch since we're calculating offsets
+            // based on the *current* size of the extra data and not based on the size
+            // at the point of writing the LFH.
+            writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE);
+            writeLongAsUint32(cDir, Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE);
+        } else {
+            writeLongAsUint32(cDir, currentEntry.getCompressedSize());
+            writeLongAsUint32(cDir, currentEntry.getSize());
+        }
+
+        curOffset += writeIntAsUint16(cDir, nameBytes.length);
+        if (currentEntry.extra != null) {
+            curOffset += writeIntAsUint16(cDir, currentEntry.extra.length);
+        } else {
+            writeIntAsUint16(cDir, 0);
+        }
+
+        writeIntAsUint16(cDir, entryCommentBytes.length); // Comment length.
+        writeIntAsUint16(cDir, 0); // Disk Start
+        writeIntAsUint16(cDir, 0); // Internal File Attributes
+        writeLongAsUint32(cDir, 0); // External File Attributes
+        writeLongAsUint32(cDir, offset);
         cDir.write(nameBytes);
         nameBytes = null;
         if (currentEntry.extra != null) {
@@ -228,16 +299,32 @@
         if (currentEntry != null) {
             closeEntry();
         }
-        int cdirSize = cDir.size();
+
+        int cdirEntriesSize = cDir.size();
+        if (archiveNeedsZip64EocdRecord) {
+            Zip64.writeZip64EocdRecordAndLocator(cDir, entries.size(), offset, cdirEntriesSize);
+        }
+
         // Write Central Dir End
-        writeLong(cDir, ENDSIG);
-        writeShort(cDir, 0); // Disk Number
-        writeShort(cDir, 0); // Start Disk
-        writeShort(cDir, entries.size()); // Number of entries
-        writeShort(cDir, entries.size()); // Number of entries
-        writeLong(cDir, cdirSize); // Size of central dir
-        writeLong(cDir, offset); // Offset of central dir
-        writeShort(cDir, commentBytes.length);
+        writeLongAsUint32(cDir, ENDSIG);
+        writeIntAsUint16(cDir, 0); // Disk Number
+        writeIntAsUint16(cDir, 0); // Start Disk
+
+        // Instead of trying to figure out *why* this archive needed a zip64 eocd record,
+        // just delegate all these values to the zip64 eocd record.
+        if (archiveNeedsZip64EocdRecord) {
+            writeIntAsUint16(cDir, 0xFFFF); // Number of entries
+            writeIntAsUint16(cDir, 0xFFFF); // Number of entries
+            writeLongAsUint32(cDir, 0xFFFFFFFF); // Size of central dir
+            writeLongAsUint32(cDir, 0xFFFFFFFF); // Offset of central dir;
+        } else {
+            writeIntAsUint16(cDir, entries.size()); // Number of entries
+            writeIntAsUint16(cDir, entries.size()); // Number of entries
+            writeLongAsUint32(cDir, cdirEntriesSize); // Size of central dir
+            writeLongAsUint32(cDir, offset); // Offset of central dir
+        }
+
+        writeIntAsUint16(cDir, commentBytes.length);
         if (commentBytes.length > 0) {
             cDir.write(commentBytes);
         }
@@ -288,14 +375,8 @@
         }
 
         checkOpen();
+        checkAndSetZip64Requirements(ze);
 
-        if (entries.contains(ze.name)) {
-            throw new ZipException("Entry already exists: " + ze.name);
-        }
-        if (entries.size() == 64*1024-1) {
-            // TODO: support Zip64.
-            throw new ZipException("Too many entries for the zip file format's 16-bit entry count");
-        }
         nameBytes = ze.name.getBytes(StandardCharsets.UTF_8);
         checkSizeIsWithinShort("Name", nameBytes);
         entryCommentBytes = EmptyArray.BYTE;
@@ -310,6 +391,7 @@
         ze.setMethod(method);
 
         currentEntry = ze;
+        currentEntry.localHeaderRelOffset = offset;
         entries.add(currentEntry.name);
 
         // Local file header.
@@ -318,30 +400,48 @@
         // Java always outputs UTF-8 filenames. (Before Java 7, the RI didn't set this flag and used
         // modified UTF-8. From Java 7, when using UTF_8 it sets this flag and uses normal UTF-8.)
         flags |= ZipFile.GPBF_UTF8_FLAG;
-        writeLong(out, LOCSIG); // Entry header
-        writeShort(out, ZIP_VERSION_2_0); // Minimum version needed to extract.
-        writeShort(out, flags);
-        writeShort(out, method);
+        writeLongAsUint32(out, LOCSIG); // Entry header
+        writeIntAsUint16(out, ZIP_VERSION_2_0); // Minimum version needed to extract.
+        writeIntAsUint16(out, flags);
+        writeIntAsUint16(out, method);
         if (currentEntry.getTime() == -1) {
             currentEntry.setTime(System.currentTimeMillis());
         }
-        writeShort(out, currentEntry.time);
-        writeShort(out, currentEntry.modDate);
+        writeIntAsUint16(out, currentEntry.time);
+        writeIntAsUint16(out, currentEntry.modDate);
 
         if (method == STORED) {
-            writeLong(out, currentEntry.crc);
-            writeLong(out, currentEntry.size);
-            writeLong(out, currentEntry.size);
+            writeLongAsUint32(out, currentEntry.crc);
+
+            if (currentEntryNeedsZip64) {
+                // NOTE: According to the spec, we're allowed to use these fields under zip64
+                // as long as the sizes are <= 4G (and omit writing the zip64 extended information header).
+                //
+                // For simplicity, we write the zip64 extended info here even if we only need it
+                // in the central directory (i.e, the case where we're turning on zip64 because the
+                // offset to this entries LFH is > 0xFFFFFFFF).
+                out.write(ZIP64_PLACEHOLDER_BYTES);  // compressed size
+                out.write(ZIP64_PLACEHOLDER_BYTES);  // uncompressed size
+            } else {
+                writeLongAsUint32(out, currentEntry.size);
+                writeLongAsUint32(out, currentEntry.size);
+            }
         } else {
-            writeLong(out, 0);
-            writeLong(out, 0);
-            writeLong(out, 0);
+            writeLongAsUint32(out, 0);
+            writeLongAsUint32(out, 0);
+            writeLongAsUint32(out, 0);
         }
-        writeShort(out, nameBytes.length);
+
+        writeIntAsUint16(out, nameBytes.length);
+
+        if (currentEntryNeedsZip64) {
+            Zip64.insertZip64ExtendedInfoToExtras(currentEntry);
+        }
+
         if (currentEntry.extra != null) {
-            writeShort(out, currentEntry.extra.length);
+            writeIntAsUint16(out, currentEntry.extra.length);
         } else {
-            writeShort(out, 0);
+            writeIntAsUint16(out, 0);
         }
         out.write(nameBytes);
         if (currentEntry.extra != null) {
@@ -349,6 +449,42 @@
         }
     }
 
+    private void checkAndSetZip64Requirements(ZipEntry entry) {
+        final long totalBytesWritten = getBytesWritten();
+        final long entriesWritten = entries.size();
+
+        currentEntryNeedsZip64 = false;
+        if (forceZip64) {
+            currentEntryNeedsZip64 = true;
+            archiveNeedsZip64EocdRecord = true;
+            return;
+        }
+
+        // In this particular case, we'll write a zip64 eocd record locator and a zip64 eocd
+        // record but we won't actually need zip64 extended info records for any of the individual
+        // entries (unless they trigger the checks below).
+        if (entriesWritten == 64*1024-1) {
+            archiveNeedsZip64EocdRecord = true;
+        }
+
+        // Check whether we'll need to write out a zip64 extended info record in both the local file header
+        // and the central directory. In addition, we will need a zip64 eocd record locator
+        // and record to mark this archive as zip64.
+        //
+        // TODO: This is an imprecise check. When method != STORED it's possible that the compressed
+        // size will be (slightly) larger than the actual size. How can we improve this ?
+        //
+        // TODO: Will we regret forcing zip64 for archive entries with unknown entry sizes ? This is
+        // standard "zip" behaviour on linux but i'm not sure if we'll end up breaking somebody as a
+        // result.
+        if (totalBytesWritten > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE ||
+                (entry.getSize() > Zip64.MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) ||
+                (entry.getSize() == -1)) {
+            currentEntryNeedsZip64 = true;
+            archiveNeedsZip64EocdRecord = true;
+        }
+    }
+
     /**
      * Sets the comment associated with the file being written. See {@link ZipFile#getComment}.
      * @throws IllegalArgumentException if the comment is >= 64 Ki encoded bytes.
@@ -386,7 +522,7 @@
         defaultCompressionMethod = method;
     }
 
-    private long writeLong(OutputStream os, long i) throws IOException {
+    static long writeLongAsUint32(OutputStream os, long i) throws IOException {
         // Write out the long value as an unsigned int
         os.write((int) (i & 0xFF));
         os.write((int) (i >> 8) & 0xFF);
@@ -395,7 +531,23 @@
         return i;
     }
 
-    private int writeShort(OutputStream os, int i) throws IOException {
+    static long writeLongAsUint64(OutputStream os, long i) throws IOException {
+        int i1 = (int) i;
+        os.write(i1 & 0xFF);
+        os.write((i1 >> 8) & 0xFF);
+        os.write((i1 >> 16) & 0xFF);
+        os.write((i1 >> 24) & 0xFF);
+
+        int i2 = (int) (i >> 32);
+        os.write(i2 & 0xFF);
+        os.write((i2 >> 8) & 0xFF);
+        os.write((i2 >> 16) & 0xFF);
+        os.write((i2 >> 24) & 0xFF);
+
+        return i;
+    }
+
+    static int writeIntAsUint16(OutputStream os, int i) throws IOException {
         os.write(i & 0xFF);
         os.write((i >> 8) & 0xFF);
         return i;
@@ -434,4 +586,11 @@
                                                " bytes");
         }
     }
+
+    private long getBytesWritten() {
+        // This cast is somewhat messy but less error prone than keeping an
+        // CountingOutputStream reference around in addition to the FilterOutputStream's
+        // out.
+        return ((CountingOutputStream) out).getCount();
+    }
 }
diff --git a/luni/src/main/java/libcore/util/CountingOutputStream.java b/luni/src/main/java/libcore/util/CountingOutputStream.java
new file mode 100644
index 0000000..cc0e1f2
--- /dev/null
+++ b/luni/src/main/java/libcore/util/CountingOutputStream.java
@@ -0,0 +1,59 @@
+/*
+ * 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.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An output stream that keeps count of the number of bytes written to it.
+ *
+ * Useful when we need to make decisions based on the size of the output, such
+ * as deciding what sort of metadata to writes to zip files.
+ */
+public class CountingOutputStream extends FilterOutputStream {
+
+    private long count;
+
+    /**
+     * Constructs a new {@code FilterOutputStream} with {@code out} as its
+     * target stream.
+     *
+     * @param out the target stream that this stream writes to.
+     */
+    public CountingOutputStream(OutputStream out) {
+        super(out);
+        count = 0;
+    }
+
+    @Override
+    public void write(byte[] buffer, int offset, int length) throws IOException {
+        out.write(buffer, offset, length);
+        count += length;
+    }
+
+    @Override
+    public void write(int oneByte) throws IOException {
+        out.write(oneByte);
+        count++;
+    }
+
+    public long getCount() {
+        return count;
+    }
+}
diff --git a/luni/src/main/native/android_system_OsConstants.cpp b/luni/src/main/native/android_system_OsConstants.cpp
index f00c922..46dabb3 100644
--- a/luni/src/main/native/android_system_OsConstants.cpp
+++ b/luni/src/main/native/android_system_OsConstants.cpp
@@ -491,6 +491,15 @@
     initConstant(env, c, "STDERR_FILENO", STDERR_FILENO);
     initConstant(env, c, "STDIN_FILENO", STDIN_FILENO);
     initConstant(env, c, "STDOUT_FILENO", STDOUT_FILENO);
+    initConstant(env, c, "ST_MANDLOCK", ST_MANDLOCK);
+    initConstant(env, c, "ST_NOATIME", ST_NOATIME);
+    initConstant(env, c, "ST_NODEV", ST_NODEV);
+    initConstant(env, c, "ST_NODIRATIME", ST_NODIRATIME);
+    initConstant(env, c, "ST_NOEXEC", ST_NOEXEC);
+    initConstant(env, c, "ST_NOSUID", ST_NOSUID);
+    initConstant(env, c, "ST_RDONLY", ST_RDONLY);
+    initConstant(env, c, "ST_RELATIME", ST_RELATIME);
+    initConstant(env, c, "ST_SYNCHRONOUS", ST_SYNCHRONOUS);
     initConstant(env, c, "S_IFBLK", S_IFBLK);
     initConstant(env, c, "S_IFCHR", S_IFCHR);
     initConstant(env, c, "S_IFDIR", S_IFDIR);
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..9c3e870 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.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 @@
     File f = createTemporaryZipFile();
     ZipOutputStream out = createZipOutputStream(f);
     ZipEntry ze = new ZipEntry("x");
+    ze.setSize(0);
     ze.setExtra(maxLengthExtra);
     out.putNextEntry(ze);
     out.closeEntry();
@@ -143,7 +145,25 @@
     zipFile.close();
   }
 
-  public void testTooLongComment() throws Exception {
+  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 = createZipOutputStream(f);
+    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");
     try {
@@ -176,7 +196,17 @@
 
     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 +218,9 @@
     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 1826140..bf18978 100644
--- a/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java
+++ b/luni/src/test/java/libcore/java/util/zip/ZipFileTest.java
@@ -196,7 +196,7 @@
             ZipFile zipFile = new ZipFile(f);
             int entryCount = 0;
             for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
-                ZipEntry zipEntry = e.nextElement();
+                e.nextElement();
                 ++entryCount;
             }
             assertEquals(1024, entryCount);
@@ -204,18 +204,62 @@
         }
     }
 
-    public void testZip64Support() throws IOException {
+    public void testZip64Support_largeNumberOfEntries() throws IOException {
+        File file = createZipFile(65550, 2);
+        ZipFile zf = null;
         try {
-            createZipFile(64*1024, 0);
-            fail(); // Make this test more like testHugeZipFile when we have Zip64 support.
-        } catch (ZipException expected) {
+            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);
+        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 {
+        final File file = createZipFile(1, 4294967410L);
+        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();
+            }
         }
     }
 
     /**
      * Compresses the given number of files, each of the given size, into a .zip archive.
      */
-    private static File createZipFile(int entryCount, int entrySize) throws IOException {
+    private static File createZipFile(int entryCount, long entrySize) throws IOException {
         File result = createTemporaryZipFile();
 
         byte[] writeBuffer = new byte[8192];
@@ -227,9 +271,9 @@
                 ZipEntry ze = new ZipEntry(Integer.toHexString(entry));
                 out.putNextEntry(ze);
 
-                for (int i = 0; i < entrySize; i += writeBuffer.length) {
+                for (long i = 0; i < entrySize; i += writeBuffer.length) {
                     random.nextBytes(writeBuffer);
-                    int byteCount = Math.min(writeBuffer.length, entrySize - i);
+                    int byteCount = (int) Math.min(writeBuffer.length, entrySize - i);
                     out.write(writeBuffer, 0, byteCount);
                 }