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