// Path: plugin/src/main/java/com/android/apksigner/core/internal/util/Pair.java
// public final class Pair<A, B> {
//     private final A mFirst;
//     private final B mSecond;
// 
//     private Pair(A first, B second) {
//         mFirst = first;
//         mSecond = second;
//     }
// 
//     public static <A, B> Pair<A, B> of(A first, B second) {
//         return new Pair<A, B>(first, second);
//     }
// 
//     public A getFirst() {
//         return mFirst;
//     }
// 
//     public B getSecond() {
//         return mSecond;
//     }
// 
//     @Override
//     public int hashCode() {
//         final int prime = 31;
//         int result = 1;
//         result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());
//         result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());
//         return result;
//     }
// 
//     @Override
//     public boolean equals(Object obj) {
//         if (this == obj) {
//             return true;
//         }
//         if (obj == null) {
//             return false;
//         }
//         if (getClass() != obj.getClass()) {
//             return false;
//         }
//         @SuppressWarnings("rawtypes")
//         Pair other = (Pair) obj;
//         if (mFirst == null) {
//             if (other.mFirst != null) {
//                 return false;
//             }
//         } else if (!mFirst.equals(other.mFirst)) {
//             return false;
//         }
//         if (mSecond == null) {
//             if (other.mSecond != null) {
//                 return false;
//             }
//         } else if (!mSecond.equals(other.mSecond)) {
//             return false;
//         }
//         return true;
//     }
// }
// 
// Path: plugin/src/main/java/com/android/apksigner/core/util/DataSource.java
// public interface DataSource {
// 
//     /**
//      * Returns the amount of data (in bytes) contained in this data source.
//      */
//     long size();
// 
//     /**
//      * Feeds the specified chunk from this data source into the provided sink.
//      *
//      * @param offset index (in bytes) at which the chunk starts inside data source
//      * @param size size (in bytes) of the chunk
//      */
//     void feed(long offset, long size, DataSink sink) throws IOException;
// 
//     /**
//      * Returns a buffer holding the contents of the specified chunk of data from this data source.
//      * Changes to the data source are not guaranteed to be reflected in the returned buffer.
//      * Similarly, changes in the buffer are not guaranteed to be reflected in the data source.
//      *
//      * <p>The returned buffer's position is {@code 0}, and the buffer's limit and capacity is
//      * {@code size}.
//      *
//      * @param offset index (in bytes) at which the chunk starts inside data source
//      * @param size size (in bytes) of the chunk
//      */
//     ByteBuffer getByteBuffer(long offset, int size) throws IOException;
// 
//     /**
//      * Copies the specified chunk from this data source into the provided destination buffer,
//      * advancing the destination buffer's position by {@code size}.
//      *
//      * @param offset index (in bytes) at which the chunk starts inside data source
//      * @param size size (in bytes) of the chunk
//      */
//     void copyTo(long offset, int size, ByteBuffer dest) throws IOException;
// 
//     /**
//      * Returns a data source representing the specified region of data of this data source. Changes
//      * to data represented by this data source will also be visible in the returned data source.
//      *
//      * @param offset index (in bytes) at which the region starts inside data source
//      * @param size size (in bytes) of the region
//      */
//     DataSource slice(long offset, long size);
// }


import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.android.apksigner.core.internal.util.Pair;
import com.android.apksigner.core.util.DataSource;
     */
    public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
        return getUnsignedInt32(
                zipEndOfCentralDirectory,
                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
    }

    /**
     * Returns the total number of records in ZIP Central Directory.
     *
     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
     */
    public static int getZipEocdCentralDirectoryTotalRecordCount(
            ByteBuffer zipEndOfCentralDirectory) {
        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
        return getUnsignedInt16(
                zipEndOfCentralDirectory,
                zipEndOfCentralDirectory.position()
                        + ZIP_EOCD_CENTRAL_DIR_TOTAL_RECORD_COUNT_OFFSET);
    }

    /**
     * Returns the ZIP End of Central Directory record of the provided ZIP file.
     *
     * @return contents of the ZIP End of Central Directory record and the record's offset in the
     *         file or {@code null} if the file does not contain the record.
     *
     * @throws IOException if an I/O error occurs while reading the file.
     */
