Merge "Add --dir option to findleaves.py"
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java
index 76f4fda..f12f02e 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/util/ByteBufferDataSource.java
@@ -28,7 +28,7 @@
public class ByteBufferDataSource implements DataSource {
private final ByteBuffer mBuffer;
- private final long mSize;
+ private final int mSize;
/**
* Constructs a new {@code ByteBufferDigestSource} based on the data contained in the provided
@@ -45,7 +45,59 @@
}
@Override
- public void feed(long offset, int size, DataSink sink) throws IOException {
+ public ByteBufferDataSource slice(long offset, long size) {
+ if ((offset == 0) && (size == mSize)) {
+ return this;
+ }
+ checkChunkValid(offset, size);
+
+ // checkChunkValid ensures that it's OK to cast offset and size to int.
+ int chunkPosition = (int) offset;
+ int chunkLimit = (int) (chunkPosition + size);
+ // Creating a slice of ByteBuffer modifies the state of the source ByteBuffer (position
+ // and limit fields, to be more specific). We thus use synchronization around these
+ // state-changing operations to make instances of this class thread-safe.
+ synchronized (mBuffer) {
+ // ByteBuffer.limit(int) and .position(int) check that that the position >= limit
+ // invariant is not broken. Thus, the only way to safely change position and limit
+ // without caring about their current values is to first set position to 0 or set the
+ // limit to capacity.
+ mBuffer.position(0);
+
+ mBuffer.limit(chunkLimit);
+ mBuffer.position(chunkPosition);
+ // Create a ByteBufferDataSource for the slice of the buffer between limit and position.
+ return new ByteBufferDataSource(mBuffer);
+ }
+ }
+
+ @Override
+ public void feed(long offset, long size, DataSink sink) throws IOException {
+ checkChunkValid(offset, size);
+
+ // checkChunkValid ensures that it's OK to cast offset and size to int.
+ int chunkPosition = (int) offset;
+ int chunkLimit = (int) (chunkPosition + size);
+ ByteBuffer chunk;
+ // Creating a slice of ByteBuffer modifies the state of the source ByteBuffer (position
+ // and limit fields, to be more specific). We thus use synchronization around these
+ // state-changing operations to make instances of this class thread-safe.
+ synchronized (mBuffer) {
+ // ByteBuffer.limit(int) and .position(int) check that that the position >= limit
+ // invariant is not broken. Thus, the only way to safely change position and limit
+ // without caring about their current values is to first set position to 0 or set the
+ // limit to capacity.
+ mBuffer.position(0);
+
+ mBuffer.limit(chunkLimit);
+ mBuffer.position(chunkPosition);
+ chunk = mBuffer.slice();
+ }
+
+ sink.consume(chunk);
+ }
+
+ private void checkChunkValid(long offset, long size) {
if (offset < 0) {
throw new IllegalArgumentException("offset: " + offset);
}
@@ -65,25 +117,5 @@
throw new IllegalArgumentException(
"offset (" + offset + ") + size (" + size + ") > source size (" + mSize +")");
}
-
- int chunkPosition = (int) offset; // safe to downcast because mSize <= Integer.MAX_VALUE
- int chunkLimit = (int) endOffset; // safe to downcast because mSize <= Integer.MAX_VALUE
- ByteBuffer chunk;
- // Creating a slice of ByteBuffer modifies the state of the source ByteBuffer (position
- // and limit fields, to be more specific). We thus use synchronization around these
- // state-changing operations to make instances of this class thread-safe.
- synchronized (mBuffer) {
- // ByteBuffer.limit(int) and .position(int) check that that the position >= limit
- // invariant is not broken. Thus, the only way to safely change position and limit
- // without caring about their current values is to first set position to 0 or set the
- // limit to capacity.
- mBuffer.position(0);
-
- mBuffer.limit(chunkLimit);
- mBuffer.position(chunkPosition);
- chunk = mBuffer.slice();
- }
-
- sink.consume(chunk);
}
}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java
index 04560cb..27ff7a8 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSource.java
@@ -43,5 +43,11 @@
* @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, int size, DataSink sink) throws IOException;
+ void feed(long offset, long size, DataSink sink) 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.
+ */
+ DataSource slice(long offset, long size);
}
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java
index 978afae..6ce0ac8 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/util/DataSources.java
@@ -12,7 +12,8 @@
/**
* Returns a {@link DataSource} backed by the provided {@link ByteBuffer}. The data source
- * represents the data contained between the position and limit of the buffer.
+ * represents the data contained between the position and limit of the buffer. Changes to the
+ * buffer's contents will be visible in the data source.
*/
public static DataSource asDataSource(ByteBuffer buffer) {
if (buffer == null) {
diff --git a/tools/signapk/src/com/android/signapk/SignApk.java b/tools/signapk/src/com/android/signapk/SignApk.java
index 1df6b80..a7c9fc3 100644
--- a/tools/signapk/src/com/android/signapk/SignApk.java
+++ b/tools/signapk/src/com/android/signapk/SignApk.java
@@ -52,6 +52,7 @@
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.security.DigestOutputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
@@ -119,6 +120,19 @@
private static final String OTACERT_NAME = "META-INF/com/android/otacert";
+ /**
+ * Extensible data block/field header ID used for storing information about alignment of
+ * uncompressed entries as well as for aligning the entries's data. See ZIP appnote.txt section
+ * 4.5 Extensible data fields.
+ */
+ private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID = (short) 0xd935;
+
+ /**
+ * Minimum size (in bytes) of the extensible data block/field used for alignment of uncompressed
+ * entries.
+ */
+ private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES = 6;
+
// bitmasks for which hash algorithms we need the manifest to include.
private static final int USE_SHA1 = 1;
private static final int USE_SHA256 = 2;
@@ -588,28 +602,40 @@
outEntry.setComment(null);
outEntry.setExtra(null);
- // 'offset' is the offset into the file at which we expect
- // the file data to begin. This is the value we need to
- // make a multiple of 'alignement'.
+ int alignment = getStoredEntryDataAlignment(name, defaultAlignment);
+ // Alignment of the entry's data is achieved by adding a data block to the entry's Local
+ // File Header extra field. The data block contains information about the alignment
+ // value and the necessary padding bytes (0x00) to achieve the alignment. This works
+ // because the entry's data will be located immediately after the extra field.
+ // See ZIP APPNOTE.txt section "4.5 Extensible data fields" for details about the format
+ // of the extra field.
+
+ // 'offset' is the offset into the file at which we expect the entry's data to begin.
+ // This is the value we need to make a multiple of 'alignment'.
offset += JarFile.LOCHDR + outEntry.getName().length();
if (firstEntry) {
- // The first entry in a jar file has an extra field of
- // four bytes that you can't get rid of; any extra
- // data you specify in the JarEntry is appended to
- // these forced four bytes. This is JAR_MAGIC in
- // JarOutputStream; the bytes are 0xfeca0000.
+ // The first entry in a jar file has an extra field of four bytes that you can't get
+ // rid of; any extra data you specify in the JarEntry is appended to these forced
+ // four bytes. This is JAR_MAGIC in JarOutputStream; the bytes are 0xfeca0000.
+ // See http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6808540
+ // and http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4138619.
offset += 4;
firstEntry = false;
}
- int alignment = getStoredEntryDataAlignment(name, defaultAlignment);
- if (alignment > 0 && (offset % alignment != 0)) {
- // Set the "extra data" of the entry to between 1 and
- // alignment-1 bytes, to make the file data begin at
- // an aligned offset.
- int needed = alignment - (int)(offset % alignment);
- outEntry.setExtra(new byte[needed]);
- offset += needed;
+ int extraPaddingSizeBytes = 0;
+ if (alignment > 0) {
+ long paddingStartOffset = offset + ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES;
+ extraPaddingSizeBytes = alignment - (int) (paddingStartOffset % alignment);
}
+ byte[] extra =
+ new byte[ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES + extraPaddingSizeBytes];
+ ByteBuffer extraBuf = ByteBuffer.wrap(extra);
+ extraBuf.order(ByteOrder.LITTLE_ENDIAN);
+ extraBuf.putShort(ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID); // Header ID
+ extraBuf.putShort((short) (2 + extraPaddingSizeBytes)); // Data Size
+ extraBuf.putShort((short) alignment);
+ outEntry.setExtra(extra);
+ offset += extra.length;
out.putNextEntry(outEntry);