/* * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ package com.facebook.crypto.streams; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; /** * An input stream that always maintains a tail of fixed length. */ public class TailInputStream extends FilterInputStream { private final byte[] mTail; private final int mTailTength; private int mCount; private boolean mEOF; protected TailInputStream(InputStream in, int tailLength) { super(in); mTail = new byte[tailLength]; mTailTength = tailLength; } @Override public int read() throws IOException { byte[] buffer = new byte[1]; int read = read(buffer, 0, 1); while (read == 0) { read = read(buffer, 0, 1); } if (read == -1) { return -1; } else { return buffer[0] & 0xFF; } } @Override public int read(byte[] buffer, int offset, int count) throws IOException { if (mEOF) { return -1; } if (count == 0) { return 0; } int read = 0; while (read == 0) { read = readTail(buffer, offset, count); } return read; } /** * Tries to read data from the delegate input stream into the buffer while extracting * a tail from it. */ private int readTail(byte[] buffer, int offset, int count) throws IOException { if (count >= mCount) { int remain = count - mCount; int readBytes = in.read(buffer, offset + mCount, remain); if (readBytes == -1) { mEOF = true; return -1; } if (mCount > 0) { System.arraycopy(mTail, 0, buffer, offset, mCount); } int dataInBuffer = mCount + readBytes; int tailBytes = in.read(mTail, 0, mTailTength); if (tailBytes == -1) { mEOF = true; tailBytes = 0; } return extractTail(buffer, dataInBuffer, tailBytes, offset); } else { // count < mCount int newLength = mCount - count; System.arraycopy(mTail, 0, buffer, offset, count); System.arraycopy(mTail, count, mTail, 0, newLength); int tailBytes = in.read(mTail, newLength, mTailTength - newLength); if (tailBytes == -1) { // reverse the copy. System.arraycopy(mTail, 0, mTail, count, newLength); System.arraycopy(buffer, offset, mTail, 0, count); mEOF = true; return -1; } else { return extractTail(buffer, count, tailBytes + newLength, offset); } } } /** * Constructs the largest tail we can extract given the state of the buffers by attempting * to back-fill the tail buffer with bytes from the readBuffer. * @param readBuffer The buffer supplied by the client. * @param bytesInBuffer The number of bytes currently in the readBuffer. * @param tailBytes The number of bytes currently in the tail buffer. * @param bufferOffset The current offset in the readBuffer. * @return number of bytes read into the readBuffer. */ private int extractTail(byte[] readBuffer, int bytesInBuffer, int tailBytes, int bufferOffset) { int toFill = mTailTength - tailBytes; int tailOffsetInBuffer = Math.max(0, bytesInBuffer - toFill) + bufferOffset; int bytesToCopy = Math.min(toFill, bytesInBuffer); if (bytesToCopy > 0) { if (tailBytes > 0) { System.arraycopy(mTail, 0, mTail, bytesToCopy, tailBytes); } System.arraycopy(readBuffer, tailOffsetInBuffer, mTail, 0, bytesToCopy); } mCount = bytesToCopy + tailBytes; return tailOffsetInBuffer - bufferOffset; } @Override public boolean markSupported() { return false; } public byte[] getTail() throws IOException { if (mCount != mTailTength) { throw new IOException("Not enough tail data"); } return mTail; } }