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 */ 41 protected abstract CloseableWithDataSource createDataSource(byte[] contents) throws IOException; 42 43 protected CloseableWithDataSource createDataSource(String contents) throws IOException { 44 return createDataSource(contents.getBytes(StandardCharsets.UTF_8)); 45 } 46 47 @Test 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 */ 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 339 private CloseableWithDataSource(DataSource dataSource, Closeable closeable) { 340 mDataSource = dataSource; 341 mCloseable = closeable; 342 } 343 344 public static CloseableWithDataSource of(DataSource dataSource) { 345 return new CloseableWithDataSource(dataSource, null); 346 } 347 348 public static CloseableWithDataSource of(DataSource dataSource, Closeable closeable) { 349 return new CloseableWithDataSource(dataSource, closeable); 350 } 351 352 public DataSource getDataSource() { 353 return mDataSource; 354 } 355 356 public Closeable getCloseable() { 357 return mCloseable; 358 } 359 360 @Override 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 372 public void consume(byte[] buf, int offset, int length) {} 373 374 @Override 375 public void consume(ByteBuffer buf) {} 376 } 377 } 378