package org.limewire.io; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.security.MessageDigest; /** * Writes in periodic intervals a checksum of the written bytes to the output * stream. {@link SecureInputStream} can verify whether or not the bytes are * still valid. */ public class SecureOutputStream extends FilterOutputStream { private final MessageDigest md; private final int dataSize; private int count = 0; private boolean open = true; public SecureOutputStream(OutputStream out) throws IOException { this(out, new CRC32MessageDigest(), 512); } public SecureOutputStream(OutputStream out, MessageDigest md) throws IOException { this(out, md, 512); } public SecureOutputStream(OutputStream out, int blockSize) throws IOException { this(out, new CRC32MessageDigest(), blockSize); } public SecureOutputStream(OutputStream out, MessageDigest md, int blockSize) throws IOException { super(out); if (md == null) { throw new NullPointerException("MessageDigest is null"); } // Make sure the digest length is defined int digestLength = md.getDigestLength(); if (digestLength == 0) { throw new IllegalArgumentException("Digest length is undefined"); } // Block size must be greater than zero if (blockSize <= 0) { throw new IllegalArgumentException("Illegal block size: " + blockSize); } // Block size must be greater than digest length if (blockSize <= digestLength) { throw new IllegalArgumentException("Block size must be greater than digest length: " + blockSize + " <= " + digestLength); } // Create the header ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.writeUTF(md.getAlgorithm()); dos.writeInt(md.getDigestLength()); dos.writeInt(blockSize); dos.close(); byte[] header = baos.toByteArray(); // Write the length of the header out.write((header.length >> 24) & 0xFF); out.write((header.length >> 16) & 0xFF); out.write((header.length >> 8) & 0xFF); out.write((header.length ) & 0xFF); // Write the header out.write(header, 0, header.length); // Write the digest of the header md.update(header, 0, header.length); byte[] digest = md.digest(); out.write(digest, 0, digest.length); md.reset(); this.md = md; // [data] + [checksum] == blockSize this.dataSize = blockSize - digestLength; } /** * Returns the block (buffer) size of the stream. */ public int getBlockSize() { return dataSize + md.getDigestLength(); } /** * Returns the MessageDigest. */ public MessageDigest getMessageDigest() { return md; } private void digest() throws IOException { byte[] digest = md.digest(); out.write(digest, 0, digest.length); md.reset(); } @Override public void write(int b) throws IOException { if (!open) { throw new IOException("Stream is closed"); } super.write(b); md.update((byte)(b & 0xFF)); count++; if (count % dataSize == 0) { digest(); } } @Override public void close() throws IOException { if (open) { open = false; // Write the digest for the last bytes if (count % dataSize != 0) { digest(); } } super.close(); } }