• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.apksig.util;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.fail;
21 
22 import java.io.Closeable;
23 import java.io.IOException;
24 import java.nio.BufferOverflowException;
25 import java.nio.ByteBuffer;
26 import java.nio.charset.StandardCharsets;
27 import org.junit.Test;
28 
29 /**
30  * Base class for testing implementations of {@link DataSource}. This class tests the contract of
31  * {@code DataSource}.
32  *
33  * <p>To subclass, provide an implementation of {@link #createDataSource(byte[])} which returns
34  * the implementation of {@code DataSource} you want to test.
35  */
36 public abstract class DataSourceTestBase {
37 
38     /**
39      * Returns a new {@link DataSource} containing the provided contents.
40      */
createDataSource(byte[] contents)41     protected abstract CloseableWithDataSource createDataSource(byte[] contents) throws IOException;
42 
createDataSource(String contents)43     protected CloseableWithDataSource createDataSource(String contents) throws IOException {
44         return createDataSource(contents.getBytes(StandardCharsets.UTF_8));
45     }
46 
47     @Test
testSize()48     public void testSize() throws Exception {
49         try (CloseableWithDataSource c = createDataSource("Hello12345")) {
50             DataSource ds = c.getDataSource();
51             assertEquals(10, ds.size());
52         }
53     }
54 
55     @Test
testSlice()56     public void testSlice() throws Exception {
57         try (CloseableWithDataSource c = createDataSource("Hello12345")) {
58             DataSource ds = c.getDataSource();
59             assertSliceEquals("123", ds, 5, 3);
60             DataSource slice = ds.slice(3, 5);
61             assertGetByteBufferEquals("lo123", slice, 0, 5);
62 
63             // Zero-length slices
64             assertSliceEquals("", ds, 0, 0);
65             assertSliceEquals("", ds, 1, 0);
66             assertSliceEquals("", ds, ds.size() - 2, 0);
67             assertSliceEquals("", ds, ds.size() - 1, 0);
68             assertSliceEquals("", ds, ds.size(), 0);
69             assertSliceEquals("", slice, 0, 0);
70             assertSliceEquals("", slice, 1, 0);
71             assertSliceEquals("", slice, slice.size() - 2, 0);
72             assertSliceEquals("", slice, slice.size() - 1, 0);
73             assertSliceEquals("", slice, slice.size(), 0);
74 
75             // Invalid slices
76             assertSliceThrowsIOOB(ds, -1, 0);
77             assertSliceThrowsIOOB(slice, -1, 0);
78             assertSliceThrowsIOOB(ds, -1, 2);
79             assertSliceThrowsIOOB(slice, -1, 2);
80             assertSliceThrowsIOOB(ds, -1, 20);
81             assertSliceThrowsIOOB(slice, -1, 20);
82             assertSliceThrowsIOOB(ds, 1, 20);
83             assertSliceThrowsIOOB(slice, 1, 20);
84             assertSliceThrowsIOOB(ds, ds.size() + 1, 0);
85             assertSliceThrowsIOOB(slice, slice.size() + 1, 0);
86             assertSliceThrowsIOOB(ds, ds.size(), 1);
87             assertSliceThrowsIOOB(slice, slice.size(), 1);
88             assertSliceThrowsIOOB(ds, ds.size() - 1, -1);
89             assertSliceThrowsIOOB(ds, slice.size() - 1, -1);
90         }
91     }
92 
93     @Test
testGetByteBuffer()94     public void testGetByteBuffer() throws Exception {
95         try (CloseableWithDataSource c = createDataSource("test1234")) {
96             DataSource ds = c.getDataSource();
97             assertGetByteBufferEquals("s", ds, 2, 1);
98             DataSource slice = ds.slice(3, 4); // "t123"
99             assertGetByteBufferEquals("2", slice, 2, 1);
100 
101             // Zero-length chunks
102             assertEquals(0, ds.getByteBuffer(0, 0).capacity());
103             assertEquals(0, ds.getByteBuffer(ds.size(), 0).capacity());
104             assertEquals(0, ds.getByteBuffer(ds.size() - 1, 0).capacity());
105             assertEquals(0, ds.getByteBuffer(ds.size() - 2, 0).capacity());
106             assertEquals(0, slice.getByteBuffer(0, 0).capacity());
107             assertEquals(0, slice.getByteBuffer(slice.size(), 0).capacity());
108             assertEquals(0, slice.getByteBuffer(slice.size() - 1, 0).capacity());
109             assertEquals(0, slice.getByteBuffer(slice.size() - 2, 0).capacity());
110 
111             // Invalid chunks
112             assertGetByteBufferThrowsIOOB(ds, -1, 0);
113             assertGetByteBufferThrowsIOOB(slice, -1, 0);
114             assertGetByteBufferThrowsIOOB(ds, -1, 2);
115             assertGetByteBufferThrowsIOOB(slice, -1, 2);
116             assertGetByteBufferThrowsIOOB(ds, -1, 20);
117             assertGetByteBufferThrowsIOOB(slice, -1, 20);
118             assertGetByteBufferThrowsIOOB(ds, 1, 20);
119             assertGetByteBufferThrowsIOOB(slice, 1, 20);
120             assertGetByteBufferThrowsIOOB(ds, ds.size() + 1, 0);
121             assertGetByteBufferThrowsIOOB(slice, slice.size() + 1, 0);
122             assertGetByteBufferThrowsIOOB(ds, ds.size(), 1);
123             assertGetByteBufferThrowsIOOB(slice, slice.size(), 1);
124             assertGetByteBufferThrowsIOOB(ds, ds.size() - 1, -1);
125             assertGetByteBufferThrowsIOOB(ds, slice.size() - 1, -1);
126         }
127     }
128 
129     @Test
testFeed()130     public void testFeed() throws Exception {
131         try (CloseableWithDataSource c = createDataSource("test1234")) {
132             DataSource ds = c.getDataSource();
133             assertFeedEquals("23", ds, 5, 2);
134             DataSource slice = ds.slice(1, 5); // "est12"
135             assertFeedEquals("t", slice, 2, 1);
136 
137             // Zero-length chunks
138             assertFeedEquals("", ds, 0, 0);
139             assertFeedEquals("", ds, 1, 0);
140             assertFeedEquals("", ds, ds.size() - 2, 0);
141             assertFeedEquals("", ds, ds.size() - 1, 0);
142             assertFeedEquals("", ds, ds.size(), 0);
143             assertFeedEquals("", slice, 0, 0);
144             assertFeedEquals("", slice, 2, 0);
145             assertFeedEquals("", slice, slice.size() - 2, 0);
146             assertFeedEquals("", slice, slice.size() - 1, 0);
147             assertFeedEquals("", slice, slice.size(), 0);
148 
149             // Invalid chunks
150             assertFeedThrowsIOOB(ds, -1, 0);
151             assertFeedThrowsIOOB(slice, -1, 0);
152             assertFeedThrowsIOOB(ds, -1, 2);
153             assertFeedThrowsIOOB(slice, -1, 2);
154             assertFeedThrowsIOOB(ds, -1, 10);
155             assertFeedThrowsIOOB(slice, -1, 10);
156             assertFeedThrowsIOOB(ds, 1, 10);
157             assertFeedThrowsIOOB(slice, 1, 10);
158             assertFeedThrowsIOOB(ds, ds.size() + 1, 0);
159             assertFeedThrowsIOOB(slice, slice.size() + 1, 0);
160             assertFeedThrowsIOOB(ds, ds.size(), 1);
161             assertFeedThrowsIOOB(slice, slice.size(), 1);
162             assertFeedThrowsIOOB(ds, ds.size() - 1, -1);
163             assertFeedThrowsIOOB(ds, slice.size() - 1, -1);
164         }
165     }
166 
167     @Test
testCopyTo()168     public void testCopyTo() throws Exception {
169         try (CloseableWithDataSource c = createDataSource("abcdefghijklmnop")) {
170             DataSource ds = c.getDataSource();
171             assertCopyToEquals("fgh", ds, 5, 3);
172             DataSource slice = ds.slice(2, 7); // "cdefghi"
173             assertCopyToEquals("efgh", slice, 2, 4);
174 
175             // Zero-length chunks
176             assertCopyToEquals("", ds, 0, 0);
177             assertCopyToEquals("", ds, 1, 0);
178             assertCopyToEquals("", ds, ds.size() - 2, 0);
179             assertCopyToEquals("", ds, ds.size() - 1, 0);
180             assertCopyToEquals("", ds, ds.size(), 0);
181             assertCopyToEquals("", slice, 0, 0);
182             assertCopyToEquals("", slice, 2, 0);
183             assertCopyToEquals("", slice, slice.size() - 2, 0);
184             assertCopyToEquals("", slice, slice.size() - 1, 0);
185             assertCopyToEquals("", slice, slice.size(), 0);
186 
187             // Invalid chunks
188             assertCopyToThrowsIOOB(ds, -1, 0);
189             assertCopyToThrowsIOOB(slice, -1, 0);
190             assertCopyToThrowsIOOB(ds, -1, 2);
191             assertCopyToThrowsIOOB(slice, -1, 2);
192             assertCopyToThrowsIOOB(ds, -1, 20);
193             assertCopyToThrowsIOOB(slice, -1, 20);
194             assertCopyToThrowsIOOB(ds, 1, 20);
195             assertCopyToThrowsIOOB(slice, 1, 20);
196             assertCopyToThrowsIOOB(ds, ds.size() + 1, 0);
197             assertCopyToThrowsIOOB(slice, slice.size() + 1, 0);
198             assertCopyToThrowsIOOB(ds, ds.size(), 1);
199             assertCopyToThrowsIOOB(slice, slice.size(), 1);
200             assertCopyToThrowsIOOB(ds, ds.size() - 1, -1);
201             assertCopyToThrowsIOOB(ds, slice.size() - 1, -1);
202 
203             // Destination buffer too small
204             ByteBuffer buf = ByteBuffer.allocate(5);
205             buf.position(2);
206             buf.limit(3);
207             assertCopyToThrowsBufferOverflow(ds, 0, 2, buf);
208             buf.position(2);
209             buf.limit(3);
210             assertCopyToThrowsBufferOverflow(slice, 1, 2, buf);
211 
212             // Destination buffer larger than chunk copied using copyTo
213             buf = ByteBuffer.allocate(10);
214             buf.position(2);
215             assertCopyToEquals("bcd", ds, 1, 3, buf);
216             buf = ByteBuffer.allocate(10);
217             buf.position(2);
218             assertCopyToEquals("fg", slice, 3, 2, buf);
219         }
220     }
221 
assertSliceEquals( String expectedContents, DataSource ds, long offset, int size)222     protected static void assertSliceEquals(
223             String expectedContents, DataSource ds, long offset, int size) throws IOException {
224         DataSource slice = ds.slice(offset, size);
225         assertEquals(size, slice.size());
226         assertGetByteBufferEquals(expectedContents, slice, 0, size);
227     }
228 
assertSliceThrowsIOOB(DataSource ds, long offset, int size)229     protected static void assertSliceThrowsIOOB(DataSource ds, long offset, int size) {
230         try {
231             ds.slice(offset, size);
232             fail();
233         } catch (IndexOutOfBoundsException expected) {}
234     }
235 
assertGetByteBufferEquals( String expectedContents, DataSource ds, long offset, int size)236     protected static void assertGetByteBufferEquals(
237             String expectedContents, DataSource ds, long offset, int size) throws IOException {
238         ByteBuffer buf = ds.getByteBuffer(offset, size);
239         assertEquals(0, buf.position());
240         assertEquals(size, buf.limit());
241         assertEquals(size, buf.capacity());
242         assertEquals(expectedContents, toString(buf));
243     }
244 
assertGetByteBufferThrowsIOOB(DataSource ds, long offset, int size)245     protected static void assertGetByteBufferThrowsIOOB(DataSource ds, long offset, int size)
246             throws IOException {
247         try {
248             ds.getByteBuffer(offset, size);
249             fail();
250         } catch (IndexOutOfBoundsException expected) {}
251     }
252 
assertFeedEquals( String expectedFedContents, DataSource ds, long offset, int size)253     protected static void assertFeedEquals(
254             String expectedFedContents, DataSource ds, long offset, int size) throws IOException {
255         ReadableDataSink out = DataSinks.newInMemoryDataSink(size);
256         ds.feed(offset, size, out);
257         assertEquals(size, out.size());
258         assertEquals(expectedFedContents, toString(out.getByteBuffer(0, size)));
259     }
260 
assertFeedThrowsIOOB(DataSource ds, long offset, long size)261     protected static void assertFeedThrowsIOOB(DataSource ds, long offset, long size)
262             throws IOException {
263         try {
264             ds.feed(offset, size, NullDataSink.INSTANCE);
265             fail();
266         } catch (IndexOutOfBoundsException expected) {}
267     }
268 
assertCopyToEquals( String expectedContents, DataSource ds, long offset, int size)269     protected static void assertCopyToEquals(
270             String expectedContents, DataSource ds, long offset, int size) throws IOException {
271         // Create a ByteBuffer backed by a section of a byte array. The ByteBuffer is on purpose not
272         // starting at offset 0 to catch issues to do with not checking ByteBuffer.arrayOffset().
273         byte[] arr = new byte[size + 10];
274         ByteBuffer buf = ByteBuffer.wrap(arr, 1, size + 5);
275         // Use non-zero position to catch issues with not checking buf.position()
276         buf.position(2);
277         // Buffer contains sufficient space for the requested copyTo operation
278         assertEquals(size + 4, buf.remaining());
279         assertCopyToEquals(expectedContents, ds, offset, size, buf);
280     }
281 
assertCopyToEquals( String expectedContents, DataSource ds, long offset, int size, ByteBuffer buf)282     private static void assertCopyToEquals(
283             String expectedContents, DataSource ds, long offset, int size, ByteBuffer buf)
284                     throws IOException {
285         int oldPosition = buf.position();
286         int oldLimit = buf.limit();
287         ds.copyTo(offset, size, buf);
288         // Position should've advanced by size whereas limit should've remained unchanged
289         assertEquals(oldPosition + size, buf.position());
290         assertEquals(oldLimit, buf.limit());
291 
292         buf.limit(buf.position());
293         buf.position(oldPosition);
294         assertEquals(expectedContents, toString(buf));
295     }
296 
assertCopyToThrowsIOOB(DataSource ds, long offset, int size)297     protected static void assertCopyToThrowsIOOB(DataSource ds, long offset, int size)
298             throws IOException {
299         ByteBuffer buf = ByteBuffer.allocate((size < 0) ? 0 : size);
300         try {
301             ds.copyTo(offset, size, buf);
302             fail();
303         } catch (IndexOutOfBoundsException expected) {}
304     }
305 
assertCopyToThrowsBufferOverflow( DataSource ds, long offset, int size, ByteBuffer buf)306     private static void assertCopyToThrowsBufferOverflow(
307             DataSource ds, long offset, int size, ByteBuffer buf) throws IOException {
308         try {
309             ds.copyTo(offset, size, buf);
310             fail();
311         } catch (BufferOverflowException expected) {}
312     }
313 
314     /**
315      * Returns the contents of the provided buffer as a string. The buffer's position and limit
316      * remain unchanged.
317      */
toString(ByteBuffer buf)318     static String toString(ByteBuffer buf) {
319         byte[] arr;
320         int offset;
321         int size = buf.remaining();
322         if (buf.hasArray()) {
323             arr = buf.array();
324             offset = buf.arrayOffset() + buf.position();
325         } else {
326             arr = new byte[buf.remaining()];
327             offset = 0;
328             int oldPos = buf.position();
329             buf.get(arr);
330             buf.position(oldPos);
331         }
332         return new String(arr, offset, size, StandardCharsets.UTF_8);
333     }
334 
335     public static class CloseableWithDataSource implements Closeable {
336         private final DataSource mDataSource;
337         private final Closeable mCloseable;
338 
CloseableWithDataSource(DataSource dataSource, Closeable closeable)339         private CloseableWithDataSource(DataSource dataSource, Closeable closeable) {
340             mDataSource = dataSource;
341             mCloseable = closeable;
342         }
343 
of(DataSource dataSource)344         public static CloseableWithDataSource of(DataSource dataSource) {
345             return new CloseableWithDataSource(dataSource, null);
346         }
347 
of(DataSource dataSource, Closeable closeable)348         public static CloseableWithDataSource of(DataSource dataSource, Closeable closeable) {
349             return new CloseableWithDataSource(dataSource, closeable);
350         }
351 
getDataSource()352         public DataSource getDataSource() {
353             return mDataSource;
354         }
355 
getCloseable()356         public Closeable getCloseable() {
357             return mCloseable;
358         }
359 
360         @Override
close()361         public void close() throws IOException {
362             if (mCloseable != null) {
363                 mCloseable.close();
364             }
365         }
366     }
367 
368     private static final class NullDataSink implements DataSink {
369         private static final NullDataSink INSTANCE = new NullDataSink();
370 
371         @Override
consume(byte[] buf, int offset, int length)372         public void consume(byte[] buf, int offset, int length) {}
373 
374         @Override
consume(ByteBuffer buf)375         public void consume(ByteBuffer buf) {}
376     }
377 }
378