// Path: plugin/src/main/java/com/android/apksigner/core/internal/util/Pair.java
// public final class Pair<A, B> {
//     private final A mFirst;
//     private final B mSecond;
// 
//     private Pair(A first, B second) {
//         mFirst = first;
//         mSecond = second;
//     }
// 
//     public static <A, B> Pair<A, B> of(A first, B second) {
//         return new Pair<A, B>(first, second);
//     }
// 
//     public A getFirst() {
//         return mFirst;
//     }
// 
//     public B getSecond() {
//         return mSecond;
//     }
// 
//     @Override
//     public int hashCode() {
//         final int prime = 31;
//         int result = 1;
//         result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());
//         result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());
//         return result;
//     }
// 
//     @Override
//     public boolean equals(Object obj) {
//         if (this == obj) {
//             return true;
//         }
//         if (obj == null) {
//             return false;
//         }
//         if (getClass() != obj.getClass()) {
//             return false;
//         }
//         @SuppressWarnings("rawtypes")
//         Pair other = (Pair) obj;
//         if (mFirst == null) {
//             if (other.mFirst != null) {
//                 return false;
//             }
//         } else if (!mFirst.equals(other.mFirst)) {
//             return false;
//         }
//         if (mSecond == null) {
//             if (other.mSecond != null) {
//                 return false;
//             }
//         } else if (!mSecond.equals(other.mSecond)) {
//             return false;
//         }
//         return true;
//     }
// }
// 
// Path: plugin/src/main/java/com/android/apksigner/core/util/DataSource.java
// public interface DataSource {
// 
//     /**
//      * Returns the amount of data (in bytes) contained in this data source.
//      */
//     long size();
// 
//     /**
//      * Feeds the specified chunk from this data source into the provided sink.
//      *
//      * @param offset index (in bytes) at which the chunk starts inside data source
//      * @param size size (in bytes) of the chunk
//      */
//     void feed(long offset, long size, DataSink sink) throws IOException;
// 
//     /**
//      * Returns a buffer holding the contents of the specified chunk of data from this data source.
//      * Changes to the data source are not guaranteed to be reflected in the returned buffer.
//      * Similarly, changes in the buffer are not guaranteed to be reflected in the data source.
//      *
//      * <p>The returned buffer's position is {@code 0}, and the buffer's limit and capacity is
//      * {@code size}.
//      *
//      * @param offset index (in bytes) at which the chunk starts inside data source
//      * @param size size (in bytes) of the chunk
//      */
//     ByteBuffer getByteBuffer(long offset, int size) throws IOException;
// 
//     /**
//      * Copies the specified chunk from this data source into the provided destination buffer,
//      * advancing the destination buffer's position by {@code size}.
//      *
//      * @param offset index (in bytes) at which the chunk starts inside data source
//      * @param size size (in bytes) of the chunk
//      */
//     void copyTo(long offset, int size, ByteBuffer dest) throws IOException;
// 
//     /**
//      * Returns a data source representing the specified region of data of this data source. Changes
//      * to data represented by this data source will also be visible in the returned data source.
//      *
//      * @param offset index (in bytes) at which the region starts inside data source
//      * @param size size (in bytes) of the region
//      */
//     DataSource slice(long offset, long size);
// }


// Path: plugin/src/main/java/com/android/apksigner/core/internal/zip/ZipUtils.java
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.android.apksigner.core.internal.util.Pair;
import com.android.apksigner.core.util.DataSource;

     */
    public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
        return getUnsignedInt32(
                zipEndOfCentralDirectory,
                zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
    }

    /**
     * Returns the total number of records in ZIP Central Directory.
     *
     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
     */
    public static int getZipEocdCentralDirectoryTotalRecordCount(
            ByteBuffer zipEndOfCentralDirectory) {
        assertByteOrderLittleEndian(zipEndOfCentralDirectory);
        return getUnsignedInt16(
                zipEndOfCentralDirectory,
                zipEndOfCentralDirectory.position()
                        + ZIP_EOCD_CENTRAL_DIR_TOTAL_RECORD_COUNT_OFFSET);
    }

    /**
     * Returns the ZIP End of Central Directory record of the provided ZIP file.
     *
     * @return contents of the ZIP End of Central Directory record and the record's offset in the
     *         file or {@code null} if the file does not contain the record.
     *
     * @throws IOException if an I/O error occurs while reading the file.
     */
    public static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord(DataSource zip)