main.java.com.amazonaws.services.s3.internal.crypto.CipherLiteInputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aws-android-sdk-s3 Show documentation
Show all versions of aws-android-sdk-s3 Show documentation
The AWS Android SDK for Amazon S3 module holds the client classes that are used for communicating with Amazon Simple Storage Service
/*
* Copyright 2013-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 com.amazonaws.services.s3.internal.crypto;
import java.io.IOException;
import java.io.InputStream;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import com.amazonaws.internal.SdkFilterInputStream;
/**
* @author Hanson Char
*
* @see CipherLite
* @see GCMCipherLite
*/
public final class CipherLiteInputStream extends SdkFilterInputStream {
private static final int MAX_RETRY = 1000;
private static final int DEFAULT_IN_BUFFER_SIZE = 512;
private final CipherLite cipherLite;
/**
* True if this input stream is currently involved in a multipart uploads;
* false otherwise. For multipart uploads, the doFinal method if the
* underlying cipher has to be triggered via the read methods rather than
* the close method, since we cann't tell if closing the input stream is due
* to a recoverable error (in which case the cipher's doFinal method should
* never be called) or normal completion (where the cipher's doFinal method
* would need to be called if it was not a multipart upload).
*/
private final boolean multipart;
private boolean eof = false;
private byte[] bufin;
private byte[] bufout;
private int curr_pos = 0;
private int max_pos = 0;
public CipherLiteInputStream(InputStream is, CipherLite cipherLite) {
this(is, cipherLite, DEFAULT_IN_BUFFER_SIZE, false);
}
public CipherLiteInputStream(InputStream is, CipherLite c, int buffsize) {
this(is, c, buffsize, false);
}
public CipherLiteInputStream(InputStream is, CipherLite c, int buffsize,
boolean multipart) {
super(is);
this.multipart = multipart;
this.cipherLite = c;
if (buffsize <= 0 || (buffsize % DEFAULT_IN_BUFFER_SIZE) != 0) {
throw new IllegalArgumentException("buffsize (" + buffsize
+ ") must be a positive multiple of "
+ DEFAULT_IN_BUFFER_SIZE);
}
this.bufin = new byte[buffsize];
}
protected CipherLiteInputStream(InputStream is) {
this(is, CipherLite.Null, DEFAULT_IN_BUFFER_SIZE, false);
}
@Override public int read() throws IOException {
if (curr_pos >= max_pos) {
if (eof)
return -1;
int count = 0;
int len;
do {
if (count > MAX_RETRY)
throw new IOException("exceeded maximum number of attempts to read next chunk of data");
len = nextChunk();
count++;
} while (len == 0);
if (len == -1)
return -1;
}
return ((int) bufout[curr_pos++] & 0xFF);
};
@Override public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte buf[], int off, int target_len) throws IOException {
if (curr_pos >= max_pos) {
// all buffered data has been read, let's get some more
if (eof)
return -1;
int count=0;
int len;
do {
if (count > MAX_RETRY)
throw new IOException("exceeded maximum number of attempts to read next chunk of data");
len = nextChunk();
count++;
} while (len == 0);
if (len == -1)
return -1;
}
if (target_len <= 0)
return 0;
int len = max_pos - curr_pos;
if (target_len < len)
len = target_len;
// if buf == null, will throw NPE as intended per javadoc
System.arraycopy(bufout, curr_pos, buf, off, len);
curr_pos += len;
return len;
}
@Override public long skip(long n) throws IOException {
abortIfNeeded();
int available = max_pos - curr_pos;
if (n > available)
n = available;
if (n < 0)
return 0;
curr_pos += n;
return n;
}
@Override public int available() {
abortIfNeeded();
return max_pos - curr_pos;
}
@Override public void close() throws IOException {
in.close();
// For multipart upload the doFinal has to be triggered via the read
// methods, since we cann't tell if the close is due to error or normal
// completion.
if (!multipart) {
if (!S3CryptoScheme.isAesGcm(cipherLite.getCipherAlgorithm())) {
try {
// simulate the RI: throw away the unprocessed data
cipherLite.doFinal();
} catch (BadPaddingException ex) {
} catch (IllegalBlockSizeException ex) {
}
}
}
curr_pos = max_pos = 0;
abortIfNeeded();
}
@Override
public boolean markSupported() {
abortIfNeeded();
return in.markSupported() && cipherLite.markSupported();
}
@Override
public void mark(int readlimit) {
abortIfNeeded();
in.mark(readlimit);
cipherLite.mark();
}
@Override
public void reset() throws IOException {
abortIfNeeded();
in.reset();
cipherLite.reset();
if (markSupported()) {
curr_pos = max_pos = 0;
eof = false;
}
}
/**
* Reads and process the next chunk of data into memory.
*
* @return the length of the data chunk read and processed, or -1 if end of
* stream.
* @throws IOException
* if there is an IO exception from the underlying input stream
*
* @throws SecurityException
* if there is authentication failure
*/
private int nextChunk() throws IOException {
abortIfNeeded();
if (eof)
return -1;
int len = in.read(bufin);
if (len == -1) {
eof = true;
try {
bufout = cipherLite.doFinal();
} catch (IllegalBlockSizeException ignore) {
// like the RI
} catch (BadPaddingException e) {
if (S3CryptoScheme.isAesGcm(cipherLite.getCipherAlgorithm()))
throw new SecurityException(e);
}
if (bufout == null) {
return -1;
}
curr_pos = 0;
return max_pos = bufout.length;
}
try {
bufout = cipherLite.update(bufin, 0, len);
} catch (IllegalStateException ignore) {
// like the RI
}
curr_pos = 0;
return max_pos = (bufout == null ? 0 : bufout.length);
}
}