package jp;

import java.io.*;
import java.security.*;
import sdsi.*;

/**
 * A DigestInputStream is a convenience filter for taking the MD5 (or
 * other digest) of a data stream as it flows from source to sink,
 * and ensuring that it matches an expected digest (hash) value. 
 *
 * @classConcise true
 * @author jonh@cs.dartmouth.edu
 */
public class DigestInputStream
	extends FilterInputStream {

	protected MessageDigest md;
	protected Hash expectedHash;
//	FileOutputStream debug;

	/**
	 * Install a digest-checker on a stream.  @param stream the source
	 * of bytes to digest @param expectedHash the expected hash value
	 * of the complete stream
	 */
	public DigestInputStream(InputStream stream, Hash expectedHash) {
		super(stream);
		this.expectedHash = expectedHash;
		try {
			md = MessageDigest.getInstance("md5");
				// TODO: actually bother to match hash type in expectedHash
//			debug = new FileOutputStream("/tmp/received-file");
//		} catch (FileNotFoundException ex) {
//			throw new RuntimeException(ex.toString());
		} catch (NoSuchAlgorithmException ex) {
			throw new RuntimeException(ex.toString());
		}
	}

	/**
	 * Call this method at the end of the stream to verify that the
	 * hash matches the expected one.
	 *
	 * @throws DigestStreamException if the hashes do not match.
	 */
	void checkHash()
		throws DigestStreamException {
		Hash computedHash = new Hash("md5", md.digest());
		if (!computedHash.equals(expectedHash)) {
			System.out.println(computedHash.toReadableString(72));
			System.out.println(expectedHash.toReadableString(72));
			throw new DigestStreamException("Hashes don't agree");
		}
	}

	/**
	 * When the stream is closed, this overriding method will
	 * automatically check the stream's digest.
	 *
	 * @throws DigestStreamException if the hashes do not match.
	 */
	public void close()
		throws java.io.IOException {
		checkHash();
		super.close();
	}

	public int read()
		throws java.io.IOException {
		int rc = super.read();
		if (rc==-1) {
			checkHash();
		} else {
			md.update((byte) rc);
//			debug.write(rc);
		}
		return rc;
	}

	public int read(byte[] p0)
		throws java.io.IOException {
		int rc = super.read(p0);
		if (rc<=0) {
			checkHash();
		} else {
			md.update(p0, 0, rc);
//			debug.write(p0, 0, rc);
		}
		return rc;
	}

	public int read(byte[] p0, int p1, int p2)
		throws java.io.IOException {
		int rc = super.read(p0, p1, p2);
		if (rc<=0) {
			checkHash();
		} else {
			md.update(p0, p1, rc);
//			debug.write(p0, p1, rc);
		}
		return rc;
	}

	public synchronized void reset()
		throws java.io.IOException {
		throw new DigestStreamException("reset() prevents hash verification.");
		// could keep track of location in file...
	}

	public long skip(long p0)
		throws java.io.IOException {
		throw new DigestStreamException("skip() prevents hash verification.");
		// could read() the bytes to get them hashed...
	}
}
