/*
* Copyright 2010 Brian Pellin.
*
* This file is part of KeePassDroid.
*
* KeePassDroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* KeePassDroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid.stream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashedBlockOutputStream extends OutputStream {
private final static int DEFAULT_BUFFER_SIZE = 1024 * 1024;
private LEDataOutputStream baseStream;
private int bufferPos = 0;
private byte[] buffer;
private long bufferIndex = 0;
public HashedBlockOutputStream(OutputStream os) {
init(os, DEFAULT_BUFFER_SIZE);
}
public HashedBlockOutputStream(OutputStream os, int bufferSize) {
if ( bufferSize <= 0 ) {
bufferSize = DEFAULT_BUFFER_SIZE;
}
init(os, bufferSize);
}
private void init(OutputStream os, int bufferSize) {
baseStream = new LEDataOutputStream(os);
buffer = new byte[bufferSize];
}
@Override
public void write(int oneByte) throws IOException {
byte[] buf = new byte[1];
buf[0] = (byte)oneByte;
write(buf, 0, 1);
}
@Override
public void close() throws IOException {
if ( bufferPos != 0 ) {
// Write remaining buffered amount
WriteHashedBlock();
}
// Write terminating block
WriteHashedBlock();
flush();
baseStream.close();
}
@Override
public void flush() throws IOException {
baseStream.flush();
}
@Override
public void write(byte[] b, int offset, int count) throws IOException {
while ( count > 0 ) {
if ( bufferPos == buffer.length ) {
WriteHashedBlock();
}
int copyLen = Math.min(buffer.length - bufferPos, count);
System.arraycopy(b, offset, buffer, bufferPos, copyLen);
offset += copyLen;
bufferPos += copyLen;
count -= copyLen;
}
}
private void WriteHashedBlock() throws IOException {
baseStream.writeUInt(bufferIndex);
bufferIndex++;
if ( bufferPos > 0 ) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IOException("SHA-256 not implemented here.");
}
byte[] hash;
md.update(buffer, 0, bufferPos);
hash = md.digest();
/*
if ( bufferPos == buffer.length) {
hash = md.digest(buffer);
} else {
byte[] b = new byte[bufferPos];
System.arraycopy(buffer, 0, b, 0, bufferPos);
hash = md.digest(b);
}
*/
baseStream.write(hash);
} else {
// Write 32-bits of zeros
baseStream.writeLong(0L);
baseStream.writeLong(0L);
baseStream.writeLong(0L);
baseStream.writeLong(0L);
}
baseStream.writeInt(bufferPos);
if ( bufferPos > 0 ) {
baseStream.write(buffer, 0, bufferPos);
}
bufferPos = 0;
}
@Override
public void write(byte[] buffer) throws IOException {
write(buffer, 0, buffer.length);
}
}