diff --git a/src/com/webcodepro/shrinkit/ByteSource.java b/src/com/webcodepro/shrinkit/ByteSource.java index a68016a..945679b 100644 --- a/src/com/webcodepro/shrinkit/ByteSource.java +++ b/src/com/webcodepro/shrinkit/ByteSource.java @@ -13,6 +13,8 @@ import java.util.GregorianCalendar; */ public class ByteSource implements ByteConstants { private InputStream inputStream; + private long bytesRead = 0; + private CRC16 crc = new CRC16(); /** * Construct a ByteSource from an InputStream. @@ -33,7 +35,12 @@ public class ByteSource implements ByteConstants { * Note that an unsigned byte needs to be returned in a larger container (ie, a short or int or long). */ public int read() throws IOException { - return inputStream.read(); + int b = inputStream.read(); + if (b != -1) { + crc.update(b); + bytesRead++; + } + return b; } /** * Get the next byte and fail if we are at EOF. @@ -51,9 +58,11 @@ public class ByteSource implements ByteConstants { public byte[] readBytes(int bytes) throws IOException { byte[] data = new byte[bytes]; int read = inputStream.read(data); + bytesRead+= read; if (read < bytes) { throw new IOException("Requested " + bytes + " bytes, but " + read + " read"); } + crc.update(data); return data; } @@ -102,4 +111,24 @@ public class ByteSource implements ByteConstants { data[TIMEREC_HOUR], data[TIMEREC_MINUTE], data[TIMEREC_SECOND]); return gc.getTime(); } + + /** + * Reset the CRC-16 to $0000. + */ + public void resetCrc() { + crc.reset(); + } + /** + * Get the current CRC-16 value. + */ + public long getCrcValue() { + return crc.getValue(); + } + + /** + * Answer with the total number of bytes read. + */ + public long getTotalBytesRead() { + return bytesRead; + } } diff --git a/src/com/webcodepro/shrinkit/MasterHeaderBlock.java b/src/com/webcodepro/shrinkit/MasterHeaderBlock.java index dca381f..8030b43 100644 --- a/src/com/webcodepro/shrinkit/MasterHeaderBlock.java +++ b/src/com/webcodepro/shrinkit/MasterHeaderBlock.java @@ -17,6 +17,7 @@ import java.util.Date; public class MasterHeaderBlock { private static final int MASTER_HEADER_LENGTH = 48; private int masterCrc; + private boolean validCrc; private long totalRecords; private Date archiveCreateWhen; private Date archiveModWhen; @@ -25,15 +26,11 @@ public class MasterHeaderBlock { /** * Create the Master Header Block, based on the ByteSource. - * To avoid byte counting, we read in the fixed size header - * and then work our way through the data. When we are done, - * that data is thrown away, and we don't need to ensure - * that we've read a consistent number of bytes. */ public MasterHeaderBlock(ByteSource bs) throws IOException { - bs = new ByteSource(bs.readBytes(MASTER_HEADER_LENGTH)); bs.checkNuFileId(); masterCrc = bs.readWord(); + bs.resetCrc(); // CRC is computed from this point to the end of the header totalRecords = bs.readLong(); archiveCreateWhen = bs.readDate(); archiveModWhen = bs.readDate(); @@ -44,6 +41,11 @@ public class MasterHeaderBlock { } else { masterEof = -1; } + // Read whatever remains of the fixed size header + while (bs.getTotalBytesRead() < MASTER_HEADER_LENGTH) { + bs.readByte(); + } + validCrc = (masterCrc == bs.getCrcValue()); } // GENERATED CODE @@ -84,4 +86,7 @@ public class MasterHeaderBlock { public void setMasterEof(long masterEof) { this.masterEof = masterEof; } + public boolean isValidCrc() { + return validCrc; + } } diff --git a/test_src/com/webcodepro/shrinkit/ByteSourceTest.java b/test_src/com/webcodepro/shrinkit/ByteSourceTest.java index f0000ca..ad3f44d 100644 --- a/test_src/com/webcodepro/shrinkit/ByteSourceTest.java +++ b/test_src/com/webcodepro/shrinkit/ByteSourceTest.java @@ -17,6 +17,7 @@ public class ByteSourceTest extends TestCase { ByteSource bs = new ByteSource("a".getBytes()); assertEquals('a', bs.read()); assertEquals(-1, bs.read()); + assertEquals(1, bs.getTotalBytesRead()); } public void testReadB() throws IOException { // Just to ensure we can get more than one byte... @@ -27,6 +28,7 @@ public class ByteSourceTest extends TestCase { assertEquals('l', bs.read()); assertEquals('o', bs.read()); assertEquals(-1, bs.read()); + assertEquals(5, bs.getTotalBytesRead()); } public void testReadBytesInt() throws IOException { // Ensure we read the requested data. @@ -34,6 +36,7 @@ public class ByteSourceTest extends TestCase { assertEquals("Hello", new String(bs.readBytes(5))); assertEquals("World", new String(bs.readBytes(5))); assertEquals(-1, bs.read()); + assertEquals(10, bs.getTotalBytesRead()); } public void textReadBytesIntError() { // Ensure that we fail appropriately @@ -43,17 +46,20 @@ public class ByteSourceTest extends TestCase { fail(); } catch (IOException ex) { assertTrue(true); // Expected + assertEquals(2, bs.getTotalBytesRead()); } } public void testCheckNuFileId() throws IOException { ByteSource bs = new ByteSource(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 }); assertTrue(bs.checkNuFileId()); + assertEquals(6, bs.getTotalBytesRead()); bs = new ByteSource("NotNuFile".getBytes()); assertFalse(bs.checkNuFileId()); } public void testCheckNuFxId() throws IOException { ByteSource bs = new ByteSource(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 }); assertTrue(bs.checkNuFxId()); + assertEquals(4, bs.getTotalBytesRead()); bs = new ByteSource("NotNuFx".getBytes()); assertFalse(bs.checkNuFxId()); } @@ -61,18 +67,22 @@ public class ByteSourceTest extends TestCase { ByteSource bs = new ByteSource(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }); assertEquals(0x0201, bs.readWord()); assertEquals(0x0403, bs.readWord()); + assertEquals(4, bs.getTotalBytesRead()); } public void testReadWordHighBitSet() throws IOException { ByteSource bs = new ByteSource(new byte[] { (byte)0xff, (byte)0xff }); assertEquals(0xffff, bs.readWord()); + assertEquals(2, bs.getTotalBytesRead()); } public void testReadLong() throws IOException { ByteSource bs = new ByteSource(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }); assertEquals(0x04030201, bs.readLong()); + assertEquals(4, bs.getTotalBytesRead()); } public void testReadLongHighBitSet() throws IOException { ByteSource bs = new ByteSource(new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff }); assertEquals(0xffffffffL, bs.readLong()); + assertEquals(4, bs.getTotalBytesRead()); } public void testReadDate() throws IOException { ByteSource bs = new ByteSource(new byte[] { @@ -84,11 +94,13 @@ public class ByteSourceTest extends TestCase { assertEquals(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 1, 10, 0).getTime(), bs.readDate()); assertEquals(new GregorianCalendar(1988, Calendar.NOVEMBER, 17, 11, 16, 0).getTime(), bs.readDate()); assertEquals(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 13, 12, 0).getTime(), bs.readDate()); + assertEquals(24, bs.getTotalBytesRead()); } public void testReadNullDate() throws IOException { ByteSource bs = new ByteSource(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // null date }); assertNull(bs.readDate()); + assertEquals(8, bs.getTotalBytesRead()); } } diff --git a/test_src/com/webcodepro/shrinkit/MasterHeaderBlockTest.java b/test_src/com/webcodepro/shrinkit/MasterHeaderBlockTest.java index 6116560..d07f54a 100644 --- a/test_src/com/webcodepro/shrinkit/MasterHeaderBlockTest.java +++ b/test_src/com/webcodepro/shrinkit/MasterHeaderBlockTest.java @@ -11,7 +11,7 @@ import junit.framework.TestCase; * @author robgreene@users.sourceforge.net */ public class MasterHeaderBlockTest extends TestCase { - public void test1() throws IOException { + public void testWithValidCrc() throws IOException { ByteSource bs = new ByteSource(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5, (byte)0xdc, 0x1b, 0x2d, 0x00, 0x00, 0x00, 0x38, 0x0c, 0x14, 0x5f, @@ -28,5 +28,19 @@ public class MasterHeaderBlockTest extends TestCase { assertEquals(new ByteSource(new byte[] {0x29, 0x0d, 0x14, 0x5f, 0x08, 0x07, 0x01, 0x04}).readDate(), b.getArchiveModWhen()); assertEquals(0x01, b.getMasterVersion()); assertEquals(0x1acae, b.getMasterEof()); + assertTrue(b.isValidCrc()); + } + + public void testWithInvalidCrc() throws IOException { + ByteSource bs = new ByteSource(new byte[] { + 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5, 0x00, 0x00, // <-- Bad CRC! + 0x2d, 0x00, 0x00, 0x00, 0x38, 0x0c, 0x14, 0x5f, + 0x08, 0x07, 0x30, 0x04, 0x29, 0x0d, 0x14, 0x5f, + 0x08, 0x07, 0x01, 0x04, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0xae, (byte)0xac, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }); + MasterHeaderBlock b = new MasterHeaderBlock(bs); + assertFalse(b.isValidCrc()); } } diff --git a/test_src/com/webcodepro/shrinkit/NuFileArchiveTest.java b/test_src/com/webcodepro/shrinkit/NuFileArchiveTest.java index 8297e70..f24b5a3 100644 --- a/test_src/com/webcodepro/shrinkit/NuFileArchiveTest.java +++ b/test_src/com/webcodepro/shrinkit/NuFileArchiveTest.java @@ -33,9 +33,9 @@ public class NuFileArchiveTest extends TestCase { MasterHeaderBlock m = a.getMasterHeaderBlock(); System.out.printf("Master Header Block\n==================\n" + "master_crc=$%x\ntotal_records=%d\narchive_create_when=%tc\narchive_mod_when=%tc\n" - + "master_version=%d\nmaster_eof=$%x\n\n", + + "master_version=%d\nmaster_eof=$%x\nisValidCrc = %b\n\n", m.getMasterCrc(), m.getTotalRecords(), m.getArchiveCreateWhen(), m.getArchiveModWhen(), - m.getMasterVersion(), m.getMasterEof()); + m.getMasterVersion(), m.getMasterEof(), m.isValidCrc()); for (HeaderBlock b : a.getHeaderBlocks()) { System.out.printf("\tHeader Block\n\t============\n"); System.out.printf("\theader_crc=$%x\n\tattrib_count=%d\n\tversion_number=%d\n\ttotal_threads=%d\n\t"