Merge "Improve efficiency of using the DataSource abstraction."
diff --git a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
index e185346..103a0ec 100644
--- a/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
+++ b/tools/apksigner/core/src/com/android/apksigner/core/internal/apk/v2/V2SchemeSigner.java
@@ -16,7 +16,6 @@
package com.android.apksigner.core.internal.apk.v2;
-import com.android.apksigner.core.internal.util.ByteBufferSink;
import com.android.apksigner.core.internal.util.Pair;
import com.android.apksigner.core.internal.zip.ZipUtils;
import com.android.apksigner.core.util.DataSource;
@@ -191,8 +190,10 @@
// offset field is treated as pointing to the offset at which the APK Signing Block will
// start.
long centralDirOffsetForDigesting = beforeCentralDir.size();
- ByteBuffer eocdBuf = copyToByteBuffer(eocd);
+ ByteBuffer eocdBuf = ByteBuffer.allocate((int) eocd.size());
eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
+ eocd.copyTo(0, (int) eocd.size(), eocdBuf);
+ eocdBuf.flip();
ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, centralDirOffsetForDigesting);
// Compute digests of APK contents.
@@ -600,15 +601,4 @@
}
return result.array();
}
-
- private static ByteBuffer copyToByteBuffer(DataSource dataSource) throws IOException {
- long dataSourceSize = dataSource.size();
- if (dataSourceSize > Integer.MAX_VALUE) {
- throw new IllegalArgumentException("Data source too large: " + dataSourceSize);
- }
- ByteBuffer result = ByteBuffer.allocate((int) dataSourceSize);
- dataSource.feed(0, result.remaining(), new ByteBufferSink(result));
- result.position(0);
- return result;
- }
}
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 f12f02e..b2d9ca1 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
@@ -35,7 +35,15 @@
* buffer between the buffer's position and limit.
*/
public ByteBufferDataSource(ByteBuffer buffer) {
- mBuffer = buffer.slice();
+ this(buffer, true);
+ }
+
+ /**
+ * Constructs a new {@code ByteBufferDigestSource} based on the data contained in the provided
+ * buffer between the buffer's position and limit.
+ */
+ private ByteBufferDataSource(ByteBuffer buffer, boolean sliceRequired) {
+ mBuffer = (sliceRequired) ? buffer.slice() : buffer;
mSize = buffer.remaining();
}
@@ -45,15 +53,12 @@
}
@Override
- public ByteBufferDataSource slice(long offset, long size) {
- if ((offset == 0) && (size == mSize)) {
- return this;
- }
+ public ByteBuffer getByteBuffer(long offset, int size) {
checkChunkValid(offset, size);
- // checkChunkValid ensures that it's OK to cast offset and size to int.
+ // checkChunkValid ensures that it's OK to cast offset to int.
int chunkPosition = (int) offset;
- int chunkLimit = (int) (chunkPosition + size);
+ int chunkLimit = 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.
@@ -66,35 +71,35 @@
mBuffer.limit(chunkLimit);
mBuffer.position(chunkPosition);
- // Create a ByteBufferDataSource for the slice of the buffer between limit and position.
- return new ByteBufferDataSource(mBuffer);
+ return mBuffer.slice();
}
}
@Override
+ public void copyTo(long offset, int size, ByteBuffer dest) {
+ dest.put(getByteBuffer(offset, size));
+ }
+
+ @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();
+ if ((size < 0) || (size > mSize)) {
+ throw new IllegalArgumentException("size: " + size + ", source size: " + mSize);
}
+ sink.consume(getByteBuffer(offset, (int) size));
+ }
- sink.consume(chunk);
+ @Override
+ public ByteBufferDataSource slice(long offset, long size) {
+ if ((offset == 0) && (size == mSize)) {
+ return this;
+ }
+ if ((size < 0) || (size > mSize)) {
+ throw new IllegalArgumentException("size: " + size + ", source size: " + mSize);
+ }
+ return new ByteBufferDataSource(
+ getByteBuffer(offset, (int) size),
+ false // no need to slice -- it's already a slice
+ );
}
private void checkChunkValid(long offset, long size) {
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 27ff7a8..e268dd2 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
@@ -17,6 +17,7 @@
package com.android.apksigner.core.util;
import java.io.IOException;
+import java.nio.ByteBuffer;
/**
* Abstract representation of a source of data.
@@ -29,6 +30,25 @@
* may have worked as the unifying abstraction.</li>
* <li>Support sources which do not fit into logical memory as a contiguous region.</li>
* </ul>
+ *
+ * <p>There are following ways to obtain a chunk of data from the data source:
+ * <ul>
+ * <li>Stream the chunk's data into a {@link DataSink} using
+ * {@link #feed(long, long, DataSink) feed}. This is best suited for scenarios where there is no
+ * need to have the chunk's data accessible at the same time, for example, when computing the
+ * digest of the chunk. If you need to keep the chunk's data around after {@code feed}
+ * completes, you must create a copy during {@code feed}. However, in that case the following
+ * methods of obtaining the chunk's data may be more appropriate.</li>
+ * <li>Obtain a {@link ByteBuffer} containing the chunk's data using
+ * {@link #getByteBuffer(long, int) getByteBuffer}. Depending on the data source, the chunk's
+ * data may or may not be copied by this operation. This is best suited for scenarios where
+ * you need to access the chunk's data in arbitrary order, but don't need to modify the data and
+ * thus don't require a copy of the data.</li>
+ * <li>Copy the chunk's data to a {@link ByteBuffer} using
+ * {@link #copyTo(long, int, ByteBuffer) copyTo}. This is best suited for scenarios where
+ * you require a copy of the chunk's data, such as to when you need to modify the data.
+ * </li>
+ * </ul>
*/
public interface DataSource {
@@ -46,8 +66,33 @@
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);
}