Improve DataSource interface.

1. This adds way to obtain a DataSource which represents a region of
   data contained in the DataSource.
2. This fixes a design bug in "feed" method where the size parameter
   was an int instead of long.
3. This fixes a bug in ByteBufferDataSource where its mSize field was
   a long instead of an int (ByteBuffer's length cannot be more than
   2^31).

Bug: 27461702
Change-Id: Ib0812784beb581f19d2412e667b8bd018f0a3c78
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) {