added digest channel impl/tests

This commit is contained in:
2021-02-22 09:57:09 -05:00
parent 86d546fd21
commit 9be0f5f81b
5 changed files with 397 additions and 0 deletions

View File

@@ -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;
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}
}