package freenet.crypt;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import freenet.client.async.ReadBucketAndFreeInputStream;
import freenet.support.api.Bucket;
import freenet.support.api.BucketFactory;
import freenet.support.io.PrependLengthOutputStream;
/** Simple utility to check and write checksums. */
public abstract class ChecksumChecker {
/** Get the length of the checksum */
public abstract int checksumLength();
/** Get an OutputStream that will write a checksum when closed. It will not close the
* underlying stream. */
public abstract OutputStream checksumWriter(OutputStream os, int skipPrefix);
public OutputStream checksumWriter(OutputStream os) {
return checksumWriter(os, 0);
}
/** Get an OutputStream that will write to a temporary Bucket, append a checksum and prepend a
* length.
* @param os The underlying stream, which will not be closed.
* @param bf Used to allocate temporary storage.
* @throws IOException
*/
public PrependLengthOutputStream checksumWriterWithLength(final OutputStream dos, BucketFactory bf) throws IOException {
return PrependLengthOutputStream.create(checksumWriter(dos, 8), bf, 0, true);
}
public abstract byte[] appendChecksum(byte[] data);
/** Verify a checksum or throw */
public void verifyChecksum(byte[] data, int offset, int length, byte[] checksum) throws ChecksumFailedException {
if(!checkChecksum(data, offset, length, checksum)) throw new ChecksumFailedException();
}
/** Verify a checksum or report.
* @return True if the checksum is correct, false otherwise. */
public abstract boolean checkChecksum(byte[] data, int offset, int length, byte[] checksum);
public abstract byte[] generateChecksum(byte[] bufToChecksum, int offset, int length);
public byte[] generateChecksum(byte[] bufToChecksum) {
return generateChecksum(bufToChecksum, 0, bufToChecksum.length);
}
public abstract int getChecksumTypeID();
// Checksum IDs.
// FIXME use an enum when we are creating them from ID's.
public static final int CHECKSUM_CRC = 1;
/** Copy bytes from one stream to another, verifying and stripping the final checksum.
* @throws IOException
* @throws ChecksumFailedException */
public abstract void copyAndStripChecksum(InputStream is, OutputStream os, long length) throws IOException, ChecksumFailedException;
/** Read from disk and verify the checksum that follows the data. If it throws, the buffer will
* be zero'ed out. */
public abstract void readAndChecksum(DataInput is, byte[] buf, int offset, int length) throws IOException, ChecksumFailedException;
public InputStream checksumReaderWithLength(InputStream dis, BucketFactory bf, long maxLength) throws IOException, ChecksumFailedException {
// IMHO it is better to implement this with copying, because then we don't start
// constructing objects from bad data...
long length = new DataInputStream(dis).readLong();
if(length < 0 || length > maxLength) throw new IOException("Bad length");
final Bucket bucket = bf.makeBucket(-1);
OutputStream os = bucket.getOutputStream();
copyAndStripChecksum(dis, os, length);
os.close();
return ReadBucketAndFreeInputStream.create(bucket);
}
public void writeAndChecksum(OutputStream os, byte[] buf, int offset, int length) throws IOException {
os.write(buf, offset, length);
os.write(generateChecksum(buf, offset, length));
}
public void writeAndChecksum(ObjectOutputStream oos, byte[] buf) throws IOException {
writeAndChecksum(oos, buf, 0, buf.length);
}
public int lengthAndChecksumOverhead() {
return 8 + checksumLength();
}
/** Create a ChecksumChecker of the specified type.
* @param checksumID The checksum type.
* @return A ChecksumChecker of the given type.
* @throws IllegalArgumentException If there is no ChecksumChecker for that ID.
*/
public static ChecksumChecker create(int checksumID) {
if(checksumID == CHECKSUM_CRC)
return new CRCChecksumChecker();
else
throw new IllegalArgumentException("Bad checksum ID");
}
}