added digest channel impl/tests
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.Channel;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The base class for a hashing Java NIO ByteChannel.
|
||||
*
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
public class AbstractDigestByteChannel implements Channel {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractDigestByteChannel.class);
|
||||
|
||||
private final DigestBuffer digest;
|
||||
private final Channel channel;
|
||||
|
||||
private boolean closed = false;
|
||||
|
||||
public AbstractDigestByteChannel(Channel channel, DigestParameters dparams) throws NoSuchAlgorithmException {
|
||||
this.digest = new DigestBuffer(dparams);
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
protected DigestBuffer getDigest() {
|
||||
return this.digest;
|
||||
}
|
||||
|
||||
public int getHashSize() {
|
||||
return this.digest.getMessageDigest().getDigestLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of bytes read as input for this channel.
|
||||
*
|
||||
* @return A number of bytes; 0 if not yet read; never negative
|
||||
*/
|
||||
public long getTotalBytesRead() {
|
||||
return this.digest.getTotalBytesRead();
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of bytes written as output for this channel.
|
||||
*
|
||||
* @return A number of bytes; 0 if not yet read; never negative
|
||||
*/
|
||||
public long getTotalBytesWritten() {
|
||||
return this.digest.getTotalBytesWritten();
|
||||
}
|
||||
|
||||
/**
|
||||
* This channel is open. If the underlying channel is closed, this channel
|
||||
* is considered not open.
|
||||
*
|
||||
* @return true if open; false if closed; default state is open
|
||||
*/
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return this.channel.isOpen() && !this.closed;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method marks this channel as closed. It does not close the
|
||||
* underlying channel. This allows for mid-channel hashing use cases.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!this.closed && Status.Started.equals(this.getDigest().getStatus()))
|
||||
this.logger.warn("A channel was closed before it was properly finished");
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
}
|
100
src/main/java/com/inteligr8/nio/HashingReadableByteChannel.java
Normal file
100
src/main/java/com/inteligr8/nio/HashingReadableByteChannel.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.security.DigestException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class HashingReadableByteChannel extends AbstractDigestByteChannel implements ReadableByteChannel {
|
||||
|
||||
protected final ReadableByteChannel rbchannel;
|
||||
|
||||
private int chunkSize;
|
||||
protected ByteBuffer readBuffer;
|
||||
|
||||
public HashingReadableByteChannel(ReadableByteChannel rbchannel, DigestParameters dparams) throws NoSuchAlgorithmException {
|
||||
super(rbchannel, dparams);
|
||||
this.rbchannel = rbchannel;
|
||||
this.setChunkSize(16384);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method overrides the default chunk size for reading this channel
|
||||
* into buffers. The lower the value, the less memory usage. The higher
|
||||
* the value, the higher performance.
|
||||
*
|
||||
* @param chunkSize A number of bytes; defaults to 16KB
|
||||
*/
|
||||
public void setChunkSize(int chunkSize) {
|
||||
if (this.readBuffer != null && this.readBuffer.hasRemaining())
|
||||
throw new IllegalStateException("You cannot change the buffer size while the buffer is actively used");
|
||||
this.chunkSize = chunkSize;
|
||||
this.readBuffer = ByteBuffer.allocate(this.chunkSize);
|
||||
this.readBuffer.flip(); // ready for reading by default
|
||||
}
|
||||
|
||||
public long getRawBytesRead() {
|
||||
return this.getTotalBytesRead();
|
||||
}
|
||||
|
||||
public long getHashBytesWritten() {
|
||||
return this.getTotalBytesWritten();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads and hashes the underlying channel until it is empty.
|
||||
* It writes the hash to the specified buffer.
|
||||
*
|
||||
* @param buffer A NIO buffer ready for writing
|
||||
* @return The number of bytes read from the underlying channel; -1 for EOF; 0 is possible and should be re-read
|
||||
* @throws IOException An I/O or crypto exception occurred
|
||||
*/
|
||||
@Override
|
||||
public int read(ByteBuffer buffer) throws IOException {
|
||||
try {
|
||||
return this._read(buffer);
|
||||
} catch (DigestException de) {
|
||||
throw new IOException("This should never happen", de);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads and hashes the underlying channel until it is empty.
|
||||
* It writes the hash to the specified buffer.
|
||||
*
|
||||
* @param buffer A NIO buffer ready for writing
|
||||
* @return The number of bytes read from the underlying channel; -1 for EOF; 0 is possible and should be re-read
|
||||
* @throws IOException An I/O or hashing exception occurred
|
||||
*/
|
||||
protected int _read(ByteBuffer buffer) throws IOException, DigestException {
|
||||
int totalBytesRead = 0;
|
||||
|
||||
this.readBuffer.compact(); // ready for writing
|
||||
int bytesRead = this.rbchannel.read(this.readBuffer);
|
||||
this.readBuffer.flip(); // ready for reading
|
||||
|
||||
if (!Status.Finished.equals(this.getDigest().getStatus())) {
|
||||
do {
|
||||
if (bytesRead > 0)
|
||||
totalBytesRead += bytesRead;
|
||||
|
||||
this.getDigest().stream(this.readBuffer);
|
||||
|
||||
this.readBuffer.compact(); // ready for writing
|
||||
bytesRead = this.rbchannel.read(this.readBuffer);
|
||||
this.readBuffer.flip(); // ready for reading
|
||||
} while (bytesRead > 0);
|
||||
}
|
||||
|
||||
if (bytesRead > 0)
|
||||
totalBytesRead += bytesRead;
|
||||
|
||||
if (totalBytesRead > 0)
|
||||
// something read from underlying channel
|
||||
return totalBytesRead;
|
||||
|
||||
return this.getDigest().flush(buffer) ? 0 : -1;
|
||||
}
|
||||
|
||||
}
|
113
src/main/java/com/inteligr8/nio/HashingWritableByteChannel.java
Normal file
113
src/main/java/com/inteligr8/nio/HashingWritableByteChannel.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.security.DigestException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
|
||||
public class HashingWritableByteChannel extends AbstractDigestByteChannel implements WritableByteChannel, Flushable {
|
||||
|
||||
protected final WritableByteChannel wbchannel;
|
||||
|
||||
public HashingWritableByteChannel(WritableByteChannel wbchannel, DigestParameters dparams) throws NoSuchAlgorithmException {
|
||||
super(wbchannel, dparams);
|
||||
this.wbchannel = wbchannel;
|
||||
}
|
||||
|
||||
public long getRawBytesRead() {
|
||||
return this.getTotalBytesRead();
|
||||
}
|
||||
|
||||
public long getHashBytesWritten() {
|
||||
return this.getTotalBytesWritten();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method hashes to the underlying channel until the specified buffer
|
||||
* is empty.
|
||||
*
|
||||
* @param buffer A NIO buffer ready for reading
|
||||
* @return The number of bytes written to the underlying channel; never negative
|
||||
* @throws IOException An I/O or hashing exception occurred
|
||||
*/
|
||||
@Override
|
||||
public int write(ByteBuffer buffer) throws IOException {
|
||||
try {
|
||||
return this._write(buffer);
|
||||
} catch (DigestException de) {
|
||||
throw new IOException("This should never happen", de);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
try {
|
||||
this._flush();
|
||||
} catch (DigestException de) {
|
||||
throw new IOException("This should never happen", de);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method flushes and closes this channel.
|
||||
*
|
||||
* @throws IOException An I/O or crypto exception occurred
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.flush();
|
||||
super.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method hashes and writes to the underlying channel until the
|
||||
* specified buffer is empty.
|
||||
*
|
||||
* @param buffer A NIO buffer ready for reading
|
||||
* @return The number of bytes written to the underlying channel; never negative
|
||||
* @throws IOException An I/O or hashing exception occurred
|
||||
* @throws DigestException
|
||||
*/
|
||||
protected int _write(ByteBuffer buffer) throws IOException, DigestException {
|
||||
if (!this.isOpen())
|
||||
return 0;
|
||||
|
||||
int bytesToWrite = buffer.remaining();
|
||||
this.getDigest().stream(buffer);
|
||||
return bytesToWrite - buffer.remaining();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method encrypts or decrypts and writes to the underlying channel
|
||||
* until the internal caches and buffers are empty.
|
||||
*
|
||||
* @return The number of bytes written to the underlying channel; never negative
|
||||
* @throws IOException An I/O or crypto exception occurred
|
||||
* @throws IllegalBlockSizeException
|
||||
* @throws BadPaddingException
|
||||
*/
|
||||
protected int _flush() throws IOException, DigestException {
|
||||
if (!this.isOpen())
|
||||
return 0;
|
||||
|
||||
int totalBytesWritten = 0;
|
||||
|
||||
// ready for writing
|
||||
ByteBuffer writeBuffer = ByteBuffer.allocate(this.getDigest().getMessageDigest().getDigestLength());
|
||||
|
||||
while (this.getDigest().flush(writeBuffer))
|
||||
;
|
||||
|
||||
writeBuffer.flip(); // ready for reading
|
||||
totalBytesWritten += this.wbchannel.write(writeBuffer);
|
||||
writeBuffer.compact(); // ready for writing
|
||||
|
||||
return totalBytesWritten;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public abstract class AbstractHashingByteChannelUnitTest {
|
||||
|
||||
private final Charset charset = Charset.forName("utf-8");
|
||||
|
||||
public Charset getCharset() {
|
||||
return this.charset;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empty() throws Exception {
|
||||
this.test("SHA-256", "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lessThan8b() throws Exception {
|
||||
this.test("SHA-1", "Hello", "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exactly8b() throws Exception {
|
||||
this.test("MD5", "Hi there", "d9385462d3deff78c352ebb3f941ce12");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void between8b16b() throws Exception {
|
||||
this.test("SHA-512", "Hello World!", "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exactly16b() throws Exception {
|
||||
this.test("sha-384", "Hello over there", "3f8b70c00fed6d8c44434508acb99671f15118854880d50d1f997951a4e40d99fb4451e8c23975f02c274aacf6253570");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void between16b32b() throws Exception {
|
||||
this.test("sha-256", "Howdy y'all compadres!", "f0e28418a2ccab2254997b1a78537e900e0368e6472470e99a1e858e04cf42d3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void moreThan32b() throws Exception {
|
||||
this.test("sha-256", "Hello to all and to all a good night", "277f6a3626c62240cebced1ae279e3cc49bf1cf3a6ffdc0ae5709033e4e3044e");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void javaDigestBuffer() throws Exception {
|
||||
this.test("sha-256", new File("src/main/java/com/inteligr8/nio/DigestBuffer.java"), "d01c874ffb0c6633a5d6ff4537da36c44d50af856970abddc1215866b0d08f44");
|
||||
}
|
||||
|
||||
public void test(String algorithm, String text, String hex) throws Exception {
|
||||
ByteBufferChannel bbchannel = new ByteBufferChannel(1024);
|
||||
bbchannel.write(this.getCharset().encode(text));
|
||||
this.test(algorithm, bbchannel, hex);
|
||||
}
|
||||
|
||||
public void test(String algorithm, File file, String hex) throws Exception {
|
||||
FileChannel fchannel = FileChannel.open(file.toPath());
|
||||
this.test(algorithm, fchannel, hex);
|
||||
}
|
||||
|
||||
public abstract void test(String algorithm, ByteChannel bchannel, String hex) throws Exception;
|
||||
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.inteligr8.nio;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.junit.Assert;
|
||||
|
||||
public class HashingReadableByteChannelUnitTest extends AbstractHashingByteChannelUnitTest {
|
||||
|
||||
@Override
|
||||
public void test(String algorithm, ByteChannel bchannel, String hex) throws Exception {
|
||||
this.test(algorithm, (ReadableByteChannel)bchannel, hex);
|
||||
}
|
||||
|
||||
public void test(String algorithm, ReadableByteChannel rbchannel, String hex) throws Exception {
|
||||
HashingReadableByteChannel hrbchannel = new HashingReadableByteChannel(rbchannel, new DigestParameters(algorithm));
|
||||
try {
|
||||
ByteBuffer bbuffer = ByteBuffer.allocate(hrbchannel.getHashSize());
|
||||
|
||||
while (hrbchannel.read(bbuffer) >= 0)
|
||||
;
|
||||
|
||||
Assert.assertEquals(hrbchannel.getHashSize(), hrbchannel.getHashBytesWritten());
|
||||
|
||||
bbuffer.flip();
|
||||
byte[] bytes = new byte[bbuffer.remaining()];
|
||||
bbuffer.get(bytes);
|
||||
Assert.assertEquals(hex, new String(Hex.encodeHex(bytes)));
|
||||
} finally {
|
||||
hrbchannel.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user