Repository: commons-crypto Updated Branches: refs/heads/master b53228834 -> ea89d8023
http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/ea89d802/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java b/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java new file mode 100644 index 0000000..2591bd9 --- /dev/null +++ b/src/test/java/org/apache/commons/crypto/stream/PositionedCryptoInputStreamTest.java @@ -0,0 +1,384 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.crypto.stream; + +import org.apache.commons.crypto.cipher.CryptoCipher; +import org.apache.commons.crypto.cipher.CipherTransformation; +import org.apache.commons.crypto.cipher.JceCipher; +import org.apache.commons.crypto.cipher.OpensslCipher; +import org.apache.commons.crypto.stream.input.Input; +import org.apache.commons.crypto.utils.ReflectionUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Properties; +import java.util.Random; + +public class PositionedCryptoInputStreamTest { + + private final int dataLen = 20000; + private byte[] testData = new byte[dataLen]; + private byte[] encData; + private Properties props = new Properties(); + private byte[] key = new byte[16]; + private byte[] iv = new byte[16]; + int bufferSize = 2048; + int bufferSizeLess = bufferSize - 1; + int bufferSizeMore = bufferSize + 1; + int length = 1024; + int lengthLess = length - 1; + int lengthMore = length + 1; + + private final String jceCipherClass = JceCipher.class.getName(); + private final String opensslCipherClass = OpensslCipher.class.getName(); + private CipherTransformation transformation = + CipherTransformation.AES_CTR_NOPADDING; + + @Before + public void before() throws IOException { + Random random = new SecureRandom(); + random.nextBytes(testData); + random.nextBytes(key); + random.nextBytes(iv); + prepareData(); + } + + private void prepareData() throws IOException { + CryptoCipher cipher = null; + try { + cipher = (CryptoCipher)ReflectionUtils.newInstance( + ReflectionUtils.getClassByName(jceCipherClass), + props, transformation); + } catch (ClassNotFoundException cnfe) { + throw new IOException("Illegal crypto cipher!"); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // encryption data + OutputStream out = new CryptoOutputStream(baos, cipher, bufferSize, + new SecretKeySpec(key,"AES"), new IvParameterSpec(iv)); + out.write(testData); + out.flush(); + out.close(); + encData = baos.toByteArray(); + } + + public void setUp() throws IOException {} + + private PositionedCryptoInputStream getCryptoInputStream(CryptoCipher cipher, + int bufferSize) throws IOException { + return new PositionedCryptoInputStream(new PositionedInputForTest( + Arrays.copyOf(encData, encData.length)), cipher, bufferSize, key, iv, 0); + } + + @Test + public void doTest() throws Exception { + testCipher(jceCipherClass); + testCipher(opensslCipherClass); + } + + private void testCipher(String cipherClass) throws Exception { + doPositionedReadTests(cipherClass); + doReadFullyTests(cipherClass); + doSeekTests(cipherClass); + doMultipleReadTest(cipherClass); + } + + // when there are multiple positioned read actions and one read action, + // they will not interfere each other. + private void doMultipleReadTest(String cipherClass) throws Exception { + PositionedCryptoInputStream in = getCryptoInputStream( + getCipher(cipherClass), bufferSize); + int position = 0; + while (in.available() > 0) { + ByteBuffer buf = ByteBuffer.allocate(length); + byte[] bytes1 = new byte[length]; + byte[] bytes2 = new byte[lengthLess]; + // do the read and position read + int pn1 = in.read(position, bytes1, 0, length); + int n = in.read(buf); + int pn2 = in.read(position, bytes2, 0, lengthLess); + + // verify the result + if (pn1 > 0) { + compareByteArray(testData, position, bytes1, pn1); + } + + if (pn2 > 0) { + compareByteArray(testData, position, bytes2, pn2); + } + + if (n > 0) { + compareByteArray(testData, position, buf.array(), n); + position += n; + } else { + break; + } + } + in.close(); + } + + private void doPositionedReadTests(String cipherClass) throws Exception { + // test with different bufferSize when position = 0 + testPositionedReadLoop(cipherClass, 0, length, bufferSize, dataLen); + testPositionedReadLoop(cipherClass, 0, length, bufferSizeLess, dataLen); + testPositionedReadLoop(cipherClass, 0, length, bufferSizeMore, dataLen); + // test with different position when bufferSize = 2048 + testPositionedReadLoop(cipherClass, dataLen / 2, length, bufferSize, dataLen); + testPositionedReadLoop(cipherClass, dataLen / 2 - 1, length, + bufferSizeLess, dataLen); + testPositionedReadLoop(cipherClass, dataLen / 2 + 1, length, + bufferSizeMore, dataLen); + // position = -1 or position = max length, read nothing and return -1 + testPositionedReadNone(cipherClass, -1, length, bufferSize); + testPositionedReadNone(cipherClass, dataLen, length, bufferSize); + } + + private void doReadFullyTests(String cipherClass) throws Exception { + // test with different bufferSize when position = 0 + testReadFullyLoop(cipherClass, 0, length, bufferSize, dataLen); + testReadFullyLoop(cipherClass, 0, length, bufferSizeLess, dataLen); + testReadFullyLoop(cipherClass, 0, length, bufferSizeMore, dataLen); + // test with different length when position = 0 + testReadFullyLoop(cipherClass, 0, length, bufferSize, dataLen); + testReadFullyLoop(cipherClass, 0, lengthLess, bufferSize, dataLen); + testReadFullyLoop(cipherClass, 0, lengthMore, bufferSize, dataLen); + // test read fully failed + testReadFullyFailed(cipherClass, -1, length, bufferSize); + testReadFullyFailed(cipherClass, dataLen, length, bufferSize); + testReadFullyFailed(cipherClass, dataLen - length + 1, length, bufferSize); + } + + private void doSeekTests(String cipherClass) throws Exception { + // test with different length when position = 0 + testSeekLoop(cipherClass, 0, length, bufferSize); + testSeekLoop(cipherClass, 0, lengthLess, bufferSize); + testSeekLoop(cipherClass, 0, lengthMore, bufferSize); + // there should be none data read when position = dataLen + testSeekLoop(cipherClass, dataLen, length, bufferSize); + // test exception when position = -1 + testSeekFailed(cipherClass, -1, bufferSize); + } + + private void testSeekLoop(String cipherClass, int position, int length, + int bufferSize) throws Exception { + PositionedCryptoInputStream in = getCryptoInputStream( + getCipher(cipherClass), bufferSize); + while (in.available() > 0) { + in.seek(position); + ByteBuffer buf = ByteBuffer.allocate(length); + int n = in.read(buf); + if (n > 0) { + compareByteArray(testData, position, buf.array(), n); + position += n; + } else { + break; + } + } + in.close(); + } + + // test for the out of index position, eg, -1. + private void testSeekFailed(String cipherClass, int position, + int bufferSize) throws Exception { + PositionedCryptoInputStream in = getCryptoInputStream( + getCipher(cipherClass), bufferSize); + try { + in.seek(position); + Assert.fail("Excepted exception for cannot seek to negative offset."); + } catch (IllegalArgumentException iae) { + } + in.close(); + } + + private void testPositionedReadLoop(String cipherClass, int position, + int length, int bufferSize, int total) throws Exception { + PositionedCryptoInputStream in = getCryptoInputStream( + getCipher(cipherClass), bufferSize); + // do the position read until the end of data + while (position < total) { + byte[] bytes = new byte[length]; + int n = in.read(position, bytes, 0, length); + if (n >= 0) { + compareByteArray(testData, position, bytes, n); + position += n; + } else { + break; + } + } + in.close(); + } + + // test for the out of index position, eg, -1. + private void testPositionedReadNone(String cipherClass, int position, + int length, int bufferSize) throws Exception { + PositionedCryptoInputStream in = getCryptoInputStream( + getCipher(cipherClass), bufferSize); + byte[] bytes = new byte[length]; + int n = in.read(position, bytes, 0, length); + Assert.assertEquals(n, -1); + in.close(); + } + + private void testReadFullyLoop(String cipherClass,int position, + int length, int bufferSize, int total) throws Exception { + PositionedCryptoInputStream in = getCryptoInputStream( + getCipher(cipherClass), bufferSize); + + // do the position read full until remain < length + while (position + length <= total) { + byte[] bytes = new byte[length]; + in.readFully(position, bytes, 0, length); + compareByteArray(testData, position, bytes, length); + position += length; + } + + in.close(); + } + + // test for the End of file reached before reading fully + private void testReadFullyFailed(String cipherClass, int position, + int length, int bufferSize) throws Exception { + PositionedCryptoInputStream in = getCryptoInputStream( + getCipher(cipherClass), bufferSize); + byte[] bytes = new byte[length]; + try { + in.readFully(position, bytes, 0, length); + Assert.fail("Excepted EOFException."); + } catch (IOException ioe) { + // excepted exception + } + in.close(); + } + + // compare the data from pos with length and data2 from 0 with length + private void compareByteArray(byte[] data1, int pos, byte[] data2, int length) { + byte[] expectedData = new byte[length]; + byte[] realData = new byte[length]; + // get the expected data with the position and length + System.arraycopy(data1, pos, expectedData, 0, length); + // get the real data + System.arraycopy(data2, 0, realData, 0, length); + Assert.assertArrayEquals(expectedData, realData); + } + + private CryptoCipher getCipher(String cipherClass) throws IOException { + try { + return (CryptoCipher)ReflectionUtils.newInstance( + ReflectionUtils.getClassByName(cipherClass), props, transformation); + } catch (ClassNotFoundException cnfe) { + throw new IOException("Illegal crypto cipher!"); + } + } + + class PositionedInputForTest implements Input { + + byte[] data; + long pos; + long count; + + public PositionedInputForTest(byte[] data) { + this.data = data; + this.pos = 0; + this.count = data.length; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + int remaining = (int)(count - pos); + if(remaining <= 0) { + return -1; + } + + int length = Math.min(dst.remaining(), remaining); + dst.put(data, (int)pos, length); + pos += length; + return length; + } + + @Override + public long skip(long n) throws IOException { + if (n <= 0) { + return 0; + } + + long remaining = count - pos; + if(remaining < n) { + n = remaining; + } + pos += n; + + return n; + } + + @Override + public int read(long position, byte[] buffer, int offset, int length) + throws IOException { + if (buffer == null) { + throw new NullPointerException(); + } else if (offset < 0 || length < 0 || length > buffer.length - offset) { + throw new IndexOutOfBoundsException(); + } + + if (position < 0 || position >= count) { + return -1; + } + + long avail = count - position; + if (length > avail) { + length = (int)avail; + } + if (length <= 0) { + return 0; + } + System.arraycopy(data, (int)position, buffer, offset, length); + return length; + } + + @Override + public void seek(long position) throws IOException { + if (pos < 0) { + throw new IOException("Negative seek offset"); + } else if (position >= 0 && position < count) { + pos = position; + } else { + // to the end of file + pos = count; + } + } + + @Override + public void close() throws IOException { + } + + @Override + public int available() throws IOException { + return (int)(count - pos); + } + } +}